2024-08-04 21:07:34 +00:00
|
|
|
#define _GNU_SOURCE
|
2024-08-12 22:59:04 +00:00
|
|
|
#include "types.h" // IWYU pragma: keep
|
2024-07-30 20:10:00 +00:00
|
|
|
#include "mangle.h"
|
2024-08-04 12:55:07 +00:00
|
|
|
#include "config.h"
|
2024-08-13 17:48:35 +00:00
|
|
|
#include "utils.h"
|
|
|
|
#include "quic.h"
|
|
|
|
#include "logging.h"
|
2024-09-26 15:11:05 +00:00
|
|
|
#include "tls.h"
|
2024-07-30 20:10:00 +00:00
|
|
|
|
2024-09-01 13:07:47 +00:00
|
|
|
#ifndef KERNEL_SPACE
|
2024-08-12 22:59:04 +00:00
|
|
|
#include <stdlib.h>
|
2024-07-30 20:10:00 +00:00
|
|
|
#endif
|
|
|
|
|
2024-08-11 21:23:08 +00:00
|
|
|
int process_packet(const uint8_t *raw_payload, uint32_t raw_payload_len) {
|
|
|
|
if (raw_payload_len > MAX_PACKET_SIZE) {
|
|
|
|
return PKT_ACCEPT;
|
|
|
|
}
|
|
|
|
|
2024-08-13 17:48:35 +00:00
|
|
|
const struct iphdr *iph;
|
2024-08-27 16:27:27 +00:00
|
|
|
const struct ip6_hdr *ip6h;
|
2024-08-13 17:48:35 +00:00
|
|
|
uint32_t iph_len;
|
|
|
|
const uint8_t *ip_payload;
|
|
|
|
uint32_t ip_payload_len;
|
|
|
|
|
2024-08-27 16:27:27 +00:00
|
|
|
int transport_proto = -1;
|
|
|
|
int ipver = netproto_version(raw_payload, raw_payload_len);
|
2024-08-13 17:48:35 +00:00
|
|
|
int ret;
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
lgtrace_start();
|
|
|
|
lgtrace_addp("IPv%d", ipver);
|
2024-09-01 13:07:47 +00:00
|
|
|
|
2024-08-27 16:27:27 +00:00
|
|
|
if (ipver == IP4VERSION) {
|
|
|
|
ret = ip4_payload_split((uint8_t *)raw_payload, raw_payload_len,
|
2024-08-13 17:48:35 +00:00
|
|
|
(struct iphdr **)&iph, &iph_len,
|
|
|
|
(uint8_t **)&ip_payload, &ip_payload_len);
|
|
|
|
|
2024-08-27 16:27:27 +00:00
|
|
|
if (ret < 0)
|
|
|
|
goto accept;
|
|
|
|
|
2024-09-01 13:07:47 +00:00
|
|
|
transport_proto = iph->protocol;
|
2024-08-27 16:27:27 +00:00
|
|
|
|
2024-08-27 17:01:34 +00:00
|
|
|
} else if (ipver == IP6VERSION && config.use_ipv6) {
|
2024-08-27 16:27:27 +00:00
|
|
|
ret = ip6_payload_split((uint8_t *)raw_payload, raw_payload_len,
|
|
|
|
(struct ip6_hdr **)&ip6h, &iph_len,
|
|
|
|
(uint8_t **)&ip_payload, &ip_payload_len);
|
|
|
|
|
|
|
|
if (ret < 0)
|
|
|
|
goto accept;
|
|
|
|
|
2024-09-01 13:07:47 +00:00
|
|
|
transport_proto = ip6h->ip6_nxt;
|
2024-08-27 16:27:27 +00:00
|
|
|
|
2024-08-27 17:01:34 +00:00
|
|
|
} else {
|
|
|
|
lgtracemsg("Unknown layer 3 protocol version: %d", ipver);
|
|
|
|
goto accept;
|
2024-08-27 16:27:27 +00:00
|
|
|
}
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
int verdict = PKT_CONTINUE;
|
|
|
|
|
|
|
|
if (transport_proto == IPPROTO_TCP)
|
|
|
|
lgtrace_addp("TCP");
|
|
|
|
else if (transport_proto == IPPROTO_UDP)
|
|
|
|
lgtrace_addp("UDP");
|
|
|
|
|
2024-08-13 17:48:35 +00:00
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
ITER_CONFIG_SECTIONS(section) {
|
|
|
|
lgtrace_addp("Section #%d", CONFIG_SECTION_NUMBER(section));
|
|
|
|
|
|
|
|
switch (transport_proto) {
|
|
|
|
case IPPROTO_TCP:
|
|
|
|
verdict = process_tcp_packet(section, raw_payload, raw_payload_len);
|
|
|
|
break;
|
|
|
|
case IPPROTO_UDP:
|
|
|
|
verdict = process_udp_packet(section, raw_payload, raw_payload_len);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (verdict == PKT_CONTINUE)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
lgtrace_end();
|
|
|
|
return verdict;
|
2024-08-13 17:48:35 +00:00
|
|
|
}
|
2024-10-13 20:31:26 +00:00
|
|
|
|
|
|
|
accept:
|
|
|
|
lgtrace_end();
|
2024-08-13 17:48:35 +00:00
|
|
|
return PKT_ACCEPT;
|
|
|
|
}
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
int process_tcp_packet(const struct section_config_t *section, const uint8_t *raw_payload, uint32_t raw_payload_len) {
|
2024-08-29 11:45:27 +00:00
|
|
|
const void *ipxh;
|
|
|
|
uint32_t iph_len;
|
2024-08-11 21:23:08 +00:00
|
|
|
const struct tcphdr *tcph;
|
|
|
|
uint32_t tcph_len;
|
|
|
|
const uint8_t *data;
|
|
|
|
uint32_t dlen;
|
|
|
|
|
2024-09-01 13:07:47 +00:00
|
|
|
|
2024-08-27 16:27:27 +00:00
|
|
|
int ipxv = netproto_version(raw_payload, raw_payload_len);
|
|
|
|
|
|
|
|
int ret = tcp_payload_split((uint8_t *)raw_payload, raw_payload_len,
|
2024-08-29 11:45:27 +00:00
|
|
|
(void *)&ipxh, &iph_len,
|
2024-08-27 16:27:27 +00:00
|
|
|
(struct tcphdr **)&tcph, &tcph_len,
|
2024-08-11 21:23:08 +00:00
|
|
|
(uint8_t **)&data, &dlen);
|
|
|
|
|
2024-08-27 16:27:27 +00:00
|
|
|
|
2024-08-11 21:23:08 +00:00
|
|
|
if (ret < 0) {
|
|
|
|
goto accept;
|
2024-08-10 23:10:18 +00:00
|
|
|
}
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
if (tcph->syn && section->synfake) {
|
2024-08-29 11:45:27 +00:00
|
|
|
lgtrace_addp("TCP syn alter");
|
2024-09-01 13:07:47 +00:00
|
|
|
|
|
|
|
NETBUF_ALLOC(payload, MAX_PACKET_SIZE);
|
|
|
|
if (!NETBUF_CHECK(payload)) {
|
|
|
|
lgerror("Allocation error", -ENOMEM);
|
|
|
|
goto accept;
|
|
|
|
}
|
|
|
|
|
2024-08-29 11:45:27 +00:00
|
|
|
memcpy(payload, ipxh, iph_len);
|
|
|
|
memcpy(payload + iph_len, tcph, tcph_len);
|
2024-10-13 20:31:26 +00:00
|
|
|
uint32_t fake_len = section->fake_sni_pkt_sz;
|
2024-08-29 12:49:01 +00:00
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
if (section->synfake_len)
|
|
|
|
fake_len = min(section->synfake_len, fake_len);
|
2024-08-29 12:49:01 +00:00
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
memcpy(payload + iph_len + tcph_len, section->fake_sni_pkt, fake_len);
|
2024-08-29 11:45:27 +00:00
|
|
|
|
|
|
|
|
|
|
|
struct tcphdr *tcph = (struct tcphdr *)(payload + iph_len);
|
|
|
|
if (ipxv == IP4VERSION) {
|
|
|
|
struct iphdr *iph = (struct iphdr *)payload;
|
2024-08-29 12:49:01 +00:00
|
|
|
iph->tot_len = htons(iph_len + tcph_len + fake_len);
|
2024-08-29 11:45:27 +00:00
|
|
|
set_ip_checksum(payload, iph_len);
|
|
|
|
set_tcp_checksum(tcph, iph, iph_len);
|
|
|
|
} else if (ipxv == IP6VERSION) {
|
|
|
|
struct ip6_hdr *ip6h = (struct ip6_hdr *)payload;
|
2024-09-01 13:07:47 +00:00
|
|
|
ip6h->ip6_plen = ntohs(tcph_len + fake_len);
|
2024-08-29 11:45:27 +00:00
|
|
|
set_ip_checksum(ip6h, iph_len);
|
|
|
|
set_tcp_checksum(tcph, ip6h, iph_len);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-08-29 12:49:01 +00:00
|
|
|
ret = instance_config.send_raw_packet(payload, iph_len + tcph_len + fake_len);
|
|
|
|
if (ret < 0) {
|
|
|
|
lgerror("send_syn_altered", ret);
|
2024-09-01 13:07:47 +00:00
|
|
|
|
|
|
|
NETBUF_FREE(payload);
|
2024-08-29 12:49:01 +00:00
|
|
|
goto accept;
|
|
|
|
}
|
2024-09-01 13:07:47 +00:00
|
|
|
|
|
|
|
NETBUF_FREE(payload);
|
2024-08-29 11:45:27 +00:00
|
|
|
goto drop;
|
|
|
|
}
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
if (tcph->syn) goto continue_flow;
|
2024-08-29 12:55:05 +00:00
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
struct tls_verdict vrd = analyze_tls_data(section, data, dlen);
|
|
|
|
lgtrace_addp("TLS analyzed");
|
2024-09-28 20:06:45 +00:00
|
|
|
|
|
|
|
if (vrd.sni_len != 0) {
|
|
|
|
lgtrace_addp("SNI detected: %.*s", vrd.sni_len, data + vrd.sni_offset);
|
|
|
|
}
|
2024-08-11 21:23:08 +00:00
|
|
|
|
|
|
|
if (vrd.target_sni) {
|
2024-08-13 17:48:35 +00:00
|
|
|
lgdebugmsg("Target SNI detected: %.*s", vrd.sni_len, data + vrd.sni_offset);
|
2024-08-11 21:23:08 +00:00
|
|
|
|
|
|
|
uint32_t payload_len = raw_payload_len;
|
2024-09-01 13:07:47 +00:00
|
|
|
NETBUF_ALLOC(payload, MAX_PACKET_SIZE);
|
|
|
|
if (!NETBUF_CHECK(payload)) {
|
|
|
|
lgerror("Allocation error", -ENOMEM);
|
|
|
|
goto accept;
|
|
|
|
}
|
|
|
|
|
2024-08-11 21:23:08 +00:00
|
|
|
memcpy(payload, raw_payload, raw_payload_len);
|
|
|
|
|
2024-08-27 16:27:27 +00:00
|
|
|
void *iph;
|
2024-08-11 21:23:08 +00:00
|
|
|
uint32_t iph_len;
|
|
|
|
struct tcphdr *tcph;
|
|
|
|
uint32_t tcph_len;
|
|
|
|
uint8_t *data;
|
|
|
|
uint32_t dlen;
|
|
|
|
|
2024-08-27 16:27:27 +00:00
|
|
|
int ret = tcp_payload_split(payload, payload_len,
|
2024-08-11 21:23:08 +00:00
|
|
|
&iph, &iph_len, &tcph, &tcph_len,
|
|
|
|
&data, &dlen);
|
2024-08-11 21:45:30 +00:00
|
|
|
|
2024-08-27 16:27:27 +00:00
|
|
|
if (ret < 0) {
|
|
|
|
lgerror("tcp_payload_split in targ_sni", ret);
|
2024-09-01 13:07:47 +00:00
|
|
|
goto accept_lc;
|
2024-08-27 16:27:27 +00:00
|
|
|
}
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
if (section->fk_winsize) {
|
|
|
|
tcph->window = htons(section->fk_winsize);
|
2024-10-12 09:19:33 +00:00
|
|
|
set_tcp_checksum(tcph, iph, iph_len);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (0) {
|
|
|
|
int delta = 2;
|
|
|
|
ret = seqovl_packet(payload, &payload_len, delta);
|
|
|
|
int ret = tcp_payload_split(payload, payload_len,
|
|
|
|
&iph, &iph_len, &tcph, &tcph_len,
|
|
|
|
&data, &dlen);
|
|
|
|
if (ret < 0) {
|
|
|
|
lgerror("seqovl_packet delta %d", ret, delta);
|
|
|
|
}
|
2024-08-11 21:45:30 +00:00
|
|
|
}
|
|
|
|
|
2024-08-11 21:23:08 +00:00
|
|
|
|
|
|
|
if (dlen > 1480 && config.verbose) {
|
2024-08-13 17:48:35 +00:00
|
|
|
lgdebugmsg("WARNING! Client Hello packet is too big and may cause issues!");
|
2024-08-11 21:23:08 +00:00
|
|
|
}
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
if (section->fake_sni) {
|
|
|
|
post_fake_sni(args_default_fake_type(section), iph, iph_len, tcph, tcph_len);
|
2024-08-11 21:23:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
size_t ipd_offset;
|
|
|
|
size_t mid_offset;
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
switch (section->fragmentation_strategy) {
|
2024-08-11 21:23:08 +00:00
|
|
|
case FRAG_STRAT_TCP: {
|
2024-09-28 08:31:46 +00:00
|
|
|
ipd_offset = vrd.sni_target_offset;
|
2024-09-28 20:06:45 +00:00
|
|
|
mid_offset = ipd_offset + vrd.sni_target_len / 2;
|
2024-08-11 21:23:08 +00:00
|
|
|
|
2024-08-17 09:51:53 +00:00
|
|
|
uint32_t poses[2];
|
|
|
|
int cnt = 0;
|
2024-08-11 21:23:08 +00:00
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
if (section->frag_sni_pos && dlen > section->frag_sni_pos) {
|
|
|
|
poses[cnt++] = section->frag_sni_pos;
|
2024-08-17 09:51:53 +00:00
|
|
|
}
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
if (section->frag_middle_sni) {
|
2024-08-17 09:51:53 +00:00
|
|
|
poses[cnt++] = mid_offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cnt > 1 && poses[0] > poses[1]) {
|
|
|
|
uint32_t tmp = poses[0];
|
|
|
|
poses[0] = poses[1];
|
|
|
|
poses[1] = tmp;
|
|
|
|
}
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
ret = send_tcp_frags(section, payload, payload_len, poses, cnt, 0);
|
2024-08-11 21:23:08 +00:00
|
|
|
if (ret < 0) {
|
|
|
|
lgerror("tcp4 send frags", ret);
|
2024-09-01 13:07:47 +00:00
|
|
|
goto accept_lc;
|
2024-08-11 21:23:08 +00:00
|
|
|
}
|
|
|
|
|
2024-09-01 13:07:47 +00:00
|
|
|
goto drop_lc;
|
2024-08-11 21:23:08 +00:00
|
|
|
}
|
|
|
|
break;
|
2024-08-27 16:27:27 +00:00
|
|
|
case FRAG_STRAT_IP:
|
2024-09-20 19:32:30 +00:00
|
|
|
if (ipxv == IP4VERSION) {
|
2024-09-28 08:31:46 +00:00
|
|
|
ipd_offset = ((char *)data - (char *)tcph) + vrd.sni_target_offset;
|
2024-09-28 20:06:45 +00:00
|
|
|
mid_offset = ipd_offset + vrd.sni_target_len / 2;
|
2024-08-11 21:23:08 +00:00
|
|
|
mid_offset += 8 - mid_offset % 8;
|
|
|
|
|
2024-08-17 09:51:53 +00:00
|
|
|
uint32_t poses[2];
|
|
|
|
int cnt = 0;
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
if (section->frag_sni_pos && dlen > section->frag_sni_pos) {
|
|
|
|
poses[cnt] = section->frag_sni_pos + ((char *)data - (char *)tcph);
|
2024-08-17 09:51:53 +00:00
|
|
|
poses[cnt] += 8 - poses[cnt] % 8;
|
|
|
|
cnt++;
|
|
|
|
}
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
if (section->frag_middle_sni) {
|
2024-08-17 09:51:53 +00:00
|
|
|
poses[cnt++] = mid_offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cnt > 1 && poses[0] > poses[1]) {
|
|
|
|
uint32_t tmp = poses[0];
|
|
|
|
poses[0] = poses[1];
|
|
|
|
poses[1] = tmp;
|
|
|
|
}
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
ret = send_ip4_frags(section, payload, payload_len, poses, cnt, 0);
|
2024-08-11 21:23:08 +00:00
|
|
|
if (ret < 0) {
|
|
|
|
lgerror("ip4 send frags", ret);
|
2024-09-01 13:07:47 +00:00
|
|
|
goto accept_lc;
|
2024-08-11 21:23:08 +00:00
|
|
|
}
|
|
|
|
|
2024-09-01 13:07:47 +00:00
|
|
|
goto drop_lc;
|
2024-08-27 16:27:27 +00:00
|
|
|
} else {
|
|
|
|
printf("WARNING: IP fragmentation is supported only for IPv4\n");
|
2024-09-20 19:32:30 +00:00
|
|
|
goto default_send;
|
2024-08-11 21:23:08 +00:00
|
|
|
}
|
|
|
|
default:
|
2024-09-20 19:32:30 +00:00
|
|
|
default_send:
|
2024-08-11 21:23:08 +00:00
|
|
|
ret = instance_config.send_raw_packet(payload, payload_len);
|
|
|
|
if (ret < 0) {
|
|
|
|
lgerror("raw pack send", ret);
|
2024-09-01 13:07:47 +00:00
|
|
|
goto accept_lc;
|
2024-08-11 21:23:08 +00:00
|
|
|
}
|
|
|
|
|
2024-09-01 13:07:47 +00:00
|
|
|
goto drop_lc;
|
2024-08-11 21:23:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-09-01 13:07:47 +00:00
|
|
|
goto drop_lc;
|
|
|
|
|
|
|
|
accept_lc:
|
|
|
|
NETBUF_FREE(payload);
|
|
|
|
goto accept;
|
|
|
|
drop_lc:
|
|
|
|
NETBUF_FREE(payload);
|
2024-08-11 21:23:08 +00:00
|
|
|
goto drop;
|
2024-09-01 13:07:47 +00:00
|
|
|
|
2024-08-11 21:23:08 +00:00
|
|
|
}
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
continue_flow:
|
|
|
|
lgtrace_addp("continue_flow");
|
|
|
|
return PKT_CONTINUE;
|
2024-08-11 21:23:08 +00:00
|
|
|
accept:
|
2024-08-27 16:27:27 +00:00
|
|
|
lgtrace_addp("accept");
|
2024-08-11 21:23:08 +00:00
|
|
|
return PKT_ACCEPT;
|
|
|
|
drop:
|
2024-08-27 16:27:27 +00:00
|
|
|
lgtrace_addp("drop");
|
2024-08-11 21:23:08 +00:00
|
|
|
return PKT_DROP;
|
|
|
|
}
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
int process_udp_packet(const struct section_config_t *section, const uint8_t *pkt, uint32_t pktlen) {
|
2024-09-01 16:56:38 +00:00
|
|
|
const void *iph;
|
2024-08-13 17:48:35 +00:00
|
|
|
uint32_t iph_len;
|
|
|
|
const struct udphdr *udph;
|
|
|
|
const uint8_t *data;
|
|
|
|
uint32_t dlen;
|
2024-09-14 08:18:45 +00:00
|
|
|
int ipver = netproto_version(pkt, pktlen);
|
2024-08-13 17:48:35 +00:00
|
|
|
|
2024-09-01 16:56:38 +00:00
|
|
|
int ret = udp_payload_split((uint8_t *)pkt, pktlen,
|
|
|
|
(void **)&iph, &iph_len,
|
2024-08-13 17:48:35 +00:00
|
|
|
(struct udphdr **)&udph,
|
|
|
|
(uint8_t **)&data, &dlen);
|
|
|
|
|
2024-09-14 08:18:45 +00:00
|
|
|
|
2024-08-13 17:48:35 +00:00
|
|
|
if (ret < 0) {
|
|
|
|
lgtrace_addp("undefined");
|
|
|
|
goto accept;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dlen > 10 && config.verbose >= VERBOSE_TRACE) {
|
|
|
|
printf("UDP payload start: [ ");
|
|
|
|
for (int i = 0; i < 10; i++) {
|
|
|
|
printf("%02x ", data[i]);
|
|
|
|
}
|
|
|
|
printf("], ");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
if (section->quic_drop) {
|
2024-10-12 09:19:33 +00:00
|
|
|
lgtrace_addp("QUIC probe");
|
|
|
|
const struct quic_lhdr *qch;
|
|
|
|
uint32_t qch_len;
|
|
|
|
struct quic_cids qci;
|
|
|
|
uint8_t *quic_raw_payload;
|
|
|
|
uint32_t quic_raw_plen;
|
|
|
|
ret = quic_parse_data((uint8_t *)data, dlen,
|
|
|
|
(struct quic_lhdr **)&qch, &qch_len, &qci,
|
|
|
|
&quic_raw_payload, &quic_raw_plen);
|
|
|
|
|
|
|
|
if (ret < 0) {
|
|
|
|
lgtrace_addp("undefined type");
|
|
|
|
goto accept_quic;
|
|
|
|
}
|
2024-08-13 17:48:35 +00:00
|
|
|
|
2024-10-12 09:19:33 +00:00
|
|
|
lgtrace_addp("QUIC detected");
|
|
|
|
uint8_t qtype = qch->type;
|
2024-08-13 17:48:35 +00:00
|
|
|
|
|
|
|
goto drop;
|
2024-10-12 09:19:33 +00:00
|
|
|
|
|
|
|
if (qch->version == QUIC_V1)
|
|
|
|
qtype = quic_convtype_v1(qtype);
|
|
|
|
else if (qch->version == QUIC_V2)
|
|
|
|
qtype = quic_convtype_v2(qtype);
|
|
|
|
|
|
|
|
if (qtype != QUIC_INITIAL_TYPE) {
|
|
|
|
lgtrace_addp("quic message type: %d", qtype);
|
|
|
|
goto accept_quic;
|
|
|
|
}
|
|
|
|
|
|
|
|
lgtrace_addp("quic initial message");
|
2024-08-13 17:48:35 +00:00
|
|
|
}
|
|
|
|
|
2024-10-12 09:19:33 +00:00
|
|
|
/*
|
|
|
|
if (1) {
|
|
|
|
lgtrace_addp("Probe udp");
|
|
|
|
if (ipver == IP4VERSION && ntohs(udph->dest) > 30) {
|
|
|
|
lgtrace_addp("udp fool");
|
|
|
|
const uint8_t *payload;
|
|
|
|
uint32_t payload_len;
|
|
|
|
|
|
|
|
uint32_t poses[10];
|
|
|
|
int cnt = 3;
|
|
|
|
|
|
|
|
poses[0] = 8;
|
|
|
|
for (int i = 1; i < cnt; i++) {
|
|
|
|
poses[i] = poses[i - 1] + 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = send_ip4_frags(pkt, pktlen, poses, cnt, 0);
|
|
|
|
if (ret < 0) {
|
|
|
|
lgerror("ip4 send frags", ret);
|
|
|
|
goto accept;
|
|
|
|
}
|
|
|
|
|
|
|
|
goto drop;
|
|
|
|
} else {
|
|
|
|
printf("WARNING: IP fragmentation is supported only for IPv4\n");
|
|
|
|
goto accept;
|
|
|
|
}
|
2024-08-13 17:48:35 +00:00
|
|
|
}
|
2024-10-12 09:19:33 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
continue_flow:
|
|
|
|
lgtrace_addp("continue_flow");
|
|
|
|
return PKT_CONTINUE;
|
|
|
|
accept_quic:
|
2024-08-13 17:48:35 +00:00
|
|
|
accept:
|
|
|
|
return PKT_ACCEPT;
|
|
|
|
drop:
|
|
|
|
return PKT_DROP;
|
|
|
|
}
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
int send_ip4_frags(const struct section_config_t *section, const uint8_t *packet, uint32_t pktlen, const uint32_t *poses, uint32_t poses_sz, uint32_t dvs) {
|
2024-08-11 21:23:08 +00:00
|
|
|
if (poses_sz == 0) {
|
2024-10-13 20:31:26 +00:00
|
|
|
if (section->seg2_delay && ((dvs > 0) ^ section->frag_sni_reverse)) {
|
2024-08-11 21:23:08 +00:00
|
|
|
if (!instance_config.send_delayed_packet) {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
lgtrace_addp("Sent %d delayed for %d", pktlen, section->seg2_delay);
|
2024-08-11 21:23:08 +00:00
|
|
|
instance_config.send_delayed_packet(
|
2024-10-13 20:31:26 +00:00
|
|
|
packet, pktlen, section->seg2_delay);
|
2024-08-11 21:23:08 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
} else {
|
2024-10-12 09:19:33 +00:00
|
|
|
lgtrace_addp("Sent %d bytes", pktlen);
|
2024-08-11 21:23:08 +00:00
|
|
|
return instance_config.send_raw_packet(
|
|
|
|
packet, pktlen);
|
|
|
|
}
|
|
|
|
} else {
|
2024-09-01 13:07:47 +00:00
|
|
|
NETBUF_ALLOC(frag1, MAX_PACKET_SIZE);
|
|
|
|
if (!NETBUF_CHECK(frag1)) {
|
|
|
|
lgerror("Allocation error", -ENOMEM);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
NETBUF_ALLOC(frag2, MAX_PACKET_SIZE);
|
|
|
|
if (!NETBUF_CHECK(frag2)) {
|
|
|
|
lgerror("Allocation error", -ENOMEM);
|
|
|
|
NETBUF_FREE(frag1);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2024-10-12 09:19:33 +00:00
|
|
|
NETBUF_ALLOC(fake_pad, MAX_PACKET_SIZE);
|
|
|
|
if (!NETBUF_CHECK(fake_pad)) {
|
|
|
|
lgerror("Allocation error", -ENOMEM);
|
|
|
|
NETBUF_FREE(frag1);
|
|
|
|
NETBUF_FREE(frag2);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2024-08-10 23:10:18 +00:00
|
|
|
uint32_t f1len = MAX_PACKET_SIZE;
|
|
|
|
uint32_t f2len = MAX_PACKET_SIZE;
|
2024-10-12 09:19:33 +00:00
|
|
|
uint32_t fake_pad_len = MAX_PACKET_SIZE;
|
2024-08-10 23:10:18 +00:00
|
|
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (dvs > poses[0]) {
|
2024-08-13 17:48:35 +00:00
|
|
|
lgerror("send_frags: Recursive dvs(%d) is more than poses0(%d)", -EINVAL, dvs, poses[0]);
|
2024-09-01 13:07:47 +00:00
|
|
|
ret = -EINVAL;
|
|
|
|
goto erret_lc;
|
2024-08-10 23:10:18 +00:00
|
|
|
}
|
|
|
|
|
2024-10-12 09:19:33 +00:00
|
|
|
uint32_t frag_pos = poses[0] - dvs;
|
|
|
|
frag_pos += 8 - frag_pos % 8;
|
|
|
|
|
|
|
|
ret = ip4_frag(packet, pktlen, frag_pos,
|
2024-08-10 23:10:18 +00:00
|
|
|
frag1, &f1len, frag2, &f2len);
|
|
|
|
|
|
|
|
if (ret < 0) {
|
2024-08-13 17:48:35 +00:00
|
|
|
lgerror("send_frags: frag: with context packet with size %d, position: %d, recursive dvs: %d", ret, pktlen, poses[0], dvs);
|
2024-09-01 13:07:47 +00:00
|
|
|
goto erret_lc;
|
2024-08-10 23:10:18 +00:00
|
|
|
}
|
|
|
|
|
2024-10-12 09:19:33 +00:00
|
|
|
dvs += frag_pos;
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
if (section->frag_sni_reverse)
|
2024-08-11 21:23:08 +00:00
|
|
|
goto send_frag2;
|
|
|
|
send_frag1:
|
2024-10-13 20:31:26 +00:00
|
|
|
ret = send_ip4_frags(section, frag1, f1len, NULL, 0, 0);
|
2024-08-10 23:10:18 +00:00
|
|
|
if (ret < 0) {
|
2024-09-01 13:07:47 +00:00
|
|
|
goto erret_lc;
|
2024-08-10 23:10:18 +00:00
|
|
|
}
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
if (section->frag_sni_reverse)
|
2024-09-01 13:07:47 +00:00
|
|
|
goto out_lc;
|
2024-08-11 21:23:08 +00:00
|
|
|
|
2024-10-12 09:19:33 +00:00
|
|
|
send_fake:
|
|
|
|
/*
|
2024-10-13 20:31:26 +00:00
|
|
|
if (section->frag_sni_faked) {
|
|
|
|
ITER_FAKE_STRAT(section->faking_strategy, strategy) {
|
2024-10-12 09:19:33 +00:00
|
|
|
uint32_t iphfl;
|
|
|
|
fake_pad_len = f2len;
|
|
|
|
ret = ip4_payload_split(frag2, f2len, NULL, &iphfl, NULL, NULL);
|
|
|
|
if (ret < 0) {
|
|
|
|
lgerror("Invalid frag2", ret);
|
|
|
|
goto erret_lc;
|
|
|
|
}
|
|
|
|
memcpy(fake_pad, frag2, iphfl + sizeof(struct udphdr));
|
|
|
|
memset(fake_pad + iphfl + sizeof(struct udphdr), 0, f2len - iphfl - sizeof(struct udphdr));
|
|
|
|
((struct iphdr *)fake_pad)->tot_len = htons(fake_pad_len);
|
|
|
|
((struct iphdr *)fake_pad)->id = 1;
|
|
|
|
((struct iphdr *)fake_pad)->ttl = 8;
|
|
|
|
((struct iphdr *)fake_pad)->frag_off = 0;
|
|
|
|
ip4_set_checksum((struct iphdr*)fake_pad);
|
|
|
|
// *(struct udphdr *)(fake_pad + iphfl) = *(struct udphdr *)(frag2 + iphfl);
|
|
|
|
ret = send_ip4_frags(fake_pad, fake_pad_len, NULL, 0, 0);
|
|
|
|
if (ret < 0) {
|
|
|
|
goto erret_lc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
if (section->frag_sni_reverse)
|
2024-10-12 09:19:33 +00:00
|
|
|
goto send_frag1;
|
|
|
|
|
2024-08-11 21:23:08 +00:00
|
|
|
send_frag2:
|
2024-10-13 20:31:26 +00:00
|
|
|
ret = send_ip4_frags(section, frag2, f2len, poses + 1, poses_sz - 1, dvs);
|
2024-08-10 23:10:18 +00:00
|
|
|
if (ret < 0) {
|
2024-09-01 13:07:47 +00:00
|
|
|
goto erret_lc;
|
2024-08-10 23:10:18 +00:00
|
|
|
}
|
2024-08-11 21:23:08 +00:00
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
if (section->frag_sni_reverse)
|
2024-10-12 09:19:33 +00:00
|
|
|
goto send_fake;
|
2024-09-01 13:07:47 +00:00
|
|
|
|
|
|
|
out_lc:
|
|
|
|
NETBUF_FREE(frag1);
|
|
|
|
NETBUF_FREE(frag2);
|
2024-10-12 09:19:33 +00:00
|
|
|
NETBUF_FREE(fake_pad);
|
2024-09-01 13:07:47 +00:00
|
|
|
goto out;
|
|
|
|
erret_lc:
|
|
|
|
NETBUF_FREE(frag1);
|
|
|
|
NETBUF_FREE(frag2);
|
2024-10-12 09:19:33 +00:00
|
|
|
NETBUF_FREE(fake_pad);
|
2024-09-01 13:07:47 +00:00
|
|
|
return ret;
|
2024-08-10 23:10:18 +00:00
|
|
|
}
|
|
|
|
|
2024-08-11 21:23:08 +00:00
|
|
|
out:
|
2024-08-10 23:10:18 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
int send_tcp_frags(const struct section_config_t *section, const uint8_t *packet, uint32_t pktlen, const uint32_t *poses, uint32_t poses_sz, uint32_t dvs) {
|
2024-08-10 23:10:18 +00:00
|
|
|
if (poses_sz == 0) {
|
2024-10-13 20:31:26 +00:00
|
|
|
if (section->seg2_delay && ((dvs > 0) ^ section->frag_sni_reverse)) {
|
2024-08-10 23:10:18 +00:00
|
|
|
if (!instance_config.send_delayed_packet) {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
instance_config.send_delayed_packet(
|
2024-10-13 20:31:26 +00:00
|
|
|
packet, pktlen, section->seg2_delay);
|
2024-08-10 23:10:18 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
} else {
|
2024-08-27 16:27:27 +00:00
|
|
|
lgtrace_addp("raw send packet of %d bytes with %d dvs", pktlen, dvs);
|
2024-08-10 23:10:18 +00:00
|
|
|
return instance_config.send_raw_packet(
|
|
|
|
packet, pktlen);
|
|
|
|
}
|
|
|
|
} else {
|
2024-09-01 13:07:47 +00:00
|
|
|
NETBUF_ALLOC(frag1, MAX_PACKET_SIZE);
|
|
|
|
if (!NETBUF_CHECK(frag1)) {
|
|
|
|
lgerror("Allocation error", -ENOMEM);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
NETBUF_ALLOC(frag2, MAX_PACKET_SIZE);
|
|
|
|
if (!NETBUF_CHECK(frag2)) {
|
|
|
|
lgerror("Allocation error", -ENOMEM);
|
|
|
|
NETBUF_FREE(frag1);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2024-08-10 23:10:18 +00:00
|
|
|
uint32_t f1len = MAX_PACKET_SIZE;
|
|
|
|
uint32_t f2len = MAX_PACKET_SIZE;
|
|
|
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (dvs > poses[0]) {
|
2024-08-13 17:48:35 +00:00
|
|
|
lgerror("send_frags: Recursive dvs(%d) is more than poses0(%d)", -EINVAL, dvs, poses[0]);
|
2024-09-01 13:07:47 +00:00
|
|
|
ret = -EINVAL;
|
|
|
|
goto erret_lc;
|
2024-08-10 23:10:18 +00:00
|
|
|
}
|
|
|
|
|
2024-08-27 16:27:27 +00:00
|
|
|
|
|
|
|
ret = tcp_frag(packet, pktlen, poses[0] - dvs,
|
2024-08-10 23:10:18 +00:00
|
|
|
frag1, &f1len, frag2, &f2len);
|
|
|
|
|
2024-10-12 09:19:33 +00:00
|
|
|
|
2024-08-27 16:27:27 +00:00
|
|
|
lgtrace_addp("Packet split in %d bytes position of payload start, dvs: %d to two packets of %d and %d lengths", poses[0], dvs, f1len, f2len);
|
|
|
|
|
2024-08-10 23:10:18 +00:00
|
|
|
if (ret < 0) {
|
2024-08-27 16:27:27 +00:00
|
|
|
lgerror("send_frags: tcp_frag: with context packet with size %d, position: %d, recursive dvs: %d", ret, pktlen, poses[0], dvs);
|
2024-09-01 13:07:47 +00:00
|
|
|
goto erret_lc;
|
2024-08-10 23:10:18 +00:00
|
|
|
}
|
|
|
|
|
2024-08-27 16:27:27 +00:00
|
|
|
|
2024-10-12 09:19:33 +00:00
|
|
|
dvs += poses[0];
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
if (section->frag_sni_reverse)
|
2024-08-10 23:10:18 +00:00
|
|
|
goto send_frag2;
|
|
|
|
|
|
|
|
send_frag1:
|
|
|
|
{
|
2024-10-13 20:31:26 +00:00
|
|
|
ret = send_tcp_frags(section, frag1, f1len, NULL, 0, 0);
|
2024-08-10 23:10:18 +00:00
|
|
|
if (ret < 0) {
|
2024-09-01 13:07:47 +00:00
|
|
|
goto erret_lc;
|
2024-08-10 23:10:18 +00:00
|
|
|
}
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
if (section->frag_sni_reverse)
|
2024-09-01 13:07:47 +00:00
|
|
|
goto out_lc;
|
2024-08-10 23:10:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
send_fake:
|
2024-10-13 20:31:26 +00:00
|
|
|
if (section->frag_sni_faked) {
|
2024-10-12 09:19:33 +00:00
|
|
|
uint32_t iphfl, tcphfl;
|
|
|
|
void *iph;
|
|
|
|
struct tcphdr *tcph;
|
|
|
|
ret = tcp_payload_split(frag2, f2len, &iph, &iphfl, &tcph, &tcphfl, NULL, NULL);
|
2024-10-13 20:31:26 +00:00
|
|
|
struct fake_type f_type = args_default_fake_type(section);
|
2024-10-12 09:19:33 +00:00
|
|
|
if ((f_type.strategy.strategy & FAKE_STRAT_PAST_SEQ) == FAKE_STRAT_PAST_SEQ) {
|
|
|
|
f_type.strategy.strategy ^= FAKE_STRAT_PAST_SEQ;
|
|
|
|
f_type.strategy.strategy |= FAKE_STRAT_RAND_SEQ;
|
|
|
|
f_type.strategy.randseq_offset = dvs;
|
2024-08-10 23:10:18 +00:00
|
|
|
}
|
2024-10-12 09:19:33 +00:00
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
f_type.seg2delay = section->seg2_delay;
|
2024-10-12 09:19:33 +00:00
|
|
|
|
|
|
|
post_fake_sni(f_type, iph, iphfl, tcph, tcphfl);
|
2024-08-10 23:10:18 +00:00
|
|
|
}
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
if (section->frag_sni_reverse)
|
2024-08-11 12:34:58 +00:00
|
|
|
goto send_frag1;
|
2024-08-10 23:10:18 +00:00
|
|
|
|
|
|
|
send_frag2:
|
|
|
|
{
|
2024-10-13 20:31:26 +00:00
|
|
|
ret = send_tcp_frags(section, frag2, f2len, poses + 1, poses_sz - 1, dvs);
|
2024-08-10 23:10:18 +00:00
|
|
|
if (ret < 0) {
|
2024-09-01 13:07:47 +00:00
|
|
|
goto erret_lc;
|
2024-08-10 23:10:18 +00:00
|
|
|
}
|
|
|
|
|
2024-10-13 20:31:26 +00:00
|
|
|
if (section->frag_sni_reverse)
|
2024-08-10 23:10:18 +00:00
|
|
|
goto send_fake;
|
|
|
|
}
|
2024-09-01 13:07:47 +00:00
|
|
|
out_lc:
|
|
|
|
NETBUF_FREE(frag1);
|
|
|
|
NETBUF_FREE(frag2);
|
|
|
|
goto out;
|
|
|
|
erret_lc:
|
|
|
|
NETBUF_FREE(frag1);
|
|
|
|
NETBUF_FREE(frag2);
|
|
|
|
return ret;
|
2024-08-10 23:10:18 +00:00
|
|
|
}
|
|
|
|
out:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-10-12 09:19:33 +00:00
|
|
|
int post_fake_sni(struct fake_type f_type,
|
|
|
|
const void *iph, unsigned int iph_len,
|
|
|
|
const struct tcphdr *tcph, unsigned int tcph_len) {
|
|
|
|
|
2024-08-27 16:27:27 +00:00
|
|
|
uint8_t rfsiph[128];
|
2024-08-11 21:23:08 +00:00
|
|
|
uint8_t rfstcph[60];
|
|
|
|
int ret;
|
2024-08-10 23:10:18 +00:00
|
|
|
|
2024-10-12 09:19:33 +00:00
|
|
|
int ipxv = netproto_version(iph, iph_len);
|
|
|
|
|
2024-08-11 21:23:08 +00:00
|
|
|
memcpy(rfsiph, iph, iph_len);
|
|
|
|
memcpy(rfstcph, tcph, tcph_len);
|
2024-08-10 23:10:18 +00:00
|
|
|
|
2024-08-27 16:27:27 +00:00
|
|
|
void *fsiph = (void *)rfsiph;
|
2024-08-11 21:23:08 +00:00
|
|
|
struct tcphdr *fstcph = (void *)rfstcph;
|
2024-08-10 23:10:18 +00:00
|
|
|
|
2024-10-12 09:19:33 +00:00
|
|
|
ITER_FAKE_STRAT(f_type.strategy.strategy, strategy) {
|
|
|
|
struct fake_type fake_seq_type = f_type;
|
|
|
|
fake_seq_type.strategy.strategy = strategy;
|
2024-07-30 20:10:00 +00:00
|
|
|
|
2024-09-28 08:31:46 +00:00
|
|
|
// one goes for default fake
|
2024-10-12 09:19:33 +00:00
|
|
|
for (int i = 0; i < fake_seq_type.sequence_len; i++) {
|
2024-09-26 15:11:05 +00:00
|
|
|
NETBUF_ALLOC(fake_sni, MAX_PACKET_SIZE);
|
|
|
|
if (!NETBUF_CHECK(fake_sni)) {
|
2024-09-01 13:07:47 +00:00
|
|
|
lgerror("Allocation error", -ENOMEM);
|
2024-09-26 15:11:05 +00:00
|
|
|
return -ENOMEM;
|
2024-09-01 13:07:47 +00:00
|
|
|
}
|
2024-09-26 15:11:05 +00:00
|
|
|
uint32_t fsn_len = MAX_PACKET_SIZE;
|
|
|
|
|
|
|
|
ret = gen_fake_sni(
|
|
|
|
fake_seq_type,
|
|
|
|
fsiph, iph_len, fstcph, tcph_len,
|
|
|
|
fake_sni, &fsn_len);
|
|
|
|
if (ret < 0) {
|
|
|
|
lgerror("gen_fake_sni", ret);
|
|
|
|
goto erret_lc;
|
2024-09-01 13:07:47 +00:00
|
|
|
}
|
|
|
|
|
2024-09-26 15:11:05 +00:00
|
|
|
lgtrace_addp("post fake sni #%d", i + 1);
|
2024-10-12 09:19:33 +00:00
|
|
|
|
|
|
|
if (f_type.seg2delay) {
|
|
|
|
ret = instance_config.send_delayed_packet(fake_sni, fsn_len, f_type.seg2delay);
|
|
|
|
} else {
|
|
|
|
ret = instance_config.send_raw_packet(fake_sni, fsn_len);
|
|
|
|
}
|
2024-09-26 15:11:05 +00:00
|
|
|
if (ret < 0) {
|
|
|
|
lgerror("send fake sni", ret);
|
|
|
|
goto erret_lc;
|
|
|
|
}
|
2024-10-12 09:19:33 +00:00
|
|
|
uint32_t iph_len;
|
|
|
|
uint32_t tcph_len;
|
|
|
|
uint32_t plen;
|
|
|
|
ret = tcp_payload_split(
|
|
|
|
fake_sni, fsn_len,
|
|
|
|
&fsiph, &iph_len,
|
|
|
|
&fstcph, &tcph_len,
|
|
|
|
NULL, &plen);
|
2024-08-14 23:31:48 +00:00
|
|
|
|
2024-10-12 09:19:33 +00:00
|
|
|
if (ret < 0) {
|
|
|
|
lgtrace_addp("continue fake seq");
|
|
|
|
goto erret_lc;
|
|
|
|
}
|
2024-08-14 23:31:48 +00:00
|
|
|
|
2024-09-01 13:07:47 +00:00
|
|
|
|
2024-10-12 09:19:33 +00:00
|
|
|
if (!(strategy == FAKE_STRAT_PAST_SEQ ||
|
|
|
|
strategy == FAKE_STRAT_RAND_SEQ)) {
|
2024-09-26 15:11:05 +00:00
|
|
|
fstcph->seq = htonl(ntohl(fstcph->seq) + plen);
|
|
|
|
}
|
2024-10-12 09:19:33 +00:00
|
|
|
|
|
|
|
if (ipxv == IP4VERSION) {
|
|
|
|
((struct iphdr *)fsiph)->id = htons(ntohs(((struct iphdr *)fsiph)->id) + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(rfsiph, fsiph, iph_len);
|
|
|
|
|
|
|
|
memcpy(rfstcph, fstcph, tcph_len);
|
|
|
|
fsiph = (void *)rfsiph;
|
|
|
|
fstcph = (void *)rfstcph;
|
2024-09-26 15:11:05 +00:00
|
|
|
|
|
|
|
NETBUF_FREE(fake_sni);
|
|
|
|
continue;
|
|
|
|
erret_lc:
|
|
|
|
NETBUF_FREE(fake_sni);
|
|
|
|
return ret;
|
2024-08-14 23:31:48 +00:00
|
|
|
}
|
2024-08-16 19:47:55 +00:00
|
|
|
}
|
|
|
|
|
2024-08-10 23:10:18 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2024-09-26 15:11:05 +00:00
|
|
|
|