nfqws: QUIC protocol recognition

This commit is contained in:
bol-van 2022-03-20 20:46:39 +03:00
parent 38dca7da70
commit 0a5ffc1a54
10 changed files with 140 additions and 62 deletions

Binary file not shown.

View File

@ -169,6 +169,7 @@ nfqws takes the following parameters:
--dpi-desync-fake-http=<filename> ; file containing fake http request. replacement for built-in --dpi-desync-fake-http=<filename> ; file containing fake http request. replacement for built-in
--dpi-desync-fake-tls=<filename> ; file containing fake TLS ClientHello (for https). replacement for built-in --dpi-desync-fake-tls=<filename> ; file containing fake TLS ClientHello (for https). replacement for built-in
--dpi-desync-fake-unknown=<filename> ; file containing unknown protocol fake payload. default is 256 zeroes --dpi-desync-fake-unknown=<filename> ; file containing unknown protocol fake payload. default is 256 zeroes
--dpi-desync-fake-quic=<filename> ; file containing fake QUIC Initial
--dpi-desync-fake-unknown-udp=<filename> ; file containing unknown udp protocol fake payload --dpi-desync-fake-unknown-udp=<filename> ; file containing unknown udp protocol fake payload
--dpi-desync-cutoff=[n|d|s]N ; apply dpi desync only to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N --dpi-desync-cutoff=[n|d|s]N ; apply dpi desync only to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N
--hostlist=<filename> ; apply fooling only to the listed hosts (one host per line, subdomains auto apply) --hostlist=<filename> ; apply fooling only to the listed hosts (one host per line, subdomains auto apply)
@ -420,9 +421,12 @@ Set conntrack timeouts appropriately.
UDP attacks are limited. Its not possible to fragment UDP on transport level, only on network (ip) level. UDP attacks are limited. Its not possible to fragment UDP on transport level, only on network (ip) level.
Only desync modes `fake`,`hopbyhop`,`destopt`,`ipfrag1` and `ipfrag2` are applicable. Only desync modes `fake`,`hopbyhop`,`destopt`,`ipfrag1` and `ipfrag2` are applicable.
`fake`,`hopbyhop`,`destopt` can be used in combo with `ipfrag2`. `fake`,`hopbyhop`,`destopt` can be used in combo with `ipfrag2`.
No protocol recognition is implemented yet so only `--dpi-desync-any-protocol` will work.
Conntrack supports udp. `--dpi-desync-cutoff` will work. UDP conntrack timeout can be set in the 4th QUIC initial packets are recognized. Decryption and hostname extraction is not supported so `--hostlist` parameter will not work.
parameter of `--ctrack-timeouts`. For other protocols desync use `--dpi-desync-any-protocol`.
Conntrack supports udp. `--dpi-desync-cutoff` will work. UDP conntrack timeout can be set in the 4th parameter of `--ctrack-timeouts`.
Fake attack is useful only for stateful DPI and useless for stateless dealing with each packet independently. Fake attack is useful only for stateful DPI and useless for stateless dealing with each packet independently.
By default fake payload is 64 zeroes. Can be overriden using `--dpi-desync-fake-unknown-udp`. By default fake payload is 64 zeroes. Can be overriden using `--dpi-desync-fake-unknown-udp`.

View File

