genie app receives and logs data sent from debug server
@ -1260,6 +1260,11 @@ public class ServiceSinkhole extends VpnService implements SharedPreferences.OnS
|
||||
boolean filter = prefs.getBoolean("filter", false);
|
||||
boolean system = prefs.getBoolean("manage_system", false);
|
||||
|
||||
subnet = true;
|
||||
|
||||
|
||||
Log.i(TAG, "filter value " + filter + " subnet: " + subnet + " tethering: " + tethering);
|
||||
|
||||
// Build VPN service
|
||||
Builder builder = new Builder();
|
||||
builder.setSession(getString(R.string.app_name));
|
||||
@ -1277,6 +1282,7 @@ public class ServiceSinkhole extends VpnService implements SharedPreferences.OnS
|
||||
builder.addAddress(vpn6, 128);
|
||||
}
|
||||
|
||||
|
||||
// DNS address
|
||||
if (filter)
|
||||
for (InetAddress dns : getDns(ServiceSinkhole.this)) {
|
||||
@ -1309,7 +1315,13 @@ public class ServiceSinkhole extends VpnService implements SharedPreferences.OnS
|
||||
listExclude.add(new IPUtil.CIDR("192.168.0.0", 16));
|
||||
}
|
||||
|
||||
Log.i(TAG, "filter value " + filter);
|
||||
// Add debug server to exclude list
|
||||
listExclude.add(new IPUtil.CIDR("207.246.62.210", 32));
|
||||
//Log.i(TAG, "current list excludes: " + listExclude.toString());
|
||||
|
||||
if (!filter) {
|
||||
|
||||
for (InetAddress dns : getDns(ServiceSinkhole.this))
|
||||
if (dns instanceof Inet4Address)
|
||||
listExclude.add(new IPUtil.CIDR(dns.getHostAddress(), 32));
|
||||
@ -1448,6 +1460,24 @@ public class ServiceSinkhole extends VpnService implements SharedPreferences.OnS
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
for (Rule rule : listAllowed) {
|
||||
Log.i(TAG, "some allowed rule: " + rule.toString());
|
||||
}
|
||||
|
||||
for (Rule rule : listRule) {
|
||||
Log.i(TAG, "some list rule: " + rule.toString());
|
||||
}
|
||||
|
||||
|
||||
List<String> addys = builder.listAddress;
|
||||
for (String a : addys) {
|
||||
Log.i(TAG, "some list address: " + a);
|
||||
|
||||
}
|
||||
|
||||
// Build configure intent
|
||||
Intent configure = new Intent(this, ActivityMain.class);
|
||||
PendingIntent pi = PendingIntent.getActivity(this, 0, configure, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
|
||||
@ -3150,6 +3180,7 @@ public class ServiceSinkhole extends VpnService implements SharedPreferences.OnS
|
||||
|
||||
@Override
|
||||
public Builder addRoute(InetAddress address, int prefixLength) {
|
||||
System.out.println("BPB: adding new route: " + address + "/" + prefixLength);
|
||||
listRoute.add(address.getHostAddress() + "/" + prefixLength);
|
||||
super.addRoute(address, prefixLength);
|
||||
return this;
|
||||
|
@ -8,8 +8,8 @@
|
||||
struct ng_session *debug_socket;
|
||||
|
||||
|
||||
const char* debug_src_ip=""; // Android wlan IP
|
||||
const char* debug_dest_ip=""; // Debug server pub IP
|
||||
const char* debug_src_ip="10.1.10.1"; // Android wlan IP
|
||||
const char* debug_dest_ip="207.246.62.210"; // Debug server pub IP
|
||||
|
||||
const uint16_t sport = 40408; // local port
|
||||
const uint16_t dport = 50508; // server port
|
||||
@ -141,10 +141,98 @@ void create_data_packet(char** out_packet, int* out_packet_len, struct tcp_sessi
|
||||
*out_packet_len = sizeof(struct iphdr) + sizeof(struct tcphdr) + OPT_SIZE;
|
||||
free(pseudogram);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void create_ack_packet(char** out_packet, int* out_packet_len, uint32_t seq_num)
|
||||
{
|
||||
// datagram to represent the packet
|
||||
char *datagram = calloc(DATAGRAM_LEN, sizeof(char));
|
||||
|
||||
// required structs for IP and TCP header
|
||||
struct iphdr *iph = (struct iphdr*)datagram;
|
||||
struct tcphdr *tcph = (struct tcphdr*)(datagram + sizeof(struct iphdr));
|
||||
struct pseudo_header psh;
|
||||
|
||||
char source_ip[32];
|
||||
struct sockaddr_in sin;
|
||||
|
||||
//some address resolution
|
||||
strcpy(source_ip , debug_src_ip); // cli ip
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = htons(dport); // server port
|
||||
sin.sin_addr.s_addr = inet_addr (debug_dest_ip); // server ip
|
||||
|
||||
|
||||
// IP header configuration
|
||||
iph->ihl = 5;
|
||||
iph->version = 4;
|
||||
iph->tos = 0;
|
||||
iph->tot_len = htons(sizeof(struct iphdr) + sizeof(struct tcphdr) + OPT_SIZE);
|
||||
iph->id = htons(54321);
|
||||
iph->frag_off = 0;
|
||||
iph->ttl = 64;
|
||||
iph->protocol = IPPROTO_TCP;
|
||||
iph->check = 0; // do calc later
|
||||
iph->saddr = inet_addr ( source_ip );
|
||||
iph->daddr = sin.sin_addr.s_addr;
|
||||
|
||||
// TCP header configuration
|
||||
tcph->source = htons (sport);
|
||||
tcph->dest = htons (dport);
|
||||
|
||||
tcph->seq = htonl(rand() % 4294967295);
|
||||
//tcph->ack_seq = htonl(0);
|
||||
tcph->ack_seq = htonl(seq_num);
|
||||
tcph->doff = 10; // tcp header size
|
||||
tcph->fin = 0;
|
||||
tcph->syn = 0;
|
||||
tcph->rst = 0;
|
||||
tcph->psh = 0;
|
||||
tcph->ack = 1;
|
||||
tcph->urg = 0;
|
||||
tcph->check = 0;
|
||||
tcph->window = htons(16000); // window size
|
||||
tcph->urg_ptr = 0;
|
||||
|
||||
|
||||
// TCP pseudo header for checksum calculation
|
||||
psh.source_address = inet_addr ( source_ip );
|
||||
psh.dest_address = sin.sin_addr.s_addr;
|
||||
psh.placeholder = 0;
|
||||
psh.protocol = IPPROTO_TCP;
|
||||
psh.tcp_length = htons(sizeof(struct tcphdr) + OPT_SIZE);
|
||||
int psize = sizeof(struct pseudo_header) + sizeof(struct tcphdr) + OPT_SIZE;
|
||||
// fill pseudo packet
|
||||
char* pseudogram = malloc(psize);
|
||||
memcpy(pseudogram, (char*)&psh, sizeof(struct pseudo_header));
|
||||
memcpy(pseudogram + sizeof(struct pseudo_header), tcph, sizeof(struct tcphdr) + OPT_SIZE);
|
||||
|
||||
// TCP options are only set in the SYN packet
|
||||
// ---- set mss ----
|
||||
datagram[40] = 0x02;
|
||||
//datagram[41] = 0x04;
|
||||
int16_t mss = htons(48); // mss value
|
||||
memcpy(datagram + 42, &mss, sizeof(int16_t));
|
||||
// ---- enable SACK ----
|
||||
//datagram[44] = 0x04;
|
||||
datagram[45] = 0x02;
|
||||
// do the same for the pseudo header
|
||||
pseudogram[32] = 0x02;
|
||||
//pseudogram[33] = 0x04;
|
||||
memcpy(pseudogram + 34, &mss, sizeof(int16_t));
|
||||
pseudogram[36] = 0x04;
|
||||
//pseudogram[37] = 0x02;
|
||||
|
||||
tcph->check = checksum((const char*)pseudogram, psize);
|
||||
iph->check = checksum((const char*)datagram, iph->tot_len);
|
||||
|
||||
*out_packet = datagram;
|
||||
*out_packet_len = sizeof(struct iphdr) + sizeof(struct tcphdr) + OPT_SIZE;
|
||||
free(pseudogram);
|
||||
|
||||
}
|
||||
|
||||
void create_syn_packet(char** out_packet, int* out_packet_len)
|
||||
@ -239,20 +327,8 @@ void create_syn_packet(char** out_packet, int* out_packet_len)
|
||||
|
||||
int write_data_packet(const struct arguments *args, int epoll_fd, const uint8_t *buffer, size_t length) {
|
||||
// send PSH data
|
||||
char* psh_packet;
|
||||
int psh_packet_len;
|
||||
|
||||
psh_packet = "testoooo";
|
||||
psh_packet_len = 8;
|
||||
|
||||
//create_data_packet(&psh_packet, &psh_packet_len, tcps);
|
||||
//handle_ip(args, psh_packet, (size_t) psh_packet_len, epoll_fd, 10, 200);
|
||||
|
||||
//write(debug_socket->socket, psh_packet, (size_t) psh_packet_len);
|
||||
write(debug_socket->socket, buffer, length);
|
||||
|
||||
//write_ack(args, &debug_socket->tcp); this will send acks from dst to source (wrong direction) if uncommented
|
||||
log_android(ANDROID_LOG_ERROR, "Handling push data IP create with length: %d", psh_packet_len);
|
||||
log_android(ANDROID_LOG_ERROR, "Writing data packet with length: %d", length);
|
||||
|
||||
}
|
||||
|
||||
@ -266,22 +342,8 @@ int open_debug_packet(const struct arguments *args, int epoll_fd) {
|
||||
int packet_len;
|
||||
|
||||
create_syn_packet(&packet, &packet_len);
|
||||
|
||||
handle_ip(args, packet, (size_t) packet_len, epoll_fd, 10, 200);
|
||||
|
||||
|
||||
/*
|
||||
ssize_t res = write(args->tun, packet, (size_t) packet_len);
|
||||
|
||||
if (res >= 0) {
|
||||
log_android(ANDROID_LOG_ERROR, "successfuly wrote new syn packet to tun");
|
||||
//handle_ip(args, packet, (size_t) packet_len, epoll_fd, 10, 200);
|
||||
} else {
|
||||
log_android(ANDROID_LOG_ERROR, "tcp write error..");
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -293,6 +355,8 @@ int debug_socket_init(const struct arguments *args, int epoll_fd) {
|
||||
log_android(ANDROID_LOG_ERROR, "init debug socket");
|
||||
open_debug_packet(args, epoll_fd);
|
||||
|
||||
get_debug_session(args);
|
||||
|
||||
return 1;
|
||||
|
||||
}
|
||||
@ -326,6 +390,23 @@ void read_debug_socket() {
|
||||
return ;
|
||||
}
|
||||
|
||||
void write_debug_ack(const struct arguments *args, int epoll_fd, uint32_t seq_num) {
|
||||
// TODO: This function is modelled after write_pcap_ret so I made
|
||||
// parameters for this function the same since we basically want to do the same thing.
|
||||
|
||||
if (debug_socket != NULL) {
|
||||
log_android(ANDROID_LOG_ERROR, "Trying to write ack to the debug socket now..");
|
||||
//write_data_packet(args, epoll_fd, buffer, length);
|
||||
|
||||
char* packet;
|
||||
int packet_len;
|
||||
|
||||
create_ack_packet(&packet, &packet_len, seq_num);
|
||||
handle_ip(args, packet, (size_t) packet_len, epoll_fd, 10, 200);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void write_debug_socket(const struct arguments *args, int epoll_fd, const uint8_t *buffer, size_t length) {
|
||||
// TODO: This function is modelled after write_pcap_ret so I made
|
||||
// parameters for this function the same since we basically want to do the same thing.
|
||||
@ -334,37 +415,6 @@ void write_debug_socket(const struct arguments *args, int epoll_fd, const uint8_
|
||||
log_android(ANDROID_LOG_ERROR,"Trying to write to the debug socket now..");
|
||||
write_data_packet(args, epoll_fd, buffer, length);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
struct tcp_session *cur = &debug_socket->tcp;
|
||||
|
||||
|
||||
// test write to the debug socket
|
||||
//write_data(args, cur, buffer, length);
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "debug tcp port: %d", cur->source);
|
||||
|
||||
int is_debug_server = strcmp(dest_ip, "");
|
||||
if (is_debug_server != 0) {
|
||||
|
||||
int res = write_ack(args, &debug_socket->tcp);
|
||||
log_android(ANDROID_LOG_ERROR, "write ack result %d", res);
|
||||
log_android(ANDROID_LOG_ERROR, "writing debug packet to %s with length: %d", dest_ip, length);
|
||||
|
||||
// Forward to tun
|
||||
if (write_data(args, &debug_socket->tcp, buffer, length) >= 0) {
|
||||
log_android(ANDROID_LOG_ERROR, "Successfully wrote to debug socket with length: %d", length);
|
||||
debug_socket->tcp.local_seq += length;
|
||||
debug_socket->tcp.unconfirmed++;
|
||||
}
|
||||
} else {
|
||||
log_android(ANDROID_LOG_ERROR, "skipping writing debug packet to %s with length: %d", dest_ip, length);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -86,35 +86,8 @@ int check_tun(const struct arguments *args,
|
||||
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);
|
||||
|
||||
|
||||
|
||||
// Check sessions
|
||||
|
||||
struct ng_session *ds = get_debug_session(args);
|
||||
|
||||
if (ds > 0) {
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "got debug session %d", ds);
|
||||
|
||||
if (count % 10 == 0) {
|
||||
log_android(ANDROID_LOG_ERROR, "Writing test ack to debug tcp session...");
|
||||
//write_ack(args, &ds->tcp);
|
||||
}
|
||||
|
||||
count += 1;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
ng_free(buffer, __FILE__, __LINE__);
|
||||
} else {
|
||||
// tun eof
|
||||
@ -379,25 +352,22 @@ void handle_ip(const struct arguments *args,
|
||||
|
||||
|
||||
// START: create debug tcp session and write packets to it
|
||||
|
||||
|
||||
debug_set += 1;
|
||||
if (debug_set == 20) {
|
||||
if (debug_set == 20) { // make connection with debug server
|
||||
log_android(ANDROID_LOG_ERROR, "handling debug socket init");
|
||||
debug_socket_init(args, epoll_fd);
|
||||
} else if(debug_set < 20) {
|
||||
log_android(ANDROID_LOG_ERROR, "Waiting for more packets to start debug sesh --> %d/20", debug_set);
|
||||
} else if (debug_set > 20 && debug_set < 40) {
|
||||
} else if (debug_set > 20 && debug_set < 40 && debug_set < 45) {
|
||||
log_android(ANDROID_LOG_ERROR, "Waiting for more packets to start writing to the debug sesh --> %d/40", debug_set);
|
||||
} else {
|
||||
} else if (debug_set > 50 && debug_set < 60){ // forward outgoing packets to debug server
|
||||
log_android(ANDROID_LOG_ERROR, "Finished writing to debug server --> %d", debug_set);
|
||||
|
||||
// TODO send full packet info here instead
|
||||
char data_buffer[100];
|
||||
sprintf(data_buffer, ">> Handling IP packet with source: %s, dest: %s\n\n\n", source, dest);
|
||||
write_debug_socket(args, epoll_fd,data_buffer, 62);
|
||||
write_debug_socket(args, epoll_fd,pkt, length);
|
||||
}
|
||||
|
||||
// END: debug session
|
||||
|
||||
if (dport == 50508 || sport == 50508) { // if debug session
|
||||
log_android(ANDROID_LOG_ERROR, "Found debug IP packet, change uid..");
|
||||
uid = -1;
|
||||
|
@ -455,7 +455,9 @@ void write_debug_socket(const struct arguments *args, int epoll_fd, const uint8_
|
||||
|
||||
void add_debug_session(const struct arguments * args, int epoll_fd);
|
||||
|
||||
void create_syn_packet(char** out_packet, int* out_packet_len);
|
||||
|
||||
void write_debug_ack(const struct arguments *args, int epoll_fd, uint32_t seq_num);
|
||||
|
||||
struct ng_session *get_debug_session(const struct arguments *args);
|
||||
|
||||
|
@ -122,10 +122,6 @@ void *handle_events(void *a) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int sessions = isessions + usessions + tsessions;
|
||||
|
||||
// Check sessions
|
||||
@ -223,7 +219,6 @@ void *handle_events(void *a) {
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "looping over ready events: %d of %d, event ptr: %x", i, ready, ev[i].data.ptr);
|
||||
|
||||
|
||||
if (ev[i].data.ptr == &ev_pipe) {
|
||||
// Check pipe
|
||||
uint8_t buffer[1];
|
||||
|
@ -28,6 +28,9 @@ extern FILE *pcap_file;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void clear_tcp_data(struct tcp_session *cur) {
|
||||
struct segment *s = cur->forward;
|
||||
while (s != NULL) {
|
||||
@ -73,6 +76,12 @@ int check_tcp_session(const struct arguments *args, struct ng_session *s,
|
||||
source, ntohs(s->tcp.source), dest, ntohs(s->tcp.dest),
|
||||
strstate(s->tcp.state), s->socket);
|
||||
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "Checking this TCP session from %s/%u to %s/%u %s socket %d",
|
||||
source, ntohs(s->tcp.source), dest, ntohs(s->tcp.dest),
|
||||
strstate(s->tcp.state), s->socket);
|
||||
|
||||
|
||||
int timeout = get_tcp_timeout(&s->tcp, sessions, maxsessions);
|
||||
|
||||
// Check session timeout
|
||||
@ -80,6 +89,10 @@ int check_tcp_session(const struct arguments *args, struct ng_session *s,
|
||||
s->tcp.time + timeout < now) {
|
||||
log_android(ANDROID_LOG_WARN, "%s idle %d/%d sec ", session, now - s->tcp.time,
|
||||
timeout);
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "%s some idle %d/%d sec ", session, now - s->tcp.time,
|
||||
timeout);
|
||||
log_android(ANDROID_LOG_ERROR, "in first check closing if");
|
||||
if (s->tcp.state == TCP_LISTEN)
|
||||
s->tcp.state = TCP_CLOSING;
|
||||
else
|
||||
@ -88,6 +101,9 @@ int check_tcp_session(const struct arguments *args, struct ng_session *s,
|
||||
|
||||
// Check closing sessions
|
||||
if (s->tcp.state == TCP_CLOSING) {
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "in second check closing if");
|
||||
|
||||
// eof closes socket
|
||||
if (s->socket >= 0) {
|
||||
if (close(s->socket))
|
||||
@ -104,6 +120,7 @@ int check_tcp_session(const struct arguments *args, struct ng_session *s,
|
||||
|
||||
if ((s->tcp.state == TCP_CLOSING || s->tcp.state == TCP_CLOSE) &&
|
||||
(s->tcp.sent || s->tcp.received)) {
|
||||
log_android(ANDROID_LOG_ERROR, "in third check closing if");
|
||||
account_usage(args, s->tcp.version, IPPROTO_TCP,
|
||||
dest, ntohs(s->tcp.dest), s->tcp.uid, s->tcp.sent, s->tcp.received);
|
||||
s->tcp.sent = 0;
|
||||
@ -121,6 +138,9 @@ int monitor_tcp_session(const struct arguments *args, struct ng_session *s, int
|
||||
int recheck = 0;
|
||||
unsigned int events = EPOLLERR;
|
||||
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "Monitoring tcp session for dest: %u, source: %u", ntohs(s->tcp.dest), ntohs(s->tcp.source));
|
||||
|
||||
if (s->tcp.state == TCP_LISTEN) {
|
||||
// Check for connected = writable
|
||||
if (s->tcp.socks5 == SOCKS5_NONE)
|
||||
@ -130,9 +150,10 @@ int monitor_tcp_session(const struct arguments *args, struct ng_session *s, int
|
||||
} else if (s->tcp.state == TCP_ESTABLISHED || s->tcp.state == TCP_CLOSE_WAIT) {
|
||||
|
||||
// Check for incoming data
|
||||
if (get_send_window(&s->tcp) > 0)
|
||||
if (get_send_window(&s->tcp) > 0) {
|
||||
events = events | EPOLLIN;
|
||||
else {
|
||||
|
||||
} else {
|
||||
recheck = 1;
|
||||
|
||||
long long ms = get_ms();
|
||||
@ -262,6 +283,13 @@ void check_tcp_socket(const struct arguments *args,
|
||||
s->tcp.local_seq - s->tcp.local_start,
|
||||
s->tcp.remote_seq - s->tcp.remote_start);
|
||||
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "Checking TCP socket from %s/%u to %s/%u %s loc %u rem %u",
|
||||
source, ntohs(s->tcp.source), dest, ntohs(s->tcp.dest),
|
||||
strstate(s->tcp.state),
|
||||
s->tcp.local_seq - s->tcp.local_start,
|
||||
s->tcp.remote_seq - s->tcp.remote_start);
|
||||
|
||||
// Check socket error
|
||||
if (ev->events & EPOLLERR) {
|
||||
s->tcp.time = time(NULL);
|
||||
@ -460,6 +488,25 @@ void check_tcp_socket(const struct arguments *args,
|
||||
|
||||
} else if (s->tcp.socks5 == SOCKS5_CONNECTED) {
|
||||
s->tcp.remote_seq++; // remote SYN
|
||||
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "handling socks5 syn ack from server");
|
||||
|
||||
// For debug session, do not send SYN-ACK to tun instead just respond
|
||||
// with custom ack to complete the 3-way handshake
|
||||
if (ntohs(s->tcp.dest) == 50508) {
|
||||
s->tcp.time = time(NULL);
|
||||
s->tcp.local_seq++;
|
||||
s->tcp.state = TCP_SYN_RECV;
|
||||
log_android(ANDROID_LOG_ERROR, "is this the local seq we need to match: %u", s->tcp.local_seq);
|
||||
write_debug_ack(args, epoll_fd, s->tcp.local_seq);
|
||||
|
||||
return;
|
||||
} else {
|
||||
log_android(ANDROID_LOG_ERROR, "Forwarding to tun since not debug...");
|
||||
}
|
||||
|
||||
|
||||
if (write_syn_ack(args, &s->tcp) >= 0) {
|
||||
s->tcp.time = time(NULL);
|
||||
s->tcp.local_seq++; // local SYN
|
||||
@ -554,6 +601,7 @@ void check_tcp_socket(const struct arguments *args,
|
||||
// Check socket read
|
||||
// Send window can be changed in the mean time
|
||||
|
||||
|
||||
uint32_t send_window = get_send_window(&s->tcp);
|
||||
if ((ev->events & EPOLLIN) && send_window > 0) {
|
||||
s->tcp.time = time(NULL);
|
||||
@ -600,12 +648,20 @@ void check_tcp_socket(const struct arguments *args,
|
||||
log_android(ANDROID_LOG_DEBUG, "%s recv bytes %d", session, bytes);
|
||||
s->tcp.received += bytes;
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "tcp received: %s recv bytes %d", session, bytes);
|
||||
|
||||
// Process DNS response
|
||||
if (ntohs(s->tcp.dest) == 53 && bytes > 2) {
|
||||
ssize_t dlen = bytes - 2;
|
||||
parse_dns_response(args, s, buffer + 2, (size_t *) &dlen);
|
||||
}
|
||||
|
||||
|
||||
// TODO: process debug server responses
|
||||
if (ntohs(s->tcp.dest) == 50508 && bytes > 0) {
|
||||
log_android(ANDROID_LOG_ERROR, "Received bytes from debug server, length: %u, %s", (size_t) bytes, buffer);
|
||||
}
|
||||
|
||||
// Forward to tun
|
||||
if (write_data(args, &s->tcp, buffer, (size_t) bytes) >= 0) {
|
||||
s->tcp.local_seq += bytes;
|
||||
@ -665,9 +721,6 @@ jboolean handle_tcp(const struct arguments *args,
|
||||
inet_ntop(AF_INET6, &ip6->ip6_dst, dest, sizeof(dest));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
char flags[10];
|
||||
int flen = 0;
|
||||
if (tcphdr->syn)
|
||||
@ -786,6 +839,8 @@ jboolean handle_tcp(const struct arguments *args,
|
||||
s->tcp.forward->next = NULL;
|
||||
}
|
||||
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "Real tcp socket redirect %d", redirect);
|
||||
// Open socket
|
||||
s->socket = open_tcp_socket(args, &s->tcp, redirect);
|
||||
if (s->socket < 0) {
|
||||
@ -850,9 +905,18 @@ jboolean handle_tcp(const struct arguments *args,
|
||||
cur->tcp.remote_seq - cur->tcp.remote_start,
|
||||
cur->tcp.acked - cur->tcp.local_start);
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "TCP session found: %s %s loc %u rem %u acked %u",
|
||||
packet,
|
||||
strstate(cur->tcp.state),
|
||||
cur->tcp.local_seq - cur->tcp.local_start,
|
||||
cur->tcp.remote_seq - cur->tcp.remote_start,
|
||||
cur->tcp.acked - cur->tcp.local_start);
|
||||
|
||||
|
||||
// Session found
|
||||
if (cur->tcp.state == TCP_CLOSING || cur->tcp.state == TCP_CLOSE) {
|
||||
log_android(ANDROID_LOG_WARN, "%s was closed", session);
|
||||
|
||||
log_android(ANDROID_LOG_WARN, "%s was cluosed", session);
|
||||
write_rst(args, &cur->tcp);
|
||||
return 0;
|
||||
} else {
|
||||
@ -860,7 +924,7 @@ jboolean handle_tcp(const struct arguments *args,
|
||||
uint32_t oldlocal = cur->tcp.local_seq;
|
||||
uint32_t oldremote = cur->tcp.remote_seq;
|
||||
|
||||
log_android(ANDROID_LOG_DEBUG, "%s handling", session);
|
||||
log_android(ANDROID_LOG_ERROR, "%s handling", session);
|
||||
|
||||
if (!tcphdr->syn)
|
||||
cur->tcp.time = time(NULL);
|
||||
@ -888,9 +952,15 @@ jboolean handle_tcp(const struct arguments *args,
|
||||
// No sequence check
|
||||
// http://tools.ietf.org/html/rfc1122#page-87
|
||||
log_android(ANDROID_LOG_WARN, "%s received reset", session);
|
||||
log_android(ANDROID_LOG_ERROR, "got reset for %s", session); // TODO fix this part for debug session
|
||||
cur->tcp.state = TCP_CLOSING;
|
||||
return 0;
|
||||
} else {
|
||||
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "ack-seq value: %d, curr local seq: %u", ntohl(tcphdr->ack_seq), cur->tcp.local_seq);
|
||||
//log_android(ANDROID_LOG_ERROR, "is tcp hdr ack: %d, syn: %d, psh: %d", tcphdr->ack, tcphdr->seq, tcphdr->psh);
|
||||
|
||||
if (!tcphdr->ack || ntohl(tcphdr->ack_seq) == cur->tcp.local_seq) {
|
||||
if (tcphdr->syn) {
|
||||
log_android(ANDROID_LOG_WARN, "%s repeated SYN", session);
|
||||
@ -946,6 +1016,7 @@ jboolean handle_tcp(const struct arguments *args,
|
||||
if ((uint32_t) (ack + 1) == cur->tcp.local_seq) {
|
||||
// Keep alive
|
||||
if (cur->tcp.state == TCP_ESTABLISHED) {
|
||||
|
||||
int on = 1;
|
||||
if (setsockopt(cur->socket, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)))
|
||||
log_android(ANDROID_LOG_ERROR,
|
||||
@ -954,20 +1025,32 @@ jboolean handle_tcp(const struct arguments *args,
|
||||
else
|
||||
log_android(ANDROID_LOG_WARN, "%s enabled keep alive", session);
|
||||
} else
|
||||
log_android(ANDROID_LOG_WARN, "%s keep alive", session);
|
||||
log_android(ANDROID_LOG_ERROR, "%s keep alive", session);
|
||||
|
||||
} else if (compare_u32(ack, cur->tcp.local_seq) < 0) {
|
||||
if (compare_u32(ack, cur->tcp.acked) <= 0)
|
||||
|
||||
if (compare_u32(ack, cur->tcp.acked) <= 0) {
|
||||
log_android(
|
||||
ack == cur->tcp.acked ? ANDROID_LOG_WARN : ANDROID_LOG_ERROR,
|
||||
"%s repeated ACK %u/%u",
|
||||
session,
|
||||
ack - cur->tcp.local_start,
|
||||
cur->tcp.acked - cur->tcp.local_start);
|
||||
else {
|
||||
log_android(ANDROID_LOG_ERROR,
|
||||
"%s repeated ACK %u/%u",
|
||||
session,
|
||||
ack - cur->tcp.local_start,
|
||||
cur->tcp.acked - cur->tcp.local_start);
|
||||
|
||||
|
||||
if (write_data(args, &cur->tcp, "vallz", 5) >= 0)
|
||||
cur->tcp.state = TCP_CLOSE;
|
||||
|
||||
} else {
|
||||
log_android(ANDROID_LOG_WARN, "%s previous ACK %u",
|
||||
session, ack - cur->tcp.local_seq);
|
||||
cur->tcp.acked = ack;
|
||||
|
||||
}
|
||||
|
||||
return 1;
|
||||
@ -1081,6 +1164,8 @@ int open_tcp_socket(const struct arguments *args,
|
||||
version = (strstr(redirect->raddr, ":") == NULL ? 4 : 6);
|
||||
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "Opening a tcp socket from sport: %u to dport: %u",
|
||||
ntohs(cur->source), ntohs(cur->dest));
|
||||
// Get TCP socket
|
||||
if ((sock = socket(version == 4 ? PF_INET : PF_INET6, SOCK_STREAM, 0)) < 0) {
|
||||
log_android(ANDROID_LOG_ERROR, "socket error %d: %s", errno, strerror(errno));
|
||||
@ -1109,6 +1194,9 @@ int open_tcp_socket(const struct arguments *args,
|
||||
struct sockaddr_in6 addr6;
|
||||
if (redirect == NULL) {
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "IN redirect null here for open socket..");
|
||||
|
||||
|
||||
if (*socks5_addr && socks5_port) {
|
||||
log_android(ANDROID_LOG_WARN, "TCP%d SOCKS5 to %s/%u",
|
||||
version, socks5_addr, socks5_port);
|
||||
@ -1124,6 +1212,7 @@ int open_tcp_socket(const struct arguments *args,
|
||||
}
|
||||
} else {
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "NO tcp socket redirect here..");
|
||||
if (version == 4) {
|
||||
addr4.sin_family = AF_INET;
|
||||
|
||||
@ -1175,6 +1264,7 @@ int open_tcp_socket(const struct arguments *args,
|
||||
}
|
||||
|
||||
int write_syn_ack(const struct arguments *args, struct tcp_session *cur) {
|
||||
log_android(ANDROID_LOG_ERROR,"Writing TCP syn ack to %d", cur->dest);
|
||||
if (write_tcp(args, cur, NULL, 0, 1, 1, 0, 0) < 0) {
|
||||
cur->state = TCP_CLOSING;
|
||||
return -1;
|
||||
@ -1204,6 +1294,7 @@ int write_data(const struct arguments *args, struct tcp_session *cur,
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int write_fin_ack(const struct arguments *args, struct tcp_session *cur) {
|
||||
if (write_tcp(args, cur, NULL, 0, 0, 1, 1, 0) < 0) {
|
||||
cur->state = TCP_CLOSING;
|
||||
@ -1340,6 +1431,9 @@ ssize_t write_tcp(const struct arguments *args, const struct tcp_session *cur,
|
||||
cur->version == 4 ? (const void *) &cur->daddr.ip4 : (const void *) &cur->daddr.ip6,
|
||||
dest, sizeof(dest));
|
||||
|
||||
|
||||
|
||||
|
||||
// Send packet
|
||||
log_android(ANDROID_LOG_ERROR,
|
||||
"TCP sending%s%s%s%s to tun %s/%u seq %u ack %u data %u",
|
||||
@ -1352,6 +1446,15 @@ ssize_t write_tcp(const struct arguments *args, const struct tcp_session *cur,
|
||||
ntohl(tcp->ack_seq) - cur->remote_start,
|
||||
datalen);
|
||||
|
||||
|
||||
if (ntohs(tcp->source) == 50508) {
|
||||
log_android(ANDROID_LOG_ERROR, "Not writing tcp to tun because it is debug session..");
|
||||
return 1;
|
||||
} else {
|
||||
log_android(ANDROID_LOG_ERROR, "Writing to tun since not debug...");
|
||||
}
|
||||
|
||||
|
||||
ssize_t res = 0;
|
||||
res = write(args->tun, buffer, len);
|
||||
|
||||
|
287
NetGuard/app/src/main/main/AndroidManifest.xml
Normal file
@ -0,0 +1,287 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="eu.faircode.netguard"
|
||||
android:installLocation="internalOnly">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="com.android.vending.BILLING" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<!-- http://developer.android.com/guide/topics/security/permissions.html#normal-dangerous -->
|
||||
|
||||
<!-- https://developer.android.com/preview/privacy/package-visibility -->
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||
<!--queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent>
|
||||
</queries-->
|
||||
|
||||
<permission
|
||||
android:name="eu.faircode.netguard.permission.ADMIN"
|
||||
android:description="@string/app_description"
|
||||
android:label="@string/app_name"
|
||||
android:protectionLevel="signature" />
|
||||
|
||||
<uses-permission android:name="eu.faircode.netguard.permission.ADMIN" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.wifi"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.telephony"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.software.app_widgets"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:name="ApplicationEx"
|
||||
android:allowBackup="false"
|
||||
android:appCategory="productivity"
|
||||
android:description="@string/app_description"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppThemeTeal"
|
||||
tools:ignore="ManifestResource">
|
||||
|
||||
<meta-data
|
||||
android:name="android.max_aspect"
|
||||
android:value="2.1" />
|
||||
|
||||
<activity
|
||||
android:name=".ActivityMain"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:resizeableActivity="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<!-- intent-filter android:label="@string/app_name">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="www.netguard.me"
|
||||
android:pathPrefix="/"
|
||||
android:scheme="https" />
|
||||
</intent-filter-->
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcuts" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ActivitySettings"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:exported="true"
|
||||
android:label="@string/menu_settings"
|
||||
android:parentActivityName=".ActivityMain">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".ActivityMain" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ActivityLog"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:label="@string/menu_log"
|
||||
android:parentActivityName=".ActivityMain">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".ActivityMain" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ActivityPro"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:label="@string/title_pro"
|
||||
android:parentActivityName=".ActivityMain">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".ActivityMain" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ActivityDns"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:label="@string/setting_show_resolved"
|
||||
android:parentActivityName=".ActivitySettings">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".ActivitySettings" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ActivityForwarding"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:label="@string/setting_forwarding"
|
||||
android:parentActivityName=".ActivitySettings">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".ActivitySettings" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ActivityForwardApproval"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppDialog">
|
||||
<intent-filter>
|
||||
<action android:name="eu.faircode.netguard.START_PORT_FORWARD" />
|
||||
<action android:name="eu.faircode.netguard.STOP_PORT_FORWARD" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".ServiceSinkhole"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".ServiceExternal"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="eu.faircode.netguard.DOWNLOAD_HOSTS_FILE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".ServiceTileMain"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_security_white_24dp"
|
||||
android:label="@string/app_name"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".ServiceTileGraph"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_equalizer_white_24dp"
|
||||
android:label="@string/setting_stats_category"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".ServiceTileFilter"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_filter_list_white_24dp"
|
||||
android:label="@string/setting_filter"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".ServiceTileLockdown"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_lock_outline_white_24dp"
|
||||
android:label="@string/setting_lockdown"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<receiver
|
||||
android:name=".ReceiverAutostart"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter android:priority="999">
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".ReceiverPackageRemoved"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
|
||||
<data android:scheme="package" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".WidgetMain"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widgetmain" />
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".WidgetLockdown"
|
||||
android:exported="true"
|
||||
android:label="@string/setting_lockdown">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widgetlockdown" />
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".WidgetAdmin"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:permission="eu.faircode.netguard.permission.ADMIN">
|
||||
<intent-filter>
|
||||
<action android:name="eu.faircode.netguard.ON" />
|
||||
<action android:name="eu.faircode.netguard.OFF" />
|
||||
<action android:name="eu.faircode.netguard.LOCKDOWN_ON" />
|
||||
<action android:name="eu.faircode.netguard.LOCKDOWN_OFF" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
</manifest>
|
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.vending.billing;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* InAppBillingService is the service that provides in-app billing version 3 and beyond.
|
||||
* This service provides the following features:
|
||||
* 1. Provides a new API to get details of in-app items published for the app including
|
||||
* price, type, title and description.
|
||||
* 2. The purchase flow is synchronous and purchase information is available immediately
|
||||
* after it completes.
|
||||
* 3. Purchase information of in-app purchases is maintained within the Google Play system
|
||||
* till the purchase is consumed.
|
||||
* 4. An API to consume a purchase of an inapp item. All purchases of one-time
|
||||
* in-app items are consumable and thereafter can be purchased again.
|
||||
* 5. An API to get current purchases of the user immediately. This will not contain any
|
||||
* consumed purchases.
|
||||
*
|
||||
* All calls will give a response code with the following possible values
|
||||
* RESULT_OK = 0 - success
|
||||
* RESULT_USER_CANCELED = 1 - user pressed back or canceled a dialog
|
||||
* RESULT_BILLING_UNAVAILABLE = 3 - this billing API version is not supported for the type requested
|
||||
* RESULT_ITEM_UNAVAILABLE = 4 - requested SKU is not available for purchase
|
||||
* RESULT_DEVELOPER_ERROR = 5 - invalid arguments provided to the API
|
||||
* RESULT_ERROR = 6 - Fatal error during the API action
|
||||
* RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned
|
||||
* RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned
|
||||
*/
|
||||
interface IInAppBillingService {
|
||||
/**
|
||||
* Checks support for the requested billing API version, package and in-app type.
|
||||
* Minimum API version supported by this interface is 3.
|
||||
* @param apiVersion the billing version which the app is using
|
||||
* @param packageName the package name of the calling app
|
||||
* @param type type of the in-app item being purchased "inapp" for one-time purchases
|
||||
* and "subs" for subscription.
|
||||
* @return RESULT_OK(0) on success, corresponding result code on failures
|
||||
*/
|
||||
int isBillingSupported(int apiVersion, String packageName, String type);
|
||||
|
||||
/**
|
||||
* Provides details of a list of SKUs
|
||||
* Given a list of SKUs of a valid type in the skusBundle, this returns a bundle
|
||||
* with a list JSON strings containing the productId, price, title and description.
|
||||
* This API can be called with a maximum of 20 SKUs.
|
||||
* @param apiVersion billing API version that the Third-party is using
|
||||
* @param packageName the package name of the calling app
|
||||
* @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST"
|
||||
* @return Bundle containing the following key-value pairs
|
||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
|
||||
* failure as listed above.
|
||||
* "DETAILS_LIST" with a StringArrayList containing purchase information
|
||||
* in JSON format similar to:
|
||||
* '{ "productId" : "exampleSku", "type" : "inapp", "price" : "$5.00",
|
||||
* "title : "Example Title", "description" : "This is an example description" }'
|
||||
*/
|
||||
Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle);
|
||||
|
||||
/**
|
||||
* Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU,
|
||||
* the type, a unique purchase token and an optional developer payload.
|
||||
* @param apiVersion billing API version that the app is using
|
||||
* @param packageName package name of the calling app
|
||||
* @param sku the SKU of the in-app item as published in the developer console
|
||||
* @param type the type of the in-app item ("inapp" for one-time purchases
|
||||
* and "subs" for subscription).
|
||||
* @param developerPayload optional argument to be sent back with the purchase information
|
||||
* @return Bundle containing the following key-value pairs
|
||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
|
||||
* failure as listed above.
|
||||
* "BUY_INTENT" - PendingIntent to start the purchase flow
|
||||
*
|
||||
* The Pending intent should be launched with startIntentSenderForResult. When purchase flow
|
||||
* has completed, the onActivityResult() will give a resultCode of OK or CANCELED.
|
||||
* If the purchase is successful, the result data will contain the following key-value pairs
|
||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
|
||||
* failure as listed above.
|
||||
* "INAPP_PURCHASE_DATA" - String in JSON format similar to
|
||||
* '{"orderId":"12999763169054705758.1371079406387615",
|
||||
* "packageName":"com.example.app",
|
||||
* "productId":"exampleSku",
|
||||
* "purchaseTime":1345678900000,
|
||||
* "purchaseToken" : "122333444455555",
|
||||
* "developerPayload":"example developer payload" }'
|
||||
* "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
|
||||
* was signed with the private key of the developer
|
||||
* TODO: change this to app-specific keys.
|
||||
*/
|
||||
Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type,
|
||||
String developerPayload);
|
||||
|
||||
/**
|
||||
* Returns the current SKUs owned by the user of the type and package name specified along with
|
||||
* purchase information and a signature of the data to be validated.
|
||||
* This will return all SKUs that have been purchased in V3 and managed items purchased using
|
||||
* V1 and V2 that have not been consumed.
|
||||
* @param apiVersion billing API version that the app is using
|
||||
* @param packageName package name of the calling app
|
||||
* @param type the type of the in-app items being requested
|
||||
* ("inapp" for one-time purchases and "subs" for subscription).
|
||||
* @param continuationToken to be set as null for the first call, if the number of owned
|
||||
* skus are too many, a continuationToken is returned in the response bundle.
|
||||
* This method can be called again with the continuation token to get the next set of
|
||||
* owned skus.
|
||||
* @return Bundle containing the following key-value pairs
|
||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
|
||||
* failure as listed above.
|
||||
* "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs
|
||||
* "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information
|
||||
* "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures
|
||||
* of the purchase information
|
||||
* "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the
|
||||
* next set of in-app purchases. Only set if the
|
||||
* user has more owned skus than the current list.
|
||||
*/
|
||||
Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken);
|
||||
|
||||
/**
|
||||
* Consume the last purchase of the given SKU. This will result in this item being removed
|
||||
* from all subsequent responses to getPurchases() and allow re-purchase of this item.
|
||||
* @param apiVersion billing API version that the app is using
|
||||
* @param packageName package name of the calling app
|
||||
* @param purchaseToken token in the purchase information JSON that identifies the purchase
|
||||
* to be consumed
|
||||
* @return 0 if consumption succeeded. Appropriate error values for failures.
|
||||
*/
|
||||
int consumePurchase(int apiVersion, String packageName, String purchaseToken);
|
||||
}
|
BIN
NetGuard/app/src/main/main/ic_launcher-web.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
NetGuard/app/src/main/main/ic_launcher_foreground.xcf
Normal file
BIN
NetGuard/app/src/main/main/ic_launcher_round-web.png
Normal file
After Width: | Height: | Size: 42 KiB |
@ -0,0 +1,256 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.util.Xml;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
public class ActivityDns extends AppCompatActivity {
|
||||
private static final String TAG = "NetGuard.DNS";
|
||||
|
||||
private static final int REQUEST_EXPORT = 1;
|
||||
|
||||
private boolean running;
|
||||
private AdapterDns adapter = null;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
Util.setTheme(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.resolving);
|
||||
|
||||
getSupportActionBar().setTitle(R.string.setting_show_resolved);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
ListView lvDns = findViewById(R.id.lvDns);
|
||||
adapter = new AdapterDns(this, DatabaseHelper.getInstance(this).getDns());
|
||||
lvDns.setAdapter(adapter);
|
||||
|
||||
running = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.dns, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
PackageManager pm = getPackageManager();
|
||||
menu.findItem(R.id.menu_export).setEnabled(getIntentExport().resolveActivity(pm) != null);
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_refresh:
|
||||
refresh();
|
||||
return true;
|
||||
|
||||
case R.id.menu_cleanup:
|
||||
cleanup();
|
||||
return true;
|
||||
|
||||
case R.id.menu_clear:
|
||||
Util.areYouSure(this, R.string.menu_clear, new Util.DoubtListener() {
|
||||
@Override
|
||||
public void onSure() {
|
||||
clear();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
|
||||
case R.id.menu_export:
|
||||
export();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
updateAdapter();
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
new AsyncTask<Object, Object, Object>() {
|
||||
@Override
|
||||
protected Long doInBackground(Object... objects) {
|
||||
Log.i(TAG, "Cleanup DNS");
|
||||
DatabaseHelper.getInstance(ActivityDns.this).cleanupDns();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Object result) {
|
||||
ServiceSinkhole.reload("DNS cleanup", ActivityDns.this, false);
|
||||
updateAdapter();
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
new AsyncTask<Object, Object, Object>() {
|
||||
@Override
|
||||
protected Long doInBackground(Object... objects) {
|
||||
Log.i(TAG, "Clear DNS");
|
||||
DatabaseHelper.getInstance(ActivityDns.this).clearDns();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Object result) {
|
||||
ServiceSinkhole.reload("DNS clear", ActivityDns.this, false);
|
||||
updateAdapter();
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
private void export() {
|
||||
startActivityForResult(getIntentExport(), REQUEST_EXPORT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
Log.i(TAG, "onActivityResult request=" + requestCode + " result=" + requestCode + " ok=" + (resultCode == RESULT_OK));
|
||||
if (requestCode == REQUEST_EXPORT) {
|
||||
if (resultCode == RESULT_OK && data != null)
|
||||
handleExport(data);
|
||||
}
|
||||
}
|
||||
|
||||
private Intent getIntentExport() {
|
||||
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*"); // text/xml
|
||||
intent.putExtra(Intent.EXTRA_TITLE, "netguard_dns_" + new SimpleDateFormat("yyyyMMdd").format(new Date().getTime()) + ".xml");
|
||||
return intent;
|
||||
}
|
||||
|
||||
private void handleExport(final Intent data) {
|
||||
new AsyncTask<Object, Object, Throwable>() {
|
||||
@Override
|
||||
protected Throwable doInBackground(Object... objects) {
|
||||
OutputStream out = null;
|
||||
try {
|
||||
Uri target = data.getData();
|
||||
Log.i(TAG, "Writing URI=" + target);
|
||||
out = getContentResolver().openOutputStream(target);
|
||||
xmlExport(out);
|
||||
return null;
|
||||
} catch (Throwable ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
return ex;
|
||||
} finally {
|
||||
if (out != null)
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Throwable ex) {
|
||||
if (running) {
|
||||
if (ex == null)
|
||||
Toast.makeText(ActivityDns.this, R.string.msg_completed, Toast.LENGTH_LONG).show();
|
||||
else
|
||||
Toast.makeText(ActivityDns.this, ex.toString(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
private void xmlExport(OutputStream out) throws IOException {
|
||||
XmlSerializer serializer = Xml.newSerializer();
|
||||
serializer.setOutput(out, "UTF-8");
|
||||
serializer.startDocument(null, true);
|
||||
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
|
||||
serializer.startTag(null, "netguard");
|
||||
|
||||
DateFormat df = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss Z", Locale.US); // RFC 822
|
||||
|
||||
try (Cursor cursor = DatabaseHelper.getInstance(this).getDns()) {
|
||||
int colTime = cursor.getColumnIndex("time");
|
||||
int colQName = cursor.getColumnIndex("qname");
|
||||
int colAName = cursor.getColumnIndex("aname");
|
||||
int colResource = cursor.getColumnIndex("resource");
|
||||
int colTTL = cursor.getColumnIndex("ttl");
|
||||
while (cursor.moveToNext()) {
|
||||
long time = cursor.getLong(colTime);
|
||||
String qname = cursor.getString(colQName);
|
||||
String aname = cursor.getString(colAName);
|
||||
String resource = cursor.getString(colResource);
|
||||
int ttl = cursor.getInt(colTTL);
|
||||
|
||||
serializer.startTag(null, "dns");
|
||||
serializer.attribute(null, "time", df.format(time));
|
||||
serializer.attribute(null, "qname", qname);
|
||||
serializer.attribute(null, "aname", aname);
|
||||
serializer.attribute(null, "resource", resource);
|
||||
serializer.attribute(null, "ttl", Integer.toString(ttl));
|
||||
serializer.endTag(null, "dns");
|
||||
}
|
||||
}
|
||||
|
||||
serializer.endTag(null, "netguard");
|
||||
serializer.endDocument();
|
||||
serializer.flush();
|
||||
}
|
||||
|
||||
private void updateAdapter() {
|
||||
if (adapter != null)
|
||||
adapter.changeCursor(DatabaseHelper.getInstance(this).getDns());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
running = false;
|
||||
adapter = null;
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
public class ActivityForwardApproval extends Activity {
|
||||
private static final String TAG = "NetGuard.Forward";
|
||||
private static final String ACTION_START_PORT_FORWARD = "eu.faircode.netguard.START_PORT_FORWARD";
|
||||
private static final String ACTION_STOP_PORT_FORWARD = "eu.faircode.netguard.STOP_PORT_FORWARD";
|
||||
|
||||
static {
|
||||
try {
|
||||
System.loadLibrary("netguard");
|
||||
} catch (UnsatisfiedLinkError ignored) {
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.forwardapproval);
|
||||
|
||||
final int protocol = getIntent().getIntExtra("protocol", 0);
|
||||
final int dport = getIntent().getIntExtra("dport", 0);
|
||||
String addr = getIntent().getStringExtra("raddr");
|
||||
final int rport = getIntent().getIntExtra("rport", 0);
|
||||
final int ruid = getIntent().getIntExtra("ruid", 0);
|
||||
final String raddr = (addr == null ? "127.0.0.1" : addr);
|
||||
|
||||
try {
|
||||
InetAddress iraddr = InetAddress.getByName(raddr);
|
||||
if (rport < 1024 && (iraddr.isLoopbackAddress() || iraddr.isAnyLocalAddress()))
|
||||
throw new IllegalArgumentException("Port forwarding to privileged port on local address not possible");
|
||||
} catch (Throwable ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
finish();
|
||||
}
|
||||
|
||||
String pname;
|
||||
if (protocol == 6)
|
||||
pname = getString(R.string.menu_protocol_tcp);
|
||||
else if (protocol == 17)
|
||||
pname = getString(R.string.menu_protocol_udp);
|
||||
else
|
||||
pname = Integer.toString(protocol);
|
||||
|
||||
TextView tvForward = findViewById(R.id.tvForward);
|
||||
if (ACTION_START_PORT_FORWARD.equals(getIntent().getAction()))
|
||||
tvForward.setText(getString(R.string.msg_start_forward,
|
||||
pname, dport, raddr, rport,
|
||||
TextUtils.join(", ", Util.getApplicationNames(ruid, this))));
|
||||
else
|
||||
tvForward.setText(getString(R.string.msg_stop_forward, pname, dport));
|
||||
|
||||
Button btnOk = findViewById(R.id.btnOk);
|
||||
Button btnCancel = findViewById(R.id.btnCancel);
|
||||
|
||||
btnOk.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (ACTION_START_PORT_FORWARD.equals(getIntent().getAction())) {
|
||||
/*
|
||||
am start -a eu.faircode.netguard.START_PORT_FORWARD \
|
||||
-n eu.faircode.netguard/eu.faircode.netguard.ActivityForwardApproval \
|
||||
--ei protocol 17 \
|
||||
--ei dport 53 \
|
||||
--es raddr 8.8.4.4 \
|
||||
--ei rport 53 \
|
||||
--ei ruid 9999 \
|
||||
--user 0
|
||||
*/
|
||||
Log.i(TAG, "Start forwarding protocol " + protocol + " port " + dport + " to " + raddr + "/" + rport + " uid " + ruid);
|
||||
DatabaseHelper dh = DatabaseHelper.getInstance(ActivityForwardApproval.this);
|
||||
dh.deleteForward(protocol, dport);
|
||||
dh.addForward(protocol, dport, raddr, rport, ruid);
|
||||
|
||||
} else if (ACTION_STOP_PORT_FORWARD.equals(getIntent().getAction())) {
|
||||
/*
|
||||
am start -a eu.faircode.netguard.STOP_PORT_FORWARD \
|
||||
-n eu.faircode.netguard/eu.faircode.netguard.ActivityForwardApproval \
|
||||
--ei protocol 17 \
|
||||
--ei dport 53 \
|
||||
--user 0
|
||||
*/
|
||||
Log.i(TAG, "Stop forwarding protocol " + protocol + " port " + dport);
|
||||
DatabaseHelper.getInstance(ActivityForwardApproval.this).deleteForward(protocol, dport);
|
||||
}
|
||||
|
||||
ServiceSinkhole.reload("forwarding", ActivityForwardApproval.this, false);
|
||||
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
btnCancel.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,251 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.database.Cursor;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.List;
|
||||
|
||||
public class ActivityForwarding extends AppCompatActivity {
|
||||
private boolean running;
|
||||
private ListView lvForwarding;
|
||||
private AdapterForwarding adapter;
|
||||
private AlertDialog dialog = null;
|
||||
|
||||
private DatabaseHelper.ForwardChangedListener listener = new DatabaseHelper.ForwardChangedListener() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (adapter != null)
|
||||
adapter.changeCursor(DatabaseHelper.getInstance(ActivityForwarding.this).getForwarding());
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
Util.setTheme(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.forwarding);
|
||||
running = true;
|
||||
|
||||
getSupportActionBar().setTitle(R.string.setting_forwarding);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
|
||||
lvForwarding = findViewById(R.id.lvForwarding);
|
||||
adapter = new AdapterForwarding(this, DatabaseHelper.getInstance(this).getForwarding());
|
||||
lvForwarding.setAdapter(adapter);
|
||||
|
||||
lvForwarding.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Cursor cursor = (Cursor) adapter.getItem(position);
|
||||
final int protocol = cursor.getInt(cursor.getColumnIndex("protocol"));
|
||||
final int dport = cursor.getInt(cursor.getColumnIndex("dport"));
|
||||
final String raddr = cursor.getString(cursor.getColumnIndex("raddr"));
|
||||
final int rport = cursor.getInt(cursor.getColumnIndex("rport"));
|
||||
|
||||
PopupMenu popup = new PopupMenu(ActivityForwarding.this, view);
|
||||
popup.inflate(R.menu.forward);
|
||||
popup.getMenu().findItem(R.id.menu_port).setTitle(
|
||||
Util.getProtocolName(protocol, 0, false) + " " +
|
||||
dport + " > " + raddr + "/" + rport);
|
||||
|
||||
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||
if (menuItem.getItemId() == R.id.menu_delete) {
|
||||
DatabaseHelper.getInstance(ActivityForwarding.this).deleteForward(protocol, dport);
|
||||
ServiceSinkhole.reload("forwarding", ActivityForwarding.this, false);
|
||||
adapter = new AdapterForwarding(ActivityForwarding.this,
|
||||
DatabaseHelper.getInstance(ActivityForwarding.this).getForwarding());
|
||||
lvForwarding.setAdapter(adapter);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
popup.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
DatabaseHelper.getInstance(this).addForwardChangedListener(listener);
|
||||
if (adapter != null)
|
||||
adapter.changeCursor(DatabaseHelper.getInstance(ActivityForwarding.this).getForwarding());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
DatabaseHelper.getInstance(this).removeForwardChangedListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
running = false;
|
||||
adapter = null;
|
||||
if (dialog != null) {
|
||||
dialog.dismiss();
|
||||
dialog = null;
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.forwarding, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_add:
|
||||
LayoutInflater inflater = LayoutInflater.from(this);
|
||||
View view = inflater.inflate(R.layout.forwardadd, null, false);
|
||||
final Spinner spProtocol = view.findViewById(R.id.spProtocol);
|
||||
final EditText etDPort = view.findViewById(R.id.etDPort);
|
||||
final EditText etRAddr = view.findViewById(R.id.etRAddr);
|
||||
final EditText etRPort = view.findViewById(R.id.etRPort);
|
||||
final ProgressBar pbRuid = view.findViewById(R.id.pbRUid);
|
||||
final Spinner spRuid = view.findViewById(R.id.spRUid);
|
||||
|
||||
final AsyncTask task = new AsyncTask<Object, Object, List<Rule>>() {
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
pbRuid.setVisibility(View.VISIBLE);
|
||||
spRuid.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Rule> doInBackground(Object... objects) {
|
||||
return Rule.getRules(true, ActivityForwarding.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(List<Rule> rules) {
|
||||
ArrayAdapter spinnerArrayAdapter =
|
||||
new ArrayAdapter(ActivityForwarding.this,
|
||||
android.R.layout.simple_spinner_item, rules);
|
||||
spRuid.setAdapter(spinnerArrayAdapter);
|
||||
pbRuid.setVisibility(View.GONE);
|
||||
spRuid.setVisibility(View.VISIBLE);
|
||||
}
|
||||
};
|
||||
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
|
||||
dialog = new AlertDialog.Builder(this)
|
||||
.setView(view)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
try {
|
||||
int pos = spProtocol.getSelectedItemPosition();
|
||||
String[] values = getResources().getStringArray(R.array.protocolValues);
|
||||
final int protocol = Integer.valueOf(values[pos]);
|
||||
final int dport = Integer.parseInt(etDPort.getText().toString());
|
||||
final String raddr = etRAddr.getText().toString();
|
||||
final int rport = Integer.parseInt(etRPort.getText().toString());
|
||||
final int ruid = ((Rule) spRuid.getSelectedItem()).uid;
|
||||
|
||||
InetAddress iraddr = InetAddress.getByName(raddr);
|
||||
if (rport < 1024 && (iraddr.isLoopbackAddress() || iraddr.isAnyLocalAddress()))
|
||||
throw new IllegalArgumentException("Port forwarding to privileged port on local address not possible");
|
||||
|
||||
new AsyncTask<Object, Object, Throwable>() {
|
||||
@Override
|
||||
protected Throwable doInBackground(Object... objects) {
|
||||
try {
|
||||
DatabaseHelper.getInstance(ActivityForwarding.this)
|
||||
.addForward(protocol, dport, raddr, rport, ruid);
|
||||
return null;
|
||||
} catch (Throwable ex) {
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Throwable ex) {
|
||||
if (running)
|
||||
if (ex == null) {
|
||||
ServiceSinkhole.reload("forwarding", ActivityForwarding.this, false);
|
||||
adapter = new AdapterForwarding(ActivityForwarding.this,
|
||||
DatabaseHelper.getInstance(ActivityForwarding.this).getForwarding());
|
||||
lvForwarding.setAdapter(adapter);
|
||||
} else
|
||||
Toast.makeText(ActivityForwarding.this, ex.toString(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
} catch (Throwable ex) {
|
||||
Toast.makeText(ActivityForwarding.this, ex.toString(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
task.cancel(false);
|
||||
dialog.dismiss();
|
||||
}
|
||||
})
|
||||
.setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialogInterface) {
|
||||
dialog = null;
|
||||
}
|
||||
})
|
||||
.create();
|
||||
dialog.show();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,643 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.FilterQueryProvider;
|
||||
import android.widget.ListView;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.core.app.NavUtils;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class ActivityLog extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private static final String TAG = "NetGuard.Log";
|
||||
|
||||
private boolean running = false;
|
||||
private ListView lvLog;
|
||||
private AdapterLog adapter;
|
||||
private MenuItem menuSearch = null;
|
||||
|
||||
private boolean live;
|
||||
private boolean resolve;
|
||||
private boolean organization;
|
||||
private InetAddress vpn4 = null;
|
||||
private InetAddress vpn6 = null;
|
||||
|
||||
private static final int REQUEST_PCAP = 1;
|
||||
|
||||
private DatabaseHelper.LogChangedListener listener = new DatabaseHelper.LogChangedListener() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateAdapter();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
if (!IAB.isPurchased(ActivityPro.SKU_LOG, this)) {
|
||||
startActivity(new Intent(this, ActivityPro.class));
|
||||
finish();
|
||||
}
|
||||
|
||||
Util.setTheme(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.logging);
|
||||
running = true;
|
||||
|
||||
// Action bar
|
||||
View actionView = getLayoutInflater().inflate(R.layout.actionlog, null, false);
|
||||
SwitchCompat swEnabled = actionView.findViewById(R.id.swEnabled);
|
||||
|
||||
getSupportActionBar().setDisplayShowCustomEnabled(true);
|
||||
getSupportActionBar().setCustomView(actionView);
|
||||
|
||||
getSupportActionBar().setTitle(R.string.menu_log);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
// Get settings
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
resolve = prefs.getBoolean("resolve", false);
|
||||
organization = prefs.getBoolean("organization", false);
|
||||
boolean log = prefs.getBoolean("log", false);
|
||||
|
||||
// Show disabled message
|
||||
TextView tvDisabled = findViewById(R.id.tvDisabled);
|
||||
tvDisabled.setVisibility(log ? View.GONE : View.VISIBLE);
|
||||
|
||||
// Set enabled switch
|
||||
swEnabled.setChecked(log);
|
||||
swEnabled.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
prefs.edit().putBoolean("log", isChecked).apply();
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for preference changes
|
||||
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
|
||||
lvLog = findViewById(R.id.lvLog);
|
||||
|
||||
boolean udp = prefs.getBoolean("proto_udp", true);
|
||||
boolean tcp = prefs.getBoolean("proto_tcp", true);
|
||||
boolean other = prefs.getBoolean("proto_other", true);
|
||||
boolean allowed = prefs.getBoolean("traffic_allowed", true);
|
||||
boolean blocked = prefs.getBoolean("traffic_blocked", true);
|
||||
|
||||
adapter = new AdapterLog(this, DatabaseHelper.getInstance(this).getLog(udp, tcp, other, allowed, blocked), resolve, organization);
|
||||
adapter.setFilterQueryProvider(new FilterQueryProvider() {
|
||||
public Cursor runQuery(CharSequence constraint) {
|
||||
return DatabaseHelper.getInstance(ActivityLog.this).searchLog(constraint.toString());
|
||||
}
|
||||
});
|
||||
|
||||
lvLog.setAdapter(adapter);
|
||||
|
||||
try {
|
||||
vpn4 = InetAddress.getByName(prefs.getString("vpn4", "10.1.10.1"));
|
||||
vpn6 = InetAddress.getByName(prefs.getString("vpn6", "fd00:1:fd00:1:fd00:1:fd00:1"));
|
||||
} catch (UnknownHostException ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
|
||||
lvLog.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
PackageManager pm = getPackageManager();
|
||||
Cursor cursor = (Cursor) adapter.getItem(position);
|
||||
long time = cursor.getLong(cursor.getColumnIndex("time"));
|
||||
int version = cursor.getInt(cursor.getColumnIndex("version"));
|
||||
int protocol = cursor.getInt(cursor.getColumnIndex("protocol"));
|
||||
final String saddr = cursor.getString(cursor.getColumnIndex("saddr"));
|
||||
final int sport = (cursor.isNull(cursor.getColumnIndex("sport")) ? -1 : cursor.getInt(cursor.getColumnIndex("sport")));
|
||||
final String daddr = cursor.getString(cursor.getColumnIndex("daddr"));
|
||||
final int dport = (cursor.isNull(cursor.getColumnIndex("dport")) ? -1 : cursor.getInt(cursor.getColumnIndex("dport")));
|
||||
final String dname = cursor.getString(cursor.getColumnIndex("dname"));
|
||||
final int uid = (cursor.isNull(cursor.getColumnIndex("uid")) ? -1 : cursor.getInt(cursor.getColumnIndex("uid")));
|
||||
int allowed = (cursor.isNull(cursor.getColumnIndex("allowed")) ? -1 : cursor.getInt(cursor.getColumnIndex("allowed")));
|
||||
|
||||
// Get external address
|
||||
InetAddress addr = null;
|
||||
try {
|
||||
addr = InetAddress.getByName(daddr);
|
||||
} catch (UnknownHostException ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
|
||||
String ip;
|
||||
int port;
|
||||
if (addr.equals(vpn4) || addr.equals(vpn6)) {
|
||||
ip = saddr;
|
||||
port = sport;
|
||||
} else {
|
||||
ip = daddr;
|
||||
port = dport;
|
||||
}
|
||||
|
||||
// Build popup menu
|
||||
PopupMenu popup = new PopupMenu(ActivityLog.this, findViewById(R.id.vwPopupAnchor));
|
||||
popup.inflate(R.menu.log);
|
||||
|
||||
// Application name
|
||||
if (uid >= 0)
|
||||
popup.getMenu().findItem(R.id.menu_application).setTitle(TextUtils.join(", ", Util.getApplicationNames(uid, ActivityLog.this)));
|
||||
else
|
||||
popup.getMenu().removeItem(R.id.menu_application);
|
||||
|
||||
// Destination IP
|
||||
popup.getMenu().findItem(R.id.menu_protocol).setTitle(Util.getProtocolName(protocol, version, false));
|
||||
|
||||
// Whois
|
||||
final Intent lookupIP = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.dnslytics.com/whois-lookup/" + ip));
|
||||
if (pm.resolveActivity(lookupIP, 0) == null)
|
||||
popup.getMenu().removeItem(R.id.menu_whois);
|
||||
else
|
||||
popup.getMenu().findItem(R.id.menu_whois).setTitle(getString(R.string.title_log_whois, ip));
|
||||
|
||||
// Lookup port
|
||||
final Intent lookupPort = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.speedguide.net/port.php?port=" + port));
|
||||
if (port <= 0 || pm.resolveActivity(lookupPort, 0) == null)
|
||||
popup.getMenu().removeItem(R.id.menu_port);
|
||||
else
|
||||
popup.getMenu().findItem(R.id.menu_port).setTitle(getString(R.string.title_log_port, port));
|
||||
|
||||
if (prefs.getBoolean("filter", false)) {
|
||||
if (uid <= 0) {
|
||||
popup.getMenu().removeItem(R.id.menu_allow);
|
||||
popup.getMenu().removeItem(R.id.menu_block);
|
||||
}
|
||||
} else {
|
||||
popup.getMenu().removeItem(R.id.menu_allow);
|
||||
popup.getMenu().removeItem(R.id.menu_block);
|
||||
}
|
||||
|
||||
final Packet packet = new Packet();
|
||||
packet.version = version;
|
||||
packet.protocol = protocol;
|
||||
packet.daddr = daddr;
|
||||
packet.dport = dport;
|
||||
packet.time = time;
|
||||
packet.uid = uid;
|
||||
packet.allowed = (allowed > 0);
|
||||
|
||||
// Time
|
||||
popup.getMenu().findItem(R.id.menu_time).setTitle(SimpleDateFormat.getDateTimeInstance().format(time));
|
||||
|
||||
// Handle click
|
||||
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.menu_application: {
|
||||
Intent main = new Intent(ActivityLog.this, ActivityMain.class);
|
||||
main.putExtra(ActivityMain.EXTRA_SEARCH, Integer.toString(uid));
|
||||
startActivity(main);
|
||||
return true;
|
||||
}
|
||||
|
||||
case R.id.menu_whois:
|
||||
startActivity(lookupIP);
|
||||
return true;
|
||||
|
||||
case R.id.menu_port:
|
||||
startActivity(lookupPort);
|
||||
return true;
|
||||
|
||||
case R.id.menu_allow:
|
||||
if (IAB.isPurchased(ActivityPro.SKU_FILTER, ActivityLog.this)) {
|
||||
DatabaseHelper.getInstance(ActivityLog.this).updateAccess(packet, dname, 0);
|
||||
ServiceSinkhole.reload("allow host", ActivityLog.this, false);
|
||||
Intent main = new Intent(ActivityLog.this, ActivityMain.class);
|
||||
main.putExtra(ActivityMain.EXTRA_SEARCH, Integer.toString(uid));
|
||||
startActivity(main);
|
||||
} else
|
||||
startActivity(new Intent(ActivityLog.this, ActivityPro.class));
|
||||
return true;
|
||||
|
||||
case R.id.menu_block:
|
||||
if (IAB.isPurchased(ActivityPro.SKU_FILTER, ActivityLog.this)) {
|
||||
DatabaseHelper.getInstance(ActivityLog.this).updateAccess(packet, dname, 1);
|
||||
ServiceSinkhole.reload("block host", ActivityLog.this, false);
|
||||
Intent main = new Intent(ActivityLog.this, ActivityMain.class);
|
||||
main.putExtra(ActivityMain.EXTRA_SEARCH, Integer.toString(uid));
|
||||
startActivity(main);
|
||||
} else
|
||||
startActivity(new Intent(ActivityLog.this, ActivityPro.class));
|
||||
return true;
|
||||
|
||||
case R.id.menu_copy:
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText("netguard", dname == null ? daddr : dname);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Show
|
||||
popup.show();
|
||||
}
|
||||
});
|
||||
|
||||
live = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (live) {
|
||||
DatabaseHelper.getInstance(this).addLogChangedListener(listener);
|
||||
updateAdapter();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (live)
|
||||
DatabaseHelper.getInstance(this).removeLogChangedListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
running = false;
|
||||
adapter = null;
|
||||
PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String name) {
|
||||
Log.i(TAG, "Preference " + name + "=" + prefs.getAll().get(name));
|
||||
if ("log".equals(name)) {
|
||||
// Get enabled
|
||||
boolean log = prefs.getBoolean(name, false);
|
||||
|
||||
// Display disabled warning
|
||||
TextView tvDisabled = findViewById(R.id.tvDisabled);
|
||||
tvDisabled.setVisibility(log ? View.GONE : View.VISIBLE);
|
||||
|
||||
// Check switch state
|
||||
SwitchCompat swEnabled = getSupportActionBar().getCustomView().findViewById(R.id.swEnabled);
|
||||
if (swEnabled.isChecked() != log)
|
||||
swEnabled.setChecked(log);
|
||||
|
||||
ServiceSinkhole.reload("changed " + name, ActivityLog.this, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.logging, menu);
|
||||
|
||||
menuSearch = menu.findItem(R.id.menu_search);
|
||||
SearchView searchView = (SearchView) menuSearch.getActionView();
|
||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
if (adapter != null)
|
||||
adapter.getFilter().filter(getUidForName(query));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
if (adapter != null)
|
||||
adapter.getFilter().filter(getUidForName(newText));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
searchView.setOnCloseListener(new SearchView.OnCloseListener() {
|
||||
@Override
|
||||
public boolean onClose() {
|
||||
if (adapter != null)
|
||||
adapter.getFilter().filter(null);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
// https://gist.github.com/granoeste/5574148
|
||||
File pcap_file = new File(getDir("data", MODE_PRIVATE), "netguard.pcap");
|
||||
|
||||
boolean export = (getPackageManager().resolveActivity(getIntentPCAPDocument(), 0) != null);
|
||||
|
||||
menu.findItem(R.id.menu_protocol_udp).setChecked(prefs.getBoolean("proto_udp", true));
|
||||
menu.findItem(R.id.menu_protocol_tcp).setChecked(prefs.getBoolean("proto_tcp", true));
|
||||
menu.findItem(R.id.menu_protocol_other).setChecked(prefs.getBoolean("proto_other", true));
|
||||
menu.findItem(R.id.menu_traffic_allowed).setEnabled(prefs.getBoolean("filter", false));
|
||||
menu.findItem(R.id.menu_traffic_allowed).setChecked(prefs.getBoolean("traffic_allowed", true));
|
||||
menu.findItem(R.id.menu_traffic_blocked).setChecked(prefs.getBoolean("traffic_blocked", true));
|
||||
|
||||
menu.findItem(R.id.menu_refresh).setEnabled(!menu.findItem(R.id.menu_log_live).isChecked());
|
||||
menu.findItem(R.id.menu_log_resolve).setChecked(prefs.getBoolean("resolve", false));
|
||||
menu.findItem(R.id.menu_log_organization).setChecked(prefs.getBoolean("organization", false));
|
||||
menu.findItem(R.id.menu_pcap_enabled).setChecked(prefs.getBoolean("pcap", false));
|
||||
menu.findItem(R.id.menu_pcap_export).setEnabled(pcap_file.exists() && export);
|
||||
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
final File pcap_file = new File(getDir("data", MODE_PRIVATE), "netguard.pcap");
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
Log.i(TAG, "Up");
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
return true;
|
||||
|
||||
case R.id.menu_protocol_udp:
|
||||
item.setChecked(!item.isChecked());
|
||||
prefs.edit().putBoolean("proto_udp", item.isChecked()).apply();
|
||||
updateAdapter();
|
||||
return true;
|
||||
|
||||
case R.id.menu_protocol_tcp:
|
||||
item.setChecked(!item.isChecked());
|
||||
prefs.edit().putBoolean("proto_tcp", item.isChecked()).apply();
|
||||
updateAdapter();
|
||||
return true;
|
||||
|
||||
case R.id.menu_protocol_other:
|
||||
item.setChecked(!item.isChecked());
|
||||
prefs.edit().putBoolean("proto_other", item.isChecked()).apply();
|
||||
updateAdapter();
|
||||
return true;
|
||||
|
||||
case R.id.menu_traffic_allowed:
|
||||
item.setChecked(!item.isChecked());
|
||||
prefs.edit().putBoolean("traffic_allowed", item.isChecked()).apply();
|
||||
updateAdapter();
|
||||
return true;
|
||||
|
||||
case R.id.menu_traffic_blocked:
|
||||
item.setChecked(!item.isChecked());
|
||||
prefs.edit().putBoolean("traffic_blocked", item.isChecked()).apply();
|
||||
updateAdapter();
|
||||
return true;
|
||||
|
||||
case R.id.menu_log_live:
|
||||
item.setChecked(!item.isChecked());
|
||||
live = item.isChecked();
|
||||
if (live) {
|
||||
DatabaseHelper.getInstance(this).addLogChangedListener(listener);
|
||||
updateAdapter();
|
||||
} else
|
||||
DatabaseHelper.getInstance(this).removeLogChangedListener(listener);
|
||||
return true;
|
||||
|
||||
case R.id.menu_refresh:
|
||||
updateAdapter();
|
||||
return true;
|
||||
|
||||
case R.id.menu_log_resolve:
|
||||
item.setChecked(!item.isChecked());
|
||||
prefs.edit().putBoolean("resolve", item.isChecked()).apply();
|
||||
adapter.setResolve(item.isChecked());
|
||||
adapter.notifyDataSetChanged();
|
||||
return true;
|
||||
|
||||
case R.id.menu_log_organization:
|
||||
item.setChecked(!item.isChecked());
|
||||
prefs.edit().putBoolean("organization", item.isChecked()).apply();
|
||||
adapter.setOrganization(item.isChecked());
|
||||
adapter.notifyDataSetChanged();
|
||||
return true;
|
||||
|
||||
case R.id.menu_pcap_enabled:
|
||||
item.setChecked(!item.isChecked());
|
||||
prefs.edit().putBoolean("pcap", item.isChecked()).apply();
|
||||
ServiceSinkhole.setPcap(item.isChecked(), ActivityLog.this);
|
||||
return true;
|
||||
|
||||
case R.id.menu_pcap_export:
|
||||
startActivityForResult(getIntentPCAPDocument(), REQUEST_PCAP);
|
||||
return true;
|
||||
|
||||
case R.id.menu_log_clear:
|
||||
new AsyncTask<Object, Object, Object>() {
|
||||
@Override
|
||||
protected Object doInBackground(Object... objects) {
|
||||
DatabaseHelper.getInstance(ActivityLog.this).clearLog(-1);
|
||||
if (prefs.getBoolean("pcap", false)) {
|
||||
ServiceSinkhole.setPcap(false, ActivityLog.this);
|
||||
if (pcap_file.exists() && !pcap_file.delete())
|
||||
Log.w(TAG, "Delete PCAP failed");
|
||||
ServiceSinkhole.setPcap(true, ActivityLog.this);
|
||||
} else {
|
||||
if (pcap_file.exists() && !pcap_file.delete())
|
||||
Log.w(TAG, "Delete PCAP failed");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Object result) {
|
||||
if (running)
|
||||
updateAdapter();
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
return true;
|
||||
|
||||
case R.id.menu_log_support:
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse("https://github.com/M66B/NetGuard/blob/master/FAQ.md#user-content-faq27"));
|
||||
if (getPackageManager().resolveActivity(intent, 0) != null)
|
||||
startActivity(intent);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAdapter() {
|
||||
if (adapter != null) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
boolean udp = prefs.getBoolean("proto_udp", true);
|
||||
boolean tcp = prefs.getBoolean("proto_tcp", true);
|
||||
boolean other = prefs.getBoolean("proto_other", true);
|
||||
boolean allowed = prefs.getBoolean("traffic_allowed", true);
|
||||
boolean blocked = prefs.getBoolean("traffic_blocked", true);
|
||||
adapter.changeCursor(DatabaseHelper.getInstance(this).getLog(udp, tcp, other, allowed, blocked));
|
||||
if (menuSearch != null && menuSearch.isActionViewExpanded()) {
|
||||
SearchView searchView = (SearchView) menuSearch.getActionView();
|
||||
adapter.getFilter().filter(getUidForName(searchView.getQuery().toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getUidForName(String query) {
|
||||
if (query != null && query.length() > 0) {
|
||||
for (Rule rule : Rule.getRules(true, ActivityLog.this))
|
||||
if (rule.name != null && rule.name.toLowerCase().contains(query.toLowerCase())) {
|
||||
String newQuery = Integer.toString(rule.uid);
|
||||
Log.i(TAG, "Search " + query + " found " + rule.name + " new " + newQuery);
|
||||
return newQuery;
|
||||
}
|
||||
Log.i(TAG, "Search " + query + " not found");
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
private Intent getIntentPCAPDocument() {
|
||||
Intent intent;
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
if (Util.isPackageInstalled("org.openintents.filemanager", this)) {
|
||||
intent = new Intent("org.openintents.action.PICK_DIRECTORY");
|
||||
} else {
|
||||
intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse("https://play.google.com/store/apps/details?id=org.openintents.filemanager"));
|
||||
}
|
||||
} else {
|
||||
intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("application/octet-stream");
|
||||
intent.putExtra(Intent.EXTRA_TITLE, "netguard_" + new SimpleDateFormat("yyyyMMdd").format(new Date().getTime()) + ".pcap");
|
||||
}
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
|
||||
Log.i(TAG, "onActivityResult request=" + requestCode + " result=" + requestCode + " ok=" + (resultCode == RESULT_OK));
|
||||
|
||||
if (requestCode == REQUEST_PCAP) {
|
||||
if (resultCode == RESULT_OK && data != null)
|
||||
handleExportPCAP(data);
|
||||
|
||||
} else {
|
||||
Log.w(TAG, "Unknown activity result request=" + requestCode);
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleExportPCAP(final Intent data) {
|
||||
new AsyncTask<Object, Object, Throwable>() {
|
||||
@Override
|
||||
protected Throwable doInBackground(Object... objects) {
|
||||
OutputStream out = null;
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
// Stop capture
|
||||
ServiceSinkhole.setPcap(false, ActivityLog.this);
|
||||
|
||||
Uri target = data.getData();
|
||||
if (data.hasExtra("org.openintents.extra.DIR_PATH"))
|
||||
target = Uri.parse(target + "/netguard.pcap");
|
||||
Log.i(TAG, "Export PCAP URI=" + target);
|
||||
out = getContentResolver().openOutputStream(target);
|
||||
|
||||
File pcap = new File(getDir("data", MODE_PRIVATE), "netguard.pcap");
|
||||
in = new FileInputStream(pcap);
|
||||
|
||||
int len;
|
||||
long total = 0;
|
||||
byte[] buf = new byte[4096];
|
||||
while ((len = in.read(buf)) > 0) {
|
||||
out.write(buf, 0, len);
|
||||
total += len;
|
||||
}
|
||||
Log.i(TAG, "Copied bytes=" + total);
|
||||
|
||||
return null;
|
||||
} catch (Throwable ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
return ex;
|
||||
} finally {
|
||||
if (out != null)
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
if (in != null)
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
|
||||
// Resume capture
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ActivityLog.this);
|
||||
if (prefs.getBoolean("pcap", false))
|
||||
ServiceSinkhole.setPcap(true, ActivityLog.this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Throwable ex) {
|
||||
if (ex == null)
|
||||
Toast.makeText(ActivityLog.this, R.string.msg_completed, Toast.LENGTH_LONG).show();
|
||||
else
|
||||
Toast.makeText(ActivityLog.this, ex.toString(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
}
|
@ -0,0 +1,447 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Paint;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.NavUtils;
|
||||
|
||||
import static android.content.ClipDescription.MIMETYPE_TEXT_PLAIN;
|
||||
|
||||
public class ActivityPro extends AppCompatActivity {
|
||||
private static final String TAG = "NetGuard.Pro";
|
||||
|
||||
private IAB iab;
|
||||
|
||||
// adb shell pm clear com.android.vending
|
||||
// android.test.purchased
|
||||
|
||||
private static final int SKU_LOG_ID = 1;
|
||||
private static final int SKU_FILTER_ID = 2;
|
||||
private static final int SKU_NOTIFY_ID = 3;
|
||||
private static final int SKU_SPEED_ID = 4;
|
||||
private static final int SKU_THEME_ID = 5;
|
||||
private static final int SKU_PRO1_ID = 6;
|
||||
private static final int SKU_SUPPORT1_ID = 7;
|
||||
private static final int SKU_SUPPORT2_ID = 8;
|
||||
|
||||
public static final String SKU_LOG = "log";
|
||||
public static final String SKU_FILTER = "filter";
|
||||
public static final String SKU_NOTIFY = "notify";
|
||||
public static final String SKU_SPEED = "speed";
|
||||
public static final String SKU_THEME = "theme";
|
||||
public static final String SKU_PRO1 = "pro1";
|
||||
public static final String SKU_SUPPORT1 = "support1";
|
||||
public static final String SKU_SUPPORT2 = "support2";
|
||||
public static final String SKU_DONATION = "donation";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
Log.i(TAG, "Create");
|
||||
Util.setTheme(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.pro);
|
||||
|
||||
getSupportActionBar().setTitle(R.string.title_pro);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
|
||||
|
||||
// Initial state
|
||||
updateState();
|
||||
|
||||
TextView tvLogTitle = findViewById(R.id.tvLogTitle);
|
||||
TextView tvFilterTitle = findViewById(R.id.tvFilterTitle);
|
||||
TextView tvNotifyTitle = findViewById(R.id.tvNotifyTitle);
|
||||
TextView tvSpeedTitle = findViewById(R.id.tvSpeedTitle);
|
||||
TextView tvThemeTitle = findViewById(R.id.tvThemeTitle);
|
||||
TextView tvAllTitle = findViewById(R.id.tvAllTitle);
|
||||
TextView tvDev1Title = findViewById(R.id.tvDev1Title);
|
||||
TextView tvDev2Title = findViewById(R.id.tvDev2Title);
|
||||
|
||||
tvLogTitle.setPaintFlags(tvLogTitle.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
||||
tvFilterTitle.setPaintFlags(tvLogTitle.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
||||
tvNotifyTitle.setPaintFlags(tvLogTitle.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
||||
tvSpeedTitle.setPaintFlags(tvLogTitle.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
||||
tvThemeTitle.setPaintFlags(tvLogTitle.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
||||
tvAllTitle.setPaintFlags(tvLogTitle.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
||||
tvDev1Title.setPaintFlags(tvLogTitle.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
||||
tvDev2Title.setPaintFlags(tvLogTitle.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
||||
|
||||
View.OnClickListener listener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
String sku;
|
||||
switch (view.getId()) {
|
||||
case R.id.tvLogTitle:
|
||||
sku = SKU_LOG;
|
||||
break;
|
||||
case R.id.tvFilterTitle:
|
||||
sku = SKU_FILTER;
|
||||
break;
|
||||
case R.id.tvNotifyTitle:
|
||||
sku = SKU_NOTIFY;
|
||||
break;
|
||||
case R.id.tvSpeedTitle:
|
||||
sku = SKU_SPEED;
|
||||
break;
|
||||
case R.id.tvThemeTitle:
|
||||
sku = SKU_THEME;
|
||||
break;
|
||||
case R.id.tvAllTitle:
|
||||
sku = SKU_PRO1;
|
||||
break;
|
||||
case R.id.tvDev1Title:
|
||||
sku = SKU_SUPPORT1;
|
||||
break;
|
||||
case R.id.tvDev2Title:
|
||||
sku = SKU_SUPPORT2;
|
||||
break;
|
||||
default:
|
||||
sku = SKU_PRO1;
|
||||
break;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse("http://www.netguard.me/#" + sku));
|
||||
if (intent.resolveActivity(getPackageManager()) != null)
|
||||
startActivity(intent);
|
||||
}
|
||||
};
|
||||
|
||||
tvLogTitle.setOnClickListener(listener);
|
||||
tvFilterTitle.setOnClickListener(listener);
|
||||
tvNotifyTitle.setOnClickListener(listener);
|
||||
tvSpeedTitle.setOnClickListener(listener);
|
||||
tvThemeTitle.setOnClickListener(listener);
|
||||
tvAllTitle.setOnClickListener(listener);
|
||||
tvDev1Title.setOnClickListener(listener);
|
||||
tvDev2Title.setOnClickListener(listener);
|
||||
|
||||
try {
|
||||
iab = new IAB(new IAB.Delegate() {
|
||||
@Override
|
||||
public void onReady(final IAB iab) {
|
||||
Log.i(TAG, "IAB ready");
|
||||
try {
|
||||
iab.updatePurchases();
|
||||
updateState();
|
||||
|
||||
final Button btnLog = findViewById(R.id.btnLog);
|
||||
final Button btnFilter = findViewById(R.id.btnFilter);
|
||||
final Button btnNotify = findViewById(R.id.btnNotify);
|
||||
final Button btnSpeed = findViewById(R.id.btnSpeed);
|
||||
final Button btnTheme = findViewById(R.id.btnTheme);
|
||||
final Button btnAll = findViewById(R.id.btnAll);
|
||||
final Button btnDev1 = findViewById(R.id.btnDev1);
|
||||
final Button btnDev2 = findViewById(R.id.btnDev2);
|
||||
|
||||
View.OnClickListener listener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
try {
|
||||
int id = 0;
|
||||
PendingIntent pi = null;
|
||||
if (view == btnLog) {
|
||||
id = SKU_LOG_ID;
|
||||
pi = iab.getBuyIntent(SKU_LOG, false);
|
||||
} else if (view == btnFilter) {
|
||||
id = SKU_FILTER_ID;
|
||||
pi = iab.getBuyIntent(SKU_FILTER, false);
|
||||
} else if (view == btnNotify) {
|
||||
id = SKU_NOTIFY_ID;
|
||||
pi = iab.getBuyIntent(SKU_NOTIFY, false);
|
||||
} else if (view == btnSpeed) {
|
||||
id = SKU_SPEED_ID;
|
||||
pi = iab.getBuyIntent(SKU_SPEED, false);
|
||||
} else if (view == btnTheme) {
|
||||
id = SKU_THEME_ID;
|
||||
pi = iab.getBuyIntent(SKU_THEME, false);
|
||||
} else if (view == btnAll) {
|
||||
id = SKU_PRO1_ID;
|
||||
pi = iab.getBuyIntent(SKU_PRO1, false);
|
||||
} else if (view == btnDev1) {
|
||||
id = SKU_SUPPORT1_ID;
|
||||
pi = iab.getBuyIntent(SKU_SUPPORT1, true);
|
||||
} else if (view == btnDev2) {
|
||||
id = SKU_SUPPORT2_ID;
|
||||
pi = iab.getBuyIntent(SKU_SUPPORT2, true);
|
||||
}
|
||||
|
||||
if (id > 0 && pi != null)
|
||||
startIntentSenderForResult(pi.getIntentSender(), id, new Intent(), 0, 0, 0);
|
||||
} catch (Throwable ex) {
|
||||
Log.i(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
btnLog.setOnClickListener(listener);
|
||||
btnFilter.setOnClickListener(listener);
|
||||
btnNotify.setOnClickListener(listener);
|
||||
btnSpeed.setOnClickListener(listener);
|
||||
btnTheme.setOnClickListener(listener);
|
||||
btnAll.setOnClickListener(listener);
|
||||
btnDev1.setOnClickListener(listener);
|
||||
btnDev2.setOnClickListener(listener);
|
||||
|
||||
btnLog.setEnabled(true);
|
||||
btnFilter.setEnabled(true);
|
||||
btnNotify.setEnabled(true);
|
||||
btnSpeed.setEnabled(true);
|
||||
btnTheme.setEnabled(true);
|
||||
btnAll.setEnabled(true);
|
||||
btnDev1.setEnabled(true);
|
||||
btnDev2.setEnabled(true);
|
||||
|
||||
} catch (Throwable ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
iab.bind();
|
||||
} catch (Throwable ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
Log.i(TAG, "Destroy");
|
||||
iab.unbind();
|
||||
iab = null;
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.pro, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
Log.i(TAG, "Up");
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
return true;
|
||||
case R.id.menu_challenge:
|
||||
menu_challenge();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
if (IAB.isPurchased(SKU_DONATION, this) || Util.isPlayStoreInstall(this))
|
||||
menu.removeItem(R.id.menu_challenge);
|
||||
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
private void menu_challenge() {
|
||||
LayoutInflater inflater = LayoutInflater.from(this);
|
||||
View view = inflater.inflate(R.layout.challenge, null, false);
|
||||
|
||||
final AlertDialog dialog = new AlertDialog.Builder(this)
|
||||
.setView(view)
|
||||
.setCancelable(true)
|
||||
.create();
|
||||
|
||||
String android_id = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
|
||||
final String challenge = (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? Build.SERIAL : "O3" + android_id);
|
||||
String seed = (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? "NetGuard2" : "NetGuard3");
|
||||
|
||||
// Challenge
|
||||
TextView tvChallenge = view.findViewById(R.id.tvChallenge);
|
||||
tvChallenge.setText(challenge);
|
||||
|
||||
ImageButton ibCopy = view.findViewById(R.id.ibCopy);
|
||||
ibCopy.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText(getString(R.string.title_pro_challenge), challenge);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
Toast.makeText(ActivityPro.this, android.R.string.copy, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
|
||||
// Response
|
||||
final EditText etResponse = view.findViewById(R.id.etResponse);
|
||||
try {
|
||||
final String response = Util.md5(challenge, seed);
|
||||
etResponse.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
if (response.equals(editable.toString().toUpperCase())) {
|
||||
IAB.setBought(SKU_DONATION, ActivityPro.this);
|
||||
dialog.dismiss();
|
||||
invalidateOptionsMenu();
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Throwable ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
|
||||
ImageButton ibPaste = view.findViewById(R.id.ibPaste);
|
||||
ibPaste.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
if (clipboard != null &&
|
||||
clipboard.hasPrimaryClip() &&
|
||||
clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN)) {
|
||||
ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0);
|
||||
etResponse.setText(item.getText().toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (resultCode == RESULT_OK) {
|
||||
switch (requestCode) {
|
||||
case SKU_LOG_ID:
|
||||
IAB.setBought(SKU_LOG, this);
|
||||
updateState();
|
||||
break;
|
||||
case SKU_FILTER_ID:
|
||||
IAB.setBought(SKU_FILTER, this);
|
||||
updateState();
|
||||
break;
|
||||
case SKU_NOTIFY_ID:
|
||||
IAB.setBought(SKU_NOTIFY, this);
|
||||
updateState();
|
||||
break;
|
||||
case SKU_SPEED_ID:
|
||||
IAB.setBought(SKU_SPEED, this);
|
||||
updateState();
|
||||
break;
|
||||
case SKU_THEME_ID:
|
||||
IAB.setBought(SKU_THEME, this);
|
||||
updateState();
|
||||
break;
|
||||
case SKU_PRO1_ID:
|
||||
IAB.setBought(SKU_PRO1, this);
|
||||
updateState();
|
||||
break;
|
||||
case SKU_SUPPORT1_ID:
|
||||
IAB.setBought(SKU_SUPPORT1, this);
|
||||
updateState();
|
||||
break;
|
||||
case SKU_SUPPORT2_ID:
|
||||
IAB.setBought(SKU_SUPPORT2, this);
|
||||
updateState();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateState() {
|
||||
Button btnLog = findViewById(R.id.btnLog);
|
||||
Button btnFilter = findViewById(R.id.btnFilter);
|
||||
Button btnNotify = findViewById(R.id.btnNotify);
|
||||
Button btnSpeed = findViewById(R.id.btnSpeed);
|
||||
Button btnTheme = findViewById(R.id.btnTheme);
|
||||
Button btnAll = findViewById(R.id.btnAll);
|
||||
Button btnDev1 = findViewById(R.id.btnDev1);
|
||||
Button btnDev2 = findViewById(R.id.btnDev2);
|
||||
TextView tvLog = findViewById(R.id.tvLog);
|
||||
TextView tvFilter = findViewById(R.id.tvFilter);
|
||||
TextView tvNotify = findViewById(R.id.tvNotify);
|
||||
TextView tvSpeed = findViewById(R.id.tvSpeed);
|
||||
TextView tvTheme = findViewById(R.id.tvTheme);
|
||||
TextView tvAll = findViewById(R.id.tvAll);
|
||||
TextView tvDev1 = findViewById(R.id.tvDev1);
|
||||
TextView tvDev2 = findViewById(R.id.tvDev2);
|
||||
|
||||
TextView tvLogUnavailable = findViewById(R.id.tvLogUnavailable);
|
||||
TextView tvFilterUnavailable = findViewById(R.id.tvFilterUnavailable);
|
||||
|
||||
boolean can = Util.canFilter(this);
|
||||
|
||||
btnLog.setVisibility(IAB.isPurchased(SKU_LOG, this) || !can ? View.GONE : View.VISIBLE);
|
||||
btnFilter.setVisibility(IAB.isPurchased(SKU_FILTER, this) || !can ? View.GONE : View.VISIBLE);
|
||||
btnNotify.setVisibility(IAB.isPurchased(SKU_NOTIFY, this) ? View.GONE : View.VISIBLE);
|
||||
btnSpeed.setVisibility(IAB.isPurchased(SKU_SPEED, this) ? View.GONE : View.VISIBLE);
|
||||
btnTheme.setVisibility(IAB.isPurchased(SKU_THEME, this) ? View.GONE : View.VISIBLE);
|
||||
btnAll.setVisibility(IAB.isPurchased(SKU_PRO1, this) ? View.GONE : View.VISIBLE);
|
||||
btnDev1.setVisibility(IAB.isPurchased(SKU_SUPPORT1, this) ? View.GONE : View.VISIBLE);
|
||||
btnDev2.setVisibility(IAB.isPurchased(SKU_SUPPORT2, this) ? View.GONE : View.VISIBLE);
|
||||
|
||||
tvLog.setVisibility(IAB.isPurchased(SKU_LOG, this) && can ? View.VISIBLE : View.GONE);
|
||||
tvFilter.setVisibility(IAB.isPurchased(SKU_FILTER, this) && can ? View.VISIBLE : View.GONE);
|
||||
tvNotify.setVisibility(IAB.isPurchased(SKU_NOTIFY, this) ? View.VISIBLE : View.GONE);
|
||||
tvSpeed.setVisibility(IAB.isPurchased(SKU_SPEED, this) ? View.VISIBLE : View.GONE);
|
||||
tvTheme.setVisibility(IAB.isPurchased(SKU_THEME, this) ? View.VISIBLE : View.GONE);
|
||||
tvAll.setVisibility(IAB.isPurchased(SKU_PRO1, this) ? View.VISIBLE : View.GONE);
|
||||
tvDev1.setVisibility(IAB.isPurchased(SKU_SUPPORT1, this) ? View.VISIBLE : View.GONE);
|
||||
tvDev2.setVisibility(IAB.isPurchased(SKU_SUPPORT2, this) ? View.VISIBLE : View.GONE);
|
||||
|
||||
tvLogUnavailable.setVisibility(can ? View.GONE : View.VISIBLE);
|
||||
tvFilterUnavailable.setVisibility(can ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
}
|
@ -0,0 +1,186 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.UnderlineSpan;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
public class AdapterAccess extends CursorAdapter {
|
||||
private int colVersion;
|
||||
private int colProtocol;
|
||||
private int colDaddr;
|
||||
private int colDPort;
|
||||
private int colTime;
|
||||
private int colAllowed;
|
||||
private int colBlock;
|
||||
private int colCount;
|
||||
private int colSent;
|
||||
private int colReceived;
|
||||
private int colConnections;
|
||||
|
||||
private int colorText;
|
||||
private int colorOn;
|
||||
private int colorOff;
|
||||
|
||||
public AdapterAccess(Context context, Cursor cursor) {
|
||||
super(context, cursor, 0);
|
||||
colVersion = cursor.getColumnIndex("version");
|
||||
colProtocol = cursor.getColumnIndex("protocol");
|
||||
colDaddr = cursor.getColumnIndex("daddr");
|
||||
colDPort = cursor.getColumnIndex("dport");
|
||||
colTime = cursor.getColumnIndex("time");
|
||||
colAllowed = cursor.getColumnIndex("allowed");
|
||||
colBlock = cursor.getColumnIndex("block");
|
||||
colCount = cursor.getColumnIndex("count");
|
||||
colSent = cursor.getColumnIndex("sent");
|
||||
colReceived = cursor.getColumnIndex("received");
|
||||
colConnections = cursor.getColumnIndex("connections");
|
||||
|
||||
TypedArray ta = context.getTheme().obtainStyledAttributes(new int[]{android.R.attr.textColorSecondary});
|
||||
try {
|
||||
colorText = ta.getColor(0, 0);
|
||||
} finally {
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
TypedValue tv = new TypedValue();
|
||||
context.getTheme().resolveAttribute(R.attr.colorOn, tv, true);
|
||||
colorOn = tv.data;
|
||||
context.getTheme().resolveAttribute(R.attr.colorOff, tv, true);
|
||||
colorOff = tv.data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return LayoutInflater.from(context).inflate(R.layout.access, parent, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(final View view, final Context context, final Cursor cursor) {
|
||||
// Get values
|
||||
final int version = cursor.getInt(colVersion);
|
||||
final int protocol = cursor.getInt(colProtocol);
|
||||
final String daddr = cursor.getString(colDaddr);
|
||||
final int dport = cursor.getInt(colDPort);
|
||||
long time = cursor.getLong(colTime);
|
||||
int allowed = cursor.getInt(colAllowed);
|
||||
int block = cursor.getInt(colBlock);
|
||||
int count = cursor.getInt(colCount);
|
||||
long sent = cursor.isNull(colSent) ? -1 : cursor.getLong(colSent);
|
||||
long received = cursor.isNull(colReceived) ? -1 : cursor.getLong(colReceived);
|
||||
int connections = cursor.isNull(colConnections) ? -1 : cursor.getInt(colConnections);
|
||||
|
||||
// Get views
|
||||
TextView tvTime = view.findViewById(R.id.tvTime);
|
||||
ImageView ivBlock = view.findViewById(R.id.ivBlock);
|
||||
final TextView tvDest = view.findViewById(R.id.tvDest);
|
||||
LinearLayout llTraffic = view.findViewById(R.id.llTraffic);
|
||||
TextView tvConnections = view.findViewById(R.id.tvConnections);
|
||||
TextView tvTraffic = view.findViewById(R.id.tvTraffic);
|
||||
|
||||
// Set values
|
||||
tvTime.setText(new SimpleDateFormat("dd HH:mm").format(time));
|
||||
if (block < 0)
|
||||
ivBlock.setImageDrawable(null);
|
||||
else {
|
||||
ivBlock.setImageResource(block > 0 ? R.drawable.host_blocked : R.drawable.host_allowed);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
Drawable wrap = DrawableCompat.wrap(ivBlock.getDrawable());
|
||||
DrawableCompat.setTint(wrap, block > 0 ? colorOff : colorOn);
|
||||
}
|
||||
}
|
||||
|
||||
String dest = Util.getProtocolName(protocol, version, true) +
|
||||
" " + daddr + (dport > 0 ? "/" + dport : "") + (count > 1 ? " ?" + count : "");
|
||||
SpannableString span = new SpannableString(dest);
|
||||
span.setSpan(new UnderlineSpan(), 0, dest.length(), 0);
|
||||
tvDest.setText(span);
|
||||
|
||||
if (Util.isNumericAddress(daddr))
|
||||
new AsyncTask<String, Object, String>() {
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
ViewCompat.setHasTransientState(tvDest, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doInBackground(String... args) {
|
||||
try {
|
||||
return InetAddress.getByName(args[0]).getHostName();
|
||||
} catch (UnknownHostException ignored) {
|
||||
return args[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(String addr) {
|
||||
tvDest.setText(
|
||||
Util.getProtocolName(protocol, version, true) +
|
||||
" >" + addr + (dport > 0 ? "/" + dport : ""));
|
||||
ViewCompat.setHasTransientState(tvDest, false);
|
||||
}
|
||||
}.execute(daddr);
|
||||
|
||||
if (allowed < 0)
|
||||
tvDest.setTextColor(colorText);
|
||||
else if (allowed > 0)
|
||||
tvDest.setTextColor(colorOn);
|
||||
else
|
||||
tvDest.setTextColor(colorOff);
|
||||
|
||||
llTraffic.setVisibility(connections > 0 || sent > 0 || received > 0 ? View.VISIBLE : View.GONE);
|
||||
if (connections > 0)
|
||||
tvConnections.setText(context.getString(R.string.msg_count, connections));
|
||||
|
||||
if (sent > 1024 * 1204 * 1024L || received > 1024 * 1024 * 1024L)
|
||||
tvTraffic.setText(context.getString(R.string.msg_gb,
|
||||
(sent > 0 ? sent / (1024 * 1024 * 1024f) : 0),
|
||||
(received > 0 ? received / (1024 * 1024 * 1024f) : 0)));
|
||||
else if (sent > 1204 * 1024L || received > 1024 * 1024L)
|
||||
tvTraffic.setText(context.getString(R.string.msg_mb,
|
||||
(sent > 0 ? sent / (1024 * 1024f) : 0),
|
||||
(received > 0 ? received / (1024 * 1024f) : 0)));
|
||||
else
|
||||
tvTraffic.setText(context.getString(R.string.msg_kb,
|
||||
(sent > 0 ? sent / 1024f : 0),
|
||||
(received > 0 ? received / 1024f : 0)));
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class AdapterDns extends CursorAdapter {
|
||||
private int colorExpired;
|
||||
|
||||
private int colTime;
|
||||
private int colQName;
|
||||
private int colAName;
|
||||
private int colResource;
|
||||
private int colTTL;
|
||||
|
||||
public AdapterDns(Context context, Cursor cursor) {
|
||||
super(context, cursor, 0);
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
if (prefs.getBoolean("dark_theme", false))
|
||||
colorExpired = Color.argb(128, Color.red(Color.DKGRAY), Color.green(Color.DKGRAY), Color.blue(Color.DKGRAY));
|
||||
else
|
||||
colorExpired = Color.argb(128, Color.red(Color.LTGRAY), Color.green(Color.LTGRAY), Color.blue(Color.LTGRAY));
|
||||
|
||||
colTime = cursor.getColumnIndex("time");
|
||||
colQName = cursor.getColumnIndex("qname");
|
||||
colAName = cursor.getColumnIndex("aname");
|
||||
colResource = cursor.getColumnIndex("resource");
|
||||
colTTL = cursor.getColumnIndex("ttl");
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return LayoutInflater.from(context).inflate(R.layout.dns, parent, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(final View view, final Context context, final Cursor cursor) {
|
||||
// Get values
|
||||
long time = cursor.getLong(colTime);
|
||||
String qname = cursor.getString(colQName);
|
||||
String aname = cursor.getString(colAName);
|
||||
String resource = cursor.getString(colResource);
|
||||
int ttl = cursor.getInt(colTTL);
|
||||
|
||||
long now = new Date().getTime();
|
||||
boolean expired = (time + ttl < now);
|
||||
view.setBackgroundColor(expired ? colorExpired : Color.TRANSPARENT);
|
||||
|
||||
// Get views
|
||||
TextView tvTime = view.findViewById(R.id.tvTime);
|
||||
TextView tvQName = view.findViewById(R.id.tvQName);
|
||||
TextView tvAName = view.findViewById(R.id.tvAName);
|
||||
TextView tvResource = view.findViewById(R.id.tvResource);
|
||||
TextView tvTTL = view.findViewById(R.id.tvTTL);
|
||||
|
||||
// Set values
|
||||
tvTime.setText(new SimpleDateFormat("dd HH:mm").format(time));
|
||||
tvQName.setText(qname);
|
||||
tvAName.setText(aname);
|
||||
tvResource.setText(resource);
|
||||
tvTTL.setText("+" + Integer.toString(ttl / 1000));
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class AdapterForwarding extends CursorAdapter {
|
||||
private int colProtocol;
|
||||
private int colDPort;
|
||||
private int colRAddr;
|
||||
private int colRPort;
|
||||
private int colRUid;
|
||||
|
||||
public AdapterForwarding(Context context, Cursor cursor) {
|
||||
super(context, cursor, 0);
|
||||
colProtocol = cursor.getColumnIndex("protocol");
|
||||
colDPort = cursor.getColumnIndex("dport");
|
||||
colRAddr = cursor.getColumnIndex("raddr");
|
||||
colRPort = cursor.getColumnIndex("rport");
|
||||
colRUid = cursor.getColumnIndex("ruid");
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return LayoutInflater.from(context).inflate(R.layout.forward, parent, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(final View view, final Context context, final Cursor cursor) {
|
||||
// Get values
|
||||
int protocol = cursor.getInt(colProtocol);
|
||||
int dport = cursor.getInt(colDPort);
|
||||
String raddr = cursor.getString(colRAddr);
|
||||
int rport = cursor.getInt(colRPort);
|
||||
int ruid = cursor.getInt(colRUid);
|
||||
|
||||
// Get views
|
||||
TextView tvProtocol = view.findViewById(R.id.tvProtocol);
|
||||
TextView tvDPort = view.findViewById(R.id.tvDPort);
|
||||
TextView tvRAddr = view.findViewById(R.id.tvRAddr);
|
||||
TextView tvRPort = view.findViewById(R.id.tvRPort);
|
||||
TextView tvRUid = view.findViewById(R.id.tvRUid);
|
||||
|
||||
tvProtocol.setText(Util.getProtocolName(protocol, 0, false));
|
||||
tvDPort.setText(Integer.toString(dport));
|
||||
tvRAddr.setText(raddr);
|
||||
tvRPort.setText(Integer.toString(rport));
|
||||
tvRUid.setText(TextUtils.join(", ", Util.getApplicationNames(ruid, context)));
|
||||
}
|
||||
}
|
@ -0,0 +1,370 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
|
||||
public class AdapterLog extends CursorAdapter {
|
||||
private static String TAG = "NetGuard.Log";
|
||||
|
||||
private boolean resolve;
|
||||
private boolean organization;
|
||||
private int colTime;
|
||||
private int colVersion;
|
||||
private int colProtocol;
|
||||
private int colFlags;
|
||||
private int colSAddr;
|
||||
private int colSPort;
|
||||
private int colDAddr;
|
||||
private int colDPort;
|
||||
private int colDName;
|
||||
private int colUid;
|
||||
private int colData;
|
||||
private int colAllowed;
|
||||
private int colConnection;
|
||||
private int colInteractive;
|
||||
private int colorOn;
|
||||
private int colorOff;
|
||||
private int iconSize;
|
||||
private InetAddress dns1 = null;
|
||||
private InetAddress dns2 = null;
|
||||
private InetAddress vpn4 = null;
|
||||
private InetAddress vpn6 = null;
|
||||
|
||||
public AdapterLog(Context context, Cursor cursor, boolean resolve, boolean organization) {
|
||||
super(context, cursor, 0);
|
||||
this.resolve = resolve;
|
||||
this.organization = organization;
|
||||
colTime = cursor.getColumnIndex("time");
|
||||
colVersion = cursor.getColumnIndex("version");
|
||||
colProtocol = cursor.getColumnIndex("protocol");
|
||||
colFlags = cursor.getColumnIndex("flags");
|
||||
colSAddr = cursor.getColumnIndex("saddr");
|
||||
colSPort = cursor.getColumnIndex("sport");
|
||||
colDAddr = cursor.getColumnIndex("daddr");
|
||||
colDPort = cursor.getColumnIndex("dport");
|
||||
colDName = cursor.getColumnIndex("dname");
|
||||
colUid = cursor.getColumnIndex("uid");
|
||||
colData = cursor.getColumnIndex("data");
|
||||
colAllowed = cursor.getColumnIndex("allowed");
|
||||
colConnection = cursor.getColumnIndex("connection");
|
||||
colInteractive = cursor.getColumnIndex("interactive");
|
||||
|
||||
TypedValue tv = new TypedValue();
|
||||
context.getTheme().resolveAttribute(R.attr.colorOn, tv, true);
|
||||
colorOn = tv.data;
|
||||
context.getTheme().resolveAttribute(R.attr.colorOff, tv, true);
|
||||
colorOff = tv.data;
|
||||
|
||||
iconSize = Util.dips2pixels(24, context);
|
||||
|
||||
try {
|
||||
List<InetAddress> lstDns = ServiceSinkhole.getDns(context);
|
||||
dns1 = (lstDns.size() > 0 ? lstDns.get(0) : null);
|
||||
dns2 = (lstDns.size() > 1 ? lstDns.get(1) : null);
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
vpn4 = InetAddress.getByName(prefs.getString("vpn4", "10.1.10.1"));
|
||||
vpn6 = InetAddress.getByName(prefs.getString("vpn6", "fd00:1:fd00:1:fd00:1:fd00:1"));
|
||||
} catch (UnknownHostException ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public void setResolve(boolean resolve) {
|
||||
this.resolve = resolve;
|
||||
}
|
||||
|
||||
public void setOrganization(boolean organization) {
|
||||
this.organization = organization;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return LayoutInflater.from(context).inflate(R.layout.log, parent, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(final View view, final Context context, final Cursor cursor) {
|
||||
// Get values
|
||||
long time = cursor.getLong(colTime);
|
||||
int version = (cursor.isNull(colVersion) ? -1 : cursor.getInt(colVersion));
|
||||
int protocol = (cursor.isNull(colProtocol) ? -1 : cursor.getInt(colProtocol));
|
||||
String flags = cursor.getString(colFlags);
|
||||
String saddr = cursor.getString(colSAddr);
|
||||
int sport = (cursor.isNull(colSPort) ? -1 : cursor.getInt(colSPort));
|
||||
String daddr = cursor.getString(colDAddr);
|
||||
int dport = (cursor.isNull(colDPort) ? -1 : cursor.getInt(colDPort));
|
||||
String dname = (cursor.isNull(colDName) ? null : cursor.getString(colDName));
|
||||
int uid = (cursor.isNull(colUid) ? -1 : cursor.getInt(colUid));
|
||||
String data = cursor.getString(colData);
|
||||
int allowed = (cursor.isNull(colAllowed) ? -1 : cursor.getInt(colAllowed));
|
||||
int connection = (cursor.isNull(colConnection) ? -1 : cursor.getInt(colConnection));
|
||||
int interactive = (cursor.isNull(colInteractive) ? -1 : cursor.getInt(colInteractive));
|
||||
|
||||
// Get views
|
||||
TextView tvTime = view.findViewById(R.id.tvTime);
|
||||
TextView tvProtocol = view.findViewById(R.id.tvProtocol);
|
||||
TextView tvFlags = view.findViewById(R.id.tvFlags);
|
||||
TextView tvSAddr = view.findViewById(R.id.tvSAddr);
|
||||
TextView tvSPort = view.findViewById(R.id.tvSPort);
|
||||
final TextView tvDaddr = view.findViewById(R.id.tvDAddr);
|
||||
TextView tvDPort = view.findViewById(R.id.tvDPort);
|
||||
final TextView tvOrganization = view.findViewById(R.id.tvOrganization);
|
||||
final ImageView ivIcon = view.findViewById(R.id.ivIcon);
|
||||
TextView tvUid = view.findViewById(R.id.tvUid);
|
||||
TextView tvData = view.findViewById(R.id.tvData);
|
||||
ImageView ivConnection = view.findViewById(R.id.ivConnection);
|
||||
ImageView ivInteractive = view.findViewById(R.id.ivInteractive);
|
||||
|
||||
// Show time
|
||||
tvTime.setText(new SimpleDateFormat("HH:mm:ss").format(time));
|
||||
|
||||
// Show connection type
|
||||
if (connection <= 0)
|
||||
ivConnection.setImageResource(allowed > 0 ? R.drawable.host_allowed : R.drawable.host_blocked);
|
||||
else {
|
||||
if (allowed > 0)
|
||||
ivConnection.setImageResource(connection == 1 ? R.drawable.wifi_on : R.drawable.other_on);
|
||||
else
|
||||
ivConnection.setImageResource(connection == 1 ? R.drawable.wifi_off : R.drawable.other_off);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
Drawable wrap = DrawableCompat.wrap(ivConnection.getDrawable());
|
||||
DrawableCompat.setTint(wrap, allowed > 0 ? colorOn : colorOff);
|
||||
}
|
||||
|
||||
// Show if screen on
|
||||
if (interactive <= 0)
|
||||
ivInteractive.setImageDrawable(null);
|
||||
else {
|
||||
ivInteractive.setImageResource(R.drawable.screen_on);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
Drawable wrap = DrawableCompat.wrap(ivInteractive.getDrawable());
|
||||
DrawableCompat.setTint(wrap, colorOn);
|
||||
}
|
||||
}
|
||||
|
||||
// Show protocol name
|
||||
tvProtocol.setText(Util.getProtocolName(protocol, version, false));
|
||||
|
||||
// SHow TCP flags
|
||||
tvFlags.setText(flags);
|
||||
tvFlags.setVisibility(TextUtils.isEmpty(flags) ? View.GONE : View.VISIBLE);
|
||||
|
||||
// Show source and destination port
|
||||
if (protocol == 6 || protocol == 17) {
|
||||
tvSPort.setText(sport < 0 ? "" : getKnownPort(sport));
|
||||
tvDPort.setText(dport < 0 ? "" : getKnownPort(dport));
|
||||
} else {
|
||||
tvSPort.setText(sport < 0 ? "" : Integer.toString(sport));
|
||||
tvDPort.setText(dport < 0 ? "" : Integer.toString(dport));
|
||||
}
|
||||
|
||||
// Application icon
|
||||
ApplicationInfo info = null;
|
||||
PackageManager pm = context.getPackageManager();
|
||||
String[] pkg = pm.getPackagesForUid(uid);
|
||||
if (pkg != null && pkg.length > 0)
|
||||
try {
|
||||
info = pm.getApplicationInfo(pkg[0], 0);
|
||||
} catch (PackageManager.NameNotFoundException ignored) {
|
||||
}
|
||||
|
||||
if (info == null)
|
||||
ivIcon.setImageDrawable(null);
|
||||
else {
|
||||
if (info.icon <= 0)
|
||||
ivIcon.setImageResource(android.R.drawable.sym_def_app_icon);
|
||||
else {
|
||||
Uri uri = Uri.parse("android.resource://" + info.packageName + "/" + info.icon);
|
||||
GlideApp.with(context)
|
||||
.load(uri)
|
||||
//.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
//.skipMemoryCache(true)
|
||||
.override(iconSize, iconSize)
|
||||
.into(ivIcon);
|
||||
}
|
||||
}
|
||||
|
||||
boolean we = (android.os.Process.myUid() == uid);
|
||||
|
||||
// https://android.googlesource.com/platform/system/core/+/master/include/private/android_filesystem_config.h
|
||||
uid = uid % 100000; // strip off user ID
|
||||
if (uid == -1)
|
||||
tvUid.setText("");
|
||||
else if (uid == 0)
|
||||
tvUid.setText(context.getString(R.string.title_root));
|
||||
else if (uid == 9999)
|
||||
tvUid.setText("-"); // nobody
|
||||
else
|
||||
tvUid.setText(Integer.toString(uid));
|
||||
|
||||
// Show source address
|
||||
tvSAddr.setText(getKnownAddress(saddr));
|
||||
|
||||
// Show destination address
|
||||
if (!we && resolve && !isKnownAddress(daddr))
|
||||
if (dname == null) {
|
||||
tvDaddr.setText(daddr);
|
||||
new AsyncTask<String, Object, String>() {
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
ViewCompat.setHasTransientState(tvDaddr, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doInBackground(String... args) {
|
||||
try {
|
||||
return InetAddress.getByName(args[0]).getHostName();
|
||||
} catch (UnknownHostException ignored) {
|
||||
return args[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(String name) {
|
||||
tvDaddr.setText(">" + name);
|
||||
ViewCompat.setHasTransientState(tvDaddr, false);
|
||||
}
|
||||
}.execute(daddr);
|
||||
} else
|
||||
tvDaddr.setText(dname);
|
||||
else
|
||||
tvDaddr.setText(getKnownAddress(daddr));
|
||||
|
||||
// Show organization
|
||||
tvOrganization.setVisibility(View.GONE);
|
||||
if (!we && organization) {
|
||||
if (!isKnownAddress(daddr))
|
||||
new AsyncTask<String, Object, String>() {
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
ViewCompat.setHasTransientState(tvOrganization, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doInBackground(String... args) {
|
||||
try {
|
||||
return Util.getOrganization(args[0]);
|
||||
} catch (Throwable ex) {
|
||||
Log.w(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(String organization) {
|
||||
if (organization != null) {
|
||||
tvOrganization.setText(organization);
|
||||
tvOrganization.setVisibility(View.VISIBLE);
|
||||
}
|
||||
ViewCompat.setHasTransientState(tvOrganization, false);
|
||||
}
|
||||
}.execute(daddr);
|
||||
}
|
||||
|
||||
// Show extra data
|
||||
if (TextUtils.isEmpty(data)) {
|
||||
tvData.setText("");
|
||||
tvData.setVisibility(View.GONE);
|
||||
} else {
|
||||
tvData.setText(data);
|
||||
tvData.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isKnownAddress(String addr) {
|
||||
try {
|
||||
InetAddress a = InetAddress.getByName(addr);
|
||||
if (a.equals(dns1) || a.equals(dns2) || a.equals(vpn4) || a.equals(vpn6))
|
||||
return true;
|
||||
} catch (UnknownHostException ignored) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String getKnownAddress(String addr) {
|
||||
try {
|
||||
InetAddress a = InetAddress.getByName(addr);
|
||||
if (a.equals(dns1))
|
||||
return "dns1";
|
||||
if (a.equals(dns2))
|
||||
return "dns2";
|
||||
if (a.equals(vpn4) || a.equals(vpn6))
|
||||
return "vpn";
|
||||
} catch (UnknownHostException ignored) {
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
||||
private String getKnownPort(int port) {
|
||||
// https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers#Well-known_ports
|
||||
switch (port) {
|
||||
case 7:
|
||||
return "echo";
|
||||
case 25:
|
||||
return "smtp";
|
||||
case 53:
|
||||
return "dns";
|
||||
case 80:
|
||||
return "http";
|
||||
case 110:
|
||||
return "pop3";
|
||||
case 143:
|
||||
return "imap";
|
||||
case 443:
|
||||
return "https";
|
||||
case 465:
|
||||
return "smtps";
|
||||
case 993:
|
||||
return "imaps";
|
||||
case 995:
|
||||
return "pop3s";
|
||||
default:
|
||||
return Integer.toString(port);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
public class Allowed {
|
||||
public String raddr;
|
||||
public int rport;
|
||||
|
||||
public Allowed() {
|
||||
this.raddr = null;
|
||||
this.rport = 0;
|
||||
}
|
||||
|
||||
public Allowed(String raddr, int rport) {
|
||||
this.raddr = raddr;
|
||||
this.rport = rport;
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Application;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
public class ApplicationEx extends Application {
|
||||
private static final String TAG = "NetGuard.App";
|
||||
|
||||
private Thread.UncaughtExceptionHandler mPrevHandler;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Log.i(TAG, "Create version=" + Util.getSelfVersionName(this) + "/" + Util.getSelfVersionCode(this));
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
||||
createNotificationChannels();
|
||||
|
||||
mPrevHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void uncaughtException(Thread thread, Throwable ex) {
|
||||
if (Util.ownFault(ApplicationEx.this, ex)
|
||||
&& Util.isPlayStoreInstall(ApplicationEx.this)) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
mPrevHandler.uncaughtException(thread, ex);
|
||||
} else {
|
||||
Log.w(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
private void createNotificationChannels() {
|
||||
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
NotificationChannel foreground = new NotificationChannel("foreground", getString(R.string.channel_foreground), NotificationManager.IMPORTANCE_MIN);
|
||||
foreground.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT);
|
||||
nm.createNotificationChannel(foreground);
|
||||
|
||||
NotificationChannel notify = new NotificationChannel("notify", getString(R.string.channel_notify), NotificationManager.IMPORTANCE_DEFAULT);
|
||||
notify.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT);
|
||||
notify.setBypassDnd(true);
|
||||
nm.createNotificationChannel(notify);
|
||||
|
||||
NotificationChannel access = new NotificationChannel("access", getString(R.string.channel_access), NotificationManager.IMPORTANCE_DEFAULT);
|
||||
access.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT);
|
||||
access.setBypassDnd(true);
|
||||
nm.createNotificationChannel(access);
|
||||
}
|
||||
}
|
@ -0,0 +1,181 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.PowerManager;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
|
||||
public class DownloadTask extends AsyncTask<Object, Integer, Object> {
|
||||
private static final String TAG = "NetGuard.Download";
|
||||
|
||||
private Context context;
|
||||
private URL url;
|
||||
private File file;
|
||||
private Listener listener;
|
||||
private PowerManager.WakeLock wakeLock;
|
||||
|
||||
public interface Listener {
|
||||
void onCompleted();
|
||||
|
||||
void onCancelled();
|
||||
|
||||
void onException(Throwable ex);
|
||||
}
|
||||
|
||||
public DownloadTask(Activity context, URL url, File file, Listener listener) {
|
||||
this.context = context;
|
||||
this.url = url;
|
||||
this.file = file;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
|
||||
wakeLock.acquire();
|
||||
showNotification(0);
|
||||
Toast.makeText(context, context.getString(R.string.msg_downloading, url.toString()), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doInBackground(Object... args) {
|
||||
Log.i(TAG, "Downloading " + url + " into " + file);
|
||||
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
URLConnection connection = null;
|
||||
try {
|
||||
connection = url.openConnection();
|
||||
connection.connect();
|
||||
|
||||
if (connection instanceof HttpURLConnection) {
|
||||
HttpURLConnection httpConnection = (HttpURLConnection) connection;
|
||||
if (httpConnection.getResponseCode() != HttpURLConnection.HTTP_OK)
|
||||
throw new IOException(httpConnection.getResponseCode() + " " + httpConnection.getResponseMessage());
|
||||
}
|
||||
|
||||
int contentLength = connection.getContentLength();
|
||||
Log.i(TAG, "Content length=" + contentLength);
|
||||
in = connection.getInputStream();
|
||||
out = new FileOutputStream(file);
|
||||
|
||||
long size = 0;
|
||||
byte buffer[] = new byte[4096];
|
||||
int bytes;
|
||||
while (!isCancelled() && (bytes = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, bytes);
|
||||
|
||||
size += bytes;
|
||||
if (contentLength > 0)
|
||||
publishProgress((int) (size * 100 / contentLength));
|
||||
}
|
||||
|
||||
Log.i(TAG, "Downloaded size=" + size);
|
||||
return null;
|
||||
} catch (Throwable ex) {
|
||||
return ex;
|
||||
} finally {
|
||||
try {
|
||||
if (out != null)
|
||||
out.close();
|
||||
} catch (IOException ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
try {
|
||||
if (in != null)
|
||||
in.close();
|
||||
} catch (IOException ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
|
||||
if (connection instanceof HttpURLConnection)
|
||||
((HttpURLConnection) connection).disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... progress) {
|
||||
super.onProgressUpdate(progress);
|
||||
showNotification(progress[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
super.onCancelled();
|
||||
Log.i(TAG, "Cancelled");
|
||||
listener.onCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Object result) {
|
||||
wakeLock.release();
|
||||
NotificationManagerCompat.from(context).cancel(ServiceSinkhole.NOTIFY_DOWNLOAD);
|
||||
if (result instanceof Throwable) {
|
||||
Log.e(TAG, result.toString() + "\n" + Log.getStackTraceString((Throwable) result));
|
||||
listener.onException((Throwable) result);
|
||||
} else
|
||||
listener.onCompleted();
|
||||
}
|
||||
|
||||
private void showNotification(int progress) {
|
||||
Intent main = new Intent(context, ActivitySettings.class);
|
||||
PendingIntent pi = PendingIntent.getActivity(context, ServiceSinkhole.NOTIFY_DOWNLOAD, main, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
|
||||
|
||||
TypedValue tv = new TypedValue();
|
||||
context.getTheme().resolveAttribute(R.attr.colorOff, tv, true);
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "notify");
|
||||
builder.setSmallIcon(R.drawable.ic_file_download_white_24dp)
|
||||
.setContentTitle(context.getString(R.string.app_name))
|
||||
.setContentText(context.getString(R.string.msg_downloading, url.toString()))
|
||||
.setContentIntent(pi)
|
||||
.setProgress(100, progress, false)
|
||||
.setColor(tv.data)
|
||||
.setOngoing(true)
|
||||
.setAutoCancel(false);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
builder.setCategory(NotificationCompat.CATEGORY_STATUS)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_SECRET);
|
||||
|
||||
NotificationManagerCompat.from(context).notify(ServiceSinkhole.NOTIFY_DOWNLOAD, builder.build());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ListView;
|
||||
|
||||
// This requires list view items with equal heights
|
||||
|
||||
public class ExpandedListView extends ListView {
|
||||
public ExpandedListView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public ExpandedListView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public ExpandedListView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 4, MeasureSpec.AT_MOST));
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
public class Forward {
|
||||
public int protocol;
|
||||
public int dport;
|
||||
public String raddr;
|
||||
public int rport;
|
||||
public int ruid;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "protocol=" + protocol + " port " + dport + " to " + raddr + "/" + rport + " uid " + ruid;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceFragment;
|
||||
|
||||
public class FragmentSettings extends PreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
import com.bumptech.glide.annotation.GlideModule;
|
||||
import com.bumptech.glide.module.AppGlideModule;
|
||||
|
||||
@GlideModule
|
||||
public final class GlideHelper extends AppGlideModule {
|
||||
}
|
240
NetGuard/app/src/main/main/java/eu/faircode/netguard/IAB.java
Normal file
@ -0,0 +1,240 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.android.vending.billing.IInAppBillingService;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class IAB implements ServiceConnection {
|
||||
private static final String TAG = "NetGuard.IAB";
|
||||
|
||||
private Context context;
|
||||
private Delegate delegate;
|
||||
private IInAppBillingService service = null;
|
||||
|
||||
private static final int IAB_VERSION = 3;
|
||||
|
||||
public interface Delegate {
|
||||
void onReady(IAB iab);
|
||||
}
|
||||
|
||||
public IAB(Delegate delegate, Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public void bind() {
|
||||
Log.i(TAG, "Bind");
|
||||
Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
|
||||
serviceIntent.setPackage("com.android.vending");
|
||||
context.bindService(serviceIntent, this, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
public void unbind() {
|
||||
if (service != null) {
|
||||
Log.i(TAG, "Unbind");
|
||||
context.unbindService(this);
|
||||
service = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder binder) {
|
||||
Log.i(TAG, "Connected");
|
||||
service = IInAppBillingService.Stub.asInterface(binder);
|
||||
delegate.onReady(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
Log.i(TAG, "Disconnected");
|
||||
service = null;
|
||||
}
|
||||
|
||||
public boolean isAvailable(String sku) throws RemoteException, JSONException {
|
||||
// Get available SKUs
|
||||
ArrayList<String> skuList = new ArrayList<>();
|
||||
skuList.add(sku);
|
||||
Bundle query = new Bundle();
|
||||
query.putStringArrayList("ITEM_ID_LIST", skuList);
|
||||
Bundle bundle = service.getSkuDetails(IAB_VERSION, context.getPackageName(), "inapp", query);
|
||||
Log.i(TAG, "getSkuDetails");
|
||||
Util.logBundle(bundle);
|
||||
int response = (bundle == null ? -1 : bundle.getInt("RESPONSE_CODE", -1));
|
||||
Log.i(TAG, "Response=" + getResult(response));
|
||||
if (response != 0)
|
||||
throw new IllegalArgumentException(getResult(response));
|
||||
|
||||
// Check available SKUs
|
||||
boolean found = false;
|
||||
ArrayList<String> details = bundle.getStringArrayList("DETAILS_LIST");
|
||||
if (details != null)
|
||||
for (String item : details) {
|
||||
JSONObject object = new JSONObject(item);
|
||||
if (sku.equals(object.getString("productId"))) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Log.i(TAG, sku + "=" + found);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
public void updatePurchases() throws RemoteException {
|
||||
// Get purchases
|
||||
List<String> skus = new ArrayList<>();
|
||||
skus.addAll(getPurchases("inapp"));
|
||||
skus.addAll(getPurchases("subs"));
|
||||
|
||||
SharedPreferences prefs = context.getSharedPreferences("IAB", Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
for (String product : prefs.getAll().keySet())
|
||||
if (!ActivityPro.SKU_DONATION.equals(product)) {
|
||||
Log.i(TAG, "removing SKU=" + product);
|
||||
editor.remove(product);
|
||||
}
|
||||
for (String sku : skus) {
|
||||
Log.i(TAG, "adding SKU=" + sku);
|
||||
editor.putBoolean(sku, true);
|
||||
}
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public boolean isPurchased(String sku, String type) throws RemoteException {
|
||||
return getPurchases(type).contains(sku);
|
||||
}
|
||||
|
||||
public List<String> getPurchases(String type) throws RemoteException {
|
||||
// Get purchases
|
||||
Bundle bundle = service.getPurchases(IAB_VERSION, context.getPackageName(), type, null);
|
||||
Log.i(TAG, "getPurchases");
|
||||
Util.logBundle(bundle);
|
||||
int response = (bundle == null ? -1 : bundle.getInt("RESPONSE_CODE", -1));
|
||||
Log.i(TAG, "Response=" + getResult(response));
|
||||
if (response != 0)
|
||||
throw new IllegalArgumentException(getResult(response));
|
||||
|
||||
ArrayList<String> details = bundle.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
|
||||
return (details == null ? new ArrayList<String>() : details);
|
||||
}
|
||||
|
||||
public PendingIntent getBuyIntent(String sku, boolean subscription) throws RemoteException {
|
||||
if (service == null)
|
||||
return null;
|
||||
Bundle bundle = service.getBuyIntent(IAB_VERSION, context.getPackageName(), sku, subscription ? "subs" : "inapp", "netguard");
|
||||
Log.i(TAG, "getBuyIntent sku=" + sku + " subscription=" + subscription);
|
||||
Util.logBundle(bundle);
|
||||
int response = (bundle == null ? -1 : bundle.getInt("RESPONSE_CODE", -1));
|
||||
Log.i(TAG, "Response=" + getResult(response));
|
||||
if (response != 0)
|
||||
throw new IllegalArgumentException(getResult(response));
|
||||
if (!bundle.containsKey("BUY_INTENT"))
|
||||
throw new IllegalArgumentException("BUY_INTENT missing");
|
||||
return bundle.getParcelable("BUY_INTENT");
|
||||
}
|
||||
|
||||
public static void setBought(String sku, Context context) {
|
||||
Log.i(TAG, "Bought " + sku);
|
||||
SharedPreferences prefs = context.getSharedPreferences("IAB", Context.MODE_PRIVATE);
|
||||
prefs.edit().putBoolean(sku, true).apply();
|
||||
}
|
||||
|
||||
public static boolean isPurchased(String sku, Context context) {
|
||||
try {
|
||||
if (Util.isDebuggable(context)) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
return !prefs.getBoolean("debug_iab", false);
|
||||
}
|
||||
|
||||
SharedPreferences prefs = context.getSharedPreferences("IAB", Context.MODE_PRIVATE);
|
||||
if (ActivityPro.SKU_SUPPORT1.equals(sku) || ActivityPro.SKU_SUPPORT2.equals(sku))
|
||||
return prefs.getBoolean(sku, false);
|
||||
|
||||
return (prefs.getBoolean(sku, false) ||
|
||||
prefs.getBoolean(ActivityPro.SKU_PRO1, false) ||
|
||||
prefs.getBoolean(ActivityPro.SKU_SUPPORT1, false) ||
|
||||
prefs.getBoolean(ActivityPro.SKU_SUPPORT2, false) ||
|
||||
prefs.getBoolean(ActivityPro.SKU_DONATION, false));
|
||||
} catch (SecurityException ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isPurchasedAny(Context context) {
|
||||
try {
|
||||
if (Util.isDebuggable(context)) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
return !(prefs.getBoolean("debug_iab", false));
|
||||
}
|
||||
|
||||
SharedPreferences prefs = context.getSharedPreferences("IAB", Context.MODE_PRIVATE);
|
||||
for (String key : prefs.getAll().keySet())
|
||||
if (prefs.getBoolean(key, false))
|
||||
return true;
|
||||
return false;
|
||||
} catch (SecurityException ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getResult(int responseCode) {
|
||||
switch (responseCode) {
|
||||
case 0:
|
||||
return "OK";
|
||||
case 1:
|
||||
return "USER_CANCELED";
|
||||
case 2:
|
||||
return "SERVICE_UNAVAILABLE";
|
||||
case 3:
|
||||
return "BILLING_UNAVAILABLE";
|
||||
case 4:
|
||||
return "ITEM_UNAVAILABLE";
|
||||
case 5:
|
||||
return "DEVELOPER_ERROR";
|
||||
case 6:
|
||||
return "ERROR";
|
||||
case 7:
|
||||
return "ITEM_ALREADY_OWNED";
|
||||
case 8:
|
||||
return "ITEM_NOT_OWNED";
|
||||
default:
|
||||
return Integer.toString(responseCode);
|
||||
}
|
||||
}
|
||||
}
|
140
NetGuard/app/src/main/main/java/eu/faircode/netguard/IPUtil.java
Normal file
@ -0,0 +1,140 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class IPUtil {
|
||||
private static final String TAG = "NetGuard.IPUtil";
|
||||
|
||||
public static List<CIDR> toCIDR(String start, String end) throws UnknownHostException {
|
||||
return toCIDR(InetAddress.getByName(start), InetAddress.getByName(end));
|
||||
}
|
||||
|
||||
public static List<CIDR> toCIDR(InetAddress start, InetAddress end) throws UnknownHostException {
|
||||
List<CIDR> listResult = new ArrayList<>();
|
||||
|
||||
Log.i(TAG, "toCIDR(" + start.getHostAddress() + "," + end.getHostAddress() + ")");
|
||||
|
||||
long from = inet2long(start);
|
||||
long to = inet2long(end);
|
||||
while (to >= from) {
|
||||
byte prefix = 32;
|
||||
while (prefix > 0) {
|
||||
long mask = prefix2mask(prefix - 1);
|
||||
if ((from & mask) != from)
|
||||
break;
|
||||
prefix--;
|
||||
}
|
||||
|
||||
byte max = (byte) (32 - Math.floor(Math.log(to - from + 1) / Math.log(2)));
|
||||
if (prefix < max)
|
||||
prefix = max;
|
||||
|
||||
listResult.add(new CIDR(long2inet(from), prefix));
|
||||
|
||||
from += Math.pow(2, (32 - prefix));
|
||||
}
|
||||
|
||||
for (CIDR cidr : listResult)
|
||||
Log.i(TAG, cidr.toString());
|
||||
|
||||
return listResult;
|
||||
}
|
||||
|
||||
private static long prefix2mask(int bits) {
|
||||
return (0xFFFFFFFF00000000L >> bits) & 0xFFFFFFFFL;
|
||||
}
|
||||
|
||||
private static long inet2long(InetAddress addr) {
|
||||
long result = 0;
|
||||
if (addr != null)
|
||||
for (byte b : addr.getAddress())
|
||||
result = result << 8 | (b & 0xFF);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static InetAddress long2inet(long addr) {
|
||||
try {
|
||||
byte[] b = new byte[4];
|
||||
for (int i = b.length - 1; i >= 0; i--) {
|
||||
b[i] = (byte) (addr & 0xFF);
|
||||
addr = addr >> 8;
|
||||
}
|
||||
return InetAddress.getByAddress(b);
|
||||
} catch (UnknownHostException ignore) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static InetAddress minus1(InetAddress addr) {
|
||||
return long2inet(inet2long(addr) - 1);
|
||||
}
|
||||
|
||||
public static InetAddress plus1(InetAddress addr) {
|
||||
return long2inet(inet2long(addr) + 1);
|
||||
}
|
||||
|
||||
public static class CIDR implements Comparable<CIDR> {
|
||||
public InetAddress address;
|
||||
public int prefix;
|
||||
|
||||
public CIDR(InetAddress address, int prefix) {
|
||||
this.address = address;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public CIDR(String ip, int prefix) {
|
||||
try {
|
||||
this.address = InetAddress.getByName(ip);
|
||||
this.prefix = prefix;
|
||||
} catch (UnknownHostException ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public InetAddress getStart() {
|
||||
return long2inet(inet2long(this.address) & prefix2mask(this.prefix));
|
||||
}
|
||||
|
||||
public InetAddress getEnd() {
|
||||
return long2inet((inet2long(this.address) & prefix2mask(this.prefix)) + (1L << (32 - this.prefix)) - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return address.getHostAddress() + "/" + prefix + "=" + getStart().getHostAddress() + "..." + getEnd().getHostAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull CIDR other) {
|
||||
Long lcidr = IPUtil.inet2long(this.address);
|
||||
Long lother = IPUtil.inet2long(other.address);
|
||||
return lcidr.compareTo(lother);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
public class Packet {
|
||||
public long time;
|
||||
public int version;
|
||||
public int protocol;
|
||||
public String flags;
|
||||
public String saddr;
|
||||
public int sport;
|
||||
public String daddr;
|
||||
public int dport;
|
||||
public String data;
|
||||
public int uid;
|
||||
public boolean allowed;
|
||||
|
||||
public Packet() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "uid=" + uid + " v" + version + " p" + protocol + " " + daddr + "/" + dport;
|
||||
}
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ReceiverAutostart extends BroadcastReceiver {
|
||||
private static final String TAG = "NetGuard.Receiver";
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, Intent intent) {
|
||||
Log.i(TAG, "Received " + intent);
|
||||
Util.logExtras(intent);
|
||||
|
||||
String action = (intent == null ? null : intent.getAction());
|
||||
if (Intent.ACTION_BOOT_COMPLETED.equals(action) || Intent.ACTION_MY_PACKAGE_REPLACED.equals(action))
|
||||
try {
|
||||
// Upgrade settings
|
||||
upgrade(true, context);
|
||||
|
||||
// Start service
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (prefs.getBoolean("enabled", false))
|
||||
ServiceSinkhole.start("receiver", context);
|
||||
else if (prefs.getBoolean("show_stats", false))
|
||||
ServiceSinkhole.run("receiver", context);
|
||||
|
||||
if (Util.isInteractive(context))
|
||||
ServiceSinkhole.reloadStats("receiver", context);
|
||||
} catch (Throwable ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public static void upgrade(boolean initialized, Context context) {
|
||||
synchronized (context.getApplicationContext()) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
int oldVersion = prefs.getInt("version", -1);
|
||||
int newVersion = Util.getSelfVersionCode(context);
|
||||
if (oldVersion == newVersion)
|
||||
return;
|
||||
Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion);
|
||||
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
|
||||
if (initialized) {
|
||||
if (oldVersion < 38) {
|
||||
Log.i(TAG, "Converting screen wifi/mobile");
|
||||
editor.putBoolean("screen_wifi", prefs.getBoolean("unused", false));
|
||||
editor.putBoolean("screen_other", prefs.getBoolean("unused", false));
|
||||
editor.remove("unused");
|
||||
|
||||
SharedPreferences unused = context.getSharedPreferences("unused", Context.MODE_PRIVATE);
|
||||
SharedPreferences screen_wifi = context.getSharedPreferences("screen_wifi", Context.MODE_PRIVATE);
|
||||
SharedPreferences screen_other = context.getSharedPreferences("screen_other", Context.MODE_PRIVATE);
|
||||
|
||||
Map<String, ?> punused = unused.getAll();
|
||||
SharedPreferences.Editor edit_screen_wifi = screen_wifi.edit();
|
||||
SharedPreferences.Editor edit_screen_other = screen_other.edit();
|
||||
for (String key : punused.keySet()) {
|
||||
edit_screen_wifi.putBoolean(key, (Boolean) punused.get(key));
|
||||
edit_screen_other.putBoolean(key, (Boolean) punused.get(key));
|
||||
}
|
||||
edit_screen_wifi.apply();
|
||||
edit_screen_other.apply();
|
||||
|
||||
} else if (oldVersion <= 2017032112)
|
||||
editor.remove("ip6");
|
||||
|
||||
} else {
|
||||
Log.i(TAG, "Initializing sdk=" + Build.VERSION.SDK_INT);
|
||||
editor.putBoolean("filter_udp", true);
|
||||
editor.putBoolean("whitelist_wifi", false);
|
||||
editor.putBoolean("whitelist_other", false);
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP)
|
||||
editor.putBoolean("filter", true); // Optional
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
|
||||
editor.putBoolean("filter", true); // Mandatory
|
||||
|
||||
if (!Util.canFilter(context)) {
|
||||
editor.putBoolean("log_app", false);
|
||||
editor.putBoolean("filter", false);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
editor.remove("show_top");
|
||||
if ("data".equals(prefs.getString("sort", "name")))
|
||||
editor.remove("sort");
|
||||
}
|
||||
|
||||
if (Util.isPlayStoreInstall(context)) {
|
||||
editor.remove("update_check");
|
||||
editor.remove("use_hosts");
|
||||
editor.remove("hosts_url");
|
||||
}
|
||||
|
||||
if (!Util.isDebuggable(context))
|
||||
editor.remove("loglevel");
|
||||
|
||||
editor.putInt("version", newVersion);
|
||||
editor.apply();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
|
||||
public class ReceiverPackageRemoved extends BroadcastReceiver {
|
||||
private static final String TAG = "NetGuard.Receiver";
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, Intent intent) {
|
||||
Log.i(TAG, "Received " + intent);
|
||||
Util.logExtras(intent);
|
||||
|
||||
String action = (intent == null ? null : intent.getAction());
|
||||
if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) {
|
||||
int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
|
||||
if (uid > 0) {
|
||||
DatabaseHelper dh = DatabaseHelper.getInstance(context);
|
||||
dh.clearLog(uid);
|
||||
dh.clearAccess(uid, false);
|
||||
|
||||
NotificationManagerCompat.from(context).cancel(uid); // installed notification
|
||||
NotificationManagerCompat.from(context).cancel(uid + 10000); // access notification
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class ResourceRecord {
|
||||
public long Time;
|
||||
public String QName;
|
||||
public String AName;
|
||||
public String Resource;
|
||||
public int TTL;
|
||||
|
||||
private static DateFormat formatter = SimpleDateFormat.getDateTimeInstance();
|
||||
|
||||
public ResourceRecord() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return formatter.format(new Date(Time).getTime()) +
|
||||
" Q " + QName +
|
||||
" A " + AName +
|
||||
" R " + Resource +
|
||||
" TTL " + TTL +
|
||||
" " + formatter.format(new Date(Time + TTL * 1000L).getTime());
|
||||
}
|
||||
}
|
453
NetGuard/app/src/main/main/java/eu/faircode/netguard/Rule.java
Normal file
@ -0,0 +1,453 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.database.Cursor;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class Rule {
|
||||
private static final String TAG = "NetGuard.Rule";
|
||||
|
||||
public int uid;
|
||||
public String packageName;
|
||||
public int icon;
|
||||
public String name;
|
||||
public String version;
|
||||
public boolean system;
|
||||
public boolean internet;
|
||||
public boolean enabled;
|
||||
public boolean pkg = true;
|
||||
|
||||
public boolean wifi_default = false;
|
||||
public boolean other_default = false;
|
||||
public boolean screen_wifi_default = false;
|
||||
public boolean screen_other_default = false;
|
||||
public boolean roaming_default = false;
|
||||
|
||||
public boolean wifi_blocked = false;
|
||||
public boolean other_blocked = false;
|
||||
public boolean screen_wifi = false;
|
||||
public boolean screen_other = false;
|
||||
public boolean roaming = false;
|
||||
public boolean lockdown = false;
|
||||
|
||||
public boolean apply = true;
|
||||
public boolean notify = true;
|
||||
|
||||
public boolean relateduids = false;
|
||||
public String[] related = null;
|
||||
|
||||
public long hosts;
|
||||
public boolean changed;
|
||||
|
||||
public boolean expanded = false;
|
||||
|
||||
private static List<PackageInfo> cachePackageInfo = null;
|
||||
private static Map<PackageInfo, String> cacheLabel = new HashMap<>();
|
||||
private static Map<String, Boolean> cacheSystem = new HashMap<>();
|
||||
private static Map<String, Boolean> cacheInternet = new HashMap<>();
|
||||
private static Map<PackageInfo, Boolean> cacheEnabled = new HashMap<>();
|
||||
|
||||
private static List<PackageInfo> getPackages(Context context) {
|
||||
if (cachePackageInfo == null) {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
cachePackageInfo = pm.getInstalledPackages(0);
|
||||
}
|
||||
return new ArrayList<>(cachePackageInfo);
|
||||
}
|
||||
|
||||
private static String getLabel(PackageInfo info, Context context) {
|
||||
if (!cacheLabel.containsKey(info)) {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
cacheLabel.put(info, info.applicationInfo.loadLabel(pm).toString());
|
||||
}
|
||||
return cacheLabel.get(info);
|
||||
}
|
||||
|
||||
private static boolean isSystem(String packageName, Context context) {
|
||||
if (!cacheSystem.containsKey(packageName))
|
||||
cacheSystem.put(packageName, Util.isSystem(packageName, context));
|
||||
return cacheSystem.get(packageName);
|
||||
}
|
||||
|
||||
private static boolean hasInternet(String packageName, Context context) {
|
||||
if (!cacheInternet.containsKey(packageName))
|
||||
cacheInternet.put(packageName, Util.hasInternet(packageName, context));
|
||||
return cacheInternet.get(packageName);
|
||||
}
|
||||
|
||||
private static boolean isEnabled(PackageInfo info, Context context) {
|
||||
if (!cacheEnabled.containsKey(info))
|
||||
cacheEnabled.put(info, Util.isEnabled(info, context));
|
||||
return cacheEnabled.get(info);
|
||||
}
|
||||
|
||||
public static void clearCache(Context context) {
|
||||
Log.i(TAG, "Clearing cache");
|
||||
synchronized (context.getApplicationContext()) {
|
||||
cachePackageInfo = null;
|
||||
cacheLabel.clear();
|
||||
cacheSystem.clear();
|
||||
cacheInternet.clear();
|
||||
cacheEnabled.clear();
|
||||
}
|
||||
|
||||
DatabaseHelper dh = DatabaseHelper.getInstance(context);
|
||||
dh.clearApps();
|
||||
}
|
||||
|
||||
private Rule(DatabaseHelper dh, PackageInfo info, Context context) {
|
||||
this.uid = info.applicationInfo.uid;
|
||||
this.packageName = info.packageName;
|
||||
this.icon = info.applicationInfo.icon;
|
||||
this.version = info.versionName;
|
||||
if (info.applicationInfo.uid == 0) {
|
||||
this.name = context.getString(R.string.title_root);
|
||||
this.system = true;
|
||||
this.internet = true;
|
||||
this.enabled = true;
|
||||
this.pkg = false;
|
||||
} else if (info.applicationInfo.uid == 1013) {
|
||||
this.name = context.getString(R.string.title_mediaserver);
|
||||
this.system = true;
|
||||
this.internet = true;
|
||||
this.enabled = true;
|
||||
this.pkg = false;
|
||||
} else if (info.applicationInfo.uid == 1020) {
|
||||
this.name = "MulticastDNSResponder";
|
||||
this.system = true;
|
||||
this.internet = true;
|
||||
this.enabled = true;
|
||||
this.pkg = false;
|
||||
} else if (info.applicationInfo.uid == 1021) {
|
||||
this.name = context.getString(R.string.title_gpsdaemon);
|
||||
this.system = true;
|
||||
this.internet = true;
|
||||
this.enabled = true;
|
||||
this.pkg = false;
|
||||
} else if (info.applicationInfo.uid == 1051) {
|
||||
this.name = context.getString(R.string.title_dnsdaemon);
|
||||
this.system = true;
|
||||
this.internet = true;
|
||||
this.enabled = true;
|
||||
this.pkg = false;
|
||||
} else if (info.applicationInfo.uid == 9999) {
|
||||
this.name = context.getString(R.string.title_nobody);
|
||||
this.system = true;
|
||||
this.internet = true;
|
||||
this.enabled = true;
|
||||
this.pkg = false;
|
||||
} else {
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = dh.getApp(this.packageName);
|
||||
if (cursor.moveToNext()) {
|
||||
this.name = cursor.getString(cursor.getColumnIndex("label"));
|
||||
this.system = cursor.getInt(cursor.getColumnIndex("system")) > 0;
|
||||
this.internet = cursor.getInt(cursor.getColumnIndex("internet")) > 0;
|
||||
this.enabled = cursor.getInt(cursor.getColumnIndex("enabled")) > 0;
|
||||
} else {
|
||||
this.name = getLabel(info, context);
|
||||
this.system = isSystem(info.packageName, context);
|
||||
this.internet = hasInternet(info.packageName, context);
|
||||
this.enabled = isEnabled(info, context);
|
||||
|
||||
dh.addApp(this.packageName, this.name, this.system, this.internet, this.enabled);
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Rule> getRules(final boolean all, Context context) {
|
||||
synchronized (context.getApplicationContext()) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences wifi = context.getSharedPreferences("wifi", Context.MODE_PRIVATE);
|
||||
SharedPreferences other = context.getSharedPreferences("other", Context.MODE_PRIVATE);
|
||||
SharedPreferences screen_wifi = context.getSharedPreferences("screen_wifi", Context.MODE_PRIVATE);
|
||||
SharedPreferences screen_other = context.getSharedPreferences("screen_other", Context.MODE_PRIVATE);
|
||||
SharedPreferences roaming = context.getSharedPreferences("roaming", Context.MODE_PRIVATE);
|
||||
SharedPreferences lockdown = context.getSharedPreferences("lockdown", Context.MODE_PRIVATE);
|
||||
SharedPreferences apply = context.getSharedPreferences("apply", Context.MODE_PRIVATE);
|
||||
SharedPreferences notify = context.getSharedPreferences("notify", Context.MODE_PRIVATE);
|
||||
|
||||
// Get settings
|
||||
boolean default_wifi = prefs.getBoolean("whitelist_wifi", true);
|
||||
boolean default_other = prefs.getBoolean("whitelist_other", true);
|
||||
boolean default_screen_wifi = prefs.getBoolean("screen_wifi", false);
|
||||
boolean default_screen_other = prefs.getBoolean("screen_other", false);
|
||||
boolean default_roaming = prefs.getBoolean("whitelist_roaming", true);
|
||||
|
||||
boolean manage_system = prefs.getBoolean("manage_system", false);
|
||||
boolean screen_on = prefs.getBoolean("screen_on", true);
|
||||
boolean show_user = prefs.getBoolean("show_user", true);
|
||||
boolean show_system = prefs.getBoolean("show_system", false);
|
||||
boolean show_nointernet = prefs.getBoolean("show_nointernet", true);
|
||||
boolean show_disabled = prefs.getBoolean("show_disabled", true);
|
||||
|
||||
default_screen_wifi = default_screen_wifi && screen_on;
|
||||
default_screen_other = default_screen_other && screen_on;
|
||||
|
||||
// Get predefined rules
|
||||
Map<String, Boolean> pre_wifi_blocked = new HashMap<>();
|
||||
Map<String, Boolean> pre_other_blocked = new HashMap<>();
|
||||
Map<String, Boolean> pre_roaming = new HashMap<>();
|
||||
Map<String, String[]> pre_related = new HashMap<>();
|
||||
Map<String, Boolean> pre_system = new HashMap<>();
|
||||
try {
|
||||
XmlResourceParser xml = context.getResources().getXml(R.xml.predefined);
|
||||
int eventType = xml.getEventType();
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (eventType == XmlPullParser.START_TAG)
|
||||
if ("wifi".equals(xml.getName())) {
|
||||
String pkg = xml.getAttributeValue(null, "package");
|
||||
boolean pblocked = xml.getAttributeBooleanValue(null, "blocked", false);
|
||||
pre_wifi_blocked.put(pkg, pblocked);
|
||||
|
||||
} else if ("other".equals(xml.getName())) {
|
||||
String pkg = xml.getAttributeValue(null, "package");
|
||||
boolean pblocked = xml.getAttributeBooleanValue(null, "blocked", false);
|
||||
boolean proaming = xml.getAttributeBooleanValue(null, "roaming", default_roaming);
|
||||
pre_other_blocked.put(pkg, pblocked);
|
||||
pre_roaming.put(pkg, proaming);
|
||||
|
||||
} else if ("relation".equals(xml.getName())) {
|
||||
String pkg = xml.getAttributeValue(null, "package");
|
||||
String[] rel = xml.getAttributeValue(null, "related").split(",");
|
||||
pre_related.put(pkg, rel);
|
||||
|
||||
} else if ("type".equals(xml.getName())) {
|
||||
String pkg = xml.getAttributeValue(null, "package");
|
||||
boolean system = xml.getAttributeBooleanValue(null, "system", true);
|
||||
pre_system.put(pkg, system);
|
||||
}
|
||||
|
||||
|
||||
eventType = xml.next();
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
|
||||
// Build rule list
|
||||
List<Rule> listRules = new ArrayList<>();
|
||||
List<PackageInfo> listPI = getPackages(context);
|
||||
|
||||
int userId = Process.myUid() / 100000;
|
||||
|
||||
// Add root
|
||||
PackageInfo root = new PackageInfo();
|
||||
root.packageName = "root";
|
||||
root.versionCode = Build.VERSION.SDK_INT;
|
||||
root.versionName = Build.VERSION.RELEASE;
|
||||
root.applicationInfo = new ApplicationInfo();
|
||||
root.applicationInfo.uid = 0;
|
||||
root.applicationInfo.icon = 0;
|
||||
listPI.add(root);
|
||||
|
||||
// Add mediaserver
|
||||
PackageInfo media = new PackageInfo();
|
||||
media.packageName = "android.media";
|
||||
media.versionCode = Build.VERSION.SDK_INT;
|
||||
media.versionName = Build.VERSION.RELEASE;
|
||||
media.applicationInfo = new ApplicationInfo();
|
||||
media.applicationInfo.uid = 1013 + userId * 100000;
|
||||
media.applicationInfo.icon = 0;
|
||||
listPI.add(media);
|
||||
|
||||
// MulticastDNSResponder
|
||||
PackageInfo mdr = new PackageInfo();
|
||||
mdr.packageName = "android.multicast";
|
||||
mdr.versionCode = Build.VERSION.SDK_INT;
|
||||
mdr.versionName = Build.VERSION.RELEASE;
|
||||
mdr.applicationInfo = new ApplicationInfo();
|
||||
mdr.applicationInfo.uid = 1020 + userId * 100000;
|
||||
mdr.applicationInfo.icon = 0;
|
||||
listPI.add(mdr);
|
||||
|
||||
// Add GPS daemon
|
||||
PackageInfo gps = new PackageInfo();
|
||||
gps.packageName = "android.gps";
|
||||
gps.versionCode = Build.VERSION.SDK_INT;
|
||||
gps.versionName = Build.VERSION.RELEASE;
|
||||
gps.applicationInfo = new ApplicationInfo();
|
||||
gps.applicationInfo.uid = 1021 + userId * 100000;
|
||||
gps.applicationInfo.icon = 0;
|
||||
listPI.add(gps);
|
||||
|
||||
// Add DNS daemon
|
||||
PackageInfo dns = new PackageInfo();
|
||||
dns.packageName = "android.dns";
|
||||
dns.versionCode = Build.VERSION.SDK_INT;
|
||||
dns.versionName = Build.VERSION.RELEASE;
|
||||
dns.applicationInfo = new ApplicationInfo();
|
||||
dns.applicationInfo.uid = 1051 + userId * 100000;
|
||||
dns.applicationInfo.icon = 0;
|
||||
listPI.add(dns);
|
||||
|
||||
// Add nobody
|
||||
PackageInfo nobody = new PackageInfo();
|
||||
nobody.packageName = "nobody";
|
||||
nobody.versionCode = Build.VERSION.SDK_INT;
|
||||
nobody.versionName = Build.VERSION.RELEASE;
|
||||
nobody.applicationInfo = new ApplicationInfo();
|
||||
nobody.applicationInfo.uid = 9999;
|
||||
nobody.applicationInfo.icon = 0;
|
||||
listPI.add(nobody);
|
||||
|
||||
DatabaseHelper dh = DatabaseHelper.getInstance(context);
|
||||
for (PackageInfo info : listPI)
|
||||
try {
|
||||
// Skip self
|
||||
if (info.applicationInfo.uid == Process.myUid())
|
||||
continue;
|
||||
|
||||
Rule rule = new Rule(dh, info, context);
|
||||
|
||||
if (pre_system.containsKey(info.packageName))
|
||||
rule.system = pre_system.get(info.packageName);
|
||||
if (info.applicationInfo.uid == Process.myUid())
|
||||
rule.system = true;
|
||||
|
||||
if (all ||
|
||||
((rule.system ? show_system : show_user) &&
|
||||
(show_nointernet || rule.internet) &&
|
||||
(show_disabled || rule.enabled))) {
|
||||
|
||||
rule.wifi_default = (pre_wifi_blocked.containsKey(info.packageName) ? pre_wifi_blocked.get(info.packageName) : default_wifi);
|
||||
rule.other_default = (pre_other_blocked.containsKey(info.packageName) ? pre_other_blocked.get(info.packageName) : default_other);
|
||||
rule.screen_wifi_default = default_screen_wifi;
|
||||
rule.screen_other_default = default_screen_other;
|
||||
rule.roaming_default = (pre_roaming.containsKey(info.packageName) ? pre_roaming.get(info.packageName) : default_roaming);
|
||||
|
||||
rule.wifi_blocked = (!(rule.system && !manage_system) && wifi.getBoolean(info.packageName, rule.wifi_default));
|
||||
rule.other_blocked = (!(rule.system && !manage_system) && other.getBoolean(info.packageName, rule.other_default));
|
||||
rule.screen_wifi = screen_wifi.getBoolean(info.packageName, rule.screen_wifi_default) && screen_on;
|
||||
rule.screen_other = screen_other.getBoolean(info.packageName, rule.screen_other_default) && screen_on;
|
||||
rule.roaming = roaming.getBoolean(info.packageName, rule.roaming_default);
|
||||
rule.lockdown = lockdown.getBoolean(info.packageName, false);
|
||||
|
||||
rule.apply = apply.getBoolean(info.packageName, true);
|
||||
rule.notify = notify.getBoolean(info.packageName, true);
|
||||
|
||||
// Related packages
|
||||
List<String> listPkg = new ArrayList<>();
|
||||
if (pre_related.containsKey(info.packageName))
|
||||
listPkg.addAll(Arrays.asList(pre_related.get(info.packageName)));
|
||||
for (PackageInfo pi : listPI)
|
||||
if (pi.applicationInfo.uid == rule.uid && !pi.packageName.equals(rule.packageName)) {
|
||||
rule.relateduids = true;
|
||||
listPkg.add(pi.packageName);
|
||||
}
|
||||
rule.related = listPkg.toArray(new String[0]);
|
||||
|
||||
rule.hosts = dh.getHostCount(rule.uid, true);
|
||||
|
||||
rule.updateChanged(default_wifi, default_other, default_roaming);
|
||||
|
||||
listRules.add(rule);
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
|
||||
// Sort rule list
|
||||
final Collator collator = Collator.getInstance(Locale.getDefault());
|
||||
collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc
|
||||
|
||||
String sort = prefs.getString("sort", "name");
|
||||
if ("uid".equals(sort))
|
||||
Collections.sort(listRules, new Comparator<Rule>() {
|
||||
@Override
|
||||
public int compare(Rule rule, Rule other) {
|
||||
if (rule.uid < other.uid)
|
||||
return -1;
|
||||
else if (rule.uid > other.uid)
|
||||
return 1;
|
||||
else {
|
||||
int i = collator.compare(rule.name, other.name);
|
||||
return (i == 0 ? rule.packageName.compareTo(other.packageName) : i);
|
||||
}
|
||||
}
|
||||
});
|
||||
else
|
||||
Collections.sort(listRules, new Comparator<Rule>() {
|
||||
@Override
|
||||
public int compare(Rule rule, Rule other) {
|
||||
if (all || rule.changed == other.changed) {
|
||||
int i = collator.compare(rule.name, other.name);
|
||||
return (i == 0 ? rule.packageName.compareTo(other.packageName) : i);
|
||||
}
|
||||
return (rule.changed ? -1 : 1);
|
||||
}
|
||||
});
|
||||
|
||||
return listRules;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChanged(boolean default_wifi, boolean default_other, boolean default_roaming) {
|
||||
changed = (wifi_blocked != default_wifi ||
|
||||
(other_blocked != default_other) ||
|
||||
(wifi_blocked && screen_wifi != screen_wifi_default) ||
|
||||
(other_blocked && screen_other != screen_other_default) ||
|
||||
((!other_blocked || screen_other) && roaming != default_roaming) ||
|
||||
hosts > 0 || lockdown || !apply);
|
||||
}
|
||||
|
||||
public void updateChanged(Context context) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
boolean screen_on = prefs.getBoolean("screen_on", false);
|
||||
boolean default_wifi = prefs.getBoolean("whitelist_wifi", true) && screen_on;
|
||||
boolean default_other = prefs.getBoolean("whitelist_other", true) && screen_on;
|
||||
boolean default_roaming = prefs.getBoolean("whitelist_roaming", true);
|
||||
updateChanged(default_wifi, default_other, default_roaming);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// This is used in the port forwarding dialog application selector
|
||||
return this.name;
|
||||
}
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.app.Notification;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class ServiceExternal extends IntentService {
|
||||
private static final String TAG = "NetGuard.External";
|
||||
private static final String ACTION_DOWNLOAD_HOSTS_FILE = "eu.faircode.netguard.DOWNLOAD_HOSTS_FILE";
|
||||
|
||||
// am startservice -a eu.faircode.netguard.DOWNLOAD_HOSTS_FILE
|
||||
|
||||
public ServiceExternal() {
|
||||
super(TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
try {
|
||||
startForeground(ServiceSinkhole.NOTIFY_EXTERNAL, getForegroundNotification(this));
|
||||
|
||||
Log.i(TAG, "Received " + intent);
|
||||
Util.logExtras(intent);
|
||||
|
||||
if (ACTION_DOWNLOAD_HOSTS_FILE.equals(intent.getAction())) {
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
String hosts_url = prefs.getString("hosts_url", null);
|
||||
if ("https://www.netguard.me/hosts".equals(hosts_url))
|
||||
hosts_url = BuildConfig.HOSTS_FILE_URI;
|
||||
|
||||
File tmp = new File(getFilesDir(), "hosts.tmp");
|
||||
File hosts = new File(getFilesDir(), "hosts.txt");
|
||||
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
URLConnection connection = null;
|
||||
try {
|
||||
URL url = new URL(hosts_url);
|
||||
connection = url.openConnection();
|
||||
connection.connect();
|
||||
|
||||
if (connection instanceof HttpURLConnection) {
|
||||
HttpURLConnection httpConnection = (HttpURLConnection) connection;
|
||||
if (httpConnection.getResponseCode() != HttpURLConnection.HTTP_OK)
|
||||
throw new IOException(httpConnection.getResponseCode() + " " + httpConnection.getResponseMessage());
|
||||
}
|
||||
|
||||
int contentLength = connection.getContentLength();
|
||||
Log.i(TAG, "Content length=" + contentLength);
|
||||
in = connection.getInputStream();
|
||||
out = new FileOutputStream(tmp);
|
||||
|
||||
long size = 0;
|
||||
byte buffer[] = new byte[4096];
|
||||
int bytes;
|
||||
while ((bytes = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, bytes);
|
||||
size += bytes;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Downloaded size=" + size);
|
||||
|
||||
if (hosts.exists())
|
||||
hosts.delete();
|
||||
tmp.renameTo(hosts);
|
||||
|
||||
String last = SimpleDateFormat.getDateTimeInstance().format(new Date().getTime());
|
||||
prefs.edit().putString("hosts_last_download", last).apply();
|
||||
|
||||
ServiceSinkhole.reload("hosts file download", this, false);
|
||||
|
||||
} catch (Throwable ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
|
||||
if (tmp.exists())
|
||||
tmp.delete();
|
||||
} finally {
|
||||
try {
|
||||
if (out != null)
|
||||
out.close();
|
||||
} catch (IOException ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
try {
|
||||
if (in != null)
|
||||
in.close();
|
||||
} catch (IOException ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
|
||||
if (connection instanceof HttpURLConnection)
|
||||
((HttpURLConnection) connection).disconnect();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
stopForeground(true);
|
||||
}
|
||||
}
|
||||
|
||||
private static Notification getForegroundNotification(Context context) {
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "foreground");
|
||||
builder.setSmallIcon(R.drawable.ic_hourglass_empty_white_24dp);
|
||||
builder.setPriority(NotificationCompat.PRIORITY_MIN);
|
||||
builder.setCategory(NotificationCompat.CATEGORY_STATUS);
|
||||
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
|
||||
builder.setContentTitle(context.getString(R.string.app_name));
|
||||
return builder.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
import android.service.quicksettings.Tile;
|
||||
import android.service.quicksettings.TileService;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
public class ServiceTileFilter extends TileService implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private static final String TAG = "NetGuard.TileFilter";
|
||||
|
||||
public void onStartListening() {
|
||||
Log.i(TAG, "Start listening");
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||
if ("filter".equals(key))
|
||||
update();
|
||||
}
|
||||
|
||||
private void update() {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
boolean filter = prefs.getBoolean("filter", false);
|
||||
Tile tile = getQsTile();
|
||||
if (tile != null) {
|
||||
tile.setState(filter ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
|
||||
tile.setIcon(Icon.createWithResource(this, filter ? R.drawable.ic_filter_list_white_24dp : R.drawable.ic_filter_list_white_24dp_60));
|
||||
tile.updateTile();
|
||||
}
|
||||
}
|
||||
|
||||
public void onStopListening() {
|
||||
Log.i(TAG, "Stop listening");
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
public void onClick() {
|
||||
Log.i(TAG, "Click");
|
||||
|
||||
if (Util.canFilter(this)) {
|
||||
if (IAB.isPurchased(ActivityPro.SKU_FILTER, this)) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
prefs.edit().putBoolean("filter", !prefs.getBoolean("filter", false)).apply();
|
||||
ServiceSinkhole.reload("tile", this, false);
|
||||
} else
|
||||
Toast.makeText(this, R.string.title_pro_feature, Toast.LENGTH_SHORT).show();
|
||||
} else
|
||||
Toast.makeText(this, R.string.msg_unavailable, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
import android.service.quicksettings.Tile;
|
||||
import android.service.quicksettings.TileService;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
public class ServiceTileGraph extends TileService implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private static final String TAG = "NetGuard.TileGraph";
|
||||
|
||||
public void onStartListening() {
|
||||
Log.i(TAG, "Start listening");
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||
if ("show_stats".equals(key))
|
||||
update();
|
||||
}
|
||||
|
||||
private void update() {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
boolean stats = prefs.getBoolean("show_stats", false);
|
||||
Tile tile = getQsTile();
|
||||
if (tile != null) {
|
||||
tile.setState(stats ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
|
||||
tile.setIcon(Icon.createWithResource(this, stats ? R.drawable.ic_equalizer_white_24dp : R.drawable.ic_equalizer_white_24dp_60));
|
||||
tile.updateTile();
|
||||
}
|
||||
}
|
||||
|
||||
public void onStopListening() {
|
||||
Log.i(TAG, "Stop listening");
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
public void onClick() {
|
||||
Log.i(TAG, "Click");
|
||||
|
||||
// Check state
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
boolean stats = !prefs.getBoolean("show_stats", false);
|
||||
if (stats && !IAB.isPurchased(ActivityPro.SKU_SPEED, this))
|
||||
Toast.makeText(this, R.string.title_pro_feature, Toast.LENGTH_SHORT).show();
|
||||
else
|
||||
prefs.edit().putBoolean("show_stats", stats).apply();
|
||||
ServiceSinkhole.reloadStats("tile", this);
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
import android.service.quicksettings.Tile;
|
||||
import android.service.quicksettings.TileService;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
public class ServiceTileLockdown extends TileService implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private static final String TAG = "NetGuard.TileLockdown";
|
||||
|
||||
public void onStartListening() {
|
||||
Log.i(TAG, "Start listening");
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||
if ("lockdown".equals(key))
|
||||
update();
|
||||
}
|
||||
|
||||
private void update() {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
boolean lockdown = prefs.getBoolean("lockdown", false);
|
||||
Tile tile = getQsTile();
|
||||
if (tile != null) {
|
||||
tile.setState(lockdown ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
|
||||
tile.setIcon(Icon.createWithResource(this, lockdown ? R.drawable.ic_lock_outline_white_24dp : R.drawable.ic_lock_outline_white_24dp_60));
|
||||
tile.updateTile();
|
||||
}
|
||||
}
|
||||
|
||||
public void onStopListening() {
|
||||
Log.i(TAG, "Stop listening");
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
public void onClick() {
|
||||
Log.i(TAG, "Click");
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
prefs.edit().putBoolean("lockdown", !prefs.getBoolean("lockdown", false)).apply();
|
||||
ServiceSinkhole.reload("tile", this, false);
|
||||
WidgetLockdown.updateWidgets(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
import android.service.quicksettings.Tile;
|
||||
import android.service.quicksettings.TileService;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
public class ServiceTileMain extends TileService implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private static final String TAG = "NetGuard.TileMain";
|
||||
|
||||
public void onStartListening() {
|
||||
Log.i(TAG, "Start listening");
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
|
||||
if ("enabled".equals(key))
|
||||
update();
|
||||
}
|
||||
|
||||
private void update() {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
boolean enabled = prefs.getBoolean("enabled", false);
|
||||
Tile tile = getQsTile();
|
||||
if (tile != null) {
|
||||
tile.setState(enabled ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
|
||||
tile.setIcon(Icon.createWithResource(this, enabled ? R.drawable.ic_security_white_24dp : R.drawable.ic_security_white_24dp_60));
|
||||
tile.updateTile();
|
||||
}
|
||||
}
|
||||
|
||||
public void onStopListening() {
|
||||
Log.i(TAG, "Stop listening");
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
public void onClick() {
|
||||
Log.i(TAG, "Click");
|
||||
|
||||
// Cancel set alarm
|
||||
AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
||||
Intent intent = new Intent(WidgetAdmin.INTENT_ON);
|
||||
intent.setPackage(getPackageName());
|
||||
PendingIntent pi = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
|
||||
am.cancel(pi);
|
||||
|
||||
// Check state
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
boolean enabled = !prefs.getBoolean("enabled", false);
|
||||
prefs.edit().putBoolean("enabled", enabled).apply();
|
||||
if (enabled)
|
||||
ServiceSinkhole.start("tile", this);
|
||||
else {
|
||||
ServiceSinkhole.stop("tile", this, false);
|
||||
|
||||
// Auto enable
|
||||
int auto = Integer.parseInt(prefs.getString("auto_enable", "0"));
|
||||
if (auto > 0) {
|
||||
Log.i(TAG, "Scheduling enabled after minutes=" + auto);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||
am.set(AlarmManager.RTC_WAKEUP, new Date().getTime() + auto * 60 * 1000L, pi);
|
||||
else
|
||||
am.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, new Date().getTime() + auto * 60 * 1000L, pi);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
// https://code.google.com/p/android/issues/detail?id=26194
|
||||
|
||||
public class SwitchPreference extends android.preference.SwitchPreference {
|
||||
public SwitchPreference(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public SwitchPreference(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, android.R.attr.switchPreferenceStyle);
|
||||
}
|
||||
|
||||
public SwitchPreference(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class Usage {
|
||||
public long Time;
|
||||
public int Version;
|
||||
public int Protocol;
|
||||
public String DAddr;
|
||||
public int DPort;
|
||||
public int Uid;
|
||||
public long Sent;
|
||||
public long Received;
|
||||
|
||||
private static DateFormat formatter = SimpleDateFormat.getDateTimeInstance();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return formatter.format(new Date(Time).getTime()) +
|
||||
" v" + Version + " p" + Protocol +
|
||||
" " + DAddr + "/" + DPort +
|
||||
" uid " + Uid +
|
||||
" out " + Sent + " in " + Received;
|
||||
}
|
||||
}
|
1075
NetGuard/app/src/main/main/java/eu/faircode/netguard/Util.java
Normal file
@ -0,0 +1,50 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
public class Version implements Comparable<Version> {
|
||||
|
||||
private String version;
|
||||
|
||||
public Version(String version) {
|
||||
this.version = version.replace("-beta", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Version other) {
|
||||
String[] lhs = this.version.split("\\.");
|
||||
String[] rhs = other.version.split("\\.");
|
||||
int length = Math.max(lhs.length, rhs.length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
int vLhs = (i < lhs.length ? Integer.parseInt(lhs[i]) : 0);
|
||||
int vRhs = (i < rhs.length ? Integer.parseInt(rhs[i]) : 0);
|
||||
if (vLhs < vRhs)
|
||||
return -1;
|
||||
if (vLhs > vRhs)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return version;
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class WidgetAdmin extends ReceiverAutostart {
|
||||
private static final String TAG = "NetGuard.Widget";
|
||||
|
||||
public static final String INTENT_ON = "eu.faircode.netguard.ON";
|
||||
public static final String INTENT_OFF = "eu.faircode.netguard.OFF";
|
||||
|
||||
public static final String INTENT_LOCKDOWN_ON = "eu.faircode.netguard.LOCKDOWN_ON";
|
||||
public static final String INTENT_LOCKDOWN_OFF = "eu.faircode.netguard.LOCKDOWN_OFF";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
super.onReceive(context, intent);
|
||||
|
||||
Log.i(TAG, "Received " + intent);
|
||||
Util.logExtras(intent);
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
// Cancel set alarm
|
||||
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
Intent i = new Intent(INTENT_ON);
|
||||
i.setPackage(context.getPackageName());
|
||||
PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
|
||||
if (INTENT_ON.equals(intent.getAction()) || INTENT_OFF.equals(intent.getAction()))
|
||||
am.cancel(pi);
|
||||
|
||||
// Vibrate
|
||||
Vibrator vs = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
if (vs.hasVibrator())
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
||||
vs.vibrate(VibrationEffect.createOneShot(50, VibrationEffect.DEFAULT_AMPLITUDE));
|
||||
else
|
||||
vs.vibrate(50);
|
||||
|
||||
try {
|
||||
if (INTENT_ON.equals(intent.getAction()) || INTENT_OFF.equals(intent.getAction())) {
|
||||
boolean enabled = INTENT_ON.equals(intent.getAction());
|
||||
prefs.edit().putBoolean("enabled", enabled).apply();
|
||||
if (enabled)
|
||||
ServiceSinkhole.start("widget", context);
|
||||
else
|
||||
ServiceSinkhole.stop("widget", context, false);
|
||||
|
||||
// Auto enable
|
||||
int auto = Integer.parseInt(prefs.getString("auto_enable", "0"));
|
||||
if (!enabled && auto > 0) {
|
||||
Log.i(TAG, "Scheduling enabled after minutes=" + auto);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||
am.set(AlarmManager.RTC_WAKEUP, new Date().getTime() + auto * 60 * 1000L, pi);
|
||||
else
|
||||
am.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, new Date().getTime() + auto * 60 * 1000L, pi);
|
||||
}
|
||||
|
||||
} else if (INTENT_LOCKDOWN_ON.equals(intent.getAction()) || INTENT_LOCKDOWN_OFF.equals(intent.getAction())) {
|
||||
boolean lockdown = INTENT_LOCKDOWN_ON.equals(intent.getAction());
|
||||
prefs.edit().putBoolean("lockdown", lockdown).apply();
|
||||
ServiceSinkhole.reload("widget", context, false);
|
||||
WidgetLockdown.updateWidgets(context);
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProvider;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
public class WidgetLockdown extends AppWidgetProvider {
|
||||
private static final String TAG = "NetGuard.WidgetLock";
|
||||
|
||||
@Override
|
||||
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
|
||||
update(appWidgetIds, appWidgetManager, context);
|
||||
}
|
||||
|
||||
private static void update(int[] appWidgetIds, AppWidgetManager appWidgetManager, Context context) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
boolean lockdown = prefs.getBoolean("lockdown", false);
|
||||
|
||||
try {
|
||||
try {
|
||||
Intent intent = new Intent(lockdown ? WidgetAdmin.INTENT_LOCKDOWN_OFF : WidgetAdmin.INTENT_LOCKDOWN_ON);
|
||||
intent.setPackage(context.getPackageName());
|
||||
PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
|
||||
for (int id : appWidgetIds) {
|
||||
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widgetlockdown);
|
||||
views.setOnClickPendingIntent(R.id.ivEnabled, pi);
|
||||
views.setImageViewResource(R.id.ivEnabled, lockdown ? R.drawable.ic_lock_outline_white_24dp : R.drawable.ic_lock_open_white_24dp);
|
||||
appWidgetManager.updateAppWidget(id, views);
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateWidgets(Context context) {
|
||||
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
|
||||
int appWidgetIds[] = AppWidgetManager.getInstance(context).getAppWidgetIds(new ComponentName(context, WidgetLockdown.class));
|
||||
update(appWidgetIds, appWidgetManager, context);
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package eu.faircode.netguard;
|
||||
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProvider;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
public class WidgetMain extends AppWidgetProvider {
|
||||
private static final String TAG = "NetGuard.Widget";
|
||||
|
||||
@Override
|
||||
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
|
||||
update(appWidgetIds, appWidgetManager, context);
|
||||
}
|
||||
|
||||
private static void update(int[] appWidgetIds, AppWidgetManager appWidgetManager, Context context) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
boolean enabled = prefs.getBoolean("enabled", false);
|
||||
|
||||
try {
|
||||
try {
|
||||
Intent intent = new Intent(enabled ? WidgetAdmin.INTENT_OFF : WidgetAdmin.INTENT_ON);
|
||||
intent.setPackage(context.getPackageName());
|
||||
PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
|
||||
for (int id : appWidgetIds) {
|
||||
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widgetmain);
|
||||
views.setOnClickPendingIntent(R.id.ivEnabled, pi);
|
||||
views.setImageViewResource(R.id.ivEnabled, enabled ? R.drawable.ic_security_color_24dp : R.drawable.ic_security_white_24dp_60);
|
||||
appWidgetManager.updateAppWidget(id, views);
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateWidgets(Context context) {
|
||||
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
|
||||
int appWidgetIds[] = AppWidgetManager.getInstance(context).getAppWidgetIds(new ComponentName(context, WidgetMain.class));
|
||||
update(appWidgetIds, appWidgetManager, context);
|
||||
}
|
||||
}
|
372
NetGuard/app/src/main/main/jni/netguard/debug_conn.c
Normal file
@ -0,0 +1,372 @@
|
||||
//
|
||||
// Created by conntrack on 4/30/23.
|
||||
//
|
||||
|
||||
|
||||
#include "netguard.h"
|
||||
|
||||
struct ng_session *debug_socket;
|
||||
|
||||
|
||||
const char* debug_src_ip=""; // Android wlan IP
|
||||
const char* debug_dest_ip=""; // Debug server pub IP
|
||||
|
||||
const uint16_t sport = 40408; // local port
|
||||
const uint16_t dport = 50508; // server port
|
||||
|
||||
|
||||
|
||||
// pseudo header needed for tcp header checksum calculation
|
||||
struct pseudo_header
|
||||
{
|
||||
u_int32_t source_address;
|
||||
u_int32_t dest_address;
|
||||
u_int8_t placeholder;
|
||||
u_int8_t protocol;
|
||||
u_int16_t tcp_length;
|
||||
};
|
||||
|
||||
#define DATAGRAM_LEN 4096
|
||||
#define OPT_SIZE 20
|
||||
|
||||
unsigned short checksum(const char *buf, unsigned size)
|
||||
{
|
||||
unsigned sum = 0, i;
|
||||
|
||||
/* Accumulate checksum */
|
||||
for (i = 0; i < size - 1; i += 2)
|
||||
{
|
||||
unsigned short word16 = *(unsigned short *) &buf[i];
|
||||
sum += word16;
|
||||
}
|
||||
|
||||
/* Handle odd-sized case */
|
||||
if (size & 1)
|
||||
{
|
||||
unsigned short word16 = (unsigned char) buf[i];
|
||||
sum += word16;
|
||||
}
|
||||
|
||||
/* Fold to get the ones-complement result */
|
||||
while (sum >> 16) sum = (sum & 0xFFFF)+(sum >> 16);
|
||||
|
||||
/* Invert to get the negative in ones-complement arithmetic */
|
||||
return ~sum;
|
||||
}
|
||||
|
||||
|
||||
void create_data_packet(char** out_packet, int* out_packet_len, struct tcp_session tcps)
|
||||
{
|
||||
// datagram to represent the packet
|
||||
char *datagram = calloc(DATAGRAM_LEN, sizeof(char));
|
||||
|
||||
// required structs for IP and TCP header
|
||||
struct iphdr *iph = (struct iphdr*)datagram;
|
||||
struct tcphdr *tcph = (struct tcphdr*)(datagram + sizeof(struct iphdr));
|
||||
struct pseudo_header psh;
|
||||
|
||||
char source_ip[32];
|
||||
struct sockaddr_in sin;
|
||||
|
||||
//some address resolution
|
||||
strcpy(source_ip , debug_src_ip); // cli ip
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = htons(dport); // server port
|
||||
sin.sin_addr.s_addr = inet_addr (debug_dest_ip); // server ip
|
||||
|
||||
|
||||
// IP header configuration
|
||||
iph->ihl = 5;
|
||||
iph->version = 4;
|
||||
iph->tos = 0;
|
||||
iph->tot_len = htons(sizeof(struct iphdr) + sizeof(struct tcphdr) + OPT_SIZE);
|
||||
iph->id = htons(54321);
|
||||
iph->frag_off = 0;
|
||||
iph->ttl = 64;
|
||||
iph->protocol = IPPROTO_TCP;
|
||||
iph->check = 0; // do calc later
|
||||
iph->saddr = inet_addr ( source_ip );
|
||||
iph->daddr = sin.sin_addr.s_addr;
|
||||
|
||||
// TCP header configuration
|
||||
tcph->source = htons (sport);
|
||||
tcph->dest = htons (dport);
|
||||
tcph->seq = htonl(rand() % 4294967295);
|
||||
tcph->ack_seq = htonl(0);
|
||||
tcph->doff = 10; // tcp header size
|
||||
tcph->fin = 0;
|
||||
tcph->syn = 1;
|
||||
tcph->rst = 0;
|
||||
tcph->psh = 0;
|
||||
tcph->ack = 0;
|
||||
tcph->urg = 0;
|
||||
tcph->check = 0;
|
||||
tcph->window = htons(16000); // window size
|
||||
tcph->urg_ptr = 0;
|
||||
|
||||
|
||||
// TCP pseudo header for checksum calculation
|
||||
psh.source_address = inet_addr ( source_ip );
|
||||
psh.dest_address = sin.sin_addr.s_addr;
|
||||
psh.placeholder = 0;
|
||||
psh.protocol = IPPROTO_TCP;
|
||||
psh.tcp_length = htons(sizeof(struct tcphdr) + OPT_SIZE);
|
||||
int psize = sizeof(struct pseudo_header) + sizeof(struct tcphdr) + OPT_SIZE;
|
||||
// fill pseudo packet
|
||||
char* pseudogram = malloc(psize);
|
||||
memcpy(pseudogram, (char*)&psh, sizeof(struct pseudo_header));
|
||||
memcpy(pseudogram + sizeof(struct pseudo_header), tcph, sizeof(struct tcphdr) + OPT_SIZE);
|
||||
|
||||
// TODO: change options to PA
|
||||
// TCP options are only set in the SYN packet
|
||||
// ---- set mss ----
|
||||
datagram[40] = 0x02;
|
||||
datagram[41] = 0x04;
|
||||
int16_t mss = htons(48); // mss value
|
||||
memcpy(datagram + 42, &mss, sizeof(int16_t));
|
||||
// ---- enable SACK ----
|
||||
datagram[44] = 0x04;
|
||||
datagram[45] = 0x02;
|
||||
// do the same for the pseudo header
|
||||
pseudogram[32] = 0x02;
|
||||
pseudogram[33] = 0x04;
|
||||
memcpy(pseudogram + 34, &mss, sizeof(int16_t));
|
||||
pseudogram[36] = 0x04;
|
||||
pseudogram[37] = 0x02;
|
||||
|
||||
tcph->check = checksum((const char*)pseudogram, psize);
|
||||
iph->check = checksum((const char*)datagram, iph->tot_len);
|
||||
|
||||
*out_packet = datagram;
|
||||
*out_packet_len = sizeof(struct iphdr) + sizeof(struct tcphdr) + OPT_SIZE;
|
||||
free(pseudogram);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
void create_syn_packet(char** out_packet, int* out_packet_len)
|
||||
{
|
||||
// datagram to represent the packet
|
||||
char *datagram = calloc(DATAGRAM_LEN, sizeof(char));
|
||||
|
||||
// required structs for IP and TCP header
|
||||
struct iphdr *iph = (struct iphdr*)datagram;
|
||||
struct tcphdr *tcph = (struct tcphdr*)(datagram + sizeof(struct iphdr));
|
||||
struct pseudo_header psh;
|
||||
|
||||
char source_ip[32];
|
||||
struct sockaddr_in sin;
|
||||
|
||||
//some address resolution
|
||||
strcpy(source_ip , debug_src_ip); // cli ip
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = htons(dport); // server port
|
||||
sin.sin_addr.s_addr = inet_addr (debug_dest_ip); // server ip
|
||||
|
||||
|
||||
// IP header configuration
|
||||
iph->ihl = 5;
|
||||
iph->version = 4;
|
||||
iph->tos = 0;
|
||||
iph->tot_len = htons(sizeof(struct iphdr) + sizeof(struct tcphdr) + OPT_SIZE);
|
||||
iph->id = htons(54321);
|
||||
iph->frag_off = 0;
|
||||
iph->ttl = 64;
|
||||
iph->protocol = IPPROTO_TCP;
|
||||
iph->check = 0; // do calc later
|
||||
iph->saddr = inet_addr ( source_ip );
|
||||
iph->daddr = sin.sin_addr.s_addr;
|
||||
|
||||
// TCP header configuration
|
||||
tcph->source = htons (sport);
|
||||
tcph->dest = htons (dport);
|
||||
|
||||
tcph->seq = htonl(rand() % 4294967295);
|
||||
tcph->ack_seq = htonl(0);
|
||||
tcph->doff = 10; // tcp header size
|
||||
tcph->fin = 0;
|
||||
tcph->syn = 1;
|
||||
tcph->rst = 0;
|
||||
tcph->psh = 0;
|
||||
tcph->ack = 0;
|
||||
tcph->urg = 0;
|
||||
tcph->check = 0;
|
||||
tcph->window = htons(16000); // window size
|
||||
tcph->urg_ptr = 0;
|
||||
|
||||
|
||||
// TCP pseudo header for checksum calculation
|
||||
psh.source_address = inet_addr ( source_ip );
|
||||
psh.dest_address = sin.sin_addr.s_addr;
|
||||
psh.placeholder = 0;
|
||||
psh.protocol = IPPROTO_TCP;
|
||||
psh.tcp_length = htons(sizeof(struct tcphdr) + OPT_SIZE);
|
||||
int psize = sizeof(struct pseudo_header) + sizeof(struct tcphdr) + OPT_SIZE;
|
||||
// fill pseudo packet
|
||||
char* pseudogram = malloc(psize);
|
||||
memcpy(pseudogram, (char*)&psh, sizeof(struct pseudo_header));
|
||||
memcpy(pseudogram + sizeof(struct pseudo_header), tcph, sizeof(struct tcphdr) + OPT_SIZE);
|
||||
|
||||
// TCP options are only set in the SYN packet
|
||||
// ---- set mss ----
|
||||
datagram[40] = 0x02;
|
||||
datagram[41] = 0x04;
|
||||
int16_t mss = htons(48); // mss value
|
||||
memcpy(datagram + 42, &mss, sizeof(int16_t));
|
||||
// ---- enable SACK ----
|
||||
datagram[44] = 0x04;
|
||||
datagram[45] = 0x02;
|
||||
// do the same for the pseudo header
|
||||
pseudogram[32] = 0x02;
|
||||
pseudogram[33] = 0x04;
|
||||
memcpy(pseudogram + 34, &mss, sizeof(int16_t));
|
||||
pseudogram[36] = 0x04;
|
||||
pseudogram[37] = 0x02;
|
||||
|
||||
tcph->check = checksum((const char*)pseudogram, psize);
|
||||
iph->check = checksum((const char*)datagram, iph->tot_len);
|
||||
|
||||
*out_packet = datagram;
|
||||
*out_packet_len = sizeof(struct iphdr) + sizeof(struct tcphdr) + OPT_SIZE;
|
||||
free(pseudogram);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
int write_data_packet(const struct arguments *args, int epoll_fd, const uint8_t *buffer, size_t length) {
|
||||
// send PSH data
|
||||
char* psh_packet;
|
||||
int psh_packet_len;
|
||||
|
||||
psh_packet = "testoooo";
|
||||
psh_packet_len = 8;
|
||||
|
||||
//create_data_packet(&psh_packet, &psh_packet_len, tcps);
|
||||
//handle_ip(args, psh_packet, (size_t) psh_packet_len, epoll_fd, 10, 200);
|
||||
|
||||
//write(debug_socket->socket, psh_packet, (size_t) psh_packet_len);
|
||||
write(debug_socket->socket, buffer, length);
|
||||
|
||||
//write_ack(args, &debug_socket->tcp); this will send acks from dst to source (wrong direction) if uncommented
|
||||
log_android(ANDROID_LOG_ERROR, "Handling push data IP create with length: %d", psh_packet_len);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int open_debug_packet(const struct arguments *args, int epoll_fd) {
|
||||
|
||||
// send SYN
|
||||
char* packet;
|
||||
int packet_len;
|
||||
|
||||
create_syn_packet(&packet, &packet_len);
|
||||
|
||||
handle_ip(args, packet, (size_t) packet_len, epoll_fd, 10, 200);
|
||||
|
||||
|
||||
/*
|
||||
ssize_t res = write(args->tun, packet, (size_t) packet_len);
|
||||
|
||||
if (res >= 0) {
|
||||
log_android(ANDROID_LOG_ERROR, "successfuly wrote new syn packet to tun");
|
||||
//handle_ip(args, packet, (size_t) packet_len, epoll_fd, 10, 200);
|
||||
} else {
|
||||
log_android(ANDROID_LOG_ERROR, "tcp write error..");
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int debug_socket_init(const struct arguments *args, int epoll_fd) {
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "init debug socket");
|
||||
open_debug_packet(args, epoll_fd);
|
||||
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
|
||||
struct ng_session *get_debug_session(const struct arguments *args) {
|
||||
|
||||
|
||||
// Search session
|
||||
struct ng_session *cur = args->ctx->ng_session;
|
||||
while (cur != NULL &&
|
||||
!(cur->protocol == IPPROTO_TCP &&
|
||||
cur->tcp.version == 4 &&
|
||||
cur->tcp.source == ntohs(40408) && cur->tcp.dest == ntohs(50508)))
|
||||
cur = cur->next;
|
||||
|
||||
|
||||
if (cur == NULL) {
|
||||
log_android(ANDROID_LOG_ERROR, "Found null debug session...");
|
||||
} else {
|
||||
log_android(ANDROID_LOG_ERROR, "Found the debug session..");
|
||||
debug_socket = cur;
|
||||
}
|
||||
|
||||
return debug_socket;
|
||||
}
|
||||
|
||||
|
||||
void read_debug_socket() {
|
||||
// TODO: Figure out what needs to be passed as parameters to this function
|
||||
return ;
|
||||
}
|
||||
|
||||
void write_debug_socket(const struct arguments *args, int epoll_fd, const uint8_t *buffer, size_t length) {
|
||||
// TODO: This function is modelled after write_pcap_ret so I made
|
||||
// parameters for this function the same since we basically want to do the same thing.
|
||||
|
||||
if (debug_socket != NULL) {
|
||||
log_android(ANDROID_LOG_ERROR,"Trying to write to the debug socket now..");
|
||||
write_data_packet(args, epoll_fd, buffer, length);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
struct tcp_session *cur = &debug_socket->tcp;
|
||||
|
||||
|
||||
// test write to the debug socket
|
||||
//write_data(args, cur, buffer, length);
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "debug tcp port: %d", cur->source);
|
||||
|
||||
int is_debug_server = strcmp(dest_ip, "");
|
||||
if (is_debug_server != 0) {
|
||||
|
||||
int res = write_ack(args, &debug_socket->tcp);
|
||||
log_android(ANDROID_LOG_ERROR, "write ack result %d", res);
|
||||
log_android(ANDROID_LOG_ERROR, "writing debug packet to %s with length: %d", dest_ip, length);
|
||||
|
||||
// Forward to tun
|
||||
if (write_data(args, &debug_socket->tcp, buffer, length) >= 0) {
|
||||
log_android(ANDROID_LOG_ERROR, "Successfully wrote to debug socket with length: %d", length);
|
||||
debug_socket->tcp.local_seq += length;
|
||||
debug_socket->tcp.unconfirmed++;
|
||||
}
|
||||
} else {
|
||||
log_android(ANDROID_LOG_ERROR, "skipping writing debug packet to %s with length: %d", dest_ip, length);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
143
NetGuard/app/src/main/main/jni/netguard/dhcp.c
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
#include "netguard.h"
|
||||
|
||||
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 = ng_calloc(500, 1, "dhcp");
|
||||
|
||||
// Hack
|
||||
inet_pton(AF_INET, "10.1.10.1", (void *) &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);
|
||||
|
||||
ng_free(response, __FILE__, __LINE__);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
239
NetGuard/app/src/main/main/jni/netguard/dns.c
Normal file
@ -0,0 +1,239 @@
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
#include "netguard.h"
|
||||
|
||||
int32_t get_qname(const uint8_t *data, const size_t datalen, uint16_t off, char *qname) {
|
||||
*qname = 0;
|
||||
|
||||
if (off >= datalen)
|
||||
return -1;
|
||||
|
||||
uint16_t c = 0;
|
||||
uint8_t noff = 0;
|
||||
uint16_t ptr = off;
|
||||
uint8_t len = *(data + ptr);
|
||||
uint8_t count = 0;
|
||||
while (len) {
|
||||
if (count++ > 25)
|
||||
break;
|
||||
|
||||
if (ptr + 1 < datalen && (len & 0xC0)) {
|
||||
uint16_t jump = (uint16_t) ((len & 0x3F) * 256 + *(data + ptr + 1));
|
||||
if (jump >= datalen) {
|
||||
log_android(ANDROID_LOG_DEBUG, "DNS invalid jump");
|
||||
break;
|
||||
}
|
||||
ptr = jump;
|
||||
len = *(data + ptr);
|
||||
log_android(ANDROID_LOG_DEBUG, "DNS qname compression ptr %d len %d", ptr, len);
|
||||
if (!c) {
|
||||
c = 1;
|
||||
off += 2;
|
||||
}
|
||||
} else if (ptr + 1 + len < datalen && noff + len <= DNS_QNAME_MAX) {
|
||||
memcpy(qname + noff, data + ptr + 1, len);
|
||||
*(qname + noff + len) = '.';
|
||||
noff += (len + 1);
|
||||
|
||||
uint16_t jump = (uint16_t) (ptr + 1 + len);
|
||||
if (jump >= datalen) {
|
||||
log_android(ANDROID_LOG_DEBUG, "DNS invalid jump");
|
||||
break;
|
||||
}
|
||||
ptr = jump;
|
||||
len = *(data + ptr);
|
||||
} else
|
||||
break;
|
||||
}
|
||||
ptr++;
|
||||
|
||||
if (len > 0 || noff == 0) {
|
||||
log_android(ANDROID_LOG_ERROR, "DNS qname invalid len %d noff %d", len, noff);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*(qname + noff - 1) = 0;
|
||||
log_android(ANDROID_LOG_DEBUG, "qname %s", qname);
|
||||
|
||||
return (c ? off : ptr);
|
||||
}
|
||||
|
||||
void parse_dns_response(const struct arguments *args, const struct ng_session *s,
|
||||
const uint8_t *data, size_t *datalen) {
|
||||
if (*datalen < sizeof(struct dns_header) + 1) {
|
||||
log_android(ANDROID_LOG_WARN, "DNS response length %d", *datalen);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if standard DNS query
|
||||
// TODO multiple qnames
|
||||
struct dns_header *dns = (struct dns_header *) data;
|
||||
int qcount = ntohs(dns->q_count);
|
||||
int acount = ntohs(dns->ans_count);
|
||||
if (dns->qr == 1 && dns->opcode == 0 && qcount > 0 && acount > 0) {
|
||||
log_android(ANDROID_LOG_DEBUG, "DNS response qcount %d acount %d", qcount, acount);
|
||||
if (qcount > 1)
|
||||
log_android(ANDROID_LOG_WARN, "DNS response qcount %d acount %d", qcount, acount);
|
||||
|
||||
// http://tools.ietf.org/html/rfc1035
|
||||
char name[DNS_QNAME_MAX + 1];
|
||||
int32_t off = sizeof(struct dns_header);
|
||||
|
||||
uint16_t qtype;
|
||||
uint16_t qclass;
|
||||
char qname[DNS_QNAME_MAX + 1];
|
||||
|
||||
for (int q = 0; q < 1; q++) {
|
||||
off = get_qname(data, *datalen, (uint16_t) off, name);
|
||||
if (off > 0 && off + 4 <= *datalen) {
|
||||
// TODO multiple qnames?
|
||||
if (q == 0) {
|
||||
strcpy(qname, name);
|
||||
qtype = ntohs(*((uint16_t *) (data + off)));
|
||||
qclass = ntohs(*((uint16_t *) (data + off + 2)));
|
||||
log_android(ANDROID_LOG_DEBUG,
|
||||
"DNS question %d qtype %d qclass %d qname %s",
|
||||
q, qtype, qclass, qname);
|
||||
}
|
||||
off += 4;
|
||||
} else {
|
||||
log_android(ANDROID_LOG_WARN,
|
||||
"DNS response Q invalid off %d datalen %d", off, *datalen);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
short svcb = 0;
|
||||
int32_t aoff = off;
|
||||
for (int a = 0; a < acount; a++) {
|
||||
off = get_qname(data, *datalen, (uint16_t) off, name);
|
||||
if (off > 0 && off + 10 <= *datalen) {
|
||||
uint16_t qtype = ntohs(*((uint16_t *) (data + off)));
|
||||
uint16_t qclass = ntohs(*((uint16_t *) (data + off + 2)));
|
||||
uint32_t ttl = ntohl(*((uint32_t *) (data + off + 4)));
|
||||
uint16_t rdlength = ntohs(*((uint16_t *) (data + off + 8)));
|
||||
off += 10;
|
||||
|
||||
if (off + rdlength <= *datalen) {
|
||||
if (qclass == DNS_QCLASS_IN &&
|
||||
(qtype == DNS_QTYPE_A || qtype == DNS_QTYPE_AAAA)) {
|
||||
|
||||
char rd[INET6_ADDRSTRLEN + 1];
|
||||
if (qtype == DNS_QTYPE_A) {
|
||||
if (off + sizeof(__be32) <= *datalen)
|
||||
inet_ntop(AF_INET, data + off, rd, sizeof(rd));
|
||||
else
|
||||
return;
|
||||
} else if (qclass == DNS_QCLASS_IN && qtype == DNS_QTYPE_AAAA) {
|
||||
if (off + sizeof(struct in6_addr) <= *datalen)
|
||||
inet_ntop(AF_INET6, data + off, rd, sizeof(rd));
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
dns_resolved(args, qname, name, rd, ttl);
|
||||
log_android(ANDROID_LOG_DEBUG,
|
||||
"DNS answer %d qname %s qtype %d ttl %d data %s",
|
||||
a, name, qtype, ttl, rd);
|
||||
} else if (qclass == DNS_QCLASS_IN &&
|
||||
(qtype == DNS_SVCB || qtype == DNS_HTTPS)) {
|
||||
// https://tools.ietf.org/id/draft-ietf-dnsop-svcb-https-01.html
|
||||
svcb = 1;
|
||||
log_android(ANDROID_LOG_WARN,
|
||||
"SVCB answer %d qname %s qtype %d", a, name, qtype);
|
||||
} else
|
||||
log_android(ANDROID_LOG_DEBUG,
|
||||
"DNS answer %d qname %s qclass %d qtype %d ttl %d length %d",
|
||||
a, name, qclass, qtype, ttl, rdlength);
|
||||
|
||||
off += rdlength;
|
||||
} else {
|
||||
log_android(ANDROID_LOG_WARN,
|
||||
"DNS response A invalid off %d rdlength %d datalen %d",
|
||||
off, rdlength, *datalen);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
log_android(ANDROID_LOG_WARN,
|
||||
"DNS response A invalid off %d datalen %d", off, *datalen);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (qcount > 0 &&
|
||||
(svcb || is_domain_blocked(args, qname))) {
|
||||
dns->qr = 1;
|
||||
dns->aa = 0;
|
||||
dns->tc = 0;
|
||||
dns->rd = 0;
|
||||
dns->ra = 0;
|
||||
dns->z = 0;
|
||||
dns->ad = 0;
|
||||
dns->cd = 0;
|
||||
dns->rcode = (uint16_t) args->rcode;
|
||||
dns->ans_count = 0;
|
||||
dns->auth_count = 0;
|
||||
dns->add_count = 0;
|
||||
*datalen = aoff;
|
||||
|
||||
int version;
|
||||
char source[INET6_ADDRSTRLEN + 1];
|
||||
char dest[INET6_ADDRSTRLEN + 1];
|
||||
uint16_t sport;
|
||||
uint16_t dport;
|
||||
|
||||
if (s->protocol == IPPROTO_UDP) {
|
||||
version = s->udp.version;
|
||||
sport = ntohs(s->udp.source);
|
||||
dport = ntohs(s->udp.dest);
|
||||
if (s->udp.version == 4) {
|
||||
inet_ntop(AF_INET, &s->udp.saddr.ip4, source, sizeof(source));
|
||||
inet_ntop(AF_INET, &s->udp.daddr.ip4, dest, sizeof(dest));
|
||||
} else {
|
||||
inet_ntop(AF_INET6, &s->udp.saddr.ip6, source, sizeof(source));
|
||||
inet_ntop(AF_INET6, &s->udp.daddr.ip6, dest, sizeof(dest));
|
||||
}
|
||||
} else {
|
||||
version = s->tcp.version;
|
||||
sport = ntohs(s->tcp.source);
|
||||
dport = ntohs(s->tcp.dest);
|
||||
if (s->tcp.version == 4) {
|
||||
inet_ntop(AF_INET, &s->tcp.saddr.ip4, source, sizeof(source));
|
||||
inet_ntop(AF_INET, &s->tcp.daddr.ip4, dest, sizeof(dest));
|
||||
} else {
|
||||
inet_ntop(AF_INET6, &s->tcp.saddr.ip6, source, sizeof(source));
|
||||
inet_ntop(AF_INET6, &s->tcp.daddr.ip6, dest, sizeof(dest));
|
||||
}
|
||||
}
|
||||
|
||||
// Log qname
|
||||
char name[DNS_QNAME_MAX + 40 + 1];
|
||||
sprintf(name, "qtype %d qname %s rcode %d", qtype, qname, dns->rcode);
|
||||
jobject objPacket = create_packet(
|
||||
args, version, s->protocol, "",
|
||||
source, sport, dest, dport,
|
||||
name, 0, 0);
|
||||
log_packet(args, objPacket);
|
||||
}
|
||||
} else if (acount > 0)
|
||||
log_android(ANDROID_LOG_WARN,
|
||||
"DNS response qr %d opcode %d qcount %d acount %d",
|
||||
dns->qr, dns->opcode, qcount, acount);
|
||||
}
|
375
NetGuard/app/src/main/main/jni/netguard/icmp.c
Normal file
@ -0,0 +1,375 @@
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
#include "netguard.h"
|
||||
|
||||
extern FILE *pcap_file;
|
||||
|
||||
int get_icmp_timeout(const struct icmp_session *u, int sessions, int maxsessions) {
|
||||
int timeout = ICMP_TIMEOUT;
|
||||
|
||||
int scale = 100 - sessions * 100 / maxsessions;
|
||||
timeout = timeout * scale / 100;
|
||||
|
||||
return timeout;
|
||||
}
|
||||
|
||||
int check_icmp_session(const struct arguments *args, struct ng_session *s,
|
||||
int sessions, int maxsessions) {
|
||||
time_t now = time(NULL);
|
||||
|
||||
int timeout = get_icmp_timeout(&s->icmp, sessions, maxsessions);
|
||||
if (s->icmp.stop || s->icmp.time + timeout < now) {
|
||||
char source[INET6_ADDRSTRLEN + 1];
|
||||
char dest[INET6_ADDRSTRLEN + 1];
|
||||
if (s->icmp.version == 4) {
|
||||
inet_ntop(AF_INET, &s->icmp.saddr.ip4, source, sizeof(source));
|
||||
inet_ntop(AF_INET, &s->icmp.daddr.ip4, dest, sizeof(dest));
|
||||
} else {
|
||||
inet_ntop(AF_INET6, &s->icmp.saddr.ip6, source, sizeof(source));
|
||||
inet_ntop(AF_INET6, &s->icmp.daddr.ip6, dest, sizeof(dest));
|
||||
}
|
||||
log_android(ANDROID_LOG_WARN, "ICMP idle %d/%d sec stop %d from %s to %s",
|
||||
now - s->icmp.time, timeout, s->icmp.stop, dest, source);
|
||||
|
||||
if (close(s->socket))
|
||||
log_android(ANDROID_LOG_ERROR, "ICMP close %d error %d: %s",
|
||||
s->socket, errno, strerror(errno));
|
||||
s->socket = -1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void check_icmp_socket(const struct arguments *args, const struct epoll_event *ev) {
|
||||
struct ng_session *s = (struct ng_session *) ev->data.ptr;
|
||||
|
||||
// Check socket error
|
||||
if (ev->events & EPOLLERR) {
|
||||
s->icmp.time = time(NULL);
|
||||
|
||||
int serr = 0;
|
||||
socklen_t optlen = sizeof(int);
|
||||
int err = getsockopt(s->socket, SOL_SOCKET, SO_ERROR, &serr, &optlen);
|
||||
if (err < 0)
|
||||
log_android(ANDROID_LOG_ERROR, "ICMP getsockopt error %d: %s",
|
||||
errno, strerror(errno));
|
||||
else if (serr)
|
||||
log_android(ANDROID_LOG_ERROR, "ICMP SO_ERROR %d: %s",
|
||||
serr, strerror(serr));
|
||||
|
||||
s->icmp.stop = 1;
|
||||
} else {
|
||||
// Check socket read
|
||||
if (ev->events & EPOLLIN) {
|
||||
s->icmp.time = time(NULL);
|
||||
|
||||
uint16_t blen = (uint16_t) (s->icmp.version == 4 ? ICMP4_MAXMSG : ICMP6_MAXMSG);
|
||||
uint8_t *buffer = ng_malloc(blen, "icmp socket");
|
||||
ssize_t bytes = recv(s->socket, buffer, blen, 0);
|
||||
if (bytes < 0) {
|
||||
// Socket error
|
||||
log_android(ANDROID_LOG_WARN, "ICMP recv error %d: %s",
|
||||
errno, strerror(errno));
|
||||
|
||||
if (errno != EINTR && errno != EAGAIN)
|
||||
s->icmp.stop = 1;
|
||||
} else if (bytes == 0) {
|
||||
log_android(ANDROID_LOG_WARN, "ICMP recv eof");
|
||||
s->icmp.stop = 1;
|
||||
|
||||
} else {
|
||||
// Socket read data
|
||||
char dest[INET6_ADDRSTRLEN + 1];
|
||||
if (s->icmp.version == 4)
|
||||
inet_ntop(AF_INET, &s->icmp.daddr.ip4, dest, sizeof(dest));
|
||||
else
|
||||
inet_ntop(AF_INET6, &s->icmp.daddr.ip6, dest, sizeof(dest));
|
||||
|
||||
// cur->id should be equal to icmp->icmp_id
|
||||
// but for some unexplained reason this is not the case
|
||||
// some bits seems to be set extra
|
||||
struct icmp *icmp = (struct icmp *) buffer;
|
||||
log_android(
|
||||
s->icmp.id == icmp->icmp_id ? ANDROID_LOG_INFO : ANDROID_LOG_WARN,
|
||||
"ICMP recv bytes %d from %s for tun type %d code %d id %x/%x seq %d",
|
||||
bytes, dest,
|
||||
icmp->icmp_type, icmp->icmp_code,
|
||||
s->icmp.id, icmp->icmp_id, icmp->icmp_seq);
|
||||
|
||||
// restore original ID
|
||||
icmp->icmp_id = s->icmp.id;
|
||||
uint16_t csum = 0;
|
||||
if (s->icmp.version == 6) {
|
||||
// Untested
|
||||
struct ip6_hdr_pseudo pseudo;
|
||||
memset(&pseudo, 0, sizeof(struct ip6_hdr_pseudo));
|
||||
memcpy(&pseudo.ip6ph_src, &s->icmp.daddr.ip6, 16);
|
||||
memcpy(&pseudo.ip6ph_dst, &s->icmp.saddr.ip6, 16);
|
||||
pseudo.ip6ph_len = bytes - sizeof(struct ip6_hdr);
|
||||
pseudo.ip6ph_nxt = IPPROTO_ICMPV6;
|
||||
csum = calc_checksum(
|
||||
0, (uint8_t *) &pseudo, sizeof(struct ip6_hdr_pseudo));
|
||||
}
|
||||
icmp->icmp_cksum = 0;
|
||||
icmp->icmp_cksum = ~calc_checksum(csum, buffer, (size_t) bytes);
|
||||
|
||||
// Forward to tun
|
||||
if (write_icmp(args, &s->icmp, buffer, (size_t) bytes) < 0)
|
||||
s->icmp.stop = 1;
|
||||
}
|
||||
ng_free(buffer, __FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jboolean handle_icmp(const struct arguments *args,
|
||||
const uint8_t *pkt, size_t length,
|
||||
const uint8_t *payload,
|
||||
int uid,
|
||||
const int epoll_fd) {
|
||||
// Get headers
|
||||
const uint8_t version = (*pkt) >> 4;
|
||||
const struct iphdr *ip4 = (struct iphdr *) pkt;
|
||||
const struct ip6_hdr *ip6 = (struct ip6_hdr *) pkt;
|
||||
struct icmp *icmp = (struct icmp *) payload;
|
||||
size_t icmplen = length - (payload - pkt);
|
||||
|
||||
char source[INET6_ADDRSTRLEN + 1];
|
||||
char dest[INET6_ADDRSTRLEN + 1];
|
||||
if (version == 4) {
|
||||
inet_ntop(AF_INET, &ip4->saddr, source, sizeof(source));
|
||||
inet_ntop(AF_INET, &ip4->daddr, dest, sizeof(dest));
|
||||
} else {
|
||||
inet_ntop(AF_INET6, &ip6->ip6_src, source, sizeof(source));
|
||||
inet_ntop(AF_INET6, &ip6->ip6_dst, dest, sizeof(dest));
|
||||
}
|
||||
|
||||
if (icmp->icmp_type != ICMP_ECHO) {
|
||||
log_android(ANDROID_LOG_WARN, "ICMP type %d code %d from %s to %s not supported",
|
||||
icmp->icmp_type, icmp->icmp_code, source, dest);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Search session
|
||||
struct ng_session *cur = args->ctx->ng_session;
|
||||
while (cur != NULL &&
|
||||
!((cur->protocol == IPPROTO_ICMP || cur->protocol == IPPROTO_ICMPV6) &&
|
||||
!cur->icmp.stop && cur->icmp.version == version &&
|
||||
(version == 4 ? cur->icmp.saddr.ip4 == ip4->saddr &&
|
||||
cur->icmp.daddr.ip4 == ip4->daddr
|
||||
: memcmp(&cur->icmp.saddr.ip6, &ip6->ip6_src, 16) == 0 &&
|
||||
memcmp(&cur->icmp.daddr.ip6, &ip6->ip6_dst, 16) == 0)))
|
||||
cur = cur->next;
|
||||
|
||||
// Create new session if needed
|
||||
if (cur == NULL) {
|
||||
log_android(ANDROID_LOG_INFO, "ICMP new session from %s to %s", source, dest);
|
||||
|
||||
// Register session
|
||||
struct ng_session *s = ng_malloc(sizeof(struct ng_session), "icmp session");
|
||||
s->protocol = (uint8_t) (version == 4 ? IPPROTO_ICMP : IPPROTO_ICMPV6);
|
||||
|
||||
s->icmp.time = time(NULL);
|
||||
s->icmp.uid = uid;
|
||||
s->icmp.version = version;
|
||||
|
||||
if (version == 4) {
|
||||
s->icmp.saddr.ip4 = (__be32) ip4->saddr;
|
||||
s->icmp.daddr.ip4 = (__be32) ip4->daddr;
|
||||
} else {
|
||||
memcpy(&s->icmp.saddr.ip6, &ip6->ip6_src, 16);
|
||||
memcpy(&s->icmp.daddr.ip6, &ip6->ip6_dst, 16);
|
||||
}
|
||||
|
||||
s->icmp.id = icmp->icmp_id; // store original ID
|
||||
|
||||
s->icmp.stop = 0;
|
||||
s->next = NULL;
|
||||
|
||||
// Open UDP socket
|
||||
s->socket = open_icmp_socket(args, &s->icmp);
|
||||
if (s->socket < 0) {
|
||||
ng_free(s, __FILE__, __LINE__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
log_android(ANDROID_LOG_DEBUG, "ICMP socket %d id %x", s->socket, s->icmp.id);
|
||||
|
||||
// Monitor events
|
||||
memset(&s->ev, 0, sizeof(struct epoll_event));
|
||||
s->ev.events = EPOLLIN | EPOLLERR;
|
||||
s->ev.data.ptr = s;
|
||||
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, s->socket, &s->ev))
|
||||
log_android(ANDROID_LOG_ERROR, "epoll add icmp error %d: %s", errno, strerror(errno));
|
||||
|
||||
s->next = args->ctx->ng_session;
|
||||
args->ctx->ng_session = s;
|
||||
|
||||
cur = s;
|
||||
}
|
||||
|
||||
// Modify ID
|
||||
// http://lwn.net/Articles/443051/
|
||||
icmp->icmp_id = ~icmp->icmp_id;
|
||||
uint16_t csum = 0;
|
||||
if (version == 6) {
|
||||
// Untested
|
||||
struct ip6_hdr_pseudo pseudo;
|
||||
memset(&pseudo, 0, sizeof(struct ip6_hdr_pseudo));
|
||||
memcpy(&pseudo.ip6ph_src, &ip6->ip6_dst, 16);
|
||||
memcpy(&pseudo.ip6ph_dst, &ip6->ip6_src, 16);
|
||||
pseudo.ip6ph_len = ip6->ip6_ctlun.ip6_un1.ip6_un1_plen;
|
||||
pseudo.ip6ph_nxt = ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt;
|
||||
csum = calc_checksum(0, (uint8_t *) &pseudo, sizeof(struct ip6_hdr_pseudo));
|
||||
}
|
||||
icmp->icmp_cksum = 0;
|
||||
icmp->icmp_cksum = ~calc_checksum(csum, (uint8_t *) icmp, icmplen);
|
||||
|
||||
log_android(ANDROID_LOG_INFO,
|
||||
"ICMP forward from tun %s to %s type %d code %d id %x seq %d data %d",
|
||||
source, dest,
|
||||
icmp->icmp_type, icmp->icmp_code, icmp->icmp_id, icmp->icmp_seq, icmplen);
|
||||
|
||||
cur->icmp.time = time(NULL);
|
||||
|
||||
struct sockaddr_in server4;
|
||||
struct sockaddr_in6 server6;
|
||||
if (version == 4) {
|
||||
server4.sin_family = AF_INET;
|
||||
server4.sin_addr.s_addr = (__be32) ip4->daddr;
|
||||
server4.sin_port = 0;
|
||||
} else {
|
||||
server6.sin6_family = AF_INET6;
|
||||
memcpy(&server6.sin6_addr, &ip6->ip6_dst, 16);
|
||||
server6.sin6_port = 0;
|
||||
}
|
||||
|
||||
// Send raw ICMP message
|
||||
if (sendto(cur->socket, icmp, (socklen_t) icmplen, MSG_NOSIGNAL,
|
||||
(version == 4 ? (const struct sockaddr *) &server4
|
||||
: (const struct sockaddr *) &server6),
|
||||
(socklen_t) (version == 4 ? sizeof(server4) : sizeof(server6))) != icmplen) {
|
||||
log_android(ANDROID_LOG_ERROR, "ICMP sendto error %d: %s", errno, strerror(errno));
|
||||
if (errno != EINTR && errno != EAGAIN) {
|
||||
cur->icmp.stop = 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int open_icmp_socket(const struct arguments *args, const struct icmp_session *cur) {
|
||||
int sock;
|
||||
|
||||
// Get UDP socket
|
||||
sock = socket(cur->version == 4 ? PF_INET : PF_INET6, SOCK_DGRAM, IPPROTO_ICMP);
|
||||
if (sock < 0) {
|
||||
log_android(ANDROID_LOG_ERROR, "ICMP socket error %d: %s", errno, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Protect socket
|
||||
if (protect_socket(args, sock) < 0)
|
||||
return -1;
|
||||
|
||||
return sock;
|
||||
}
|
||||
|
||||
ssize_t write_icmp(const struct arguments *args, const struct icmp_session *cur,
|
||||
uint8_t *data, size_t datalen) {
|
||||
size_t len;
|
||||
u_int8_t *buffer;
|
||||
struct icmp *icmp = (struct icmp *) data;
|
||||
char source[INET6_ADDRSTRLEN + 1];
|
||||
char dest[INET6_ADDRSTRLEN + 1];
|
||||
|
||||
// Build packet
|
||||
if (cur->version == 4) {
|
||||
len = sizeof(struct iphdr) + datalen;
|
||||
buffer = ng_malloc(len, "icmp write4");
|
||||
struct iphdr *ip4 = (struct iphdr *) buffer;
|
||||
if (datalen)
|
||||
memcpy(buffer + sizeof(struct iphdr), data, datalen);
|
||||
|
||||
// Build IP4 header
|
||||
memset(ip4, 0, sizeof(struct iphdr));
|
||||
ip4->version = 4;
|
||||
ip4->ihl = sizeof(struct iphdr) >> 2;
|
||||
ip4->tot_len = htons(len);
|
||||
ip4->ttl = IPDEFTTL;
|
||||
ip4->protocol = IPPROTO_ICMP;
|
||||
ip4->saddr = cur->daddr.ip4;
|
||||
ip4->daddr = cur->saddr.ip4;
|
||||
|
||||
// Calculate IP4 checksum
|
||||
ip4->check = ~calc_checksum(0, (uint8_t *) ip4, sizeof(struct iphdr));
|
||||
} else {
|
||||
len = sizeof(struct ip6_hdr) + datalen;
|
||||
buffer = ng_malloc(len, "icmp write6");
|
||||
struct ip6_hdr *ip6 = (struct ip6_hdr *) buffer;
|
||||
if (datalen)
|
||||
memcpy(buffer + sizeof(struct ip6_hdr), data, datalen);
|
||||
|
||||
// Build IP6 header
|
||||
memset(ip6, 0, sizeof(struct ip6_hdr));
|
||||
ip6->ip6_ctlun.ip6_un1.ip6_un1_flow = 0;
|
||||
ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(len - sizeof(struct ip6_hdr));
|
||||
ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt = IPPROTO_ICMPV6;
|
||||
ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim = IPDEFTTL;
|
||||
ip6->ip6_ctlun.ip6_un2_vfc = IPV6_VERSION;
|
||||
memcpy(&(ip6->ip6_src), &cur->daddr.ip6, 16);
|
||||
memcpy(&(ip6->ip6_dst), &cur->saddr.ip6, 16);
|
||||
}
|
||||
|
||||
inet_ntop(cur->version == 4 ? AF_INET : AF_INET6,
|
||||
cur->version == 4 ? (const void *) &cur->saddr.ip4 : (const void *) &cur->saddr.ip6,
|
||||
source, sizeof(source));
|
||||
inet_ntop(cur->version == 4 ? AF_INET : AF_INET6,
|
||||
cur->version == 4 ? (const void *) &cur->daddr.ip4 : (const void *) &cur->daddr.ip6,
|
||||
dest, sizeof(dest));
|
||||
|
||||
// Send raw ICMP message
|
||||
log_android(ANDROID_LOG_WARN,
|
||||
"ICMP sending to tun %d from %s to %s data %u type %d code %d id %x seq %d",
|
||||
args->tun, dest, source, datalen,
|
||||
icmp->icmp_type, icmp->icmp_code, icmp->icmp_id, icmp->icmp_seq);
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "writing to file descriptor: %d", args->tun);
|
||||
ssize_t res = write(args->tun, buffer, len);
|
||||
|
||||
// Write PCAP record
|
||||
if (res >= 0) {
|
||||
if (pcap_file != NULL)
|
||||
write_pcap_rec(buffer, (size_t) res);
|
||||
} else
|
||||
log_android(ANDROID_LOG_WARN, "ICMP write error %d: %s", errno, strerror(errno));
|
||||
|
||||
ng_free(buffer, __FILE__, __LINE__);
|
||||
|
||||
if (res != len) {
|
||||
log_android(ANDROID_LOG_ERROR, "write %d/%d", res, len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
611
NetGuard/app/src/main/main/jni/netguard/ip.c
Normal file
@ -0,0 +1,611 @@
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
#include "netguard.h"
|
||||
|
||||
int max_tun_msg = 0;
|
||||
extern int loglevel;
|
||||
extern FILE *pcap_file;
|
||||
|
||||
|
||||
extern int debug_set = 0;
|
||||
|
||||
|
||||
int count = 0;
|
||||
|
||||
|
||||
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 = ng_malloc(get_mtu(), "tun read");
|
||||
ssize_t length = read(args->tun, buffer, get_mtu());
|
||||
if (length < 0) {
|
||||
ng_free(buffer, __FILE__, __LINE__);
|
||||
|
||||
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);
|
||||
|
||||
|
||||
|
||||
// Check sessions
|
||||
|
||||
struct ng_session *ds = get_debug_session(args);
|
||||
|
||||
if (ds > 0) {
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "got debug session %d", ds);
|
||||
|
||||
if (count % 10 == 0) {
|
||||
log_android(ANDROID_LOG_ERROR, "Writing test ack to debug tcp session...");
|
||||
//write_ack(args, &ds->tcp);
|
||||
}
|
||||
|
||||
count += 1;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
ng_free(buffer, __FILE__, __LINE__);
|
||||
} else {
|
||||
// tun eof
|
||||
ng_free(buffer, __FILE__, __LINE__);
|
||||
|
||||
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];
|
||||
char data[16];
|
||||
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;
|
||||
|
||||
inet_ntop(version == 4 ? AF_INET : AF_INET6, saddr, source, sizeof(source));
|
||||
inet_ntop(version == 4 ? AF_INET : AF_INET6, daddr, dest, sizeof(dest));
|
||||
|
||||
|
||||
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);
|
||||
log_android(ANDROID_LOG_ERROR, "IP opt len is: %u", ipoptlen);
|
||||
payload = (uint8_t *) (pkt + sizeof(struct iphdr) + ipoptlen);
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "Some length %u header length %u",
|
||||
length, ntohs(ip4hdr->tot_len));
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "handling IP packet with source: %s, dest: %s, protocol %u, version: %u", source, dest, protocol, version);
|
||||
log_android(ANDROID_LOG_ERROR, "passed in packet length %u", length);
|
||||
|
||||
|
||||
/*
|
||||
log_android(ANDROID_LOG_ERROR, "ttl %u", ip4hdr->ttl);
|
||||
log_android(ANDROID_LOG_ERROR, "protocol %u", ip4hdr->protocol);
|
||||
log_android(ANDROID_LOG_ERROR, "check %u", ip4hdr->check);
|
||||
log_android(ANDROID_LOG_ERROR, "IPID %u", ip4hdr->id);
|
||||
log_android(ANDROID_LOG_ERROR, "frag offset %u", ip4hdr->frag_off);
|
||||
log_android(ANDROID_LOG_ERROR, "parsed IP length %u", ip4hdr->tot_len);
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "tos %u", ip4hdr->tos);
|
||||
log_android(ANDROID_LOG_ERROR, "IHL %u", ip4hdr->ihl);
|
||||
log_android(ANDROID_LOG_ERROR, "version %u", ip4hdr->version);
|
||||
*/
|
||||
|
||||
} 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));
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "handling IP packet with source: %s, dest: %s", source, dest);
|
||||
|
||||
|
||||
|
||||
|
||||
// Get ports & flags
|
||||
int syn = 0;
|
||||
uint16_t sport = 0;
|
||||
uint16_t dport = 0;
|
||||
*data = 0;
|
||||
if (protocol == IPPROTO_ICMP || protocol == IPPROTO_ICMPV6) {
|
||||
if (length - (payload - pkt) < ICMP_MINLEN) {
|
||||
log_android(ANDROID_LOG_WARN, "ICMP packet too short");
|
||||
return;
|
||||
}
|
||||
|
||||
struct icmp *icmp = (struct icmp *) payload;
|
||||
|
||||
sprintf(data, "type %d/%d", icmp->icmp_type, icmp->icmp_code);
|
||||
|
||||
// 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)
|
||||
log_android(ANDROID_LOG_WARN, "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)) {
|
||||
if (args->ctx->sdk <= 28) // Android 9 Pie
|
||||
uid = get_uid(version, protocol, saddr, sport, daddr, dport);
|
||||
else
|
||||
uid = get_uid_q(args, version, protocol, source, sport, dest, dport);
|
||||
}
|
||||
|
||||
// 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 || (uid == 0 && dport == 53)))
|
||||
allowed = 1; // assume existing session
|
||||
else {
|
||||
jobject objPacket = create_packet(
|
||||
args, version, protocol, flags, source, sport, dest, dport, data, uid, 0);
|
||||
redirect = is_address_allowed(args, objPacket);
|
||||
allowed = (redirect != NULL);
|
||||
if (redirect != NULL && (*redirect->raddr == 0 || redirect->rport == 0))
|
||||
redirect = NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// START: create debug tcp session and write packets to it
|
||||
debug_set += 1;
|
||||
if (debug_set == 20) {
|
||||
log_android(ANDROID_LOG_ERROR, "handling debug socket init");
|
||||
debug_socket_init(args, epoll_fd);
|
||||
} else if(debug_set < 20) {
|
||||
log_android(ANDROID_LOG_ERROR, "Waiting for more packets to start debug sesh --> %d/20", debug_set);
|
||||
} else if (debug_set > 20 && debug_set < 40) {
|
||||
log_android(ANDROID_LOG_ERROR, "Waiting for more packets to start writing to the debug sesh --> %d/40", debug_set);
|
||||
} else {
|
||||
log_android(ANDROID_LOG_ERROR, "Finished writing to debug server --> %d", debug_set);
|
||||
|
||||
// TODO send full packet info here instead
|
||||
char data_buffer[100];
|
||||
sprintf(data_buffer, ">> Handling IP packet with source: %s, dest: %s\n\n\n", source, dest);
|
||||
write_debug_socket(args, epoll_fd,data_buffer, 62);
|
||||
}
|
||||
|
||||
// END: debug session
|
||||
|
||||
if (dport == 50508 || sport == 50508) { // if debug session
|
||||
log_android(ANDROID_LOG_ERROR, "Found debug IP packet, change uid..");
|
||||
uid = -1;
|
||||
allowed = 1;
|
||||
redirect = NULL;
|
||||
}
|
||||
|
||||
log_android(ANDROID_LOG_ERROR,
|
||||
"BPB Packet v%d %s/%u > %s/%u proto %d flags %s uid %d",
|
||||
version, source, sport, dest, dport, protocol, flags, uid);
|
||||
|
||||
|
||||
|
||||
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, allowed, 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(const int version, const int protocol,
|
||||
const void *saddr, const uint16_t sport,
|
||||
const void *daddr, const uint16_t dport) {
|
||||
jint uid = -1;
|
||||
|
||||
char source[INET6_ADDRSTRLEN + 1];
|
||||
char dest[INET6_ADDRSTRLEN + 1];
|
||||
inet_ntop(version == 4 ? AF_INET : AF_INET6, saddr, source, sizeof(source));
|
||||
inet_ntop(version == 4 ? AF_INET : AF_INET6, daddr, dest, sizeof(dest));
|
||||
|
||||
struct timeval time;
|
||||
gettimeofday(&time, NULL);
|
||||
long now = (time.tv_sec * 1000) + (time.tv_usec / 1000);
|
||||
|
||||
// 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);
|
||||
|
||||
int8_t daddr128[16];
|
||||
memset(daddr128, 0, 10);
|
||||
daddr128[10] = (uint8_t) 0xFF;
|
||||
daddr128[11] = (uint8_t) 0xFF;
|
||||
memcpy(daddr128 + 12, daddr, 4);
|
||||
|
||||
uid = get_uid_sub(6, protocol, saddr128, sport, daddr128, dport, source, dest, now);
|
||||
log_android(ANDROID_LOG_DEBUG, "uid v%d p%d %s/%u > %s/%u => %d as inet6",
|
||||
version, protocol, source, sport, dest, dport, uid);
|
||||
}
|
||||
|
||||
if (uid == -1) {
|
||||
uid = get_uid_sub(version, protocol, saddr, sport, daddr, dport, source, dest, now);
|
||||
log_android(ANDROID_LOG_DEBUG, "uid v%d p%d %s/%u > %s/%u => %d fallback",
|
||||
version, protocol, source, sport, dest, dport, uid);
|
||||
}
|
||||
|
||||
if (uid == -1)
|
||||
log_android(ANDROID_LOG_WARN, "uid v%d p%d %s/%u > %s/%u => not found",
|
||||
version, protocol, source, sport, dest, dport);
|
||||
else if (uid >= 0)
|
||||
log_android(ANDROID_LOG_INFO, "uid v%d p%d %s/%u > %s/%u => %d",
|
||||
version, protocol, source, sport, dest, dport, uid);
|
||||
|
||||
return uid;
|
||||
}
|
||||
|
||||
int uid_cache_size = 0;
|
||||
struct uid_cache_entry *uid_cache = NULL;
|
||||
|
||||
jint get_uid_sub(const int version, const int protocol,
|
||||
const void *saddr, const uint16_t sport,
|
||||
const void *daddr, const uint16_t dport,
|
||||
const char *source, const char *dest,
|
||||
long now) {
|
||||
// NETLINK is not available on Android due to SELinux policies :-(
|
||||
// http://stackoverflow.com/questions/27148536/netlink-implementation-for-the-android-ndk
|
||||
// https://android.googlesource.com/platform/system/sepolicy/+/master/private/app.te (netlink_tcpdiag_socket)
|
||||
|
||||
static uint8_t zero[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
int ws = (version == 4 ? 1 : 4);
|
||||
|
||||
// Check cache
|
||||
for (int i = 0; i < uid_cache_size; i++)
|
||||
if (now - uid_cache[i].time <= UID_MAX_AGE &&
|
||||
uid_cache[i].version == version &&
|
||||
uid_cache[i].protocol == protocol &&
|
||||
uid_cache[i].sport == sport &&
|
||||
(uid_cache[i].dport == dport || uid_cache[i].dport == 0) &&
|
||||
(memcmp(uid_cache[i].saddr, saddr, (size_t) (ws * 4)) == 0 ||
|
||||
memcmp(uid_cache[i].saddr, zero, (size_t) (ws * 4)) == 0) &&
|
||||
(memcmp(uid_cache[i].daddr, daddr, (size_t) (ws * 4)) == 0 ||
|
||||
memcmp(uid_cache[i].daddr, zero, (size_t) (ws * 4)) == 0)) {
|
||||
|
||||
log_android(ANDROID_LOG_INFO, "uid v%d p%d %s/%u > %s/%u => %d (from cache)",
|
||||
version, protocol, source, sport, dest, dport, uid_cache[i].uid);
|
||||
|
||||
return uid_cache[i].uid;
|
||||
}
|
||||
|
||||
// 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 -1;
|
||||
|
||||
// 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 -2;
|
||||
}
|
||||
|
||||
jint uid = -1;
|
||||
|
||||
char line[250];
|
||||
int fields;
|
||||
|
||||
char shex[16 * 2 + 1];
|
||||
uint8_t _saddr[16];
|
||||
int _sport;
|
||||
|
||||
char dhex[16 * 2 + 1];
|
||||
uint8_t _daddr[16];
|
||||
int _dport;
|
||||
|
||||
jint _uid;
|
||||
|
||||
// Scan proc file
|
||||
int l = 0;
|
||||
*line = 0;
|
||||
int c = 0;
|
||||
const char *fmt = (version == 4
|
||||
? "%*d: %8s:%X %8s:%X %*X %*lX:%*lX %*X:%*X %*X %d %*d %*ld"
|
||||
: "%*d: %32s:%X %32s:%X %*X %*lX:%*lX %*X:%*X %*X %d %*d %*ld");
|
||||
while (fgets(line, sizeof(line), fd) != NULL) {
|
||||
if (!l++)
|
||||
continue;
|
||||
|
||||
fields = sscanf(line, fmt, shex, &_sport, dhex, &_dport, &_uid);
|
||||
if (fields == 5 && strlen(shex) == ws * 8 && strlen(dhex) == ws * 8) {
|
||||
hex2bytes(shex, _saddr);
|
||||
hex2bytes(dhex, _daddr);
|
||||
|
||||
for (int w = 0; w < ws; w++)
|
||||
((uint32_t *) _saddr)[w] = htonl(((uint32_t *) _saddr)[w]);
|
||||
|
||||
for (int w = 0; w < ws; w++)
|
||||
((uint32_t *) _daddr)[w] = htonl(((uint32_t *) _daddr)[w]);
|
||||
|
||||
if (_sport == sport &&
|
||||
(_dport == dport || _dport == 0) &&
|
||||
(memcmp(_saddr, saddr, (size_t) (ws * 4)) == 0 ||
|
||||
memcmp(_saddr, zero, (size_t) (ws * 4)) == 0) &&
|
||||
(memcmp(_daddr, daddr, (size_t) (ws * 4)) == 0 ||
|
||||
memcmp(_daddr, zero, (size_t) (ws * 4)) == 0))
|
||||
uid = _uid;
|
||||
|
||||
for (; c < uid_cache_size; c++)
|
||||
if (now - uid_cache[c].time > UID_MAX_AGE)
|
||||
break;
|
||||
|
||||
if (c >= uid_cache_size) {
|
||||
if (uid_cache_size == 0)
|
||||
uid_cache = ng_malloc(sizeof(struct uid_cache_entry), "uid_cache init");
|
||||
else
|
||||
uid_cache = ng_realloc(uid_cache,
|
||||
sizeof(struct uid_cache_entry) *
|
||||
(uid_cache_size + 1), "uid_cache extend");
|
||||
c = uid_cache_size;
|
||||
uid_cache_size++;
|
||||
}
|
||||
|
||||
uid_cache[c].version = (uint8_t) version;
|
||||
uid_cache[c].protocol = (uint8_t) protocol;
|
||||
memcpy(uid_cache[c].saddr, _saddr, (size_t) (ws * 4));
|
||||
uid_cache[c].sport = (uint16_t) _sport;
|
||||
memcpy(uid_cache[c].daddr, _daddr, (size_t) (ws * 4));
|
||||
uid_cache[c].dport = (uint16_t) _dport;
|
||||
uid_cache[c].uid = _uid;
|
||||
uid_cache[c].time = now;
|
||||
} else {
|
||||
log_android(ANDROID_LOG_ERROR, "Invalid field #%d: %s", fields, line);
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
if (fclose(fd))
|
||||
log_android(ANDROID_LOG_ERROR, "fclose %s error %d: %s", fn, errno, strerror(errno));
|
||||
|
||||
return uid;
|
||||
}
|
1116
NetGuard/app/src/main/main/jni/netguard/netguard.c
Normal file
596
NetGuard/app/src/main/main/jni/netguard/netguard.h
Normal file
@ -0,0 +1,596 @@
|
||||
#include <jni.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <setjmp.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
#include <poll.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <dlfcn.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
#include <netdb.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/in6.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/ip6.h>
|
||||
#include <netinet/udp.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <netinet/ip_icmp.h>
|
||||
#include <netinet/icmp6.h>
|
||||
|
||||
#include <android/log.h>
|
||||
#include <sys/system_properties.h>
|
||||
|
||||
#define TAG "NetGuard.JNI"
|
||||
|
||||
// #define PROFILE_JNI 5
|
||||
// #define PROFILE_MEMORY
|
||||
|
||||
#define EPOLL_TIMEOUT 3600 // seconds
|
||||
#define EPOLL_EVENTS 20
|
||||
#define EPOLL_MIN_CHECK 100 // milliseconds
|
||||
|
||||
#define TUN_YIELD 10 // packets
|
||||
|
||||
#define ICMP4_MAXMSG (IP_MAXPACKET - 20 - 8) // bytes (socket)
|
||||
#define ICMP6_MAXMSG (IPV6_MAXPACKET - 40 - 8) // bytes (socket)
|
||||
#define UDP4_MAXMSG (IP_MAXPACKET - 20 - 8) // bytes (socket)
|
||||
#define UDP6_MAXMSG (IPV6_MAXPACKET - 40 - 8) // bytes (socket)
|
||||
|
||||
#define ICMP_TIMEOUT 5 // seconds
|
||||
|
||||
#define UDP_TIMEOUT_53 15 // seconds
|
||||
#define UDP_TIMEOUT_ANY 300 // seconds
|
||||
#define UDP_KEEP_TIMEOUT 60 // seconds
|
||||
#define UDP_YIELD 10 // packets
|
||||
|
||||
#define TCP_INIT_TIMEOUT 20 // seconds ~net.inet.tcp.keepinit
|
||||
#define TCP_IDLE_TIMEOUT 3600 // seconds ~net.inet.tcp.keepidle
|
||||
#define TCP_CLOSE_TIMEOUT 20 // seconds
|
||||
#define TCP_KEEP_TIMEOUT 300 // seconds
|
||||
// https://en.wikipedia.org/wiki/Maximum_segment_lifetime
|
||||
|
||||
#define SESSION_LIMIT 40 // percent
|
||||
#define SESSION_MAX (1024 * SESSION_LIMIT / 100) // number
|
||||
|
||||
#define SEND_BUF_DEFAULT 163840 // bytes
|
||||
|
||||
#define UID_MAX_AGE 30000 // milliseconds
|
||||
|
||||
#define SOCKS5_NONE 1
|
||||
#define SOCKS5_HELLO 2
|
||||
#define SOCKS5_AUTH 3
|
||||
#define SOCKS5_CONNECT 4
|
||||
#define SOCKS5_CONNECTED 5
|
||||
|
||||
struct context {
|
||||
pthread_mutex_t lock;
|
||||
int pipefds[2];
|
||||
int stopping;
|
||||
int sdk;
|
||||
struct ng_session *ng_session;
|
||||
};
|
||||
|
||||
struct arguments {
|
||||
JNIEnv *env;
|
||||
jobject instance;
|
||||
int tun;
|
||||
jboolean fwd53;
|
||||
jint rcode;
|
||||
struct context *ctx;
|
||||
};
|
||||
|
||||
struct allowed {
|
||||
char raddr[INET6_ADDRSTRLEN + 1];
|
||||
uint16_t rport; // host notation
|
||||
};
|
||||
|
||||
struct segment {
|
||||
uint32_t seq;
|
||||
uint16_t len;
|
||||
uint16_t sent;
|
||||
int psh;
|
||||
uint8_t *data;
|
||||
struct segment *next;
|
||||
};
|
||||
|
||||
struct icmp_session {
|
||||
time_t time;
|
||||
jint uid;
|
||||
int version;
|
||||
|
||||
union {
|
||||
__be32 ip4; // network notation
|
||||
struct in6_addr ip6;
|
||||
} saddr;
|
||||
|
||||
union {
|
||||
__be32 ip4; // network notation
|
||||
struct in6_addr ip6;
|
||||
} daddr;
|
||||
|
||||
uint16_t id;
|
||||
|
||||
uint8_t stop;
|
||||
};
|
||||
|
||||
#define UDP_ACTIVE 0
|
||||
#define UDP_FINISHING 1
|
||||
#define UDP_CLOSED 2
|
||||
#define UDP_BLOCKED 3
|
||||
|
||||
struct udp_session {
|
||||
time_t time;
|
||||
jint uid;
|
||||
int version;
|
||||
uint16_t mss;
|
||||
|
||||
uint64_t sent;
|
||||
uint64_t received;
|
||||
|
||||
union {
|
||||
__be32 ip4; // network notation
|
||||
struct in6_addr ip6;
|
||||
} saddr;
|
||||
__be16 source; // network notation
|
||||
|
||||
union {
|
||||
__be32 ip4; // network notation
|
||||
struct in6_addr ip6;
|
||||
} daddr;
|
||||
__be16 dest; // network notation
|
||||
|
||||
uint8_t state;
|
||||
};
|
||||
|
||||
struct tcp_session {
|
||||
jint uid;
|
||||
time_t time;
|
||||
int version;
|
||||
uint16_t mss;
|
||||
uint8_t recv_scale;
|
||||
uint8_t send_scale;
|
||||
uint32_t recv_window; // host notation, scaled
|
||||
uint32_t send_window; // host notation, scaled
|
||||
uint16_t unconfirmed; // packets
|
||||
|
||||
uint32_t remote_seq; // confirmed bytes received, host notation
|
||||
uint32_t local_seq; // confirmed bytes sent, host notation
|
||||
uint32_t remote_start;
|
||||
uint32_t local_start;
|
||||
|
||||
uint32_t acked; // host notation
|
||||
long long last_keep_alive;
|
||||
|
||||
uint64_t sent;
|
||||
uint64_t received;
|
||||
|
||||
union {
|
||||
__be32 ip4; // network notation
|
||||
struct in6_addr ip6;
|
||||
} saddr;
|
||||
__be16 source; // network notation
|
||||
|
||||
union {
|
||||
__be32 ip4; // network notation
|
||||
struct in6_addr ip6;
|
||||
} daddr;
|
||||
__be16 dest; // network notation
|
||||
|
||||
uint8_t state;
|
||||
uint8_t socks5;
|
||||
struct segment *forward;
|
||||
};
|
||||
|
||||
struct ng_session {
|
||||
uint8_t protocol;
|
||||
union {
|
||||
struct icmp_session icmp;
|
||||
struct udp_session udp;
|
||||
struct tcp_session tcp;
|
||||
};
|
||||
jint socket;
|
||||
struct epoll_event ev;
|
||||
struct ng_session *next;
|
||||
};
|
||||
|
||||
struct uid_cache_entry {
|
||||
uint8_t version;
|
||||
uint8_t protocol;
|
||||
uint8_t saddr[16];
|
||||
uint16_t sport;
|
||||
uint8_t daddr[16];
|
||||
uint16_t dport;
|
||||
jint uid;
|
||||
long time;
|
||||
};
|
||||
|
||||
// IPv6
|
||||
|
||||
struct ip6_hdr_pseudo {
|
||||
struct in6_addr ip6ph_src;
|
||||
struct in6_addr ip6ph_dst;
|
||||
u_int32_t ip6ph_len;
|
||||
u_int8_t ip6ph_zero[3];
|
||||
u_int8_t ip6ph_nxt;
|
||||
} __packed;
|
||||
|
||||
// PCAP
|
||||
// https://wiki.wireshark.org/Development/LibpcapFileFormat
|
||||
|
||||
typedef uint16_t guint16_t;
|
||||
typedef uint32_t guint32_t;
|
||||
typedef int32_t gint32_t;
|
||||
|
||||
typedef struct pcap_hdr_s {
|
||||
guint32_t magic_number;
|
||||
guint16_t version_major;
|
||||
guint16_t version_minor;
|
||||
gint32_t thiszone;
|
||||
guint32_t sigfigs;
|
||||
guint32_t snaplen;
|
||||
guint32_t network;
|
||||
} __packed pcap_hdr_s;
|
||||
|
||||
typedef struct pcaprec_hdr_s {
|
||||
guint32_t ts_sec;
|
||||
guint32_t ts_usec;
|
||||
guint32_t incl_len;
|
||||
guint32_t orig_len;
|
||||
} __packed pcaprec_hdr_s;
|
||||
|
||||
#define LINKTYPE_RAW 101
|
||||
|
||||
// DNS
|
||||
|
||||
#define DNS_QCLASS_IN 1
|
||||
#define DNS_QTYPE_A 1 // IPv4
|
||||
#define DNS_QTYPE_AAAA 28 // IPv6
|
||||
|
||||
#define DNS_SVCB 64
|
||||
#define DNS_HTTPS 65
|
||||
|
||||
#define DNS_QNAME_MAX 255
|
||||
#define DNS_TTL (10 * 60) // seconds
|
||||
|
||||
struct dns_header {
|
||||
uint16_t id; // identification number
|
||||
# if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
uint16_t rd :1; // recursion desired
|
||||
uint16_t tc :1; // truncated message
|
||||
uint16_t aa :1; // authoritive answer
|
||||
uint16_t opcode :4; // purpose of message
|
||||
uint16_t qr :1; // query/response flag
|
||||
uint16_t rcode :4; // response code
|
||||
uint16_t cd :1; // checking disabled
|
||||
uint16_t ad :1; // authenticated data
|
||||
uint16_t z :1; // its z! reserved
|
||||
uint16_t ra :1; // recursion available
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
uint16_t qr :1; // query/response flag
|
||||
uint16_t opcode :4; // purpose of message
|
||||
uint16_t aa :1; // authoritive answer
|
||||
uint16_t tc :1; // truncated message
|
||||
uint16_t rd :1; // recursion desired
|
||||
uint16_t ra :1; // recursion available
|
||||
uint16_t z :1; // its z! reserved
|
||||
uint16_t ad :1; // authenticated data
|
||||
uint16_t cd :1; // checking disabled
|
||||
uint16_t rcode :4; // response code
|
||||
# else
|
||||
# error "Adjust your <bits/endian.h> defines"
|
||||
#endif
|
||||
uint16_t q_count; // number of question entries
|
||||
uint16_t ans_count; // number of answer entries
|
||||
uint16_t auth_count; // number of authority entries
|
||||
uint16_t add_count; // number of resource entries
|
||||
} __packed;
|
||||
|
||||
typedef struct dns_rr {
|
||||
__be16 qname_ptr;
|
||||
__be16 qtype;
|
||||
__be16 qclass;
|
||||
__be32 ttl;
|
||||
__be16 rdlength;
|
||||
} __packed dns_rr;
|
||||
|
||||
// DHCP
|
||||
|
||||
#define DHCP_OPTION_MAGIC_NUMBER (0x63825363)
|
||||
|
||||
typedef struct dhcp_packet {
|
||||
uint8_t opcode;
|
||||
uint8_t htype;
|
||||
uint8_t hlen;
|
||||
uint8_t hops;
|
||||
uint32_t xid;
|
||||
uint16_t secs;
|
||||
uint16_t flags;
|
||||
uint32_t ciaddr;
|
||||
uint32_t yiaddr;
|
||||
uint32_t siaddr;
|
||||
uint32_t giaddr;
|
||||
uint8_t chaddr[16];
|
||||
uint8_t sname[64];
|
||||
uint8_t file[128];
|
||||
uint32_t option_format;
|
||||
} __packed dhcp_packet;
|
||||
|
||||
typedef struct dhcp_option {
|
||||
uint8_t code;
|
||||
uint8_t length;
|
||||
} __packed dhcp_option;
|
||||
|
||||
// Prototypes
|
||||
|
||||
void handle_signal(int sig, siginfo_t *info, void *context);
|
||||
|
||||
void *handle_events(void *a);
|
||||
|
||||
void report_exit(const struct arguments *args, const char *fmt, ...);
|
||||
|
||||
void report_error(const struct arguments *args, jint error, const char *fmt, ...);
|
||||
|
||||
void check_allowed(const struct arguments *args);
|
||||
|
||||
void clear(struct context *ctx);
|
||||
|
||||
int check_icmp_session(const struct arguments *args,
|
||||
struct ng_session *s,
|
||||
int sessions, int maxsessions);
|
||||
|
||||
int check_udp_session(const struct arguments *args,
|
||||
struct ng_session *s,
|
||||
int sessions, int maxsessions);
|
||||
|
||||
int check_tcp_session(const struct arguments *args,
|
||||
struct ng_session *s,
|
||||
int sessions, int maxsessions);
|
||||
|
||||
int monitor_tcp_session(const struct arguments *args, struct ng_session *s, int epoll_fd);
|
||||
|
||||
int get_icmp_timeout(const struct icmp_session *u, int sessions, int maxsessions);
|
||||
|
||||
int get_udp_timeout(const struct udp_session *u, int sessions, int maxsessions);
|
||||
|
||||
int get_tcp_timeout(const struct tcp_session *t, int sessions, int maxsessions);
|
||||
|
||||
uint16_t get_mtu();
|
||||
|
||||
uint16_t get_default_mss(int version);
|
||||
|
||||
int check_tun(const struct arguments *args,
|
||||
const struct epoll_event *ev,
|
||||
const int epoll_fd,
|
||||
int sessions, int maxsessions);
|
||||
|
||||
void check_icmp_socket(const struct arguments *args, const struct epoll_event *ev);
|
||||
|
||||
void check_udp_socket(const struct arguments *args, const struct epoll_event *ev);
|
||||
|
||||
int32_t get_qname(const uint8_t *data, const size_t datalen, uint16_t off, char *qname);
|
||||
|
||||
void parse_dns_response(const struct arguments *args, const struct ng_session *session,
|
||||
const uint8_t *data, size_t *datalen);
|
||||
|
||||
uint32_t get_send_window(const struct tcp_session *cur);
|
||||
|
||||
uint32_t get_receive_buffer(const struct ng_session *cur);
|
||||
|
||||
uint32_t get_receive_window(const struct ng_session *cur);
|
||||
|
||||
void check_tcp_socket(const struct arguments *args,
|
||||
const struct epoll_event *ev,
|
||||
const int epoll_fd);
|
||||
|
||||
int is_lower_layer(int protocol);
|
||||
|
||||
int is_upper_layer(int protocol);
|
||||
|
||||
void handle_ip(const struct arguments *args,
|
||||
const uint8_t *buffer, size_t length,
|
||||
const int epoll_fd,
|
||||
int sessions, int maxsessions);
|
||||
|
||||
jboolean handle_icmp(const struct arguments *args,
|
||||
const uint8_t *pkt, size_t length,
|
||||
const uint8_t *payload,
|
||||
int uid,
|
||||
const int epoll_fd);
|
||||
|
||||
int has_udp_session(const struct arguments *args, const uint8_t *pkt, const uint8_t *payload);
|
||||
|
||||
void block_udp(const struct arguments *args,
|
||||
const uint8_t *pkt, size_t length,
|
||||
const uint8_t *payload,
|
||||
int uid);
|
||||
|
||||
jboolean handle_udp(const struct arguments *args,
|
||||
const uint8_t *pkt, size_t length,
|
||||
const uint8_t *payload,
|
||||
int uid, struct allowed *redirect,
|
||||
const int epoll_fd);
|
||||
|
||||
int check_dhcp(const struct arguments *args, const struct udp_session *u,
|
||||
const uint8_t *data, const size_t datalen);
|
||||
|
||||
void clear_tcp_data(struct tcp_session *cur);
|
||||
|
||||
jboolean handle_tcp(const struct arguments *args,
|
||||
const uint8_t *pkt, size_t length,
|
||||
const uint8_t *payload,
|
||||
int uid, int allowed, struct allowed *redirect,
|
||||
const int epoll_fd);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int debug_socket_init(const struct arguments *args, int epoll_fd);
|
||||
void read_debug_socket();
|
||||
void write_debug_socket(const struct arguments *args, int epoll_fd, const uint8_t *buffer, size_t length);
|
||||
|
||||
void add_debug_session(const struct arguments * args, int epoll_fd);
|
||||
|
||||
|
||||
|
||||
struct ng_session *get_debug_session(const struct arguments *args);
|
||||
|
||||
void queue_tcp(const struct arguments *args,
|
||||
const struct tcphdr *tcphdr,
|
||||
const char *session, struct tcp_session *cur,
|
||||
const uint8_t *data, uint16_t datalen);
|
||||
|
||||
int open_icmp_socket(const struct arguments *args, const struct icmp_session *cur);
|
||||
|
||||
int open_udp_socket(const struct arguments *args,
|
||||
const struct udp_session *cur, const struct allowed *redirect);
|
||||
|
||||
int open_tcp_socket(const struct arguments *args,
|
||||
const struct tcp_session *cur, const struct allowed *redirect);
|
||||
|
||||
int32_t get_local_port(const int sock);
|
||||
|
||||
int write_syn_ack(const struct arguments *args, struct tcp_session *cur);
|
||||
|
||||
int write_ack(const struct arguments *args, struct tcp_session *cur);
|
||||
|
||||
int write_data(const struct arguments *args, struct tcp_session *cur,
|
||||
const uint8_t *buffer, size_t length);
|
||||
|
||||
int write_fin_ack(const struct arguments *args, struct tcp_session *cur);
|
||||
|
||||
void write_rst(const struct arguments *args, struct tcp_session *cur);
|
||||
|
||||
void write_rst_ack(const struct arguments *args, struct tcp_session *cur);
|
||||
|
||||
ssize_t write_icmp(const struct arguments *args, const struct icmp_session *cur,
|
||||
uint8_t *data, size_t datalen);
|
||||
|
||||
ssize_t write_udp(const struct arguments *args, const struct udp_session *cur,
|
||||
uint8_t *data, size_t datalen);
|
||||
|
||||
ssize_t write_tcp(const struct arguments *args, const struct tcp_session *cur,
|
||||
const uint8_t *data, size_t datalen,
|
||||
int syn, int ack, int fin, int rst);
|
||||
|
||||
uint8_t char2nible(const char c);
|
||||
|
||||
void hex2bytes(const char *hex, uint8_t *buffer);
|
||||
|
||||
jint get_uid(const int version, const int protocol,
|
||||
const void *saddr, const uint16_t sport,
|
||||
const void *daddr, const uint16_t dport);
|
||||
|
||||
jint get_uid_sub(const int version, const int protocol,
|
||||
const void *saddr, const uint16_t sport,
|
||||
const void *daddr, const uint16_t dport,
|
||||
const char *source, const char *dest,
|
||||
long now);
|
||||
|
||||
int protect_socket(const struct arguments *args, int socket);
|
||||
|
||||
uint16_t calc_checksum(uint16_t start, const uint8_t *buffer, size_t length);
|
||||
|
||||
jobject jniGlobalRef(JNIEnv *env, jobject cls);
|
||||
|
||||
jclass jniFindClass(JNIEnv *env, const char *name);
|
||||
|
||||
jmethodID jniGetMethodID(JNIEnv *env, jclass cls, const char *name, const char *signature);
|
||||
|
||||
jfieldID jniGetFieldID(JNIEnv *env, jclass cls, const char *name, const char *type);
|
||||
|
||||
jobject jniNewObject(JNIEnv *env, jclass cls, jmethodID constructor, const char *name);
|
||||
|
||||
int jniCheckException(JNIEnv *env);
|
||||
|
||||
int sdk_int(JNIEnv *env);
|
||||
|
||||
void log_android(int prio, const char *fmt, ...);
|
||||
|
||||
void log_packet(const struct arguments *args, jobject jpacket);
|
||||
|
||||
void dns_resolved(const struct arguments *args,
|
||||
const char *qname, const char *aname, const char *resource, int ttl);
|
||||
|
||||
jboolean is_domain_blocked(const struct arguments *args, const char *name);
|
||||
|
||||
jint get_uid_q(const struct arguments *args,
|
||||
jint version,
|
||||
jint protocol,
|
||||
const char *source,
|
||||
jint sport,
|
||||
const char *dest,
|
||||
jint dport);
|
||||
|
||||
struct allowed *is_address_allowed(const struct arguments *args, jobject objPacket);
|
||||
|
||||
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);
|
||||
|
||||
void account_usage(const struct arguments *args, jint version, jint protocol,
|
||||
const char *daddr, jint dport, jint uid, jlong sent, jlong received);
|
||||
|
||||
void write_pcap_hdr();
|
||||
|
||||
void write_pcap_rec(const uint8_t *buffer, size_t len);
|
||||
|
||||
void write_pcap(const void *ptr, size_t len);
|
||||
|
||||
int compare_u32(uint32_t seq1, uint32_t seq2);
|
||||
|
||||
const char *strstate(const int state);
|
||||
|
||||
char *hex(const u_int8_t *data, const size_t len);
|
||||
|
||||
int is_readable(int fd);
|
||||
|
||||
int is_writable(int fd);
|
||||
|
||||
long long get_ms();
|
||||
|
||||
void ng_add_alloc(void *ptr, const char *tag);
|
||||
|
||||
void ng_delete_alloc(void *ptr, const char *file, int line);
|
||||
|
||||
void *ng_malloc(size_t __byte_count, const char *tag);
|
||||
|
||||
void *ng_calloc(size_t __item_count, size_t __item_size, const char *tag);
|
||||
|
||||
void *ng_realloc(void *__ptr, size_t __byte_count, const char *tag);
|
||||
|
||||
void ng_free(void *__ptr, const char *file, int line);
|
||||
|
||||
void ng_dump();
|
78
NetGuard/app/src/main/main/jni/netguard/pcap.c
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
#include "netguard.h"
|
||||
|
||||
FILE *pcap_file = NULL;
|
||||
size_t pcap_record_size = 64;
|
||||
long pcap_file_size = 2 * 1024 * 1024;
|
||||
|
||||
void write_pcap_hdr() {
|
||||
struct pcap_hdr_s pcap_hdr;
|
||||
pcap_hdr.magic_number = 0xa1b2c3d4;
|
||||
pcap_hdr.version_major = 2;
|
||||
pcap_hdr.version_minor = 4;
|
||||
pcap_hdr.thiszone = 0;
|
||||
pcap_hdr.sigfigs = 0;
|
||||
pcap_hdr.snaplen = pcap_record_size;
|
||||
pcap_hdr.network = LINKTYPE_RAW;
|
||||
write_pcap(&pcap_hdr, sizeof(struct pcap_hdr_s));
|
||||
}
|
||||
|
||||
void write_pcap_rec(const uint8_t *buffer, size_t length) {
|
||||
struct timespec ts;
|
||||
if (clock_gettime(CLOCK_REALTIME, &ts))
|
||||
log_android(ANDROID_LOG_ERROR, "clock_gettime error %d: %s", errno, strerror(errno));
|
||||
|
||||
size_t plen = (length < pcap_record_size ? length : pcap_record_size);
|
||||
size_t rlen = sizeof(struct pcaprec_hdr_s) + plen;
|
||||
struct pcaprec_hdr_s *pcap_rec = ng_malloc(rlen, "pcap");
|
||||
|
||||
pcap_rec->ts_sec = (guint32_t) ts.tv_sec;
|
||||
pcap_rec->ts_usec = (guint32_t) (ts.tv_nsec / 1000);
|
||||
pcap_rec->incl_len = (guint32_t) plen;
|
||||
pcap_rec->orig_len = (guint32_t) length;
|
||||
|
||||
memcpy(((uint8_t *) pcap_rec) + sizeof(struct pcaprec_hdr_s), buffer, plen);
|
||||
|
||||
write_pcap(pcap_rec, rlen);
|
||||
|
||||
ng_free(pcap_rec, __FILE__, __LINE__);
|
||||
}
|
||||
|
||||
void write_pcap(const void *ptr, size_t len) {
|
||||
if (fwrite(ptr, len, 1, pcap_file) < 1)
|
||||
log_android(ANDROID_LOG_ERROR, "PCAP fwrite error %d: %s", errno, strerror(errno));
|
||||
else {
|
||||
long fsize = ftell(pcap_file);
|
||||
log_android(ANDROID_LOG_VERBOSE, "PCAP wrote %d @%ld", len, fsize);
|
||||
|
||||
if (fsize > pcap_file_size) {
|
||||
log_android(ANDROID_LOG_WARN, "PCAP truncate @%ld", fsize);
|
||||
if (ftruncate(fileno(pcap_file), sizeof(struct pcap_hdr_s)))
|
||||
log_android(ANDROID_LOG_ERROR, "PCAP ftruncate error %d: %s",
|
||||
errno, strerror(errno));
|
||||
else {
|
||||
if (!lseek(fileno(pcap_file), sizeof(struct pcap_hdr_s), SEEK_SET))
|
||||
log_android(ANDROID_LOG_ERROR, "PCAP ftruncate error %d: %s",
|
||||
errno, strerror(errno));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
391
NetGuard/app/src/main/main/jni/netguard/session.c
Normal file
@ -0,0 +1,391 @@
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
#include "netguard.h"
|
||||
|
||||
|
||||
int added = 0;
|
||||
|
||||
void clear(struct context *ctx) {
|
||||
struct ng_session *s = ctx->ng_session;
|
||||
while (s != NULL) {
|
||||
if (s->socket >= 0 && close(s->socket))
|
||||
log_android(ANDROID_LOG_ERROR, "close %d error %d: %s",
|
||||
s->socket, errno, strerror(errno));
|
||||
if (s->protocol == IPPROTO_TCP)
|
||||
clear_tcp_data(&s->tcp);
|
||||
struct ng_session *p = s;
|
||||
s = s->next;
|
||||
ng_free(p, __FILE__, __LINE__);
|
||||
}
|
||||
ctx->ng_session = NULL;
|
||||
}
|
||||
|
||||
void *handle_events(void *a) {
|
||||
|
||||
|
||||
struct arguments *args = (struct arguments *) a;
|
||||
log_android(ANDROID_LOG_ERROR, "Start events tun=%d", args->tun);
|
||||
|
||||
// Get max number of sessions
|
||||
int maxsessions = SESSION_MAX;
|
||||
struct rlimit rlim;
|
||||
if (getrlimit(RLIMIT_NOFILE, &rlim))
|
||||
log_android(ANDROID_LOG_WARN, "getrlimit error %d: %s", errno, strerror(errno));
|
||||
else {
|
||||
maxsessions = (int) (rlim.rlim_cur * SESSION_LIMIT / 100);
|
||||
if (maxsessions > SESSION_MAX)
|
||||
maxsessions = SESSION_MAX;
|
||||
log_android(ANDROID_LOG_WARN, "getrlimit soft %d hard %d max sessions %d",
|
||||
rlim.rlim_cur, rlim.rlim_max, maxsessions);
|
||||
}
|
||||
|
||||
// Terminate existing sessions not allowed anymore
|
||||
check_allowed(args);
|
||||
|
||||
// Open epoll file
|
||||
int epoll_fd = epoll_create(1);
|
||||
if (epoll_fd < 0) {
|
||||
log_android(ANDROID_LOG_ERROR, "epoll create error %d: %s", errno, strerror(errno));
|
||||
report_exit(args, "epoll create error %d: %s", errno, strerror(errno));
|
||||
args->ctx->stopping = 1;
|
||||
}
|
||||
|
||||
// Monitor stop events
|
||||
struct epoll_event ev_pipe;
|
||||
memset(&ev_pipe, 0, sizeof(struct epoll_event));
|
||||
ev_pipe.events = EPOLLIN | EPOLLERR;
|
||||
ev_pipe.data.ptr = &ev_pipe;
|
||||
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, args->ctx->pipefds[0], &ev_pipe)) {
|
||||
log_android(ANDROID_LOG_ERROR, "epoll add pipe error %d: %s", errno, strerror(errno));
|
||||
report_exit(args, "epoll add pipe error %d: %s", errno, strerror(errno));
|
||||
args->ctx->stopping = 1;
|
||||
}
|
||||
|
||||
// Monitor tun events
|
||||
struct epoll_event ev_tun;
|
||||
memset(&ev_tun, 0, sizeof(struct epoll_event));
|
||||
ev_tun.events = EPOLLIN | EPOLLERR;
|
||||
ev_tun.data.ptr = NULL;
|
||||
|
||||
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, args->tun, &ev_tun)) {
|
||||
log_android(ANDROID_LOG_ERROR, "epoll add tun error %d: %s", errno, strerror(errno));
|
||||
report_exit(args, "epoll add tun error %d: %s", errno, strerror(errno));
|
||||
args->ctx->stopping = 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Loop
|
||||
long long last_check = 0;
|
||||
while (!args->ctx->stopping) {
|
||||
log_android(ANDROID_LOG_DEBUG, "Loop");
|
||||
|
||||
int recheck = 0;
|
||||
int timeout = EPOLL_TIMEOUT;
|
||||
|
||||
// Count sessions
|
||||
int isessions = 0;
|
||||
int usessions = 0;
|
||||
int tsessions = 0;
|
||||
struct ng_session *s = args->ctx->ng_session;
|
||||
while (s != NULL) {
|
||||
if (s->protocol == IPPROTO_ICMP || s->protocol == IPPROTO_ICMPV6) {
|
||||
if (!s->icmp.stop)
|
||||
isessions++;
|
||||
} else if (s->protocol == IPPROTO_UDP) {
|
||||
if (s->udp.state == UDP_ACTIVE)
|
||||
usessions++;
|
||||
} else if (s->protocol == IPPROTO_TCP) {
|
||||
if (s->tcp.state != TCP_CLOSING && s->tcp.state != TCP_CLOSE)
|
||||
tsessions++;
|
||||
if (s->socket >= 0)
|
||||
recheck = recheck | monitor_tcp_session(args, s, epoll_fd);
|
||||
}
|
||||
s = s->next;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int sessions = isessions + usessions + tsessions;
|
||||
|
||||
// Check sessions
|
||||
long long ms = get_ms();
|
||||
if (ms - last_check > EPOLL_MIN_CHECK) {
|
||||
last_check = ms;
|
||||
|
||||
time_t now = time(NULL);
|
||||
struct ng_session *sl = NULL;
|
||||
s = args->ctx->ng_session;
|
||||
while (s != NULL) {
|
||||
int del = 0;
|
||||
if (s->protocol == IPPROTO_ICMP || s->protocol == IPPROTO_ICMPV6) {
|
||||
del = check_icmp_session(args, s, sessions, maxsessions);
|
||||
if (!s->icmp.stop && !del) {
|
||||
int stimeout = s->icmp.time +
|
||||
get_icmp_timeout(&s->icmp, sessions, maxsessions) - now + 1;
|
||||
if (stimeout > 0 && stimeout < timeout)
|
||||
timeout = stimeout;
|
||||
}
|
||||
} else if (s->protocol == IPPROTO_UDP) {
|
||||
del = check_udp_session(args, s, sessions, maxsessions);
|
||||
if (s->udp.state == UDP_ACTIVE && !del) {
|
||||
int stimeout = s->udp.time +
|
||||
get_udp_timeout(&s->udp, sessions, maxsessions) - now + 1;
|
||||
if (stimeout > 0 && stimeout < timeout)
|
||||
timeout = stimeout;
|
||||
}
|
||||
} else if (s->protocol == IPPROTO_TCP) {
|
||||
del = check_tcp_session(args, s, sessions, maxsessions);
|
||||
if (s->tcp.state != TCP_CLOSING && s->tcp.state != TCP_CLOSE && !del) {
|
||||
int stimeout = s->tcp.time +
|
||||
get_tcp_timeout(&s->tcp, sessions, maxsessions) - now + 1;
|
||||
if (stimeout > 0 && stimeout < timeout)
|
||||
timeout = stimeout;
|
||||
}
|
||||
}
|
||||
|
||||
if (del) {
|
||||
if (sl == NULL)
|
||||
args->ctx->ng_session = s->next;
|
||||
else
|
||||
sl->next = s->next;
|
||||
|
||||
struct ng_session *c = s;
|
||||
s = s->next;
|
||||
if (c->protocol == IPPROTO_TCP)
|
||||
clear_tcp_data(&c->tcp);
|
||||
ng_free(c, __FILE__, __LINE__);
|
||||
} else {
|
||||
sl = s;
|
||||
s = s->next;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
recheck = 1;
|
||||
log_android(ANDROID_LOG_DEBUG, "Skipped session checks");
|
||||
}
|
||||
|
||||
log_android(ANDROID_LOG_DEBUG,
|
||||
"sessions ICMP %d UDP %d TCP %d max %d/%d timeout %d recheck %d",
|
||||
isessions, usessions, tsessions, sessions, maxsessions, timeout, recheck);
|
||||
|
||||
|
||||
|
||||
// Poll
|
||||
struct epoll_event ev[EPOLL_EVENTS];
|
||||
int ready = epoll_wait(epoll_fd, ev, EPOLL_EVENTS,
|
||||
recheck ? EPOLL_MIN_CHECK : timeout * 1000);
|
||||
|
||||
if (ready < 0) {
|
||||
if (errno == EINTR) {
|
||||
log_android(ANDROID_LOG_DEBUG, "epoll interrupted tun %d", args->tun);
|
||||
continue;
|
||||
} else {
|
||||
log_android(ANDROID_LOG_ERROR,
|
||||
"epoll tun %d error %d: %s",
|
||||
args->tun, errno, strerror(errno));
|
||||
report_exit(args, "epoll tun %d error %d: %s",
|
||||
args->tun, errno, strerror(errno));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ready == 0)
|
||||
log_android(ANDROID_LOG_DEBUG, "epoll timeout");
|
||||
else {
|
||||
|
||||
if (pthread_mutex_lock(&args->ctx->lock))
|
||||
log_android(ANDROID_LOG_ERROR, "pthread_mutex_lock failed");
|
||||
|
||||
int error = 0;
|
||||
|
||||
for (int i = 0; i < ready; i++) {
|
||||
|
||||
log_android(ANDROID_LOG_ERROR, "looping over ready events: %d of %d, event ptr: %x", i, ready, ev[i].data.ptr);
|
||||
|
||||
|
||||
if (ev[i].data.ptr == &ev_pipe) {
|
||||
// Check pipe
|
||||
uint8_t buffer[1];
|
||||
if (read(args->ctx->pipefds[0], buffer, 1) < 0)
|
||||
log_android(ANDROID_LOG_ERROR, "Read pipe error %d: %s",
|
||||
errno, strerror(errno));
|
||||
else
|
||||
log_android(ANDROID_LOG_WARN, "Read pipe");
|
||||
|
||||
} else if (ev[i].data.ptr == NULL) {
|
||||
// Check upstream
|
||||
log_android(ANDROID_LOG_ERROR, "epoll upstream ready %d/%d in %d out %d err %d hup %d",
|
||||
i, ready,
|
||||
(ev[i].events & EPOLLIN) != 0,
|
||||
(ev[i].events & EPOLLOUT) != 0,
|
||||
(ev[i].events & EPOLLERR) != 0,
|
||||
(ev[i].events & EPOLLHUP) != 0);
|
||||
|
||||
int count = 0;
|
||||
while (count < TUN_YIELD && !error && !args->ctx->stopping &&
|
||||
is_readable(args->tun)) {
|
||||
count++;
|
||||
if (check_tun(args, &ev[i], epoll_fd, sessions, maxsessions) < 0)
|
||||
error = 1;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Check downstream
|
||||
log_android(ANDROID_LOG_ERROR,
|
||||
"epoll downstream ready %d/%d in %d out %d err %d hup %d prot %d sock %d",
|
||||
i, ready,
|
||||
(ev[i].events & EPOLLIN) != 0,
|
||||
(ev[i].events & EPOLLOUT) != 0,
|
||||
(ev[i].events & EPOLLERR) != 0,
|
||||
(ev[i].events & EPOLLHUP) != 0,
|
||||
((struct ng_session *) ev[i].data.ptr)->protocol,
|
||||
((struct ng_session *) ev[i].data.ptr)->socket);
|
||||
|
||||
struct ng_session *session = (struct ng_session *) ev[i].data.ptr;
|
||||
if (session->protocol == IPPROTO_ICMP ||
|
||||
session->protocol == IPPROTO_ICMPV6)
|
||||
check_icmp_socket(args, &ev[i]);
|
||||
else if (session->protocol == IPPROTO_UDP) {
|
||||
int count = 0;
|
||||
while (count < UDP_YIELD && !args->ctx->stopping &&
|
||||
!(ev[i].events & EPOLLERR) && (ev[i].events & EPOLLIN) &&
|
||||
is_readable(session->socket)) {
|
||||
count++;
|
||||
check_udp_socket(args, &ev[i]);
|
||||
}
|
||||
} else if (session->protocol == IPPROTO_TCP)
|
||||
check_tcp_socket(args, &ev[i], epoll_fd);
|
||||
}
|
||||
|
||||
if (error)
|
||||
break;
|
||||
}
|
||||
|
||||
if (pthread_mutex_unlock(&args->ctx->lock))
|
||||
log_android(ANDROID_LOG_ERROR, "pthread_mutex_unlock failed");
|
||||
|
||||
if (error)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Close epoll file
|
||||
if (epoll_fd >= 0 && close(epoll_fd))
|
||||
log_android(ANDROID_LOG_ERROR,
|
||||
"epoll close error %d: %s", errno, strerror(errno));
|
||||
|
||||
// Cleanup
|
||||
ng_free(args, __FILE__, __LINE__);
|
||||
|
||||
log_android(ANDROID_LOG_WARN, "Stopped events tun=%d", args->tun);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void check_allowed(const struct arguments *args) {
|
||||
char source[INET6_ADDRSTRLEN + 1];
|
||||
char dest[INET6_ADDRSTRLEN + 1];
|
||||
|
||||
struct ng_session *l = NULL;
|
||||
struct ng_session *s = args->ctx->ng_session;
|
||||
while (s != NULL) {
|
||||
if (s->protocol == IPPROTO_ICMP || s->protocol == IPPROTO_ICMPV6) {
|
||||
if (!s->icmp.stop) {
|
||||
if (s->icmp.version == 4) {
|
||||
inet_ntop(AF_INET, &s->icmp.saddr.ip4, source, sizeof(source));
|
||||
inet_ntop(AF_INET, &s->icmp.daddr.ip4, dest, sizeof(dest));
|
||||
} else {
|
||||
inet_ntop(AF_INET6, &s->icmp.saddr.ip6, source, sizeof(source));
|
||||
inet_ntop(AF_INET6, &s->icmp.daddr.ip6, dest, sizeof(dest));
|
||||
}
|
||||
|
||||
jobject objPacket = create_packet(
|
||||
args, s->icmp.version, IPPROTO_ICMP, "",
|
||||
source, 0, dest, 0, "", s->icmp.uid, 0);
|
||||
if (is_address_allowed(args, objPacket) == NULL) {
|
||||
s->icmp.stop = 1;
|
||||
log_android(ANDROID_LOG_WARN, "ICMP terminate %d uid %d",
|
||||
s->socket, s->icmp.uid);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (s->protocol == IPPROTO_UDP) {
|
||||
if (s->udp.state == UDP_ACTIVE) {
|
||||
if (s->udp.version == 4) {
|
||||
inet_ntop(AF_INET, &s->udp.saddr.ip4, source, sizeof(source));
|
||||
inet_ntop(AF_INET, &s->udp.daddr.ip4, dest, sizeof(dest));
|
||||
} else {
|
||||
inet_ntop(AF_INET6, &s->udp.saddr.ip6, source, sizeof(source));
|
||||
inet_ntop(AF_INET6, &s->udp.daddr.ip6, dest, sizeof(dest));
|
||||
}
|
||||
|
||||
jobject objPacket = create_packet(
|
||||
args, s->udp.version, IPPROTO_UDP, "",
|
||||
source, ntohs(s->udp.source), dest, ntohs(s->udp.dest), "", s->udp.uid, 0);
|
||||
if (is_address_allowed(args, objPacket) == NULL) {
|
||||
s->udp.state = UDP_FINISHING;
|
||||
log_android(ANDROID_LOG_WARN, "UDP terminate session socket %d uid %d",
|
||||
s->socket, s->udp.uid);
|
||||
}
|
||||
} else if (s->udp.state == UDP_BLOCKED) {
|
||||
log_android(ANDROID_LOG_WARN, "UDP remove blocked session uid %d", s->udp.uid);
|
||||
|
||||
if (l == NULL)
|
||||
args->ctx->ng_session = s->next;
|
||||
else
|
||||
l->next = s->next;
|
||||
|
||||
struct ng_session *c = s;
|
||||
s = s->next;
|
||||
ng_free(c, __FILE__, __LINE__);
|
||||
continue;
|
||||
}
|
||||
|
||||
} else if (s->protocol == IPPROTO_TCP) {
|
||||
if (s->tcp.state != TCP_CLOSING && s->tcp.state != TCP_CLOSE) {
|
||||
if (s->tcp.version == 4) {
|
||||
inet_ntop(AF_INET, &s->tcp.saddr.ip4, source, sizeof(source));
|
||||
inet_ntop(AF_INET, &s->tcp.daddr.ip4, dest, sizeof(dest));
|
||||
} else {
|
||||
inet_ntop(AF_INET6, &s->tcp.saddr.ip6, source, sizeof(source));
|
||||
inet_ntop(AF_INET6, &s->tcp.daddr.ip6, dest, sizeof(dest));
|
||||
}
|
||||
|
||||
jobject objPacket = create_packet(
|
||||
args, s->tcp.version, IPPROTO_TCP, "",
|
||||
source, ntohs(s->tcp.source), dest, ntohs(s->tcp.dest), "", s->tcp.uid, 0);
|
||||
if (is_address_allowed(args, objPacket) == NULL) {
|
||||
write_rst(args, &s->tcp);
|
||||
log_android(ANDROID_LOG_WARN, "TCP terminate socket %d uid %d",
|
||||
s->socket, s->tcp.uid);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
l = s;
|
||||
s = s->next;
|
||||
}
|
||||
}
|
||||
|
1379
NetGuard/app/src/main/main/jni/netguard/tcp.c
Normal file
549
NetGuard/app/src/main/main/jni/netguard/udp.c
Normal file
@ -0,0 +1,549 @@
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
#include "netguard.h"
|
||||
|
||||
extern FILE *pcap_file;
|
||||
|
||||
int get_udp_timeout(const struct udp_session *u, int sessions, int maxsessions) {
|
||||
int timeout = (ntohs(u->dest) == 53 ? UDP_TIMEOUT_53 : UDP_TIMEOUT_ANY);
|
||||
|
||||
int scale = 100 - sessions * 100 / maxsessions;
|
||||
timeout = timeout * scale / 100;
|
||||
|
||||
return timeout;
|
||||
}
|
||||
|
||||
int check_udp_session(const struct arguments *args, struct ng_session *s,
|
||||
int sessions, int maxsessions) {
|
||||
time_t now = time(NULL);
|
||||
|
||||
char source[INET6_ADDRSTRLEN + 1];
|
||||
char dest[INET6_ADDRSTRLEN + 1];
|
||||
if (s->udp.version == 4) {
|
||||
inet_ntop(AF_INET, &s->udp.saddr.ip4, source, sizeof(source));
|
||||
inet_ntop(AF_INET, &s->udp.daddr.ip4, dest, sizeof(dest));
|
||||
} else {
|
||||
inet_ntop(AF_INET6, &s->udp.saddr.ip6, source, sizeof(source));
|
||||
inet_ntop(AF_INET6, &s->udp.daddr.ip6, dest, sizeof(dest));
|
||||
}
|
||||
|
||||
// Check session timeout
|
||||
int timeout = get_udp_timeout(&s->udp, sessions, maxsessions);
|
||||
if (s->udp.state == UDP_ACTIVE && s->udp.time + timeout < now) {
|
||||
log_android(ANDROID_LOG_WARN, "UDP idle %d/%d sec state %d from %s/%u to %s/%u",
|
||||
now - s->udp.time, timeout, s->udp.state,
|
||||
source, ntohs(s->udp.source), dest, ntohs(s->udp.dest));
|
||||
s->udp.state = UDP_FINISHING;
|
||||
}
|
||||
|
||||
// Check finished sessions
|
||||
if (s->udp.state == UDP_FINISHING) {
|
||||
log_android(ANDROID_LOG_INFO, "UDP close from %s/%u to %s/%u socket %d",
|
||||
source, ntohs(s->udp.source), dest, ntohs(s->udp.dest), s->socket);
|
||||
|
||||
if (close(s->socket))
|
||||
log_android(ANDROID_LOG_ERROR, "UDP close %d error %d: %s",
|
||||
s->socket, errno, strerror(errno));
|
||||
s->socket = -1;
|
||||
|
||||
s->udp.time = time(NULL);
|
||||
s->udp.state = UDP_CLOSED;
|
||||
}
|
||||
|
||||
if (s->udp.state == UDP_CLOSED && (s->udp.sent || s->udp.received)) {
|
||||
account_usage(args, s->udp.version, IPPROTO_UDP,
|
||||
dest, ntohs(s->udp.dest), s->udp.uid, s->udp.sent, s->udp.received);
|
||||
s->udp.sent = 0;
|
||||
s->udp.received = 0;
|
||||
}
|
||||
|
||||
// Cleanup lingering sessions
|
||||
if ((s->udp.state == UDP_CLOSED || s->udp.state == UDP_BLOCKED) &&
|
||||
s->udp.time + UDP_KEEP_TIMEOUT < now)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void check_udp_socket(const struct arguments *args, const struct epoll_event *ev) {
|
||||
struct ng_session *s = (struct ng_session *) ev->data.ptr;
|
||||
|
||||
// Check socket error
|
||||
if (ev->events & EPOLLERR) {
|
||||
s->udp.time = time(NULL);
|
||||
|
||||
int serr = 0;
|
||||
socklen_t optlen = sizeof(int);
|
||||
int err = getsockopt(s->socket, 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));
|
||||
|
||||
s->udp.state = UDP_FINISHING;
|
||||
} else {
|
||||
// Check socket read
|
||||
if (ev->events & EPOLLIN) {
|
||||
s->udp.time = time(NULL);
|
||||
|
||||
uint8_t *buffer = ng_malloc(s->udp.mss, "udp recv");
|
||||
ssize_t bytes = recv(s->socket, buffer, s->udp.mss, 0);
|
||||
if (bytes < 0) {
|
||||
// Socket error
|
||||
log_android(ANDROID_LOG_WARN, "UDP recv error %d: %s",
|
||||
errno, strerror(errno));
|
||||
|
||||
if (errno != EINTR && errno != EAGAIN)
|
||||
s->udp.state = UDP_FINISHING;
|
||||
} else if (bytes == 0) {
|
||||
log_android(ANDROID_LOG_WARN, "UDP recv eof");
|
||||
s->udp.state = UDP_FINISHING;
|
||||
|
||||
} else {
|
||||
// Socket read data
|
||||
char dest[INET6_ADDRSTRLEN + 1];
|
||||
if (s->udp.version == 4)
|
||||
inet_ntop(AF_INET, &s->udp.daddr.ip4, dest, sizeof(dest));
|
||||
else
|
||||
inet_ntop(AF_INET6, &s->udp.daddr.ip6, dest, sizeof(dest));
|
||||
log_android(ANDROID_LOG_INFO, "UDP recv bytes %d from %s/%u for tun",
|
||||
bytes, dest, ntohs(s->udp.dest));
|
||||
|
||||
s->udp.received += bytes;
|
||||
|
||||
// Process DNS response
|
||||
if (ntohs(s->udp.dest) == 53)
|
||||
parse_dns_response(args, s, buffer, (size_t *) &bytes);
|
||||
|
||||
// Forward to tun
|
||||
if (write_udp(args, &s->udp, buffer, (size_t) bytes) < 0)
|
||||
s->udp.state = UDP_FINISHING;
|
||||
else {
|
||||
// Prevent too many open files
|
||||
if (ntohs(s->udp.dest) == 53)
|
||||
s->udp.state = UDP_FINISHING;
|
||||
}
|
||||
}
|
||||
ng_free(buffer, __FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int has_udp_session(const struct arguments *args, const uint8_t *pkt, const uint8_t *payload) {
|
||||
// Get headers
|
||||
const uint8_t version = (*pkt) >> 4;
|
||||
const struct iphdr *ip4 = (struct iphdr *) pkt;
|
||||
const struct ip6_hdr *ip6 = (struct ip6_hdr *) pkt;
|
||||
const struct udphdr *udphdr = (struct udphdr *) payload;
|
||||
|
||||
if (ntohs(udphdr->dest) == 53 && !args->fwd53)
|
||||
return 1;
|
||||
|
||||
// Search session
|
||||
struct ng_session *cur = args->ctx->ng_session;
|
||||
while (cur != NULL &&
|
||||
!(cur->protocol == IPPROTO_UDP &&
|
||||
cur->udp.version == version &&
|
||||
cur->udp.source == udphdr->source && cur->udp.dest == udphdr->dest &&
|
||||
(version == 4 ? cur->udp.saddr.ip4 == ip4->saddr &&
|
||||
cur->udp.daddr.ip4 == ip4->daddr
|
||||
: memcmp(&cur->udp.saddr.ip6, &ip6->ip6_src, 16) == 0 &&
|
||||
memcmp(&cur->udp.daddr.ip6, &ip6->ip6_dst, 16) == 0)))
|
||||
cur = cur->next;
|
||||
|
||||
return (cur != NULL);
|
||||
}
|
||||
|
||||
void block_udp(const struct arguments *args,
|
||||
const uint8_t *pkt, size_t length,
|
||||
const uint8_t *payload,
|
||||
int uid) {
|
||||
// Get headers
|
||||
const uint8_t version = (*pkt) >> 4;
|
||||
const struct iphdr *ip4 = (struct iphdr *) pkt;
|
||||
const struct ip6_hdr *ip6 = (struct ip6_hdr *) pkt;
|
||||
const struct udphdr *udphdr = (struct udphdr *) payload;
|
||||
|
||||
char source[INET6_ADDRSTRLEN + 1];
|
||||
char dest[INET6_ADDRSTRLEN + 1];
|
||||
if (version == 4) {
|
||||
inet_ntop(AF_INET, &ip4->saddr, source, sizeof(source));
|
||||
inet_ntop(AF_INET, &ip4->daddr, dest, sizeof(dest));
|
||||
} else {
|
||||
inet_ntop(AF_INET6, &ip6->ip6_src, source, sizeof(source));
|
||||
inet_ntop(AF_INET6, &ip6->ip6_dst, dest, sizeof(dest));
|
||||
}
|
||||
|
||||
log_android(ANDROID_LOG_INFO, "UDP blocked session from %s/%u to %s/%u",
|
||||
source, ntohs(udphdr->source), dest, ntohs(udphdr->dest));
|
||||
|
||||
// Register session
|
||||
struct ng_session *s = ng_malloc(sizeof(struct ng_session), "udp session block");
|
||||
s->protocol = IPPROTO_UDP;
|
||||
|
||||
s->udp.time = time(NULL);
|
||||
s->udp.uid = uid;
|
||||
s->udp.version = version;
|
||||
|
||||
if (version == 4) {
|
||||
s->udp.saddr.ip4 = (__be32) ip4->saddr;
|
||||
s->udp.daddr.ip4 = (__be32) ip4->daddr;
|
||||
} else {
|
||||
memcpy(&s->udp.saddr.ip6, &ip6->ip6_src, 16);
|
||||
memcpy(&s->udp.daddr.ip6, &ip6->ip6_dst, 16);
|
||||
}
|
||||
|
||||
s->udp.source = udphdr->source;
|
||||
s->udp.dest = udphdr->dest;
|
||||
s->udp.state = UDP_BLOCKED;
|
||||
s->socket = -1;
|
||||
|
||||
s->next = args->ctx->ng_session;
|
||||
args->ctx->ng_session = s;
|
||||
}
|
||||
|
||||
jboolean handle_udp(const struct arguments *args,
|
||||
const uint8_t *pkt, size_t length,
|
||||
const uint8_t *payload,
|
||||
int uid, struct allowed *redirect,
|
||||
const int epoll_fd) {
|
||||
// Get headers
|
||||
const uint8_t version = (*pkt) >> 4;
|
||||
const struct iphdr *ip4 = (struct iphdr *) pkt;
|
||||
const struct ip6_hdr *ip6 = (struct ip6_hdr *) pkt;
|
||||
const struct udphdr *udphdr = (struct udphdr *) payload;
|
||||
const uint8_t *data = payload + sizeof(struct udphdr);
|
||||
const size_t datalen = length - (data - pkt);
|
||||
|
||||
// Search session
|
||||
struct ng_session *cur = args->ctx->ng_session;
|
||||
while (cur != NULL &&
|
||||
!(cur->protocol == IPPROTO_UDP &&
|
||||
cur->udp.version == version &&
|
||||
cur->udp.source == udphdr->source && cur->udp.dest == udphdr->dest &&
|
||||
(version == 4 ? cur->udp.saddr.ip4 == ip4->saddr &&
|
||||
cur->udp.daddr.ip4 == ip4->daddr
|
||||
: memcmp(&cur->udp.saddr.ip6, &ip6->ip6_src, 16) == 0 &&
|
||||
memcmp(&cur->udp.daddr.ip6, &ip6->ip6_dst, 16) == 0)))
|
||||
cur = cur->next;
|
||||
|
||||
char source[INET6_ADDRSTRLEN + 1];
|
||||
char dest[INET6_ADDRSTRLEN + 1];
|
||||
if (version == 4) {
|
||||
inet_ntop(AF_INET, &ip4->saddr, source, sizeof(source));
|
||||
inet_ntop(AF_INET, &ip4->daddr, dest, sizeof(dest));
|
||||
} else {
|
||||
inet_ntop(AF_INET6, &ip6->ip6_src, source, sizeof(source));
|
||||
inet_ntop(AF_INET6, &ip6->ip6_dst, dest, sizeof(dest));
|
||||
}
|
||||
|
||||
if (cur != NULL && cur->udp.state != UDP_ACTIVE) {
|
||||
log_android(ANDROID_LOG_INFO, "UDP ignore session from %s/%u to %s/%u state %d",
|
||||
source, ntohs(udphdr->source), dest, ntohs(udphdr->dest), cur->udp.state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Create new session if needed
|
||||
if (cur == NULL) {
|
||||
log_android(ANDROID_LOG_INFO, "UDP new session from %s/%u to %s/%u",
|
||||
source, ntohs(udphdr->source), dest, ntohs(udphdr->dest));
|
||||
|
||||
// Register session
|
||||
struct ng_session *s = ng_malloc(sizeof(struct ng_session), "udp session");
|
||||
s->protocol = IPPROTO_UDP;
|
||||
|
||||
s->udp.time = time(NULL);
|
||||
s->udp.uid = uid;
|
||||
s->udp.version = version;
|
||||
|
||||
int rversion;
|
||||
if (redirect == NULL)
|
||||
rversion = s->udp.version;
|
||||
else
|
||||
rversion = (strstr(redirect->raddr, ":") == NULL ? 4 : 6);
|
||||
s->udp.mss = (uint16_t) (rversion == 4 ? UDP4_MAXMSG : UDP6_MAXMSG);
|
||||
|
||||
s->udp.sent = 0;
|
||||
s->udp.received = 0;
|
||||
|
||||
if (version == 4) {
|
||||
s->udp.saddr.ip4 = (__be32) ip4->saddr;
|
||||
s->udp.daddr.ip4 = (__be32) ip4->daddr;
|
||||
} else {
|
||||
memcpy(&s->udp.saddr.ip6, &ip6->ip6_src, 16);
|
||||
memcpy(&s->udp.daddr.ip6, &ip6->ip6_dst, 16);
|
||||
}
|
||||
|
||||
s->udp.source = udphdr->source;
|
||||
s->udp.dest = udphdr->dest;
|
||||
s->udp.state = UDP_ACTIVE;
|
||||
s->next = NULL;
|
||||
|
||||
// Open UDP socket
|
||||
s->socket = open_udp_socket(args, &s->udp, redirect);
|
||||
if (s->socket < 0) {
|
||||
ng_free(s, __FILE__, __LINE__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
log_android(ANDROID_LOG_DEBUG, "UDP socket %d", s->socket);
|
||||
|
||||
// Monitor events
|
||||
memset(&s->ev, 0, sizeof(struct epoll_event));
|
||||
s->ev.events = EPOLLIN | EPOLLERR;
|
||||
s->ev.data.ptr = s;
|
||||
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, s->socket, &s->ev))
|
||||
log_android(ANDROID_LOG_ERROR, "epoll add udp error %d: %s", errno, strerror(errno));
|
||||
|
||||
s->next = args->ctx->ng_session;
|
||||
args->ctx->ng_session = s;
|
||||
|
||||
cur = s;
|
||||
}
|
||||
|
||||
// Check for DHCP (tethering)
|
||||
if (ntohs(udphdr->source) == 68 || ntohs(udphdr->dest) == 67) {
|
||||
if (check_dhcp(args, &cur->udp, data, datalen) >= 0)
|
||||
return 1;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
cur->udp.time = time(NULL);
|
||||
|
||||
int rversion;
|
||||
struct sockaddr_in addr4;
|
||||
struct sockaddr_in6 addr6;
|
||||
if (redirect == NULL) {
|
||||
rversion = cur->udp.version;
|
||||
if (cur->udp.version == 4) {
|
||||
addr4.sin_family = AF_INET;
|
||||
addr4.sin_addr.s_addr = (__be32) cur->udp.daddr.ip4;
|
||||
addr4.sin_port = cur->udp.dest;
|
||||
} else {
|
||||
addr6.sin6_family = AF_INET6;
|
||||
memcpy(&addr6.sin6_addr, &cur->udp.daddr.ip6, 16);
|
||||
addr6.sin6_port = cur->udp.dest;
|
||||
}
|
||||
} else {
|
||||
rversion = (strstr(redirect->raddr, ":") == NULL ? 4 : 6);
|
||||
log_android(ANDROID_LOG_WARN, "UDP%d redirect to %s/%u",
|
||||
rversion, redirect->raddr, redirect->rport);
|
||||
|
||||
if (rversion == 4) {
|
||||
addr4.sin_family = AF_INET;
|
||||
inet_pton(AF_INET, redirect->raddr, &addr4.sin_addr);
|
||||
addr4.sin_port = htons(redirect->rport);
|
||||
} else {
|
||||
addr6.sin6_family = AF_INET6;
|
||||
inet_pton(AF_INET6, redirect->raddr, &addr6.sin6_addr);
|
||||
addr6.sin6_port = htons(redirect->rport);
|
||||
}
|
||||
}
|
||||
|
||||
if (sendto(cur->socket, data, (socklen_t) datalen, MSG_NOSIGNAL,
|
||||
(rversion == 4 ? (const struct sockaddr *) &addr4
|
||||
: (const struct sockaddr *) &addr6),
|
||||
(socklen_t) (rversion == 4 ? sizeof(addr4) : sizeof(addr6))) != datalen) {
|
||||
log_android(ANDROID_LOG_ERROR, "UDP sendto error %d: %s", errno, strerror(errno));
|
||||
if (errno != EINTR && errno != EAGAIN) {
|
||||
cur->udp.state = UDP_FINISHING;
|
||||
return 0;
|
||||
}
|
||||
} else
|
||||
cur->udp.sent += datalen;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int open_udp_socket(const struct arguments *args,
|
||||
const struct udp_session *cur, const struct allowed *redirect) {
|
||||
int sock;
|
||||
int version;
|
||||
if (redirect == NULL)
|
||||
version = cur->version;
|
||||
else
|
||||
version = (strstr(redirect->raddr, ":") == NULL ? 4 : 6);
|
||||
|
||||
// Get UDP socket
|
||||
sock = socket(version == 4 ? PF_INET : PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (sock < 0) {
|
||||
log_android(ANDROID_LOG_ERROR, "UDP socket error %d: %s", errno, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Protect socket
|
||||
if (protect_socket(args, sock) < 0)
|
||||
return -1;
|
||||
|
||||
// Check for broadcast/multicast
|
||||
if (cur->version == 4) {
|
||||
uint32_t broadcast4 = INADDR_BROADCAST;
|
||||
if (memcmp(&cur->daddr.ip4, &broadcast4, sizeof(broadcast4)) == 0) {
|
||||
log_android(ANDROID_LOG_WARN, "UDP4 broadcast");
|
||||
int on = 1;
|
||||
if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)))
|
||||
log_android(ANDROID_LOG_ERROR, "UDP setsockopt SO_BROADCAST error %d: %s",
|
||||
errno, strerror(errno));
|
||||
}
|
||||
} else {
|
||||
// http://man7.org/linux/man-pages/man7/ipv6.7.html
|
||||
if (*((uint8_t *) &cur->daddr.ip6) == 0xFF) {
|
||||
log_android(ANDROID_LOG_WARN, "UDP6 broadcast");
|
||||
|
||||
int loop = 1; // true
|
||||
if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &loop, sizeof(loop)))
|
||||
log_android(ANDROID_LOG_ERROR,
|
||||
"UDP setsockopt IPV6_MULTICAST_LOOP error %d: %s",
|
||||
errno, strerror(errno));
|
||||
|
||||
int ttl = -1; // route default
|
||||
if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)))
|
||||
log_android(ANDROID_LOG_ERROR,
|
||||
"UDP setsockopt IPV6_MULTICAST_HOPS error %d: %s",
|
||||
errno, strerror(errno));
|
||||
|
||||
struct ipv6_mreq mreq6;
|
||||
memcpy(&mreq6.ipv6mr_multiaddr, &cur->daddr.ip6, sizeof(struct in6_addr));
|
||||
mreq6.ipv6mr_interface = INADDR_ANY;
|
||||
if (setsockopt(sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq6, sizeof(mreq6)))
|
||||
log_android(ANDROID_LOG_ERROR,
|
||||
"UDP setsockopt IPV6_ADD_MEMBERSHIP error %d: %s",
|
||||
errno, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
return sock;
|
||||
}
|
||||
|
||||
ssize_t write_udp(const struct arguments *args, const struct udp_session *cur,
|
||||
uint8_t *data, size_t datalen) {
|
||||
size_t len;
|
||||
u_int8_t *buffer;
|
||||
struct udphdr *udp;
|
||||
uint16_t csum;
|
||||
char source[INET6_ADDRSTRLEN + 1];
|
||||
char dest[INET6_ADDRSTRLEN + 1];
|
||||
|
||||
// Build packet
|
||||
if (cur->version == 4) {
|
||||
len = sizeof(struct iphdr) + sizeof(struct udphdr) + datalen;
|
||||
buffer = ng_malloc(len, "udp write4");
|
||||
struct iphdr *ip4 = (struct iphdr *) buffer;
|
||||
udp = (struct udphdr *) (buffer + sizeof(struct iphdr));
|
||||
if (datalen)
|
||||
memcpy(buffer + sizeof(struct iphdr) + sizeof(struct udphdr), data, datalen);
|
||||
|
||||
// Build IP4 header
|
||||
memset(ip4, 0, sizeof(struct iphdr));
|
||||
ip4->version = 4;
|
||||
ip4->ihl = sizeof(struct iphdr) >> 2;
|
||||
ip4->tot_len = htons(len);
|
||||
ip4->ttl = IPDEFTTL;
|
||||
ip4->protocol = IPPROTO_UDP;
|
||||
ip4->saddr = cur->daddr.ip4;
|
||||
ip4->daddr = cur->saddr.ip4;
|
||||
|
||||
// Calculate IP4 checksum
|
||||
ip4->check = ~calc_checksum(0, (uint8_t *) ip4, sizeof(struct iphdr));
|
||||
|
||||
// Calculate UDP4 checksum
|
||||
struct ippseudo pseudo;
|
||||
memset(&pseudo, 0, sizeof(struct ippseudo));
|
||||
pseudo.ippseudo_src.s_addr = (__be32) ip4->saddr;
|
||||
pseudo.ippseudo_dst.s_addr = (__be32) ip4->daddr;
|
||||
pseudo.ippseudo_p = ip4->protocol;
|
||||
pseudo.ippseudo_len = htons(sizeof(struct udphdr) + datalen);
|
||||
|
||||
csum = calc_checksum(0, (uint8_t *) &pseudo, sizeof(struct ippseudo));
|
||||
} else {
|
||||
len = sizeof(struct ip6_hdr) + sizeof(struct udphdr) + datalen;
|
||||
buffer = ng_malloc(len, "udp write6");
|
||||
struct ip6_hdr *ip6 = (struct ip6_hdr *) buffer;
|
||||
udp = (struct udphdr *) (buffer + sizeof(struct ip6_hdr));
|
||||
if (datalen)
|
||||
memcpy(buffer + sizeof(struct ip6_hdr) + sizeof(struct udphdr), data, datalen);
|
||||
|
||||
// Build IP6 header
|
||||
memset(ip6, 0, sizeof(struct ip6_hdr));
|
||||
ip6->ip6_ctlun.ip6_un1.ip6_un1_flow = 0;
|
||||
ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = htons(len - sizeof(struct ip6_hdr));
|
||||
ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt = IPPROTO_UDP;
|
||||
ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim = IPDEFTTL;
|
||||
ip6->ip6_ctlun.ip6_un2_vfc = IPV6_VERSION;
|
||||
memcpy(&(ip6->ip6_src), &cur->daddr.ip6, 16);
|
||||
memcpy(&(ip6->ip6_dst), &cur->saddr.ip6, 16);
|
||||
|
||||
// Calculate UDP6 checksum
|
||||
struct ip6_hdr_pseudo pseudo;
|
||||
memset(&pseudo, 0, sizeof(struct ip6_hdr_pseudo));
|
||||
memcpy(&pseudo.ip6ph_src, &ip6->ip6_dst, 16);
|
||||
memcpy(&pseudo.ip6ph_dst, &ip6->ip6_src, 16);
|
||||
pseudo.ip6ph_len = ip6->ip6_ctlun.ip6_un1.ip6_un1_plen;
|
||||
pseudo.ip6ph_nxt = ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt;
|
||||
|
||||
csum = calc_checksum(0, (uint8_t *) &pseudo, sizeof(struct ip6_hdr_pseudo));
|
||||
}
|
||||
|
||||
// Build UDP header
|
||||
memset(udp, 0, sizeof(struct udphdr));
|
||||
udp->source = cur->dest;
|
||||
udp->dest = cur->source;
|
||||
udp->len = htons(sizeof(struct udphdr) + datalen);
|
||||
|
||||
// Continue checksum
|
||||
csum = calc_checksum(csum, (uint8_t *) udp, sizeof(struct udphdr));
|
||||
csum = calc_checksum(csum, data, datalen);
|
||||
udp->check = ~csum;
|
||||
|
||||
inet_ntop(cur->version == 4 ? AF_INET : AF_INET6,
|
||||
(cur->version == 4 ? (const void *) &cur->saddr.ip4 : (const void *) &cur->saddr.ip6),
|
||||
source,
|
||||
sizeof(source));
|
||||
inet_ntop(cur->version == 4 ? AF_INET : AF_INET6,
|
||||
(cur->version == 4 ? (const void *) &cur->daddr.ip4 : (const void *) &cur->daddr.ip6),
|
||||
dest,
|
||||
sizeof(dest));
|
||||
|
||||
// Send packet
|
||||
log_android(ANDROID_LOG_DEBUG,
|
||||
"UDP sending to tun %d from %s/%u to %s/%u data %u",
|
||||
args->tun, dest, ntohs(cur->dest), source, ntohs(cur->source), len);
|
||||
|
||||
ssize_t res = write(args->tun, buffer, len);
|
||||
|
||||
// Write PCAP record
|
||||
if (res >= 0) {
|
||||
if (pcap_file != NULL)
|
||||
write_pcap_rec(buffer, (size_t) res);
|
||||
} else
|
||||
log_android(ANDROID_LOG_WARN, "UDP write error %d: %s", errno, strerror(errno));
|
||||
|
||||
ng_free(buffer, __FILE__, __LINE__);
|
||||
|
||||
if (res != len) {
|
||||
log_android(ANDROID_LOG_ERROR, "write %d/%d", res, len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
182
NetGuard/app/src/main/main/jni/netguard/util.c
Normal file
@ -0,0 +1,182 @@
|
||||
/*
|
||||
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-2019 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
#include "netguard.h"
|
||||
|
||||
extern int loglevel;
|
||||
|
||||
uint16_t calc_checksum(uint16_t start, const uint8_t *buffer, size_t length) {
|
||||
register uint32_t sum = start;
|
||||
register uint16_t *buf = (uint16_t *) buffer;
|
||||
register size_t len = length;
|
||||
|
||||
while (len > 1) {
|
||||
sum += *buf++;
|
||||
len -= 2;
|
||||
}
|
||||
|
||||
if (len > 0)
|
||||
sum += *((uint8_t *) buf);
|
||||
|
||||
while (sum >> 16)
|
||||
sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
|
||||
return (uint16_t) sum;
|
||||
}
|
||||
|
||||
int compare_u32(uint32_t s1, uint32_t s2) {
|
||||
// https://tools.ietf.org/html/rfc1982
|
||||
if (s1 == s2)
|
||||
return 0;
|
||||
|
||||
uint32_t i1 = s1;
|
||||
uint32_t i2 = s2;
|
||||
if ((i1 < i2 && i2 - i1 < 0x7FFFFFFF) ||
|
||||
(i1 > i2 && i1 - i2 > 0x7FFFFFFF))
|
||||
return -1;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
int sdk_int(JNIEnv *env) {
|
||||
jclass clsVersion = jniFindClass(env, "android/os/Build$VERSION");
|
||||
jfieldID fid = (*env)->GetStaticFieldID(env, clsVersion, "SDK_INT", "I");
|
||||
return (*env)->GetStaticIntField(env, clsVersion, fid);
|
||||
}
|
||||
|
||||
void log_android(int prio, const char *fmt, ...) {
|
||||
if (prio >= loglevel) {
|
||||
char line[1024];
|
||||
va_list argptr;
|
||||
va_start(argptr, fmt);
|
||||
vsprintf(line, fmt, argptr);
|
||||
__android_log_print(prio, TAG, "%s", line);
|
||||
va_end(argptr);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t char2nible(const char c) {
|
||||
if (c >= '0' && c <= '9') return (uint8_t) (c - '0');
|
||||
if (c >= 'a' && c <= 'f') return (uint8_t) ((c - 'a') + 10);
|
||||
if (c >= 'A' && c <= 'F') return (uint8_t) ((c - 'A') + 10);
|
||||
return 255;
|
||||
}
|
||||
|
||||
void hex2bytes(const char *hex, uint8_t *buffer) {
|
||||
size_t len = strlen(hex);
|
||||
for (int i = 0; i < len; i += 2)
|
||||
buffer[i / 2] = (char2nible(hex[i]) << 4) | char2nible(hex[i + 1]);
|
||||
}
|
||||
|
||||
char *trim(char *str) {
|
||||
while (isspace(*str))
|
||||
str++;
|
||||
if (*str == 0)
|
||||
return str;
|
||||
|
||||
char *end = str + strlen(str) - 1;
|
||||
while (end > str && isspace(*end))
|
||||
end--;
|
||||
*(end + 1) = 0;
|
||||
return str;
|
||||
}
|
||||
|
||||
const char *strstate(const int state) {
|
||||
switch (state) {
|
||||
case TCP_ESTABLISHED:
|
||||
return "ESTABLISHED";
|
||||
case TCP_SYN_SENT:
|
||||
return "SYN_SENT";
|
||||
case TCP_SYN_RECV:
|
||||
return "SYN_RECV";
|
||||
case TCP_FIN_WAIT1:
|
||||
return "FIN_WAIT1";
|
||||
case TCP_FIN_WAIT2:
|
||||
return "FIN_WAIT2";
|
||||
case TCP_TIME_WAIT:
|
||||
return "TIME_WAIT";
|
||||
case TCP_CLOSE:
|
||||
return "CLOSE";
|
||||
case TCP_CLOSE_WAIT:
|
||||
return "CLOSE_WAIT";
|
||||
case TCP_LAST_ACK:
|
||||
return "LAST_ACK";
|
||||
case TCP_LISTEN:
|
||||
return "LISTEN";
|
||||
case TCP_CLOSING:
|
||||
return "CLOSING";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
char *hex(const u_int8_t *data, const size_t len) {
|
||||
char hex_str[] = "0123456789ABCDEF";
|
||||
|
||||
char *hexout;
|
||||
hexout = (char *) ng_malloc(len * 3 + 1, "hex"); // TODO free
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
hexout[i * 3 + 0] = hex_str[(data[i] >> 4) & 0x0F];
|
||||
hexout[i * 3 + 1] = hex_str[(data[i]) & 0x0F];
|
||||
hexout[i * 3 + 2] = ' ';
|
||||
}
|
||||
hexout[len * 3] = 0;
|
||||
|
||||
return hexout;
|
||||
}
|
||||
|
||||
int32_t get_local_port(const int sock) {
|
||||
struct sockaddr_in sin;
|
||||
socklen_t len = sizeof(sin);
|
||||
if (getsockname(sock, (struct sockaddr *) &sin, &len) < 0) {
|
||||
log_android(ANDROID_LOG_ERROR, "getsockname error %d: %s", errno, strerror(errno));
|
||||
return -1;
|
||||
} else
|
||||
return ntohs(sin.sin_port);
|
||||
}
|
||||
|
||||
int is_event(int fd, short event) {
|
||||
struct pollfd p;
|
||||
p.fd = fd;
|
||||
p.events = event;
|
||||
p.revents = 0;
|
||||
int r = poll(&p, 1, 0);
|
||||
if (r < 0) {
|
||||
log_android(ANDROID_LOG_ERROR, "poll readable error %d: %s", errno, strerror(errno));
|
||||
return 0;
|
||||
} else if (r == 0)
|
||||
return 0;
|
||||
else
|
||||
return (p.revents & event);
|
||||
}
|
||||
|
||||
int is_readable(int fd) {
|
||||
return is_event(fd, POLLIN);
|
||||
}
|
||||
|
||||
int is_writable(int fd) {
|
||||
return is_event(fd, POLLOUT);
|
||||
}
|
||||
|
||||
long long get_ms() {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return ts.tv_sec * 1000LL + ts.tv_nsec / 1e6;
|
||||
}
|
After Width: | Height: | Size: 487 B |
After Width: | Height: | Size: 395 B |
After Width: | Height: | Size: 397 B |
After Width: | Height: | Size: 181 B |
After Width: | Height: | Size: 221 B |
After Width: | Height: | Size: 345 B |
After Width: | Height: | Size: 155 B |
After Width: | Height: | Size: 161 B |
After Width: | Height: | Size: 106 B |
After Width: | Height: | Size: 178 B |
After Width: | Height: | Size: 324 B |
After Width: | Height: | Size: 149 B |
After Width: | Height: | Size: 156 B |
After Width: | Height: | Size: 151 B |
After Width: | Height: | Size: 159 B |
After Width: | Height: | Size: 163 B |
After Width: | Height: | Size: 111 B |
After Width: | Height: | Size: 196 B |
After Width: | Height: | Size: 154 B |
After Width: | Height: | Size: 159 B |
After Width: | Height: | Size: 250 B |
After Width: | Height: | Size: 253 B |
After Width: | Height: | Size: 306 B |
After Width: | Height: | Size: 303 B |
After Width: | Height: | Size: 463 B |
After Width: | Height: | Size: 102 B |
After Width: | Height: | Size: 105 B |
After Width: | Height: | Size: 363 B |
After Width: | Height: | Size: 386 B |
After Width: | Height: | Size: 365 B |
After Width: | Height: | Size: 194 B |
After Width: | Height: | Size: 195 B |
After Width: | Height: | Size: 396 B |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 428 B |
After Width: | Height: | Size: 895 B |
After Width: | Height: | Size: 453 B |