Improve connections handling

- Improve netd resolution with root capture
- Fix nDPI detection not performed on some closed connections
- Fix missing blacklist match on some connections
This commit is contained in:
emanuele-f 2021-10-21 18:50:28 +02:00
parent eece3e8cce
commit 47ddb23e4b
4 changed files with 86 additions and 46 deletions

View File

@ -239,6 +239,7 @@ static void destroy_connection(zdtun_t *tun, const zdtun_conn_t *conn_info) {
data->update_type |= CONN_UPDATE_STATS;
notify_connection(&proxy->conns_updates, tuple, data);
conn_end_ndpi_detection(data, proxy, tuple);
data->status = zdtun_conn_get_status(conn_info);
data->to_purge = true;
} else

View File

@ -33,12 +33,12 @@
/* ******************************************************* */
typedef struct {
struct pcap_conn {
zdtun_5tuple_t tuple;
conn_data_t *data;
UT_hash_handle hh;
} pcap_conn_t;
};
/* ******************************************************* */
@ -250,11 +250,32 @@ cleanup:
/* ******************************************************* */
static void remove_connection(vpnproxy_data_t *proxy, const pcap_conn_t *conn) {
switch (conn->tuple.ipproto) {
case IPPROTO_TCP:
proxy->stats.num_tcp_conn--;
break;
case IPPROTO_UDP:
proxy->stats.num_udp_conn--;
break;
case IPPROTO_ICMP:
proxy->stats.num_icmp_conn--;
break;
}
HASH_DELETE(hh, proxy->connections, conn);
free(conn);
}
/* ******************************************************* */
// Determines when a connection gets closed
static void update_connection_status(pcap_conn_t *conn, zdtun_pkt_t *pkt, uint8_t dir) {
static void update_connection_status(vpnproxy_data_t *proxy, pcap_conn_t *conn, zdtun_pkt_t *pkt, uint8_t dir) {
if((conn->data->status >= CONN_STATUS_CLOSED) || (pkt->flags & ZDTUN_PKT_IS_FRAGMENT))
return;
zdtun_conn_status_t old_status = conn->data->status;
if(conn->tuple.ipproto == IPPROTO_TCP) {
struct tcphdr *tcp = pkt->tcp;
@ -294,16 +315,28 @@ static void update_connection_status(pcap_conn_t *conn, zdtun_pkt_t *pkt, uint8_
conn->data->pending_dns_queries--;
// Close the connection as soon as all the responses arrive
if(conn->data->pending_dns_queries == 0)
if(conn->data->pending_dns_queries == 0) {
conn->data->status = CONN_STATUS_CLOSED;
// Remove the connection from the hash to ensure that if the DNS connection is
// reused for a new query, it will generated a new connection, to properly
// extract and handle the new DNS query. This also happens for AAAA + A queries.
conn->data->to_purge = true;
remove_connection(proxy, conn);
}
}
}
}
if(old_status != conn->data->status) {
conn->data->update_type |= CONN_UPDATE_STATS;
notify_connection(&proxy->conns_updates, &conn->tuple, conn->data);
}
}
/* ******************************************************* */
static void handle_packet(vpnproxy_data_t *proxy, pcap_conn_t **connections, pcapd_hdr_t *hdr, const char *buffer) {
static void handle_packet(vpnproxy_data_t *proxy, pcapd_hdr_t *hdr, const char *buffer) {
zdtun_pkt_t pkt;
pcap_conn_t *conn = NULL;
uint8_t from_tun = (hdr->flags & PCAPD_FLAG_TX); // NOTE: the direction uses an heuristic so it may be wrong
@ -329,14 +362,14 @@ static void handle_packet(vpnproxy_data_t *proxy, pcap_conn_t **connections, pca
tupleSwapPeers(&pkt.tuple);
}
HASH_FIND(hh, *connections, &pkt.tuple, sizeof(zdtun_5tuple_t), conn);
HASH_FIND(hh, proxy->connections, &pkt.tuple, sizeof(zdtun_5tuple_t), conn);
if(!conn) {
// from_tun may be wrong, search in the other direction
from_tun = !from_tun;
tupleSwapPeers(&pkt.tuple);
HASH_FIND(hh, *connections, &pkt.tuple, sizeof(zdtun_5tuple_t), conn);
HASH_FIND(hh, proxy->connections, &pkt.tuple, sizeof(zdtun_5tuple_t), conn);
if(!conn) {
if((pkt.flags & ZDTUN_PKT_IS_FRAGMENT) && !(pkt.flags & ZDTUN_PKT_IS_FIRST_FRAGMENT)) {
@ -365,7 +398,7 @@ static void handle_packet(vpnproxy_data_t *proxy, pcap_conn_t **connections, pca
conn->tuple = pkt.tuple;
conn->data = data;
HASH_ADD(hh, *connections, tuple, sizeof(zdtun_5tuple_t), conn);
HASH_ADD(hh, proxy->connections, tuple, sizeof(zdtun_5tuple_t), conn);
data->incr_id = proxy->incr_id++;
notify_connection(&proxy->new_conns, &pkt.tuple, data);
@ -393,15 +426,15 @@ static void handle_packet(vpnproxy_data_t *proxy, pcap_conn_t **connections, pca
uint64_t pkt_ms = (uint64_t)hdr->ts.tv_sec * 1000 + hdr->ts.tv_usec / 1000;
account_packet(proxy, &pkt, from_tun, &conn->tuple, conn->data, pkt_ms);
update_connection_status(conn, &pkt, !from_tun);
update_connection_status(proxy, conn, &pkt, !from_tun);
}
/* ******************************************************* */
static void purge_expired_connections(vpnproxy_data_t *proxy, pcap_conn_t **connections, uint8_t purge_all) {
static void purge_expired_connections(vpnproxy_data_t *proxy, uint8_t purge_all) {
pcap_conn_t *conn, *tmp;
HASH_ITER(hh, *connections, conn, tmp) {
HASH_ITER(hh, proxy->connections, conn, tmp) {
uint64_t timeout = 0;
switch(conn->tuple.ipproto) {
@ -433,20 +466,7 @@ static void purge_expired_connections(vpnproxy_data_t *proxy, pcap_conn_t **conn
} else
conn_free_data(conn->data);
switch (conn->tuple.ipproto) {
case IPPROTO_TCP:
proxy->stats.num_tcp_conn--;
break;
case IPPROTO_UDP:
proxy->stats.num_udp_conn--;
break;
case IPPROTO_ICMP:
proxy->stats.num_icmp_conn--;
break;
}
HASH_DELETE(hh, *connections, conn);
free(conn);
remove_connection(proxy, conn);
}
}
}
@ -457,7 +477,6 @@ int run_root(vpnproxy_data_t *proxy) {
int sock = -1;
int rv = -1;
char buffer[65535];
pcap_conn_t *connections = NULL;
u_int64_t next_purge_ms;
zdtun_callbacks_t callbacks = {.send_client = (void*)1};
@ -509,13 +528,13 @@ int run_root(vpnproxy_data_t *proxy) {
}
proxy->num_dropped_pkts = hdr.pkt_drops;
handle_packet(proxy, &connections, &hdr, buffer);
handle_packet(proxy, &hdr, buffer);
housekeeping:
run_housekeeping(proxy);
if(proxy->now_ms >= next_purge_ms) {
purge_expired_connections(proxy, &connections, 0);
purge_expired_connections(proxy, 0);
next_purge_ms = proxy->now_ms + PERIODIC_PURGE_TIMEOUT_MS;
}
}
@ -523,7 +542,7 @@ int run_root(vpnproxy_data_t *proxy) {
rv = 0;
cleanup:
purge_expired_connections(proxy, &connections, 1 /* purge_all */);
purge_expired_connections(proxy, 1 /* purge_all */);
if(proxy->tun) zdtun_finalize(proxy->tun);
if(sock > 0) close(sock);

View File

@ -84,6 +84,11 @@ void conn_free_data(conn_data_t *data) {
/* ******************************************************* */
void notify_connection(conn_array_t *arr, const zdtun_5tuple_t *tuple, conn_data_t *data) {
// End the detection when the connection is closed
// Always check this, even pending_notification are present
if(data->status >= CONN_STATUS_CLOSED)
conn_end_ndpi_detection(data, global_proxy, tuple);
if(data->pending_notification)
return;
@ -294,6 +299,21 @@ const char *getProtoName(struct ndpi_detection_module_struct *mod, ndpi_protocol
/* ******************************************************* */
static bool check_blacklisted_domain(vpnproxy_data_t *proxy, conn_data_t *data, const zdtun_5tuple_t *tuple) {
if(proxy->malware_detection.bl && data->info && data->info[0] && !data->blacklisted_domain) {
data->blacklisted_domain = blacklist_match_domain(proxy->malware_detection.bl, data->info);
if(data->blacklisted_domain) {
char appbuf[64];
char buf[512];
get_appname_by_uid(proxy, data->uid, appbuf, sizeof(appbuf));
log_w("Blacklisted domain [%s]: %s [%s]", data->info, zdtun_5tuple2str(tuple, buf, sizeof(buf)), appbuf);
}
}
}
/* ******************************************************* */
conn_data_t* new_connection(vpnproxy_data_t *proxy, const zdtun_5tuple_t *tuple, int uid) {
conn_data_t *data = calloc(1, sizeof(conn_data_t));
@ -361,6 +381,8 @@ conn_data_t* new_connection(vpnproxy_data_t *proxy, const zdtun_5tuple_t *tuple,
}
}
}
check_blacklisted_domain(proxy, data, tuple);
}
if(proxy->malware_detection.bl && (tuple->ipver == 4)) {
@ -444,17 +466,8 @@ void conn_end_ndpi_detection(conn_data_t *data, vpnproxy_data_t *proxy, const zd
break;
}
if(proxy->malware_detection.bl && data->info && data->info[0] && !data->blacklisted_domain) {
// TODO early match
data->blacklisted_domain = blacklist_match_domain(proxy->malware_detection.bl, data->info);
if(data->blacklisted_domain) {
char appbuf[64];
char buf[512];
get_appname_by_uid(proxy, data->uid, appbuf, sizeof(appbuf));
log_w("Blacklisted domain [%s]: %s [%s]", data->info, zdtun_5tuple2str(tuple, buf, sizeof(buf)), appbuf);
}
}
// TODO early match
check_blacklisted_domain(proxy, data, tuple);
data->update_type |= CONN_UPDATE_INFO;
conn_free_ndpi(data);
@ -599,12 +612,13 @@ static void process_ndpi_packet(conn_data_t *data, vpnproxy_data_t *proxy,
if((!data->request_done) && !data->ndpi_flow->packet.tcp_retransmission)
process_request_data(data, pkt, from_tun);
if(!from_tun && ((data->l7proto.master_protocol == NDPI_PROTOCOL_DNS)
|| (data->l7proto.app_protocol == NDPI_PROTOCOL_DNS)))
bool is_dns = ((data->l7proto.master_protocol == NDPI_PROTOCOL_DNS) ||
(data->l7proto.app_protocol == NDPI_PROTOCOL_DNS));
if(!from_tun && is_dns)
process_dns_reply(data, proxy, pkt);
if(giveup || ((data->l7proto.app_protocol != NDPI_PROTOCOL_UNKNOWN) &&
(!ndpi_extra_dissection_possible(proxy->ndpi, data->ndpi_flow))))
!ndpi_extra_dissection_possible(proxy->ndpi, data->ndpi_flow)))
conn_end_ndpi_detection(data, proxy, &pkt->tuple);
}
@ -1045,10 +1059,10 @@ void account_packet(vpnproxy_data_t *proxy, const zdtun_pkt_t *pkt, uint8_t from
// nDPI cannot handle fragments, since they miss the L4 layer (see ndpi_iph_is_valid_and_not_fragmented)
process_ndpi_packet(data, proxy, pkt, from_tun);
if((data->l7proto.master_protocol == NDPI_PROTOCOL_DNS)
if(((data->l7proto.master_protocol == NDPI_PROTOCOL_DNS) || (data->l7proto.app_protocol == NDPI_PROTOCOL_DNS))
&& (data->uid == UID_NETD)
&& (data->sent_pkts + data->rcvd_pkts == 1)
&& ((netd_resolve_waiting > 0) || (next_connections_dump > (proxy->now_ms - NETD_RESOLVE_DELAY_MS)))) {
&& ((netd_resolve_waiting > 0) || ((next_connections_dump - NETD_RESOLVE_DELAY_MS) < proxy->now_ms))) {
if(netd_resolve_waiting == 0) {
// Wait before sending the dump to possibly resolve netd DNS connections uid.
// Only delay for the first DNS request, to avoid excessive delay.
@ -1181,7 +1195,10 @@ static int run_tun(JNIEnv *env, jclass vpn, int tunfd, jint sdk) {
}
proxy.malware_detection.bl = bl;
// the CaptureService will ask for reload via reload_blacklists_now
if(reload_blacklists_now) {
reload_blacklists_now = false;
reload_blacklists(&proxy);
}
}
signal(SIGPIPE, SIG_IGN);

View File

@ -107,6 +107,8 @@ typedef struct {
UT_hash_handle hh;
} uid_to_app_t;
typedef struct pcap_conn pcap_conn_t;
typedef struct vpnproxy_data {
int tunfd;
int incr_id;
@ -139,6 +141,7 @@ typedef struct vpnproxy_data {
bool root_capture;
zdtun_statistics_t stats;
uid_to_app_t *uid2app;
pcap_conn_t *connections; // root only
struct {
bool enabled;