@ -215,6 +215,7 @@ nfqws
--dpi-desync-fake-http=<filename> ; файл, содержащий фейковый http запрос для dpi-desync=fake, на замену стандартному w3.org --dpi-desync-fake-http=<filename> ; файл, содержащий фейковый http запрос для dpi-desync=fake, на замену стандартному w3.org
--dpi-desync-fake-tls=<filename> ; файл, содержащий фейковый tls clienthello для dpi-desync=fake, на замену стандартному w3.org --dpi-desync-fake-tls=<filename> ; файл, содержащий фейковый tls clienthello для dpi-desync=fake, на замену стандартному w3.org
--dpi-desync-fake-unknown=<filename> ; файл, содержащий фейковый пейлоад неизвестного протокола для dpi-desync=fake, на замену стандартным нулям 256 байт --dpi-desync-fake-unknown=<filename> ; файл, содержащий фейковый пейлоад неизвестного протокола для dpi-desync=fake, на замену стандартным нулям 256 байт
--dpi-desync-fake-quic=<filename> ; файл, содержащий фейковый QUIC Initial
--dpi-desync-fake-unknown-udp=<filename> ; файл, содержащий фейковый пейлоад неизвестного udp протокола для dpi-desync=fake, на замену стандартным нулям 64 байт --dpi-desync-fake-unknown-udp=<filename> ; файл, содержащий фейковый пейлоад неизвестного udp протокола для dpi-desync=fake, на замену стандартным нулям 64 байт
--dpi-desync-cutoff=[n|d|s]N ; применять dpi desync только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру меньше N --dpi-desync-cutoff=[n|d|s]N ; применять dpi desync только в исходящих пакетах (n), пакетах данных (d), относительных sequence (s) по номеру меньше N
--hostlist=<filename> ; применять дурение только к хостам из листа --hostlist=<filename> ; применять дурение только к хостам из листа
@ -454,8 +455,8 @@ window size итоговый размер окна стал максимальн
Атаки на udp более ограничены в возможностях. udp нельзя фрагментировать иначе, чем на уровне ip. Атаки на udp более ограничены в возможностях. udp нельзя фрагментировать иначе, чем на уровне ip.
Для UDP действуют только режимы десинхронизации fake,hopbyhop,destopt,ipfrag1,ipfrag2. Для UDP действуют только режимы десинхронизации fake,hopbyhop,destopt,ipfrag1,ipfrag2.
Возможно сочетание fake,hopbyhop,destopt с ipfrag2. Возможно сочетание fake,hopbyhop,destopt с ipfrag2.
Обязательно указание --dpi-desync-any-protocol, иначе десинхронизация работать не будет, Поддерживается определение пакетов QUIC Initial без расшифровки содержимого и имени хоста, то есть параметр
поскольку протокол неизвестен, а никакие протоколы пока не определяются. --hostlist не будет работать. Для десинхронизации других протоколов обязательно указывать --dpi-desync-any-protocol.
Реализован conntrack для udp. Можно пользоваться --dpi-desync-cutoff. Таймаут conntrack для udp Реализован conntrack для udp. Можно пользоваться --dpi-desync-cutoff. Таймаут conntrack для udp
можно изменить 4-м параметром в --ctrack-timeouts. можно изменить 4-м параметром в --ctrack-timeouts.
Атака fake полезна только для stateful DPI, она бесполезна для анализа на уровне отдельных пакетов. Атака fake полезна только для stateful DPI, она бесполезна для анализа на уровне отдельных пакетов.

View File

@ -1,5 +1,5 @@
# this custom script in addition to MODE=nfqws runs desync to all QUIC initial packets, without ipset/hostlist filtering # this custom script in addition to MODE=nfqws runs desync to all QUIC initial packets, without ipset/hostlist filtering
# need to add to config : NFQWS_OPT_DESYNC_QUIC="--dpi-desync-any-protocol --dpi-desync=fake --dpi-desync-cutoff=d4" # need to add to config : NFQWS_OPT_DESYNC_QUIC="--dpi-desync=fake"
# NOTE : do not use TTL fooling. chromium QUIC engine breaks sessions if TTL expired in transit received # NOTE : do not use TTL fooling. chromium QUIC engine breaks sessions if TTL expired in transit received
QNUM2=$(($QNUM+10)) QNUM2=$(($QNUM+10))

View File

@ -1,5 +1,5 @@
# this custom script in addition to MODE=nfqws runs desync to all QUIC initial packets, without ipset/hostlist filtering # this custom script in addition to MODE=nfqws runs desync to all QUIC initial packets, without ipset/hostlist filtering
# need to add to config : NFQWS_OPT_DESYNC_QUIC="--dpi-desync-any-protocol --dpi-desync=fake --dpi-desync-cutoff=d4" # need to add to config : NFQWS_OPT_DESYNC_QUIC="--dpi-desync=fake"
# NOTE : do not use TTL fooling. chromium QUIC engine breaks sessions if TTL expired in transit received # NOTE : do not use TTL fooling. chromium QUIC engine breaks sessions if TTL expired in transit received
QNUM2=$(($QNUM+10)) QNUM2=$(($QNUM+10))

