nfqws: tls client hello reassemble

This commit is contained in:
bol-van
2023-11-15 19:36:34 +03:00
parent f25f1f104b
commit a9a4cd5cb4
23 changed files with 377 additions and 149 deletions

View File

@@ -25,9 +25,23 @@ static void connswap(const t_conn *c, t_conn *c2)
c2->dport = c->sport;
}
void ConntrackClearHostname(t_ctrack *track)
{
if (track->hostname)
{
free(track->hostname);
track->hostname = NULL;
}
}
static void ConntrackClearTrack(t_ctrack *track)
{
ConntrackClearHostname(track);
ReasmClear(&track->reasm_orig);
}
static void ConntrackFreeElem(t_conntrack_pool *elem)
{
if (elem->track.hostname) free(elem->track.hostname);
ConntrackClearTrack(&elem->track);
free(elem);
}
@@ -309,3 +323,36 @@ void ConntrackPoolDump(const t_conntrack *p)
t->track.req_retrans_counter, t->track.b_cutoff, t->track.b_wssize_cutoff, t->track.b_desync_cutoff, t->track.hostname, ConntrackProtoName(t->track.l7proto));
};
}
void ReasmClear(t_reassemble *reasm)
{
if (reasm->packet)
{
free(reasm->packet);
reasm->packet = NULL;
}
reasm->size = reasm->size_present = 0;
}
bool ReasmInit(t_reassemble *reasm, size_t size_requested, uint32_t seq_start)
{
reasm->packet = malloc(size_requested);
if (!reasm->packet) return false;
reasm->size = size_requested;
reasm->size_present = 0;
reasm->seq = seq_start;
return true;
}
bool ReasmFeed(t_reassemble *reasm, uint32_t seq, const void *payload, size_t len)
{
if (reasm->seq!=seq) return false; // fail session if out of sequence
size_t szcopy;
szcopy = reasm->size - reasm->size_present;
if (len<szcopy) szcopy = len;
memcpy(reasm->packet + reasm->size_present, payload, szcopy);
reasm->size_present += szcopy;
reasm->seq += (uint32_t)szcopy;
return true;
}

View File

