Add IPv6 support

IPv6 must be manually enabled from the preferences. However, when an IPv6 gateway
is not available, the connections will fail with network unreachable.

Closes #2
This commit is contained in:
emanuele-f
2021-03-18 19:23:21 +01:00
parent 08e65d7223
commit 47e1d88aab
13 changed files with 260 additions and 111 deletions
@@ -61,7 +61,9 @@ import com.emanuelef.remote_capture.model.VPNStats;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
public class CaptureService extends VpnService implements Runnable {
private static final String TAG = "CaptureService";
@@ -78,6 +80,7 @@ public class CaptureService extends VpnService implements Runnable {
private String tls_proxy_address;
private Prefs.DumpMode dump_mode;
private boolean tls_decryption_enabled;
private boolean ipv6_enabled;
private int collector_port;
private int http_server_port;
private int tls_proxy_port;
@@ -99,9 +102,11 @@ public class CaptureService extends VpnService implements Runnable {
public static final int CONNECTIONS_LOG_SIZE = 8192;
public static final String FALLBACK_DNS_SERVER = "8.8.8.8";
public static final String IPV6_DNS_SERVER = "2001:4860:4860::8888";
/* The IP address of the virtual network interface */
public static final String VPN_IP_ADDRESS = "10.215.173.1";
public static final String VPN_IP6_ADDRESS = "fd00:2:fd00:1:fd00:1:fd00:1";
/* The DNS server IP address to use to internally analyze the DNS requests.
* It must be in the same subnet of the VPN network interface.
@@ -180,6 +185,7 @@ public class CaptureService extends VpnService implements Runnable {
tls_proxy_address = Prefs.getTlsProxyAddress(prefs);
tls_proxy_port = Prefs.getTlsProxyPort(prefs);
dump_mode = Prefs.getDumpMode(prefs);
ipv6_enabled = Prefs.getIPv6Enabled(prefs);
last_bytes = 0;
last_connections = 0;
@@ -231,6 +237,19 @@ public class CaptureService extends VpnService implements Runnable {
.addRoute("128.0.0.0", 1)
.addDnsServer(vpn_dns);
if(ipv6_enabled) {
builder.addAddress(VPN_IP6_ADDRESS, 128);
// Route unicast IPv6 addresses
builder.addRoute("2000::", 3);
try {
builder.addDnsServer(InetAddress.getByName(IPV6_DNS_SERVER));
} catch (UnknownHostException e) {
Log.w(TAG, "Could not set IPv6 DNS server");
}
}
if((app_filter != null) && (!app_filter.isEmpty())) {
Log.d(TAG, "Setting app filter: " + app_filter);
@@ -498,6 +517,8 @@ public class CaptureService extends VpnService implements Runnable {
return(dns_server);
}
public String getIpv6DnsServer() { return(IPV6_DNS_SERVER); }
public String getPcapCollectorAddress() {
return(collector_address);
}
@@ -512,6 +533,8 @@ public class CaptureService extends VpnService implements Runnable {
public int getTlsProxyPort() { return(tls_proxy_port); }
public int getIPv6Enabled() { return(ipv6_enabled ? 1 : 0); }
// returns 1 if dumpPcapData should be called
public int dumpPcapToJava() {
return(((dump_mode == Prefs.DumpMode.HTTP_SERVER) || (dump_mode == Prefs.DumpMode.PCAP_FILE)) ? 1 : 0);
@@ -534,10 +557,8 @@ public class CaptureService extends VpnService implements Runnable {
InetSocketAddress local = new InetSocketAddress(saddr, sport);
InetSocketAddress remote = new InetSocketAddress(daddr, dport);
Log.i(TAG, "Get uid local=" + local + " remote=" + remote);
int uid = cm.getConnectionOwnerUid(protocol, local, remote);
Log.i(TAG, "Get uid=" + uid);
return uid;
Log.d(TAG, "Get uid local=" + local + " remote=" + remote);
return cm.getConnectionOwnerUid(protocol, local, remote);
}
public void sendConnectionsDump(ConnectionDescriptor[] new_conns, ConnectionDescriptor[] conns_updates) {
@@ -257,8 +257,6 @@ public class ConnectionsRegister {
public synchronized String dumpConnectionsCsv(Context context, int uidFilter) {
StringBuilder builder = new StringBuilder();
String statusOpen = context.getString(R.string.conn_status_open);
String statusClosed = context.getString(R.string.conn_status_closed);
AppsResolver resolver = new AppsResolver(context);
// Header
@@ -279,7 +277,7 @@ public class ConnectionsRegister {
builder.append(conn.uid); builder.append(",");
builder.append((app != null) ? app.getName() : ""); builder.append(",");
builder.append(conn.l7proto); builder.append(",");
builder.append(conn.closed ? statusClosed : statusOpen); builder.append(",");
builder.append(conn.status); builder.append(",");
builder.append((conn.info != null) ? conn.info : ""); builder.append(",");
builder.append(conn.sent_bytes); builder.append(",");
builder.append(conn.rcvd_bytes); builder.append(",");
@@ -116,11 +116,7 @@ public class ConnectionDetailsActivity extends AppCompatActivity {
mDurationView.setText(Utils.formatDuration(conn.last_seen - conn.first_seen));
mFirstSeen.setText(Utils.formatEpochFull(this, conn.first_seen));
mLastSeen.setText(Utils.formatEpochFull(this, conn.last_seen));
if(conn.closed)
mStatus.setText(R.string.conn_status_closed);
else
mStatus.setText(R.string.conn_status_open);
mStatus.setText(conn.status);
}
}
@@ -53,12 +53,15 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
public static class ViewHolder extends RecyclerView.ViewHolder {
ImageView icon;
View statusInd;
TextView statusInd;
TextView remote;
TextView l7proto;
TextView traffic;
TextView appName;
TextView eta;
final String mStatusOpen;
final String mStatusError;
final String mProtoAndPort;
ViewHolder(View itemView) {
super(itemView);
@@ -70,6 +73,11 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
statusInd = itemView.findViewById(R.id.status_ind);
appName = itemView.findViewById(R.id.app_name);
eta = itemView.findViewById(R.id.eta);
Context context = itemView.getContext();
mStatusOpen = context.getString(R.string.conn_status_open);
mStatusError = context.getString(R.string.error);
mProtoAndPort = context.getString(R.string.proto_and_port);
}
public void bindConn(Context context, ConnectionDescriptor conn, AppsResolver apps, Drawable unknownIcon) {
@@ -86,22 +94,33 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
remote.setText(conn.dst_ip);
if(conn.dst_port != 0)
l7Text = String.format(context.getResources().getString(R.string.proto_and_port), conn.l7proto, conn.dst_port);
l7Text = String.format(mProtoAndPort, conn.l7proto, conn.dst_port);
else
l7Text = conn.l7proto;
if(conn.ipver == 6)
l7Text = l7Text + ", IPv6";
l7proto.setText(l7Text);
String info_txt = (app != null) ? app.getName() : Integer.toString(conn.uid);
appName.setText(info_txt);
traffic.setText(Utils.formatBytes(conn.sent_bytes + conn.rcvd_bytes));
if(conn.closed) {
if(conn.status.equals("CLOSED")) {
eta.setText(Utils.formatEpochShort(context, conn.first_seen));
eta.setVisibility(View.VISIBLE);
statusInd.setVisibility(View.GONE);
} else {
if(conn.status.equals("ERROR")) {
statusInd.setText(mStatusError);
statusInd.setTextColor(0xFFF20015);
} else {
statusInd.setText(mStatusOpen);
statusInd.setTextColor(0xFF28BC36);
}
eta.setVisibility(View.GONE);
statusInd.setVisibility(View.VISIBLE);
}
@@ -24,6 +24,7 @@ import java.io.Serializable;
/* Equivalent of zdtun_conn_t from zdtun and conn_data_t from vpnproxy.c */
public class ConnectionDescriptor implements Serializable {
/* Metadata */
public int ipver;
public int ipproto;
public String src_ip;
public String dst_ip;
@@ -42,16 +43,17 @@ public class ConnectionDescriptor implements Serializable {
public String l7proto;
public int uid;
public int incr_id;
public boolean closed;
public String status;
/* Invoked by native code
* NOTE: interleaving String and int in the parameters is not good as it makes the app crash
* nto the emulator! Better to put the strings first. */
public void setData(String _src_ip, String _dst_ip, String _info, String _url, String _l7proto,
int _ipproto, int _src_port, int _dst_port, long _first_seen, long _last_seen,
public void setData(String _src_ip, String _dst_ip, String _status, String _info, String _url, String _l7proto,
int _ipver, int _ipproto, int _src_port, int _dst_port, long _first_seen, long _last_seen,
long _sent_bytes, long _rcvd_bytes, int _sent_pkts, int _rcvd_pkts, int _uid,
int _incr_id, boolean _closed) {
int _incr_id) {
/* Metadata */
ipver = _ipver;
ipproto = _ipproto;
src_ip = _src_ip;
dst_ip = _dst_ip;
@@ -65,11 +67,11 @@ public class ConnectionDescriptor implements Serializable {
rcvd_bytes = _rcvd_bytes;
sent_pkts = _sent_pkts;
rcvd_pkts = _rcvd_pkts;
status = _status;
info = _info;
url = _url;
l7proto = _l7proto;
uid = _uid;
incr_id = _incr_id;
closed = _closed;
}
}
@@ -35,6 +35,7 @@ public class Prefs {
public static final String PREF_PCAP_DUMP_MODE = "pcap_dump_mode";
public static final String PREF_PCAP_URI = "pcap_path";
public static final String DEFAULT_DUMP_MODE = DUMP_HTTP_SERVER;
public static final String PREF_IPV6_ENABLED = "ipv6_enabled";
public enum DumpMode {
NONE,
@@ -63,4 +64,5 @@ public class Prefs {
public static String getTlsProxyAddress(SharedPreferences p) { return(p.getString(PREF_TLS_PROXY_IP_KEY, "0.0.0.0")); }
public static int getTlsProxyPort(SharedPreferences p) { return(Integer.parseInt(p.getString(Prefs.PREF_TLS_PROXY_PORT_KEY, "8080"))); }
public static String getAppFilter(SharedPreferences p) { return(p.getString(PREF_APP_FILTER, "")); }
public static boolean getIPv6Enabled(SharedPreferences p) { return(p.getBoolean(PREF_IPV6_ENABLED, false)); }
}
+49 -17
View File
@@ -75,7 +75,8 @@ static jint get_uid_proc(int ipver, int ipproto, const char *conn_shex,
if(!lines++)
continue;
// Search string first, only then use slower scanf
//log_android(ANDROID_LOG_INFO, "[try] %s", line);
if(sscanf(line, fmt, shex, &sport, dhex, &dport, &uid) == 5) {
//log_android(ANDROID_LOG_DEBUG, "[try] %s:%d -> %s:%d [%d]", shex, sport, dhex, dport, uid);
@@ -96,7 +97,24 @@ static jint get_uid_proc(int ipver, int ipproto, const char *conn_shex,
/* ******************************************************* */
// TODO support ipv6
#if 0
static char* tohex(const uint8_t *src, int srcsize, char *dst, int dstsize) {
static const char *hex = "0123456789ABCDEF";
int j = 0;
for(int i=0; (i < srcsize) && (j+2 < dstsize); i++) {
dst[j++] = hex[(src[i] >> 4)];
dst[j++] = hex[(src[i] & 0x0F)];
}
dst[j] = '\0';
return dst;
}
#endif
/* ******************************************************* */
static jint get_uid_slow(const zdtun_5tuple_t *conn_info) {
char shex[33], dhex[33];
jint rv;
@@ -106,16 +124,28 @@ static jint get_uid_slow(const zdtun_5tuple_t *conn_info) {
u_int16_t sport = ntohs(conn_info->src_port);
u_int16_t dport = ntohs(conn_info->dst_port);
sprintf(shex, "%08X", conn_info->src_ip);
sprintf(dhex, "%08X", conn_info->dst_ip);
if(conn_info->ipver == 4) {
sprintf(shex, "%08X", conn_info->src_ip.ip4);
sprintf(dhex, "%08X", conn_info->dst_ip.ip4);
rv = get_uid_proc(4, conn_info->ipproto, shex, dhex, sport, dport);
rv = get_uid_proc(4, conn_info->ipproto, shex, dhex, sport, dport);
if (rv == UID_UNKNOWN) {
// Search for IPv4-mapped IPv6 addresses
// https://tools.ietf.org/html/rfc3493#section-3.7
sprintf(shex, "0000000000000000FFFF0000%08X", conn_info->src_ip);
sprintf(dhex, "0000000000000000FFFF0000%08X", conn_info->dst_ip);
if (rv == UID_UNKNOWN) {
// Search for IPv4-mapped IPv6 addresses
// https://tools.ietf.org/html/rfc3493#section-3.7
sprintf(shex, "0000000000000000FFFF0000%08X", conn_info->src_ip.ip4);
sprintf(dhex, "0000000000000000FFFF0000%08X", conn_info->dst_ip.ip4);
rv = get_uid_proc(6, conn_info->ipproto, shex, dhex, sport, dport);
}
} else {
const uint32_t *src = conn_info->src_ip.ip6.in6_u.u6_addr32;
const uint32_t *dst = conn_info->dst_ip.ip6.in6_u.u6_addr32;
sprintf(shex, "%08X%08X%08X%08X", src[0], src[1], src[2], src[3]);
sprintf(dhex, "%08X%08X%08X%08X", dst[0], dst[1], dst[2], dst[3]);
//log_android(ANDROID_LOG_INFO, "HEX %s %s", shex, dhex);
rv = get_uid_proc(6, conn_info->ipproto, shex, dhex, sport, dport);
}
@@ -128,12 +158,14 @@ static jint get_uid_slow(const zdtun_5tuple_t *conn_info) {
/* ******************************************************* */
// TODO support IPv6
static jint get_uid_q(uid_resolver_t *resolver,
const zdtun_5tuple_t *conn_info) {
JNIEnv *env = resolver->env;
jint juid = UID_UNKNOWN;
int version = 4;
int version = conn_info->ipver;
int family = (version == 4) ? AF_INET : AF_INET6;
char srcip[INET6_ADDRSTRLEN];
char dstip[INET6_ADDRSTRLEN];
// getUidQ only works for TCP/UDP connections
if((conn_info->ipproto != IPPROTO_TCP) && (conn_info->ipproto != IPPROTO_UDP))
@@ -151,12 +183,12 @@ static jint get_uid_q(uid_resolver_t *resolver,
u_int16_t sport = ntohs(conn_info->src_port);
u_int16_t dport = ntohs(conn_info->dst_port);
struct in_addr addr;
addr.s_addr = conn_info->src_ip;
jstring jsource = (*env)->NewStringUTF(env, inet_ntoa(addr));
addr.s_addr = conn_info->dst_ip;
jstring jdest = (*env)->NewStringUTF(env, inet_ntoa(addr));
inet_ntop(family, &conn_info->src_ip, srcip, sizeof(srcip));
inet_ntop(family, &conn_info->dst_ip, dstip, sizeof(dstip));
jstring jsource = (*env)->NewStringUTF(env, srcip);
jstring jdest = (*env)->NewStringUTF(env, dstip);
if((jsource != NULL) && (jdest != NULL)) {
juid = (*env)->CallIntMethod(
+130 -69
View File
@@ -17,8 +17,7 @@
* Copyright 2020-21 - Emanuele Faranda
*/
#include <netinet/udp.h>
#include <netinet/ip.h>
#include <ndpi_api.h>
#include <ndpi_typedefs.h>
#include "utils.c"
#include "ndpi_master_protos.c"
@@ -171,7 +170,7 @@ static void conns_clear(conn_array_t *arr, bool free_all) {
for(int i=0; i < arr->cur_items; i++) {
vpn_conn_t *slot = &arr->items[i];
if(slot->data && (slot->data->closed || free_all))
if(slot->data && ((slot->data->status >= CONN_STATUS_CLOSED) || free_all))
free_connection_data(slot->data);
}
@@ -196,7 +195,7 @@ static u_int32_t getIPv4Pref(JNIEnv *env, jobject vpn_inst, const char *key) {
log_android(ANDROID_LOG_DEBUG, "getIPv4Pref(%s) = %s", key, value);
if(inet_aton(value, &addr) == 0)
log_android(ANDROID_LOG_ERROR, "%s() returned invalid address", key);
log_android(ANDROID_LOG_ERROR, "%s() returned invalid IPv4 address", key);
(*env)->ReleaseStringUTFChars(env, obj, value);
}
@@ -208,6 +207,29 @@ static u_int32_t getIPv4Pref(JNIEnv *env, jobject vpn_inst, const char *key) {
/* ******************************************************* */
static struct in6_addr getIPv6Pref(JNIEnv *env, jobject vpn_inst, const char *key) {
struct in6_addr addr = {0};
jmethodID midMethod = jniGetMethodID(env, cls.vpn_service, key, "()Ljava/lang/String;");
jstring obj = (*env)->CallObjectMethod(env, vpn_inst, midMethod);
if(!jniCheckException(env)) {
const char *value = (*env)->GetStringUTFChars(env, obj, 0);
log_android(ANDROID_LOG_DEBUG, "getIPv6Pref(%s) = %s", key, value);
if(inet_pton(AF_INET6, value, &addr) != 1)
log_android(ANDROID_LOG_ERROR, "%s() returned invalid IPv6 address", key);
(*env)->ReleaseStringUTFChars(env, obj, value);
}
(*env)->DeleteLocalRef(env, obj);
return(addr);
}
/* ******************************************************* */
static jint getIntPref(JNIEnv *env, jobject vpn_inst, const char *key) {
jint value;
jmethodID midMethod = jniGetMethodID(env, cls.vpn_service, key, "()I");
@@ -298,9 +320,11 @@ const char *getProtoName(struct ndpi_detection_module_struct *mod, ndpi_protocol
/* ******************************************************* */
static void process_ndpi_packet(conn_data_t *data, vpnproxy_data_t *proxy, const char *packet,
int size, uint8_t from_tap) {
static void process_ndpi_packet(conn_data_t *data, vpnproxy_data_t *proxy, const zdtun_conn_t *conn_info,
const char *packet, int size, uint8_t from_tap) {
bool giveup = ((data->sent_pkts + data->rcvd_pkts) >= MAX_DPI_PACKETS);
const zdtun_5tuple_t *tuple = zdtun_conn_get_5tuple(conn_info);
giveup = true; // todo fixme
data->l7proto = ndpi_detection_process_packet(proxy->ndpi, data->ndpi_flow, (const u_char *)packet,
size, data->last_seen,
@@ -309,7 +333,7 @@ static void process_ndpi_packet(conn_data_t *data, vpnproxy_data_t *proxy, const
if(giveup || ((data->l7proto.app_protocol != NDPI_PROTOCOL_UNKNOWN) &&
(!ndpi_extra_dissection_possible(proxy->ndpi, data->ndpi_flow)))) {
if (data->l7proto.app_protocol == NDPI_PROTOCOL_UNKNOWN) {
if(data->l7proto.app_protocol == NDPI_PROTOCOL_UNKNOWN) {
uint8_t proto_guessed;
data->l7proto = ndpi_detection_giveup(proxy->ndpi, data->ndpi_flow, 1 /* Guess */,
@@ -319,8 +343,8 @@ static void process_ndpi_packet(conn_data_t *data, vpnproxy_data_t *proxy, const
if(data->l7proto.master_protocol == 0)
data->l7proto.master_protocol = data->l7proto.app_protocol;
log_android(ANDROID_LOG_DEBUG, "l7proto: app=%d, master=%d",
data->l7proto.app_protocol, data->l7proto.master_protocol);
log_android(ANDROID_LOG_DEBUG, "nDPI completed[ipver=%d, proto=%d] -> l7proto: app=%d, master=%d",
tuple->ipver, tuple->ipproto, data->l7proto.app_protocol, data->l7proto.master_protocol);
switch (data->l7proto.master_protocol) {
case NDPI_PROTOCOL_DNS:
@@ -380,7 +404,7 @@ static bool shouldIgnoreConn(vpnproxy_data_t *proxy, const zdtun_5tuple_t *tuple
#endif
// ignore some internal communications, e.g. DNS-over-TLS check on port 853
if((tuple->dst_ip == proxy->vpn_dns) && (ntohs(tuple->dst_port) != 53))
if((tuple->ipver == 4) && (tuple->dst_ip.ip4 == proxy->vpn_dns) && (ntohs(tuple->dst_port) != 53))
return true;
return false;
@@ -417,9 +441,10 @@ static void account_packet(zdtun_t *tun, const char *packet, int size, uint8_t f
}
data->last_seen = time(NULL);
data->status = zdtun_conn_get_status(conn_info);
if(data->ndpi_flow)
process_ndpi_packet(data, proxy, packet, size, from_tap);
process_ndpi_packet(data, proxy, conn_info, packet, size, from_tap);
if(shouldIgnoreConn(proxy, zdtun_conn_get_5tuple(conn_info), data)) {
//log_android(ANDROID_LOG_DEBUG, "Ignoring connection: UID=%d [filter=%d]", data->uid, proxy->uid_filter);
@@ -559,7 +584,7 @@ static void destroy_connection(zdtun_t *tun, const zdtun_conn_t *conn_info) {
/* Will free the other data in sendConnectionsDump */
free_ndpi(data);
data->closed = true;
data->status = zdtun_conn_get_status(conn_info);
if(!data->pending_notification && !shouldIgnoreConn(proxy, zdtun_conn_get_5tuple(conn_info), data)) {
// Send last notification
@@ -572,32 +597,10 @@ static void destroy_connection(zdtun_t *tun, const zdtun_conn_t *conn_info) {
/*
* If the packet contains a DNS request then rewrite server address
* with public DNS server.
* with public DNS server. Non UDP DNS connections are dropped to block DoH queries which do not
* allow us to extract the requested domain name.
*/
static int check_dns_req_dnat(struct vpnproxy_data *proxy, zdtun_pkt_t *pkt, zdtun_conn_t *conn) {
struct dns_packet *dns_data;
int dns_length;
// TODO support TCP
if(pkt->tuple.ipproto != IPPROTO_UDP)
return(0);
dns_length = pkt->l7_len;
dns_data = (struct dns_packet*) pkt->l7;
if(dns_length < sizeof(struct dns_packet))
return(0);
if((ntohs(pkt->udp->uh_dport) != 53) ||
(pkt->tuple.dst_ip != proxy->vpn_dns) || (pkt->tuple.src_ip != proxy->vpn_ipv4))
// Not VPN DNS
return(0);
if((dns_data->flags & DNS_FLAGS_MASK) != DNS_TYPE_REQUEST)
return(0);
log_android(ANDROID_LOG_DEBUG, "Detected DNS query[%u]", dns_length);
static bool check_dns_req_allowed(struct vpnproxy_data *proxy, zdtun_pkt_t *pkt, zdtun_conn_t *conn) {
if(new_dns_server != 0) {
// Reload DNS server
proxy->dns_server = new_dns_server;
@@ -606,14 +609,42 @@ static int check_dns_req_dnat(struct vpnproxy_data *proxy, zdtun_pkt_t *pkt, zdt
log_android(ANDROID_LOG_DEBUG, "Using new DNS server");
}
/*
* Direct the packet to the public DNS server. Checksum recalculation is not strictly necessary
* here as zdtun will proxy the connection.
*/
zdtun_conn_dnat(conn, proxy->dns_server, 0);
proxy->num_dns_requests++;
bool is_dns_server = ((pkt->tuple.ipver == 4) && (pkt->tuple.dst_ip.ip4 == proxy->vpn_dns))
|| ((pkt->tuple.ipver == 6) && (memcmp(&pkt->tuple.dst_ip.ip6, &proxy->ipv6.dns_server, 16) == 0));
return(1);
if(!is_dns_server)
return(true);
if((pkt->tuple.ipproto == IPPROTO_UDP) && (ntohs(pkt->udp->uh_dport) == 53)) {
int dns_length = pkt->l7_len;
if(dns_length >= sizeof(struct dns_packet)) {
struct dns_packet *dns_data = (struct dns_packet*) pkt->l7;
if((dns_data->flags & DNS_FLAGS_MASK) != DNS_TYPE_REQUEST)
return(true);
log_android(ANDROID_LOG_DEBUG, "Detected DNS query[%u]", dns_length);
proxy->num_dns_requests++;
if(pkt->tuple.ipver == 4) {
/*
* Direct the packet to the public DNS server. Checksum recalculation is not strictly necessary
* here as zdtun will proxy the connection.
*/
zdtun_ip_t dnatip = {0};
dnatip.ip4 = proxy->dns_server;
zdtun_conn_dnat(conn, &dnatip, pkt->udp->uh_dport);
}
return(true);
}
}
log_android(ANDROID_LOG_DEBUG, "blocking packet directed to the DNS server");
// block everything else (e.g. DoH)
return(false);
}
/* ******************************************************* */
@@ -637,21 +668,24 @@ static void check_tls_mitm(zdtun_t *tun, struct vpnproxy_data *proxy, zdtun_pkt_
uint16_t port = ntohs(pkt->tuple.dst_port);
if (port == 443) {
zdtun_conn_dnat(conn, mitm_ip, mitm_port);
zdtun_ip_t dnatip = {0};
dnatip.ip4 = mitm_ip;
zdtun_conn_dnat(conn, &dnatip, mitm_port);
data->mitm_header_needed = true;
}
} else if(data->mitm_header_needed && (pkt->l7_len > 0)) {
} else if(data->mitm_header_needed && (pkt->l7_len > 0) && (pkt->tuple.ipver == 4)) {
// TODO allow IPv6 -> IPv4 redirection
/* First L7 packet, send the mitmproxy header first */
mitm_proxy_hdr_t *mitm = (mitm_proxy_hdr_t*) mitm_pkt.l7;
/* Fix the packet with the correct peers */
mitm_pkt.tuple.src_ip = mitm_pkt.ip->saddr = pkt->ip->saddr;
mitm_pkt.tuple.dst_ip = mitm_pkt.ip->daddr = mitm_ip;
mitm_pkt.tuple.src_ip.ip4 = mitm_pkt.ip4->saddr = pkt->ip4->saddr;
mitm_pkt.tuple.dst_ip.ip4 = mitm_pkt.ip4->daddr = mitm_ip;
mitm_pkt.tuple.src_port = mitm_pkt.tcp->th_sport = pkt->tcp->th_sport;
mitm_pkt.tuple.dst_port = mitm_pkt.tcp->th_dport = mitm_port;
/* Send the original (pre-nat) IP and port */
mitm->dst_ip = pkt->tuple.dst_ip;
mitm->dst_ip = pkt->tuple.dst_ip.ip4;
mitm->dst_port = pkt->tuple.dst_port;
zdtun_send_oob(tun, &mitm_pkt, conn);
@@ -697,17 +731,16 @@ static int net2tap(zdtun_t *tun, char *pkt_buf, int pkt_size, const zdtun_conn_t
/* ******************************************************* */
static int dumpConnection(vpnproxy_data_t *proxy, const vpn_conn_t *conn, jobject arr, int idx) {
char srcip[64], dstip[64];
char srcip[INET6_ADDRSTRLEN], dstip[INET6_ADDRSTRLEN];
struct in_addr addr;
JNIEnv *env = proxy->env;
const zdtun_5tuple_t *conn_info = &conn->tuple;
const conn_data_t *data = conn->data;
int rv = 0;
int family = (conn->tuple.ipver == 4) ? AF_INET : AF_INET6;
addr.s_addr = conn_info->src_ip;
strncpy(srcip, inet_ntoa(addr), sizeof(srcip));
addr.s_addr = conn_info->dst_ip;
strncpy(dstip, inet_ntoa(addr), sizeof(dstip));
inet_ntop(family, &conn_info->src_ip, srcip, sizeof(srcip));
inet_ntop(family, &conn_info->dst_ip, dstip, sizeof(dstip));
#if 0
log_android(ANDROID_LOG_INFO, "DUMP: [proto=%d]: %s:%u -> %s:%u [%d]",
@@ -717,6 +750,7 @@ static int dumpConnection(vpnproxy_data_t *proxy, const vpn_conn_t *conn, jobjec
data->uid);
#endif
jobject status_string = (*env)->NewStringUTF(env, zdtun_conn_status2str(data->status));
jobject info_string = (*env)->NewStringUTF(env, data->info ? data->info : "");
jobject url_string = (*env)->NewStringUTF(env, data->url ? data->url : "");
jobject proto_string = (*env)->NewStringUTF(env, getProtoName(proxy->ndpi, data->l7proto, conn_info->ipproto));
@@ -728,12 +762,12 @@ static int dumpConnection(vpnproxy_data_t *proxy, const vpn_conn_t *conn, jobjec
/* NOTE: as an alternative to pass all the params into the constructor, GetFieldID and
* SetIntField like methods could be used. */
(*env)->CallVoidMethod(env, conn_descriptor, mids.connSetData,
src_string, dst_string, info_string, url_string, proto_string,
conn_info->ipproto, ntohs(conn_info->src_port),
src_string, dst_string, status_string, info_string, url_string, proto_string,
conn_info->ipver, conn_info->ipproto, ntohs(conn_info->src_port),
ntohs(conn_info->dst_port),
data->first_seen, data->last_seen, data->sent_bytes,
data->rcvd_bytes, data->sent_pkts,
data->rcvd_pkts, data->uid, data->incr_id, data->closed);
data->rcvd_pkts, data->uid, data->incr_id);
if(jniCheckException(env))
rv = -1;
else {
@@ -750,6 +784,7 @@ static int dumpConnection(vpnproxy_data_t *proxy, const vpn_conn_t *conn, jobjec
rv = -1;
}
(*env)->DeleteLocalRef(env, status_string);
(*env)->DeleteLocalRef(env, info_string);
(*env)->DeleteLocalRef(env, url_string);
(*env)->DeleteLocalRef(env, proto_string);
@@ -912,7 +947,7 @@ static int run_tun(JNIEnv *env, jclass vpn, int tapfd, jint sdk) {
mids.connInit = jniGetMethodID(env, cls.conn, "<init>", "()V");
mids.connSetData = jniGetMethodID(env, cls.conn, "setData",
/* NOTE: must match ConnectionDescriptor::setData */
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIJJJJIIIIZ)V");
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIIJJJJIIII)V");
mids.statsInit = jniGetMethodID(env, cls.stats, "<init>", "()V");
mids.statsSetData = jniGetMethodID(env, cls.stats, "setData", "(JJIIIIIIII)V");
@@ -939,6 +974,10 @@ static int run_tun(JNIEnv *env, jclass vpn, int tapfd, jint sdk) {
.enabled = (bool) getIntPref(env, vpn, "getTlsDecryptionEnabled"),
.proxy_ip = getIPv4Pref(env, vpn, "getTlsProxyAddress"),
.proxy_port = htons(getIntPref(env, vpn, "getTlsProxyPort")),
},
.ipv6 = {
.enabled = (bool) getIntPref(env, vpn, "getIPv6Enabled"),
.dns_server = getIPv6Pref(env, vpn, "getIpv6DnsServer"),
}
};
@@ -1046,27 +1085,49 @@ static int run_tun(JNIEnv *env, jclass vpn, int tapfd, jint sdk) {
goto housekeeping;
}
zdtun_conn_t *conn = zdtun_lookup(tun, &pkt.tuple, 1 /* create if not exists */);
if((pkt.tuple.ipver == 6) && (!proxy.ipv6.enabled)) {
char buf[512];
log_android(ANDROID_LOG_DEBUG, "ignoring IPv6 packet: %s",
zdtun_5tuple2str(&pkt.tuple, buf, sizeof(buf)));
goto housekeeping;
}
// Skip established TCP connections
uint8_t is_tcp_established = ((pkt.tuple.ipproto == IPPROTO_TCP) &&
(!(pkt.tcp->th_flags & TH_SYN) || (pkt.tcp->th_flags & TH_ACK)));
zdtun_conn_t *conn = zdtun_lookup(tun, &pkt.tuple, !is_tcp_established);
if (!conn) {
proxy.num_dropped_connections++;
log_android(ANDROID_LOG_ERROR, "zdtun_lookup failed");
if(!is_tcp_established) {
char buf[512];
proxy.num_dropped_connections++;
log_android(ANDROID_LOG_ERROR, "zdtun_lookup failed: %s",
zdtun_5tuple2str(&pkt.tuple, buf, sizeof(buf)));
} else {
char buf[512];
log_android(ANDROID_LOG_DEBUG, "skipping established TCP: %s",
zdtun_5tuple2str(&pkt.tuple, buf, sizeof(buf)));
}
goto housekeeping;
}
if((check_dns_req_dnat(&proxy, &pkt, conn) == 0)
&& (pkt.tuple.dst_ip == proxy.vpn_dns)) {
log_android(ANDROID_LOG_DEBUG, "ignoring packet directed to the virtual DNS server");
if(!check_dns_req_allowed(&proxy, &pkt, conn))
goto housekeeping;
}
if(proxy.tls_decryption.enabled)
check_tls_mitm(tun, &proxy, &pkt, conn);
if ((rc = zdtun_forward(tun, &pkt, conn)) != 0) {
log_android(ANDROID_LOG_ERROR, "zdtun_forward (proto=%d) failed with code %d", pkt.tuple.ipproto, rc);
proxy.num_dropped_connections++;
if((rc = zdtun_forward(tun, &pkt, conn)) != 0) {
char buf[512];
log_android(ANDROID_LOG_ERROR, "zdtun_forward failed: %s",
zdtun_5tuple2str(&pkt.tuple, buf, sizeof(buf)));
proxy.num_dropped_connections++;
zdtun_destroy_conn(tun, conn);
goto housekeeping;
}
@@ -1166,4 +1227,4 @@ Java_com_emanuelef_remote_1capture_CaptureService_setDnsServer(JNIEnv *env, jcla
if(inet_aton(value, &addr) != 0)
new_dns_server = addr.s_addr;
}
}
+7 -2
View File
@@ -20,7 +20,7 @@
#include <jni.h>
#include "zdtun.h"
#include "uid_resolver.h"
#include "ndpi_api.h"
#include <ndpi_api.h>
#ifndef REMOTE_CAPTURE_VPNPROXY_H
#define REMOTE_CAPTURE_VPNPROXY_H
@@ -51,10 +51,10 @@ typedef struct conn_data {
jlong rcvd_bytes;
jint sent_pkts;
jint rcvd_pkts;
zdtun_conn_status_t status;
char *info;
char *url;
jint uid;
bool closed;
bool pending_notification;
bool mitm_header_needed;
} conn_data_t;
@@ -107,6 +107,11 @@ typedef struct vpnproxy_data {
u_int32_t proxy_port;
} tls_decryption;
struct {
bool enabled;
struct in6_addr dns_server;
} ipv6;
capture_stats_t capture_stats;
} vpnproxy_data_t;
+2 -2
View File
@@ -41,11 +41,11 @@
android:id="@+id/status_ind"
android:textSize="12sp"
android:layout_width="wrap_content"
android:textColor="#28BC36"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:text="@string/conn_status_open" />
tools:textColor="#FF28BC36"
tools:text="@string/conn_status_open" />
<TextView
android:id="@+id/eta"
+4
View File
@@ -108,5 +108,9 @@
<string name="get_app">Get it:</string>
<string name="tls_how_to">How to setup TLS Decryption</string>
<string name="unknown_app">Unknown</string>
<string name="vpn_prefs">VPN</string>
<string name="enable_ipv6">Enable IPv6</string>
<string name="enable_ipv6_summary">Allow IPv6 internet connections. This should only be enabled on networks with an IPv6 gateway.</string>
<string name="error">Error</string>
</resources>
@@ -73,4 +73,13 @@
app:defaultValue="@string/default_proxy_port"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/vpn_prefs" app:iconSpaceReserved="false">
<SwitchPreference
app:key="ipv6_enabled"
app:title="@string/enable_ipv6"
app:iconSpaceReserved="false"
app:summary="@string/enable_ipv6_summary"
app:defaultValue="false" />
</PreferenceCategory>
</PreferenceScreen>