View File

@ -656,8 +656,19 @@ packet_process_result dpi_desync_udp_packet(uint8_t *data_pkt, size_t len_pkt, s
size_t fake_size; size_t fake_size;
bool b; bool b;
if (!params.desync_any_proto) return res; if (IsQUICInitial(data_payload,len_payload))
DLOG("applying tampering to unknown protocol\n") {
DLOG("packet contains QUIC initial\n")
fake = params.fake_quic;
fake_size = params.fake_quic_size;
}
else
{
if (!params.desync_any_proto) return res;
DLOG("applying tampering to unknown protocol\n")
fake = params.fake_unknown_udp;
fake_size = params.fake_unknown_udp_size;
}
enum dpi_desync_mode desync_mode = params.desync_mode; enum dpi_desync_mode desync_mode = params.desync_mode;
uint8_t fooling_orig = FOOL_NONE; uint8_t fooling_orig = FOOL_NONE;
@ -667,9 +678,6 @@ packet_process_result dpi_desync_udp_packet(uint8_t *data_pkt, size_t len_pkt, s
else ttl_fake = params.desync_ttl ? params.desync_ttl : ttl_orig; else ttl_fake = params.desync_ttl ? params.desync_ttl : ttl_orig;
extract_endpoints(ip, ip6hdr, NULL, udphdr, &src, &dst); extract_endpoints(ip, ip6hdr, NULL, udphdr, &src, &dst);
fake = params.fake_unknown_udp;
fake_size = params.fake_unknown_udp_size;
if (params.debug) if (params.debug)
{ {
printf("dpi desync src="); printf("dpi desync src=");

View File

@ -528,6 +528,7 @@ static void exithelp()
" --dpi-desync-fake-http=<filename>\t; file containing fake http request\n" " --dpi-desync-fake-http=<filename>\t; file containing fake http request\n"
" --dpi-desync-fake-tls=<filename>\t; file containing fake TLS ClientHello (for https)\n" " --dpi-desync-fake-tls=<filename>\t; file containing fake TLS ClientHello (for https)\n"
" --dpi-desync-fake-unknown=<filename>\t; file containing unknown protocol fake payload\n" " --dpi-desync-fake-unknown=<filename>\t; file containing unknown protocol fake payload\n"
" --dpi-desync-fake-quic=<filename>\t; file containing fake QUIC Initial\n"
" --dpi-desync-fake-unknown-udp=<filename> ; file containing unknown udp protocol fake payload\n" " --dpi-desync-fake-unknown-udp=<filename> ; file containing unknown udp protocol fake payload\n"
" --dpi-desync-cutoff=[n|d|s]N\t\t; apply dpi desync only to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N\n" " --dpi-desync-cutoff=[n|d|s]N\t\t; apply dpi desync only to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N\n"
" --hostlist=<filename>\t\t\t; apply dpi desync only to the listed hosts (one host per line, subdomains auto apply)\n", " --hostlist=<filename>\t\t\t; apply dpi desync only to the listed hosts (one host per line, subdomains auto apply)\n",
@ -614,6 +615,7 @@ int main(int argc, char **argv)
memcpy(params.fake_tls,fake_tls_clienthello_default,params.fake_tls_size); memcpy(params.fake_tls,fake_tls_clienthello_default,params.fake_tls_size);
params.fake_http_size = strlen(fake_http_request_default); params.fake_http_size = strlen(fake_http_request_default);
memcpy(params.fake_http,fake_http_request_default,params.fake_http_size); memcpy(params.fake_http,fake_http_request_default,params.fake_http_size);
params.fake_quic_size = 256;
params.fake_unknown_size = 256; params.fake_unknown_size = 256;
params.fake_unknown_udp_size = 64; params.fake_unknown_udp_size = 64;
params.wscale=-1; // default - dont change scale factor (client) params.wscale=-1; // default - dont change scale factor (client)
@ -676,9 +678,10 @@ int main(int argc, char **argv)
{"dpi-desync-fake-http",required_argument,0,0},// optidx=28 {"dpi-desync-fake-http",required_argument,0,0},// optidx=28
{"dpi-desync-fake-tls",required_argument,0,0},// optidx=29 {"dpi-desync-fake-tls",required_argument,0,0},// optidx=29
{"dpi-desync-fake-unknown",required_argument,0,0},// optidx=30 {"dpi-desync-fake-unknown",required_argument,0,0},// optidx=30
{"dpi-desync-fake-unknown-udp",required_argument,0,0},// optidx=31 {"dpi-desync-fake-quic",required_argument,0,0},// optidx=31
{"dpi-desync-cutoff",required_argument,0,0},// optidx=32 {"dpi-desync-fake-unknown-udp",required_argument,0,0},// optidx=32
{"hostlist",required_argument,0,0}, // optidx=33 {"dpi-desync-cutoff",required_argument,0,0},// optidx=33
{"hostlist",required_argument,0,0}, // optidx=34
{NULL,0,NULL,0} {NULL,0,NULL,0}
}; };
if (argc < 2) exithelp(); if (argc < 2) exithelp();
@ -955,18 +958,22 @@ int main(int argc, char **argv)
params.fake_unknown_size = sizeof(params.fake_unknown); params.fake_unknown_size = sizeof(params.fake_unknown);
load_file_or_exit(optarg,params.fake_unknown,&params.fake_unknown_size); load_file_or_exit(optarg,params.fake_unknown,&params.fake_unknown_size);
break; break;
case 31: /* dpi-desync-fake-unknown-udp */ case 31: /* dpi-desync-fake-quic */
params.fake_quic_size = sizeof(params.fake_quic);
load_file_or_exit(optarg,params.fake_quic,&params.fake_quic_size);
break;
case 32: /* dpi-desync-fake-unknown-udp */
params.fake_unknown_udp_size = sizeof(params.fake_unknown_udp); params.fake_unknown_udp_size = sizeof(params.fake_unknown_udp);
load_file_or_exit(optarg,params.fake_unknown_udp,&params.fake_unknown_udp_size); load_file_or_exit(optarg,params.fake_unknown_udp,&params.fake_unknown_udp_size);
break; break;
case 32: /* desync-cutoff */ case 33: /* desync-cutoff */
if (!parse_cutoff(optarg, &params.desync_cutoff, &params.desync_cutoff_mode)) if (!parse_cutoff(optarg, &params.desync_cutoff, &params.desync_cutoff_mode))
{ {
fprintf(stderr, "invalid desync-cutoff value\n"); fprintf(stderr, "invalid desync-cutoff value\n");
exit_clean(1); exit_clean(1);
} }
break; break;
case 33: /* hostlist */ case 34: /* hostlist */
if (!LoadHostList(&params.hostlist, optarg)) if (!LoadHostList(&params.hostlist, optarg))
exit_clean(1); exit_clean(1);
strncpy(params.hostfile,optarg,sizeof(params.hostfile)); strncpy(params.hostfile,optarg,sizeof(params.hostfile));

View File

@ -48,8 +48,8 @@ struct params_s
uint32_t desync_badseq_increment, desync_badseq_ack_increment; uint32_t desync_badseq_increment, desync_badseq_ack_increment;
char hostfile[256]; char hostfile[256];
strpool *hostlist; strpool *hostlist;
uint8_t fake_http[1432],fake_tls[1432],fake_unknown[1432],fake_unknown_udp[1472]; uint8_t fake_http[1432],fake_tls[1432],fake_unknown[1432],fake_unknown_udp[1472],fake_quic[1472];
size_t fake_http_size,fake_tls_size,fake_unknown_size,fake_unknown_udp_size; size_t fake_http_size,fake_tls_size,fake_unknown_size,fake_unknown_udp_size,fake_quic_size;
bool droproot; bool droproot;
uid_t uid; uid_t uid;
gid_t gid; gid_t gid;

View File

@ -22,22 +22,22 @@ bool IsHttp(const uint8_t *data, size_t len)
} }
bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host) bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host)
{ {
const uint8_t *p, *s, *e=data+len; const uint8_t *p, *s, *e = data + len;
p = (uint8_t*)strncasestr((char*)data, "\nHost:", len); p = (uint8_t*)strncasestr((char*)data, "\nHost:", len);
if (!p) return false; if (!p) return false;
p+=6; p += 6;
while(p<e && (*p==' ' || *p=='\t')) p++; while (p < e && (*p == ' ' || *p == '\t')) p++;
s=p; s = p;
while(s<e && (*s!='\r' && *s!='\n' && *s!=' ' && *s!='\t')) s++; while (s < e && (*s != '\r' && *s != '\n' && *s != ' ' && *s != '\t')) s++;
if (s>p) if (s > p)
{ {
size_t slen = s-p; size_t slen = s - p;
if (host && len_host) if (host && len_host)
{ {
if (slen>=len_host) slen=len_host-1; if (slen >= len_host) slen = len_host - 1;
for(size_t i=0;i<slen;i++) host[i]=tolower(p[i]); for (size_t i = 0; i < slen; i++) host[i] = tolower(p[i]);
host[slen]=0; host[slen] = 0;
} }
return true; return true;
} }
@ -45,7 +45,7 @@ bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_hos
} }
bool IsTLSClientHello(const uint8_t *data, size_t len) bool IsTLSClientHello(const uint8_t *data, size_t len)
{ {
return len>=6 && data[0]==0x16 && data[1]==0x03 && data[2]>=0x01 && data[2]<=0x03 && data[5]==0x01 && (ntohs(*(uint16_t*)(data+3))+5)<=len; return len >= 6 && data[0] == 0x16 && data[1] == 0x03 && data[2] >= 0x01 && data[2] <= 0x03 && data[5] == 0x01 && (ntohs(*(uint16_t*)(data + 3)) + 5) <= len;
} }
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)
{ {
@ -66,36 +66,36 @@ bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **
// <CompressionMethods> // <CompressionMethods>
// u16 ExtensionsLength // u16 ExtensionsLength
size_t l,ll; size_t l, ll;
l = 1+2+2+1+3+2+32; l = 1 + 2 + 2 + 1 + 3 + 2 + 32;
// SessionIDLength // SessionIDLength
if (len<(l+1)) return false; if (len < (l + 1)) return false;
ll = data[6]<<16 | data[7]<<8 | data[8]; // HandshakeProtocol length ll = data[6] << 16 | data[7] << 8 | data[8]; // HandshakeProtocol length
if (len<(ll+9)) return false; if (len < (ll + 9)) return false;
l += data[l]+1; l += data[l] + 1;
// CipherSuitesLength // CipherSuitesLength
if (len<(l+2)) return false; if (len < (l + 2)) return false;
l += ntohs(*(uint16_t*)(data+l))+2; l += ntohs(*(uint16_t*)(data + l)) + 2;
// CompressionMethodsLength // CompressionMethodsLength
if (len<(l+1)) return false; if (len < (l + 1)) return false;
l += data[l]+1; l += data[l] + 1;
// ExtensionsLength // ExtensionsLength
if (len<(l+2)) return false; if (len < (l + 2)) return false;
data+=l; len-=l; data += l; len -= l;
l=ntohs(*(uint16_t*)data); l = ntohs(*(uint16_t*)data);
data+=2; len-=2; data += 2; len -= 2;
if (l<len) return false; if (l < len) return false;
uint16_t ntype=htons(type); uint16_t ntype = htons(type);
while(l>=4) while (l >= 4)
{ {
uint16_t etype=*(uint16_t*)data; uint16_t etype = *(uint16_t*)data;
size_t elen=ntohs(*(uint16_t*)(data+2)); size_t elen = ntohs(*(uint16_t*)(data + 2));
data+=4; l-=4; data += 4; l -= 4;
if (l<elen) break; if (l < elen) break;
if (etype==ntype) if (etype == ntype)
{ {
if (ext && len_ext) if (ext && len_ext)
{ {
@ -104,7 +104,7 @@ bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **
} }
return true; return true;
} }
data+=elen; l-=elen; data += elen; l -= elen;
} }
return false; return false;
@ -114,19 +114,76 @@ bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len
const uint8_t *ext; const uint8_t *ext;
size_t elen; size_t elen;
if (!TLSFindExt(data,len,0,&ext,&elen)) return false; if (!TLSFindExt(data, len, 0, &ext, &elen)) return false;
// u16 data+0 - name list length // u16 data+0 - name list length
// u8 data+2 - server name type. 0=host_name // u8 data+2 - server name type. 0=host_name
// u16 data+3 - server name length // u16 data+3 - server name length
if (elen<5 || ext[2]!=0) return false; if (elen < 5 || ext[2] != 0) return false;
size_t slen = ntohs(*(uint16_t*)(ext+3)); size_t slen = ntohs(*(uint16_t*)(ext + 3));
ext+=5; elen-=5; ext += 5; elen -= 5;
if (slen<elen) return false; if (slen < elen) return false;
if (ext && len_host) if (ext && len_host)
{ {
if (slen>=len_host) slen=len_host-1; if (slen >= len_host) slen = len_host - 1;
for(size_t i=0;i<slen;i++) host[i]=tolower(ext[i]); for (size_t i = 0; i < slen; i++) host[i] = tolower(ext[i]);
host[slen]=0; host[slen] = 0;
} }
return true; return true;
} }
#define QUIC_MAX_CID_LENGTH 20
/* Returns the QUIC draft version or 0 if not applicable. */
static inline uint8_t quic_draft_version(uint32_t version) {
/* IETF Draft versions */
if ((version >> 8) == 0xff0000) {
return (uint8_t)version;
}
/* Facebook mvfst, based on draft -22. */
if (version == 0xfaceb001) {
return 22;
}
/* Facebook mvfst, based on draft -27. */
if (version == 0xfaceb002 || version == 0xfaceb00e) {
return 27;
}
/* GQUIC Q050, T050 and T051: they are not really based on any drafts,
* but we must return a sensible value */
if (version == 0x51303530 ||
version == 0x54303530 ||
version == 0x54303531) {
return 27;
}
/* https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-15
"Versions that follow the pattern 0x?a?a?a?a are reserved for use in
forcing version negotiation to be exercised"
It is tricky to return a correct draft version: such number is primarily
used to select a proper salt (which depends on the version itself), but
we don't have a real version here! Let's hope that we need to handle
only latest drafts... */
if ((version & 0x0F0F0F0F) == 0x0a0a0a0a) {
return 29;
}
/* QUIC (final?) constants for v1 are defined in draft-33, but draft-34 is the
final draft version */
if (version == 0x00000001) {
return 34;
}
/* QUIC Version 2 */
/* TODO: for the time being use 100 as a number for V2 and let see how v2 drafts evolve */
if (version == 0x709A50C4) {
return 100;
}
return 0;
}
bool IsQUICInitial(uint8_t *data, size_t len)
{
// long header, fixed bit, type=initial
if (len < 512 || (data[0] & 0xF0) != 0xC0) return false;
uint8_t *p = data + 1;
uint32_t ver = ntohl(*(uint32_t*)p);
if (quic_draft_version(ver) < 11) return false;
p += 4;
if (!*p || *p > QUIC_MAX_CID_LENGTH) return false;
return true;
}

View File

@ -9,3 +9,4 @@ bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_hos
bool IsTLSClientHello(const uint8_t *data, size_t len); 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 TLSFindExt(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 TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host);
bool IsQUICInitial(uint8_t *data, size_t len);