@@ -34,6 +34,14 @@ typedef struct
uint8_t l4proto; // IPPROTO_TCP, IPPROTO_UDP
} t_conn;
// this structure helps to reassemble continuous packets streams. it does not support out-of-orders
typedef struct {
uint8_t *packet; // allocated for size during reassemble request. requestor must know the message size.
uint32_t seq; // current seq number. if a packet comes with an unexpected seq - it fails reassemble session.
size_t size; // expected message size. success means that we have received exactly 'size' bytes and have them in 'packet'
size_t size_present; // how many bytes already stored in 'packet'
} t_reassemble;
// SYN - SYN or SYN/ACK received
// ESTABLISHED - any except SYN or SYN/ACK received
// FIN - FIN or RST received
@@ -55,11 +63,14 @@ typedef struct
uint8_t scale_orig, scale_reply; // last seen window scale factor. SCALE_NONE if none
uint8_t req_retrans_counter; // number of request retransmissions
uint32_t req_seq; // sequence number of the request (to track retransmissions)
bool req_seq_start_present, req_seq_present;
uint32_t req_seq_start,req_seq_end; // sequence interval of the request (to track retransmissions)
bool b_cutoff; // mark for deletion
bool b_wssize_cutoff, b_desync_cutoff;
t_reassemble reasm_orig;
t_l7proto l7proto;
char *hostname;
} t_ctrack;
@@ -85,3 +96,11 @@ bool ConntrackPoolDrop(t_conntrack *p, const struct ip *ip, const struct ip6_hdr
void CaonntrackExtractConn(t_conn *c, bool bReverse, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr);
void ConntrackPoolDump(const t_conntrack *p);
void ConntrackPoolPurge(t_conntrack *p);
void ConntrackClearHostname(t_ctrack *track);
bool ReasmInit(t_reassemble *reasm, size_t size_requested, uint32_t seq_start);
void ReasmClear(t_reassemble *reasm);
// false means reassemble session has failed and we should ReasmClear() it
bool ReasmFeed(t_reassemble *reasm, uint32_t seq, const void *payload, size_t len);
inline static bool ReasmIsEmpty(t_reassemble *reasm) {return !reasm->size;}
inline static bool ReasmIsFull(t_reassemble *reasm) {return !ReasmIsEmpty(reasm) && (reasm->size==reasm->size_present);}

View File

@@ -155,13 +155,14 @@ static void maybe_cutoff(t_ctrack *ctrack, uint8_t proto)
ctrack->b_wssize_cutoff |= cutoff_test(ctrack, params.wssize_cutoff, params.wssize_cutoff_mode);
ctrack->b_desync_cutoff |= cutoff_test(ctrack, params.desync_cutoff, params.desync_cutoff_mode);
// we do not need conntrack entry anymore if all cutoff conditions are either not defined or reached
// do not drop udp entry because it will be recreated when next packet arrives
if (proto==IPPROTO_TCP)
// we do not need conntrack entry anymore if all cutoff conditions are either not defined or reached
// do not drop udp entry because it will be recreated when next packet arrives
ctrack->b_cutoff |= \
(!params.wssize || ctrack->b_wssize_cutoff) &&
(!params.desync_cutoff || ctrack->b_desync_cutoff) &&
(!*params.hostlist_auto_filename || ctrack->req_retrans_counter==RETRANS_COUNTER_STOP);
(!*params.hostlist_auto_filename || ctrack->req_retrans_counter==RETRANS_COUNTER_STOP) &&
ReasmIsEmpty(&ctrack->reasm_orig);
}
}
static void wssize_cutoff(t_ctrack *ctrack)
@@ -172,8 +173,16 @@ static void wssize_cutoff(t_ctrack *ctrack)
maybe_cutoff(ctrack, IPPROTO_TCP);
}
}
static void forced_wssize_cutoff(t_ctrack *ctrack)
{
if (ctrack && params.wssize && !ctrack->b_wssize_cutoff)
{
DLOG("forced wssize-cutoff\n");
wssize_cutoff(ctrack);
}
}
static void ctrack_stop_req_counter(t_ctrack *ctrack)
static void ctrack_stop_retrans_counter(t_ctrack *ctrack)
{
if (ctrack && *params.hostlist_auto_filename)
{
@@ -187,24 +196,26 @@ static bool auto_hostlist_retrans(t_ctrack *ctrack, uint8_t l4proto, int thresho
{
if (*params.hostlist_auto_filename && ctrack && ctrack->req_retrans_counter!=RETRANS_COUNTER_STOP)
{
ctrack->req_retrans_counter++;
DLOG("req retrans counter : %u/%u\n",ctrack->req_retrans_counter, threshold);
if (ctrack->req_retrans_counter >= threshold)
{
DLOG("req retrans threshold reached\n");
ctrack_stop_req_counter(ctrack);
return true;
}
if (l4proto==IPPROTO_TCP)
{
if (!ctrack->req_seq) ctrack->req_seq = ctrack->seq_last;
if (ctrack->seq_last != ctrack->req_seq)
{
DLOG("another request, not retransmission. stop tracking.\n");
ctrack_stop_req_counter(ctrack);
return false;
}
if (!ctrack->req_seq_present)
return false;
if (!seq_within(ctrack->seq_last, ctrack->req_seq_start, ctrack->req_seq_end))
{
DLOG("req retrans : tcp seq %u not within the req range %u-%u. stop tracking.\n", ctrack->seq_last, ctrack->req_seq_start, ctrack->req_seq_end);
ctrack_stop_retrans_counter(ctrack);
ctrack->req_seq_present = false;
return false;
}
}
ctrack->req_retrans_counter++;
if (ctrack->req_retrans_counter >= threshold)
{
DLOG("req retrans threshold reached : %u/%u\n",ctrack->req_retrans_counter, threshold);
ctrack_stop_retrans_counter(ctrack);
return true;
}
DLOG("req retrans counter : %u/%u\n",ctrack->req_retrans_counter, threshold);
}
return false;
}
@@ -227,7 +238,7 @@ static void auto_hostlist_failed(const char *hostname)
HOSTLIST_DEBUGLOG_APPEND("%s : fail counter %d/%d", hostname, fail_counter->counter, params.hostlist_auto_fail_threshold);
if (fail_counter->counter >= params.hostlist_auto_fail_threshold)
{
DLOG("auto hostlist : fail threshold reached. adding %s to auto hostlist\n", hostname);
DLOG("auto hostlist : fail threshold reached. about to add %s to auto hostlist\n", hostname);
HostFailPoolDel(&params.hostlist_auto_fail_counters, fail_counter);
DLOG("auto hostlist : rechecking %s to avoid duplicates\n", hostname);
@@ -255,7 +266,69 @@ static void auto_hostlist_failed(const char *hostname)
}
}
#define CONNTRACK_REQUIRED (params.wssize || params.desync_cutoff || *params.hostlist_auto_filename)
static void process_retrans_fail(t_ctrack *ctrack, uint8_t proto)
{
if (ctrack && ctrack->hostname && auto_hostlist_retrans(ctrack, proto, params.hostlist_auto_retrans_threshold))
{
HOSTLIST_DEBUGLOG_APPEND("%s : tcp retrans threshold reached", ctrack->hostname);
auto_hostlist_failed(ctrack->hostname);
}
}
static bool reasm_start(t_ctrack *ctrack, t_reassemble *reasm, size_t sz, size_t szMax, const uint8_t *data_payload, size_t len_payload)
{
ReasmClear(reasm);
if (sz<=szMax)
{
if (ReasmInit(reasm,sz,ctrack->seq_last))
{
ReasmFeed(reasm,ctrack->seq_last,data_payload,len_payload);
DLOG("starting reassemble. now we have %zu/%zu\n",reasm->size_present,reasm->size);
return true;
}
else
DLOG("reassemble init failed. out of memory\n");
}
else
DLOG("unexpected large payload for reassemble: size=%zu\n",sz);
return false;
}
static bool reasm_orig_start(t_ctrack *ctrack, size_t sz, size_t szMax, const uint8_t *data_payload, size_t len_payload)
{
return reasm_start(ctrack,&ctrack->reasm_orig,sz,szMax,data_payload,len_payload);
}
static bool reasm_feed(t_ctrack *ctrack, t_reassemble *reasm, const uint8_t *data_payload, size_t len_payload)
{
if (ctrack && !ReasmIsEmpty(reasm))
{
if (ReasmFeed(reasm,ctrack->seq_last,data_payload,len_payload))
{
DLOG("reassemble : feeding data payload size=%zu. now we have %zu/%zu\n",len_payload,reasm->size_present,reasm->size)
return true;
}
else
{
ReasmClear(reasm);
DLOG("reassemble session failed\n")
}
}
return false;
}
static bool reasm_orig_feed(t_ctrack *ctrack, const uint8_t *data_payload, size_t len_payload)
{
return reasm_feed(ctrack, &ctrack->reasm_orig, data_payload, len_payload);
}
static void reasm_orig_fin(t_ctrack *ctrack)
{
if (ctrack && ReasmIsFull(&ctrack->reasm_orig))
{
DLOG("reassemble session finished\n");
ReasmClear(&ctrack->reasm_orig);
}
}
// result : true - drop original packet, false = dont drop
packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout, uint8_t *data_pkt, size_t len_pkt, struct ip *ip, struct ip6_hdr *ip6hdr, struct tcphdr *tcphdr, size_t len_tcp, uint8_t *data_payload, size_t len_payload)
{
@@ -271,13 +344,10 @@ packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout,
if (!!ip == !!ip6hdr) return res; // one and only one must be present
if (CONNTRACK_REQUIRED)
{
ConntrackPoolPurge(&params.conntrack);
if (ConntrackPoolFeed(&params.conntrack, ip, ip6hdr, tcphdr, NULL, len_payload, &ctrack, &bReverse))
maybe_cutoff(ctrack, IPPROTO_TCP);
HostFailPoolPurgeRateLimited(&params.hostlist_auto_fail_counters);
}
ConntrackPoolPurge(&params.conntrack);
if (ConntrackPoolFeed(&params.conntrack, ip, ip6hdr, tcphdr, NULL, len_payload, &ctrack, &bReverse))
maybe_cutoff(ctrack, IPPROTO_TCP);
HostFailPoolPurgeRateLimited(&params.hostlist_auto_fail_counters);
//ConntrackPoolDump(&params.conntrack);
@@ -290,15 +360,16 @@ packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout,
if (bReverse)
{
// process reply packets for auto hostlist mode
// by looking at RSTs or HTTP replies we decide whether original request looks to be blocked by DPI
if (*params.hostlist_auto_filename && ctrack && ctrack->hostname && ctrack->req_retrans_counter != RETRANS_COUNTER_STOP)
// by looking at RSTs or HTTP replies we decide whether original request looks like DPI blocked
// we only process first-sequence replies. do not react to subsequent redirects or RSTs
if (*params.hostlist_auto_filename && ctrack && ctrack->hostname && (ctrack->ack_last-ctrack->ack0)==1)
{
bool bFail=false, bStop=false;
bool bFail=false;
if (tcphdr->th_flags & TH_RST)
{
DLOG("incoming RST detected for hostname %s\n", ctrack->hostname);
HOSTLIST_DEBUGLOG_APPEND("%s : incoming RST", ctrack->hostname);
bFail = bStop = true;
bFail = true;
}
else if (len_payload && ctrack->l7proto==HTTP)
{
@@ -319,12 +390,11 @@ packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout,
// received not http reply. do not monitor this connection anymore
DLOG("incoming unknown HTTP data detected for hostname %s\n", ctrack->hostname);
}
bStop = true;
}
if (bFail)
auto_hostlist_failed(ctrack->hostname);
if (bStop)
ctrack_stop_req_counter(ctrack);
if (tcphdr->th_flags & TH_RST)
ConntrackClearHostname(ctrack); // do not react to further dup RSTs
}
return res; // nothing to do. do not waste cpu
@@ -408,78 +478,119 @@ packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout,
bool bIsHttp;
bool bKnownProtocol = false;
uint8_t *p, *phost;
if ((bIsHttp = IsHttp(data_payload,len_payload)))
const uint8_t *rdata_payload = data_payload;
size_t rlen_payload = len_payload;
if (reasm_orig_feed(ctrack,data_payload,len_payload))
{
rdata_payload = ctrack->reasm_orig.packet;
rlen_payload = ctrack->reasm_orig.size_present;
}
if ((bIsHttp = IsHttp(rdata_payload,rlen_payload)))
{
DLOG("packet contains HTTP request\n")
if (ctrack && !ctrack->l7proto) ctrack->l7proto = HTTP;
if (params.wssize)
{
DLOG("forced wssize-cutoff\n");
wssize_cutoff(ctrack);
}
forced_wssize_cutoff(ctrack);
fake = params.fake_http;
fake_size = params.fake_http_size;
if (params.hostlist || params.debug) bHaveHost=HttpExtractHost(data_payload,len_payload,host,sizeof(host));
if (params.hostlist && !bHaveHost)
if (params.hostlist || params.hostlist_exclude)
{
DLOG("not applying tampering to HTTP without Host:\n")
return res;
}
bKnownProtocol = true;
}
else if (IsTLSClientHello(data_payload,len_payload))
{
DLOG("packet contains TLS ClientHello\n")
if (ctrack && !ctrack->l7proto) ctrack->l7proto = TLS;
if (params.wssize)
{
DLOG("forced wssize-cutoff\n");
wssize_cutoff(ctrack);
}
fake = params.fake_tls;
fake_size = params.fake_tls_size;
if (params.hostlist || params.desync_skip_nosni || params.debug)
{
bHaveHost=TLSHelloExtractHost(data_payload,len_payload,host,sizeof(host));
if (params.desync_skip_nosni && !bHaveHost)
bHaveHost=HttpExtractHost(rdata_payload,rlen_payload,host,sizeof(host));
if (!bHaveHost)
{
DLOG("not applying tampering to TLS ClientHello without hostname in the SNI\n")
DLOG("not applying tampering to HTTP without Host:\n")
process_retrans_fail(ctrack, IPPROTO_TCP);
reasm_orig_fin(ctrack);
return res;
}
}
if (ctrack)
{
// we do not reassemble http
if (!ctrack->req_seq_present)
{
ctrack->req_seq_start=ctrack->seq_last;
ctrack->req_seq_end=ctrack->pos_orig-1;
ctrack->req_seq_start_present=ctrack->req_seq_present=true;
DLOG("req retrans : tcp seq interval %u-%u\n",ctrack->req_seq_start,ctrack->req_seq_end);
}
}
bKnownProtocol = true;
}
else
else if (IsTLSClientHello(rdata_payload,rlen_payload,TLS_PARTIALS_ENABLE))
{
bool bReqFull = IsTLSRecordFull(rdata_payload,rlen_payload);
DLOG(bReqFull ? "packet contains full TLS ClientHello\n" : "packet contains partial TLS ClientHello\n")
fake = params.fake_tls;
fake_size = params.fake_tls_size;
bHaveHost=TLSHelloExtractHost(rdata_payload,rlen_payload,host,sizeof(host),TLS_PARTIALS_ENABLE);
if (ctrack)
{
if (!ctrack->l7proto) ctrack->l7proto = TLS;
if (!bReqFull && ReasmIsEmpty(&ctrack->reasm_orig))
// do not reconstruct unexpected large payload (they are feeding garbage ?)
reasm_orig_start(ctrack,TLSRecordLen(data_payload),4096,data_payload,len_payload);
if (!ctrack->req_seq_start_present)
{
// lower bound of request seq interval
ctrack->req_seq_start=ctrack->seq_last;
ctrack->req_seq_start_present=true;
}
if (!ctrack->req_seq_present && bReqFull)
{
// upper bound of request seq interval
ctrack->req_seq_end=ctrack->pos_orig-1;
ctrack->req_seq_present=ctrack->req_seq_start_present;
DLOG("req retrans : seq interval %u-%u\n",ctrack->req_seq_start,ctrack->req_seq_end);
}
}
if (bReqFull || !ctrack || ReasmIsEmpty(&ctrack->reasm_orig)) forced_wssize_cutoff(ctrack);
if (params.desync_skip_nosni && !bHaveHost)
{
DLOG("not applying tampering to TLS ClientHello without hostname in the SNI\n")
process_retrans_fail(ctrack, IPPROTO_TCP);
reasm_orig_fin(ctrack);
return res;
}
bKnownProtocol = true;
}
reasm_orig_fin(ctrack);
rdata_payload=NULL;
if (bHaveHost)
{
bool bExcluded;
DLOG("hostname: %s\n",host)
if ((params.hostlist || params.hostlist_exclude) && !HostlistCheck(params.hostlist, params.hostlist_exclude, host, &bExcluded))
{
DLOG("not applying tampering to this request\n")
if (ctrack)
{
if (!bExcluded && *params.hostlist_auto_filename)
{
if (!ctrack->hostname) ctrack->hostname=strdup(host);
process_retrans_fail(ctrack, IPPROTO_TCP);
}
else
ctrack_stop_retrans_counter(ctrack);
}
return res;
}
ctrack_stop_retrans_counter(ctrack);
}
process_retrans_fail(ctrack, IPPROTO_TCP);
if (!bKnownProtocol)
{
// received unknown payload. it means we are out of the request retransmission phase. stop counter
ctrack_stop_req_counter(ctrack);
if (!params.desync_any_proto) return res;
DLOG("applying tampering to unknown protocol\n")
fake = params.fake_unknown;
fake_size = params.fake_unknown_size;
}
if (bHaveHost)
{
DLOG("hostname: %s\n",host)
bool bExcluded;
if ((params.hostlist || params.hostlist_exclude) && !HostlistCheck(params.hostlist, params.hostlist_exclude, host, &bExcluded))
{
DLOG("not applying tampering to this request\n")
if (!bExcluded && *params.hostlist_auto_filename && ctrack)
{
if (!ctrack->hostname) ctrack->hostname=strdup(host);
if (auto_hostlist_retrans(ctrack, IPPROTO_TCP, params.hostlist_auto_retrans_threshold))
{
HOSTLIST_DEBUGLOG_APPEND("%s : tcp retrans threshold reached", ctrack->hostname);
auto_hostlist_failed(host);
}
}
return res;
}
}
if (bIsHttp && (params.hostcase || params.hostnospace || params.domcase) && (phost = (uint8_t*)memmem(data_payload, len_payload, "\r\nHost: ", 8)))
{
if (params.hostcase)
@@ -773,6 +884,7 @@ packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout,
return frag;
}
}
}
return res;
@@ -793,13 +905,10 @@ packet_process_result dpi_desync_udp_packet(uint32_t fwmark, const char *ifout,
if (!!ip == !!ip6hdr) return res; // one and only one must be present
if (CONNTRACK_REQUIRED)
{
ConntrackPoolPurge(&params.conntrack);
if (ConntrackPoolFeed(&params.conntrack, ip, ip6hdr, NULL, udphdr, len_payload, &ctrack, &bReverse))
maybe_cutoff(ctrack, IPPROTO_UDP);
HostFailPoolPurgeRateLimited(&params.hostlist_auto_fail_counters);
}
ConntrackPoolPurge(&params.conntrack);
if (ConntrackPoolFeed(&params.conntrack, ip, ip6hdr, NULL, udphdr, len_payload, &ctrack, &bReverse))
maybe_cutoff(ctrack, IPPROTO_UDP);
HostFailPoolPurgeRateLimited(&params.hostlist_auto_fail_counters);
//ConntrackPoolDump(&params.conntrack);
@@ -883,7 +992,7 @@ packet_process_result dpi_desync_udp_packet(uint32_t fwmark, const char *ifout,
else
{
// received payload without host. it means we are out of the request retransmission phase. stop counter
ctrack_stop_req_counter(ctrack);
ctrack_stop_retrans_counter(ctrack);
if (IsWireguardHandshakeInitiation(data_payload,len_payload))
{
@@ -920,11 +1029,7 @@ packet_process_result dpi_desync_udp_packet(uint32_t fwmark, const char *ifout,
if (!bExcluded && *params.hostlist_auto_filename && ctrack)
{
if (!ctrack->hostname) ctrack->hostname=strdup(host);
if (auto_hostlist_retrans(ctrack, IPPROTO_UDP, params.hostlist_auto_retrans_threshold))
{
HOSTLIST_DEBUGLOG_APPEND("%s : udp retrans threshold reached", ctrack->hostname);
auto_hostlist_failed(host);
}
process_retrans_fail(ctrack, IPPROTO_UDP);
}
return res;
}

View File

@@ -5,6 +5,7 @@
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <time.h>
void hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit)
{
@@ -149,12 +150,12 @@ void dbgprint_socket_buffers(int fd)
bool set_socket_buffers(int fd, int rcvbuf, int sndbuf)
{
DLOG("set_socket_buffers fd=%d rcvbuf=%d sndbuf=%d\n", fd, rcvbuf, sndbuf)
if (rcvbuf && setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(int)) < 0)
{
perror("setsockopt (SO_RCVBUF)");
close(fd);
return false;
}
if (rcvbuf && setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(int)) < 0)
{
perror("setsockopt (SO_RCVBUF)");
close(fd);
return false;
}
if (sndbuf && setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(int)) < 0)
{
perror("setsockopt (SO_SNDBUF)");
@@ -188,6 +189,11 @@ void phton64(uint8_t *p, uint64_t v)
p[7] = (uint8_t)(v >> 0);
}
bool seq_within(uint32_t s, uint32_t s1, uint32_t s2)
{
return s2>=s1 && s>=s1 && s<=s2 || s2<s1 && (s<=s2 || s>=s1);
}
bool ipv6_addr_is_zero(const struct in6_addr *a)
{
return !memcmp(a,"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",16);

View File

@@ -5,6 +5,7 @@
#include <sys/socket.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdio.h>
#include "params.h"
@@ -19,6 +20,8 @@ void print_sockaddr(const struct sockaddr *sa);
void ntop46(const struct sockaddr *sa, char *str, size_t len);
void ntop46_port(const struct sockaddr *sa, char *str, size_t len);
bool seq_within(uint32_t s, uint32_t s1, uint32_t s2);
void dbgprint_socket_buffers(int fd);
bool set_socket_buffers(int fd, int rcvbuf, int sndbuf);

View File

@@ -1,6 +1,5 @@
#pragma once
#include "params.h"
#include "pools.h"
#include "conntrack.h"
#include "desync.h"
@@ -12,6 +11,8 @@
#include <stdbool.h>
#include <stdio.h>
#define TLS_PARTIALS_ENABLE true
#define Q_RCVBUF (128*1024) // in bytes
#define Q_SNDBUF (64*1024) // in bytes
#define RAW_SNDBUF (64*1024) // in bytes

View File

@@ -108,12 +108,25 @@ bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *
}
bool IsTLSClientHello(const uint8_t *data, size_t len)
uint16_t TLSRecordDataLen(const uint8_t *data)
{
return len >= 6 && data[0] == 0x16 && data[1] == 0x03 && data[2] >= 0x01 && data[2] <= 0x03 && data[5] == 0x01 && (pntoh16(data + 3) + 5) <= len;
return pntoh16(data + 3);
}
bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext)
size_t TLSRecordLen(const uint8_t *data)
{
return TLSRecordDataLen(data) + 5;
}
bool IsTLSRecordFull(const uint8_t *data, size_t len)
{
return TLSRecordLen(data)<=len;
}
bool IsTLSClientHello(const uint8_t *data, size_t len, bool bPartialIsOK)
{
return len >= 6 && data[0] == 0x16 && data[1] == 0x03 && data[2] >= 0x01 && data[2] <= 0x03 && data[5] == 0x01 && (bPartialIsOK || TLSRecordLen(data) <= len);
}
// bPartialIsOK=true - accept partial packets not containing the whole TLS message
bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK)
{
// +0
// u8 HandshakeType: ClientHello
@@ -133,8 +146,11 @@ bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const
l = 1 + 3 + 2 + 32;
// SessionIDLength
if (len < (l + 1)) return false;
ll = data[1] << 16 | data[2] << 8 | data[3]; // HandshakeProtocol length
if (len < (ll + 4)) return false;
if (!bPartialIsOK)
{
ll = data[1] << 16 | data[2] << 8 | data[3]; // HandshakeProtocol length
if (len < (ll + 4)) return false;
}
l += data[l] + 1;
// CipherSuitesLength
if (len < (l + 2)) return false;
@@ -148,7 +164,15 @@ bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const
data += l; len -= l;
l = pntoh16(data);
data += 2; len -= 2;
if (len < l) return false;
if (bPartialIsOK)
{
if (len < l) l = len;
}
else
{
if (len < l) return false;
}
while (l >= 4)
{
@@ -170,14 +194,14 @@ bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const
return false;
}
bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext)
bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK)
{
// +0
// u8 ContentType: Handshake
// u16 Version: TLS1.0
// u16 Length
if (!IsTLSClientHello(data, len)) return false;
return TLSFindExtInHandshake(data + 5, len - 5, type, ext, len_ext);
if (!IsTLSClientHello(data, len, bPartialIsOK)) return false;
return TLSFindExtInHandshake(data + 5, len - 5, type, ext, len_ext, bPartialIsOK);
}
static bool TLSExtractHostFromExt(const uint8_t *ext, size_t elen, char *host, size_t len_host)
{
@@ -196,20 +220,20 @@ static bool TLSExtractHostFromExt(const uint8_t *ext, size_t elen, char *host, s
}
return true;
}
bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host)
bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK)
{
const uint8_t *ext;
size_t elen;
if (!TLSFindExt(data, len, 0, &ext, &elen)) return false;
if (!TLSFindExt(data, len, 0, &ext, &elen, bPartialIsOK)) return false;
return TLSExtractHostFromExt(ext, elen, host, len_host);
}
bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host)
bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK)
{
const uint8_t *ext;
size_t elen;
if (!TLSFindExtInHandshake(data, len, 0, &ext, &elen)) return false;
if (!TLSFindExtInHandshake(data, len, 0, &ext, &elen, bPartialIsOK)) return false;
return TLSExtractHostFromExt(ext, elen, host, len_host);
}
@@ -580,7 +604,7 @@ bool QUICExtractHostFromInitial(const uint8_t *data, size_t data_len, char *host
if (!IsQUICCryptoHello(defrag, defrag_len, &hello_offset, &hello_len)) return false;
if (bIsCryptoHello) *bIsCryptoHello=true;
return TLSHelloExtractHostFromHandshake(defrag + hello_offset, hello_len, host, len_host);
return TLSHelloExtractHostFromHandshake(defrag + hello_offset, hello_len, host, len_host, true);
}
bool IsQUICInitial(const uint8_t *data, size_t len)

View File

@@ -5,6 +5,7 @@
#include <stdbool.h>
#include "crypto/sha.h"
#include "crypto/aes-gcm.h"
#include "helpers.h"
bool IsHttp(const uint8_t *data, size_t len);
// header must be passed like this : "\nHost:"
@@ -17,11 +18,14 @@ int HttpReplyCode(const uint8_t *data, size_t len);
// must be pre-checked by IsHttpReply
bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host);
bool IsTLSClientHello(const uint8_t *data, size_t len);
bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext);
bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext);
bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host);
bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host);
uint16_t TLSRecordDataLen(const uint8_t *data);
size_t TLSRecordLen(const uint8_t *data);
bool IsTLSRecordFull(const uint8_t *data, size_t len);
bool IsTLSClientHello(const uint8_t *data, size_t len, bool bPartialIsOK);
bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK);
bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK);
bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK);
bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK);
bool IsWireguardHandshakeInitiation(const uint8_t *data, size_t len);
bool IsDhtD1(const uint8_t *data, size_t len);