mirror of
https://github.com/hufrea/byedpi.git
synced 2024-12-22 06:15:14 +00:00
init
This commit is contained in:
commit
08e1e1d8c7
10
Makefile
Normal file
10
Makefile
Normal file
@ -0,0 +1,10 @@
|
||||
TARGET = ciadpi
|
||||
CC ?= gcc
|
||||
CFLAGS += -std=c99 -O2
|
||||
SOURCES = packets.c main.c conev.c proxy.c desync.c
|
||||
|
||||
all:
|
||||
$(CC) $(CFLAGS) $(SOURCES) -I . -o $(TARGET)
|
||||
|
||||
clean:
|
||||
rm -f $(TARGET) *.o
|
194
conev.c
Normal file
194
conev.c
Normal file
@ -0,0 +1,194 @@
|
||||
#define CONEV_H
|
||||
#include <conev.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
struct poolhd *init_pool(int count)
|
||||
{
|
||||
struct poolhd *pool = malloc(sizeof(struct poolhd));
|
||||
if (!pool) {
|
||||
return 0;
|
||||
}
|
||||
pool->max = count;
|
||||
pool->count = 0;
|
||||
|
||||
#ifndef NOEPOLL
|
||||
int efd = epoll_create(count);
|
||||
if (efd < 0) {
|
||||
perror("epoll_create");
|
||||
free(pool);
|
||||
return 0;
|
||||
}
|
||||
pool->efd = efd;
|
||||
#endif
|
||||
pool->pevents = malloc(sizeof(*pool->pevents) * count);
|
||||
pool->links = malloc(sizeof(*pool->links) * count);
|
||||
pool->items = malloc(sizeof(*pool->items) * count);
|
||||
|
||||
if (!pool->pevents || !pool->links || !pool->items) {
|
||||
destroy_pool(pool);
|
||||
return 0;
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
pool->links[i] = &(pool->items[i]);
|
||||
}
|
||||
return pool;
|
||||
}
|
||||
|
||||
|
||||
struct eval *add_event(struct poolhd *pool, enum eid type,
|
||||
int fd, int e)
|
||||
{
|
||||
if (pool->count >= pool->max)
|
||||
return 0;
|
||||
struct eval *val = pool->links[pool->count];
|
||||
|
||||
val->fd = fd;
|
||||
val->index = pool->count;
|
||||
val->type = type;
|
||||
val->pair = 0;
|
||||
val->send_count = 0;
|
||||
val->flag = 0;
|
||||
|
||||
#ifndef NOEPOLL
|
||||
struct epoll_event ev = {
|
||||
EPOLLIN | EPOLLERR | EPOLLRDHUP | e, {val}
|
||||
};
|
||||
if (epoll_ctl(pool->efd, EPOLL_CTL_ADD, fd, &ev)) {
|
||||
return 0;
|
||||
}
|
||||
val->events = ev.events;
|
||||
#else
|
||||
struct pollfd *pfd = &(pool->pevents[pool->count]);
|
||||
|
||||
pfd->fd = fd;
|
||||
pfd->events = POLLIN | POLLERR | POLLRDHUP | e;
|
||||
pfd->revents = 0;
|
||||
#endif
|
||||
|
||||
pool->count++;
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
void del_event(struct poolhd *pool, struct eval *val)
|
||||
{
|
||||
if (!val->fd) {
|
||||
return;
|
||||
}
|
||||
close(val->fd);
|
||||
val->fd = 0;
|
||||
pool->count--;
|
||||
|
||||
struct eval *ev = pool->links[pool->count];
|
||||
if (ev != val)
|
||||
{
|
||||
int index = val->index;
|
||||
pool->links[index] = ev;
|
||||
pool->links[pool->count] = val;
|
||||
#ifdef NOEPOLL
|
||||
pool->pevents[index] = pool->pevents[pool->count];
|
||||
#endif
|
||||
ev->index = index;
|
||||
}
|
||||
if (val->pair) {
|
||||
val->pair->pair = 0;
|
||||
del_event(pool, val->pair);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void destroy_pool(struct poolhd *pool)
|
||||
{
|
||||
for (int x = 0; x < pool->count; x++) {
|
||||
struct eval *val = pool->links[x];
|
||||
if (val->fd) close(val->fd);
|
||||
}
|
||||
if (pool->items) {
|
||||
free(pool->items);
|
||||
}
|
||||
if (pool->links) {
|
||||
free(pool->links);
|
||||
}
|
||||
if (pool->pevents) {
|
||||
free(pool->pevents);
|
||||
}
|
||||
#ifndef NOEPOLL
|
||||
if (pool->efd)
|
||||
close(pool->efd);
|
||||
#endif
|
||||
memset(pool, 0, sizeof(*pool));
|
||||
free(pool);
|
||||
}
|
||||
|
||||
|
||||
#ifndef NOEPOLL
|
||||
struct eval *next_event(struct poolhd *pool, int *offs, int *type)
|
||||
{
|
||||
int i = *offs;
|
||||
if (i < 0) {
|
||||
i = (epoll_wait(pool->efd, pool->pevents, pool->max, -1) - 1);
|
||||
if (i < 0) {
|
||||
perror("epoll_wait");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
*offs = i - 1;
|
||||
*type = pool->pevents[i].events;
|
||||
return pool->pevents[i].data.ptr;
|
||||
}
|
||||
|
||||
|
||||
int mod_etype(struct poolhd *pool, struct eval *val, int type, char add)
|
||||
{
|
||||
struct epoll_event ev = {
|
||||
.events = val->events, .data = {val}
|
||||
};
|
||||
if (add)
|
||||
ev.events |= type;
|
||||
else
|
||||
ev.events &= ~type;
|
||||
val->events = ev.events;
|
||||
int s = epoll_ctl(pool->efd, EPOLL_CTL_MOD, val->fd, &ev);
|
||||
if (s)
|
||||
perror("epoll_ctl");
|
||||
return s;
|
||||
}
|
||||
|
||||
#else
|
||||
struct eval *next_event(struct poolhd *pool, int *offs, int *typel)
|
||||
{
|
||||
for (int i = *offs; ; i--) {
|
||||
if (i < 0) {
|
||||
if (poll(pool->pevents, pool->count, -1) <= 0) {
|
||||
perror("poll");
|
||||
return 0;
|
||||
}
|
||||
i = pool->count - 1;
|
||||
}
|
||||
short type = pool->pevents[i].revents;
|
||||
if (!type)
|
||||
continue;
|
||||
|
||||
pool->pevents[i].revents = 0;
|
||||
*offs = i - 1;
|
||||
*typel = type;
|
||||
return pool->links[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int mod_etype(struct poolhd *pool, struct eval *val, int type, char add)
|
||||
{
|
||||
int index = val->index;
|
||||
if (add)
|
||||
pool->pevents[index].events |= type;
|
||||
else
|
||||
pool->pevents[index].events &= ~type;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
83
conev.h
Normal file
83
conev.h
Normal file
@ -0,0 +1,83 @@
|
||||
#include <stdint.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#ifndef __linux__
|
||||
#define NOEPOLL
|
||||
#endif
|
||||
|
||||
#ifndef NOEPOLL
|
||||
#include <sys/epoll.h>
|
||||
#define POLLIN EPOLLIN
|
||||
#define POLLOUT EPOLLOUT
|
||||
#define POLLERR EPOLLERR
|
||||
#define POLLHUP EPOLLHUP
|
||||
#define POLLRDHUP EPOLLRDHUP
|
||||
#else
|
||||
#include <sys/poll.h>
|
||||
#ifndef POLLRDHUP
|
||||
#define POLLRDHUP POLLHUP
|
||||
#endif
|
||||
#endif
|
||||
|
||||
enum eid {
|
||||
EV_ACCEPT,
|
||||
EV_REQUEST,
|
||||
EV_CONNECT,
|
||||
EV_IGNORE,
|
||||
EV_TUNNEL
|
||||
};
|
||||
|
||||
#define FLAG_NOSEND 1
|
||||
#define FLAG_HTTP 2
|
||||
#define FLAG_S4 4
|
||||
#define FLAG_S5 8
|
||||
#define FLAG_CONN 16
|
||||
|
||||
#ifndef CONEV_H
|
||||
char *eid_name[] = {
|
||||
"EV_ACCEPT",
|
||||
"EV_REQUEST",
|
||||
"EV_CONNECT",
|
||||
"EV_IGNORE",
|
||||
"EV_TUNNEL"
|
||||
};
|
||||
#endif
|
||||
|
||||
struct eval {
|
||||
int fd;
|
||||
int index;
|
||||
enum eid type;
|
||||
struct eval *pair;
|
||||
size_t send_count;
|
||||
int flag;
|
||||
#ifndef NOEPOLL
|
||||
uint32_t events;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct poolhd {
|
||||
int max;
|
||||
int count;
|
||||
int efd;
|
||||
struct eval **links;
|
||||
struct eval *items;
|
||||
#ifndef NOEPOLL
|
||||
struct epoll_event *pevents;
|
||||
#else
|
||||
struct pollfd *pevents;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct poolhd *init_pool(int count);
|
||||
|
||||
struct eval *add_event(struct poolhd *pool, enum eid type, int fd, int e);
|
||||
|
||||
struct eval *add_pair(struct poolhd *pool, struct eval *val, int sfd, int e);
|
||||
|
||||
void del_event(struct poolhd *pool, struct eval *val);
|
||||
|
||||
void destroy_pool(struct poolhd *pool);
|
||||
|
||||
struct eval *next_event(struct poolhd *pool, int *offs, int *type);
|
||||
|
||||
int mod_etype(struct poolhd *pool, struct eval *val, int type, char rm);
|
159
desync.c
Normal file
159
desync.c
Normal file
@ -0,0 +1,159 @@
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sys/sendfile.h>
|
||||
#else
|
||||
#include <sys/uio.h>
|
||||
#define sendfile(outfd, infd, start, len) sendfile(infd, outfd, start, len, 0, 0)
|
||||
#endif
|
||||
|
||||
#ifdef MFD_CLOEXEC
|
||||
#include <sys/syscall.h>
|
||||
#define memfd_create(name, flags) syscall(__NR_memfd_create, name, flags);
|
||||
#else
|
||||
#define memfd_create(name, flags) fileno(tmpfile())
|
||||
#endif
|
||||
|
||||
#include <params.h>
|
||||
#include <packets.h>
|
||||
|
||||
|
||||
int fake_attack(int sfd, char *buffer, ssize_t n, int cnt, int pos)
|
||||
{
|
||||
struct packet pkt = cnt != IS_HTTP ? fake_tls : fake_http;
|
||||
size_t psz = pkt.size;
|
||||
|
||||
int ffd = memfd_create("name", O_RDWR);
|
||||
if (ffd < 0) {
|
||||
perror("memfd_create");
|
||||
return -1;
|
||||
}
|
||||
char *p = 0;
|
||||
int status = -1;
|
||||
|
||||
while (status) {
|
||||
if (ftruncate(ffd, pos) < 0) {
|
||||
perror("ftruncate");
|
||||
break;
|
||||
}
|
||||
p = mmap(0, pos, PROT_WRITE, MAP_SHARED, ffd, 0);
|
||||
if (p == MAP_FAILED) {
|
||||
perror("mmap");
|
||||
break;
|
||||
}
|
||||
memcpy(p, pkt.data, psz < pos ? psz : pos);
|
||||
|
||||
if (setsockopt(sfd, IPPROTO_IP, IP_TTL,
|
||||
¶ms.ttl, sizeof(params.ttl)) < 0) {
|
||||
perror("setsockopt IP_TTL");
|
||||
break;
|
||||
}
|
||||
if (sendfile(sfd, ffd, 0, pos) < 0) {
|
||||
perror("sendfile");
|
||||
break;
|
||||
}
|
||||
usleep(params.sfdelay);
|
||||
memcpy(p, buffer, pos);
|
||||
|
||||
if (setsockopt(sfd, IPPROTO_IP, IP_TTL,
|
||||
¶ms.def_ttl, sizeof(int)) < 0) {
|
||||
perror("setsockopt IP_TTL");
|
||||
break;
|
||||
}
|
||||
if (send(sfd, buffer + pos, n - pos, 0) < 0) {
|
||||
perror("send");
|
||||
break;
|
||||
}
|
||||
status = 0;
|
||||
}
|
||||
if (p) munmap(p, pos);
|
||||
close(ffd);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
int disorder_attack(int sfd, char *buffer, ssize_t n, int pos)
|
||||
{
|
||||
int bttl = 1;
|
||||
if (setsockopt(sfd, IPPROTO_IP, IP_TTL,
|
||||
(char *)&bttl, sizeof(bttl)) < 0) {
|
||||
perror("setsockopt IP_TTL");
|
||||
return -1;
|
||||
}
|
||||
if (send(sfd, buffer, pos, 0) < 0) {
|
||||
perror("send");
|
||||
return -1;
|
||||
}
|
||||
if (setsockopt(sfd, IPPROTO_IP, IP_TTL,
|
||||
(char *)¶ms.def_ttl, sizeof(int)) < 0) {
|
||||
perror("setsockopt IP_TTL");
|
||||
return -1;
|
||||
}
|
||||
if (send(sfd, buffer + pos, n - pos, 0) < 0) {
|
||||
perror("send");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int desync(int sfd, char *buffer, ssize_t n)
|
||||
{
|
||||
int pos = params.split;
|
||||
char *host = 0;
|
||||
int len = 0, type = 0;
|
||||
|
||||
if ((len = parse_tls(buffer, n, &host))) {
|
||||
type = IS_HTTPS;
|
||||
}
|
||||
else if ((len = parse_http(buffer, n, &host, 0))) {
|
||||
type = IS_HTTP;
|
||||
}
|
||||
LOG(LOG_S, "host: %.*s\n", len, host);
|
||||
|
||||
if (type == IS_HTTP && params.mod_http) {
|
||||
if (mod_http(buffer, n, params.mod_http)) {
|
||||
fprintf(stderr, "mod http error\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (host && params.split_host)
|
||||
pos += (host - buffer);
|
||||
else if (pos < 0)
|
||||
pos += n;
|
||||
|
||||
LOG(LOG_L, "split pos: %d, n: %ld\n", pos, n);
|
||||
|
||||
if (pos <= 0 || pos >= n ||
|
||||
params.attack == DESYNC_NONE ||
|
||||
(!type && params.de_known))
|
||||
{
|
||||
if (send(sfd, buffer, n, 0) < 0) {
|
||||
perror("send");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else switch (params.attack) {
|
||||
case DESYNC_FAKE:
|
||||
return fake_attack(sfd, buffer, n, type, pos);
|
||||
|
||||
case DESYNC_DISORDER:
|
||||
return disorder_attack(sfd, buffer, n, pos);
|
||||
|
||||
case DESYNC_SPLIT:
|
||||
default:
|
||||
if (send(sfd, buffer, pos, 0) < 0) {
|
||||
perror("send");
|
||||
return -1;
|
||||
}
|
||||
if (send(sfd, buffer + pos, n - pos, 0) < 0) {
|
||||
perror("send");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
407
main.c
Normal file
407
main.c
Normal file
@ -0,0 +1,407 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <getopt.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <params.h>
|
||||
#include <proxy.h>
|
||||
#include <packets.h>
|
||||
|
||||
#define VERSION 1
|
||||
|
||||
struct packet fake_tls = {
|
||||
sizeof(tls_data), tls_data
|
||||
},
|
||||
fake_http = {
|
||||
sizeof(http_data), http_data
|
||||
};
|
||||
|
||||
|
||||
struct params params = {
|
||||
.ttl = 8,
|
||||
.split = 3,
|
||||
.sfdelay = 3000,
|
||||
.attack = DESYNC_NONE,
|
||||
.split_host = 0,
|
||||
.def_ttl = 0,
|
||||
.mod_http = 0,
|
||||
|
||||
.mode = MODE_PROXY_S,
|
||||
.ipv6 = 1,
|
||||
.resolve = 1,
|
||||
.de_known = 0,
|
||||
.max_open = 512,
|
||||
|
||||
.bfsize = 16384,
|
||||
.send_bfsz = 65536,
|
||||
.nack_max = 65536 * 2 - 16384,
|
||||
.debug = 0
|
||||
};
|
||||
|
||||
|
||||
char *ftob(char *name)
|
||||
{
|
||||
char *buffer = 0;
|
||||
|
||||
FILE *file = fopen(name, "rb");
|
||||
if (!file)
|
||||
return 0;
|
||||
do {
|
||||
if (fseek(file, 0, SEEK_END)) {
|
||||
break;
|
||||
}
|
||||
long size = ftell(file);
|
||||
if (!size || fseek(file, 0, SEEK_SET)) {
|
||||
break;
|
||||
}
|
||||
if (!(buffer = malloc(size))) {
|
||||
break;
|
||||
}
|
||||
if (fread(buffer, 1, size, file) != size) {
|
||||
free(buffer);
|
||||
buffer = 0;
|
||||
}
|
||||
} while (0);
|
||||
fclose(file);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
void daemonize(void)
|
||||
{
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
perror("fork");
|
||||
exit(1);
|
||||
}
|
||||
else if (pid) {
|
||||
exit(0);
|
||||
}
|
||||
if (setsid() < 0) {
|
||||
exit(1);
|
||||
}
|
||||
close(STDIN_FILENO);
|
||||
close(STDOUT_FILENO);
|
||||
close(STDERR_FILENO);
|
||||
|
||||
open("/dev/null", O_RDWR);
|
||||
dup(0);
|
||||
dup(0);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct sockaddr_ina s = {
|
||||
.in = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_port = htons(1080)
|
||||
}};
|
||||
|
||||
char daemon = 0;
|
||||
char *pidfile = 0;
|
||||
|
||||
const char help_text[] = {
|
||||
//"Proxy:\n"
|
||||
" -i, --ip, <ip> Listening IP address\n"
|
||||
" -p, --port <num> Listening port num\n"
|
||||
" -D, --daemon Daemonize\n"
|
||||
" -f, --pidfile <file> Write pid to file\n"
|
||||
" -c, --max-conn <count> Connection count limit, default 512\n"
|
||||
#ifdef __linux__
|
||||
" -T, --transparent Get address with getsockopt\n"
|
||||
#endif
|
||||
" -N, --no-domain Deny domain resolving\n"
|
||||
" -K, --desync-known Desync only HTTP and TLS with SNI\n"
|
||||
//"Desync:\n"
|
||||
" -m, --method <s|d|f> Desync method: split,disorder,fake\n"
|
||||
" -s, --split-pos <offset> Split position, default 3\n"
|
||||
" -H, --split-at-host Add Host/SNI offset to split position\n"
|
||||
" -t, --ttl <num> TTL of fake packets, default 8\n"
|
||||
" -l, --fake-tls <file>\n"
|
||||
" -o, --fake-http <file> Set custom fake packet\n"
|
||||
" -n, --tls-sni <str> Change SNI in fake CH\n"
|
||||
" -M, --mod-http <h,d,r> Modify http: hcsmix,dcsmix,rmspace\n"
|
||||
};
|
||||
|
||||
const struct option options[] = {
|
||||
{"daemon", 0, 0, 'D'},
|
||||
{"transparent", 0, 0, 'T'},
|
||||
{"http", 0, 0, 'P'}, //
|
||||
{"socks", 0, 0, 'O'},
|
||||
{"no-domain", 0, 0, 'N'},
|
||||
{"no-ipv6", 0, 0, 'X'}, //
|
||||
{"desync-known ", 0, 0, 'K'},
|
||||
{"split-at-host", 0, 0, 'H'},
|
||||
{"help", 0, 0, 'h'},
|
||||
{"version", 0, 0, 'v'},
|
||||
{"pidfile", 1, 0, 'f'},
|
||||
{"ip", 1, 0, 'i'},
|
||||
{"port", 1, 0, 'p'},
|
||||
{"bfs", 1, 0, 'b'}, //
|
||||
{"snd-bfs", 1, 0, 'B'}, //
|
||||
{"max-conn", 1, 0, 'c'},
|
||||
{"method", 1, 0, 'm'},
|
||||
{"split-pos", 1, 0, 's'},
|
||||
{"ttl", 1, 0, 't'},
|
||||
{"fake-tls", 1, 0, 'l'},
|
||||
{"fake-http", 1, 0, 'o'},
|
||||
{"tls-sni", 1, 0, 'n'},
|
||||
{"mod-http", 1, 0, 'M'},
|
||||
{"global-ttl", 1, 0, 'g'}, //
|
||||
{"delay", 1, 0, 'w'}, //
|
||||
{"debug", 1, 0, 'x'}, //
|
||||
|
||||
{0}
|
||||
};
|
||||
int rez;
|
||||
int invalid = 0;
|
||||
|
||||
long val = 0;
|
||||
char *end = 0;
|
||||
|
||||
while (!invalid && (rez = getopt_long_only(argc, argv,
|
||||
"DTPONXKHhvf:i:p:b:B:c:m:s:t:l:o:n:M:g:w:x:", options, 0)) != -1) {
|
||||
switch (rez) {
|
||||
|
||||
case 'D':
|
||||
daemon = 1;
|
||||
break;
|
||||
case 'f':
|
||||
pidfile = optarg;
|
||||
break;
|
||||
case 'T':
|
||||
params.mode = MODE_TRANSPARENT;
|
||||
break;
|
||||
case 'O':
|
||||
params.mode = MODE_PROXY_S;
|
||||
break;
|
||||
case 'N':
|
||||
params.resolve = 0;
|
||||
break;
|
||||
case 'X':
|
||||
params.ipv6 = 0;
|
||||
break;
|
||||
case 'P':
|
||||
params.mode = MODE_PROXY_H;
|
||||
break;
|
||||
case 'K':
|
||||
params.de_known = 1;
|
||||
break;
|
||||
case 'h':
|
||||
printf(help_text);
|
||||
return 0;
|
||||
case 'v':
|
||||
printf("%d\n", VERSION);
|
||||
return 0;
|
||||
|
||||
case 'b': //
|
||||
val = strtol(optarg, &end, 0);
|
||||
if (val <= 0 || val > INT_MAX/4 || *end)
|
||||
invalid = 1;
|
||||
else
|
||||
params.bfsize = val;
|
||||
break;
|
||||
|
||||
case 'B': //
|
||||
val = strtol(optarg, &end, 0);
|
||||
if (val <= 0 || val > INT_MAX || *end)
|
||||
invalid = 1;
|
||||
else
|
||||
params.send_bfsz = val;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
if (strchr(optarg, ':'))
|
||||
s.in.sin_family = AF_INET6;
|
||||
else
|
||||
s.in.sin_family = AF_INET;
|
||||
|
||||
if (!inet_pton(s.in.sin_family, optarg,
|
||||
(s.in.sin_family == AF_INET ?
|
||||
(char *)&s.in.sin_addr : (char *)&s.in6.sin6_addr)))
|
||||
invalid = 1;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
val = strtol(optarg, &end, 0);
|
||||
if (val <= 0 || val > 0xffff || *end)
|
||||
invalid = 1;
|
||||
else
|
||||
s.in.sin_port = htons(val);
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
val = strtol(optarg, &end, 0);
|
||||
if (val <= 0 || val >= (0xffff/2) || *end)
|
||||
invalid = 1;
|
||||
else
|
||||
params.max_open = val;
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
if (params.attack != DESYNC_NONE) {
|
||||
fprintf(stderr, "methods incompatible\n");
|
||||
invalid = 1;
|
||||
}
|
||||
else switch (*optarg) {
|
||||
case 's':
|
||||
params.attack = DESYNC_SPLIT;
|
||||
break;
|
||||
case 'd':
|
||||
params.attack = DESYNC_DISORDER;
|
||||
break;
|
||||
case 'f':
|
||||
params.attack = DESYNC_FAKE;
|
||||
break;
|
||||
default:
|
||||
invalid = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 's':
|
||||
val = strtol(optarg, &end, 0);
|
||||
if (val < INT_MIN || val > INT_MAX || *end)
|
||||
invalid = 1;
|
||||
else
|
||||
params.split = val;
|
||||
break;
|
||||
|
||||
case 'H':
|
||||
params.split_host = 1;
|
||||
break;
|
||||
|
||||
case 't':
|
||||
val = strtol(optarg, &end, 0);
|
||||
if (val <= 0 || val > 255 || *end)
|
||||
invalid = 1;
|
||||
else
|
||||
params.ttl = val;
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
if (change_tls_sni(optarg, fake_tls.data, fake_tls.size)) {
|
||||
fprintf(stderr, "error chsni\n");
|
||||
return -1;
|
||||
}
|
||||
printf("sni: %s\n", optarg);
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
fake_tls.data = ftob(optarg);
|
||||
if (!fake_tls.data) {
|
||||
perror("read file");
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
fake_http.data = ftob(optarg);
|
||||
if (!fake_http.data) {
|
||||
perror("read file");
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
end = optarg;
|
||||
while (end && !invalid) {
|
||||
switch (*end) {
|
||||
case 'r':
|
||||
params.mod_http |= MH_SPACE;
|
||||
break;
|
||||
case 'h':
|
||||
params.mod_http |= MH_HMIX;
|
||||
break;
|
||||
case 'd':
|
||||
params.mod_http |= MH_DMIX;
|
||||
break;
|
||||
default:
|
||||
invalid = 1;
|
||||
continue;
|
||||
}
|
||||
end = strchr(end, ',');
|
||||
if (end) end++;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'g': //
|
||||
val = strtol(optarg, &end, 0);
|
||||
if (val <= 0 || val > 255 || *end)
|
||||
invalid = 1;
|
||||
else
|
||||
params.def_ttl = val;
|
||||
break;
|
||||
|
||||
case 'w': //
|
||||
params.sfdelay = strtoul(optarg, &end, 0);
|
||||
if (optarg == end || *end)
|
||||
invalid = 1;
|
||||
break;
|
||||
|
||||
case 'x': //
|
||||
params.debug = strtol(optarg, 0, 0);
|
||||
if (params.debug < 0)
|
||||
invalid = 1;
|
||||
break;
|
||||
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -1;
|
||||
|
||||
default:
|
||||
printf("?: %c\n", rez);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (invalid) {
|
||||
fprintf(stderr, "invalid value: -%c %s\n", rez, optarg);
|
||||
return -1;
|
||||
}
|
||||
if (params.send_bfsz * 2 <= params.bfsize) {
|
||||
fprintf(stderr, "send buffer too small\n");
|
||||
return -1;
|
||||
} else {
|
||||
params.nack_max = val * 2 - params.bfsize;
|
||||
}
|
||||
FILE *file;
|
||||
if (pidfile) {
|
||||
file = fopen(pidfile, "w");
|
||||
if (!file) {
|
||||
perror("fopen");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (daemon) {
|
||||
daemonize();
|
||||
}
|
||||
if (pidfile) {
|
||||
fprintf(file, "%d", getpid());
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
if (!params.def_ttl) {
|
||||
int orig_ttl, fd;
|
||||
socklen_t tsize = sizeof(orig_ttl);
|
||||
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
|
||||
perror("socket");
|
||||
return -1;
|
||||
}
|
||||
if (getsockopt(fd, IPPROTO_IP, IP_TTL,
|
||||
(char *)&orig_ttl, &tsize) < 0) {
|
||||
perror("getsockopt IP_TTL");
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
close(fd);
|
||||
params.def_ttl = orig_ttl;
|
||||
}
|
||||
return listener(s);
|
||||
}
|
214
packets.c
Normal file
214
packets.c
Normal file
@ -0,0 +1,214 @@
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <packets.h>
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#define ANTOHS(data, i) \
|
||||
(uint16_t)((data[i] << 8) + (uint8_t)data[i + 1])
|
||||
|
||||
|
||||
char tls_data[517] = {
|
||||
"\x16\x03\x01\x02\x00\x01\x00\x01\xfc\x03\x03\x03\x5f"
|
||||
"\x6f\x2c\xed\x13\x22\xf8\xdc\xb2\xf2\x60\x48\x2d\x72"
|
||||
"\x66\x6f\x57\xdd\x13\x9d\x1b\x37\xdc\xfa\x36\x2e\xba"
|
||||
"\xf9\x92\x99\x3a\x20\xf9\xdf\x0c\x2e\x8a\x55\x89\x82"
|
||||
"\x31\x63\x1a\xef\xa8\xbe\x08\x58\xa7\xa3\x5a\x18\xd3"
|
||||
"\x96\x5f\x04\x5c\xb4\x62\xaf\x89\xd7\x0f\x8b\x00\x3e"
|
||||
"\x13\x02\x13\x03\x13\x01\xc0\x2c\xc0\x30\x00\x9f\xcc"
|
||||
"\xa9\xcc\xa8\xcc\xaa\xc0\x2b\xc0\x2f\x00\x9e\xc0\x24"
|
||||
"\xc0\x28\x00\x6b\xc0\x23\xc0\x27\x00\x67\xc0\x0a\xc0"
|
||||
"\x14\x00\x39\xc0\x09\xc0\x13\x00\x33\x00\x9d\x00\x9c"
|
||||
"\x00\x3d\x00\x3c\x00\x35\x00\x2f\x00\xff\x01\x00\x01"
|
||||
"\x75\x00\x00\x00\x16\x00\x14\x00\x00\x11\x77\x77\x77"
|
||||
"\x2e\x77\x69\x6b\x69\x70\x65\x64\x69\x61\x2e\x6f\x72"
|
||||
"\x67\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x16"
|
||||
"\x00\x14\x00\x1d\x00\x17\x00\x1e\x00\x19\x00\x18\x01"
|
||||
"\x00\x01\x01\x01\x02\x01\x03\x01\x04\x00\x10\x00\x0e"
|
||||
"\x00\x0c\x02\x68\x32\x08\x68\x74\x74\x70\x2f\x31\x2e"
|
||||
"\x31\x00\x16\x00\x00\x00\x17\x00\x00\x00\x31\x00\x00"
|
||||
"\x00\x0d\x00\x2a\x00\x28\x04\x03\x05\x03\x06\x03\x08"
|
||||
"\x07\x08\x08\x08\x09\x08\x0a\x08\x0b\x08\x04\x08\x05"
|
||||
"\x08\x06\x04\x01\x05\x01\x06\x01\x03\x03\x03\x01\x03"
|
||||
"\x02\x04\x02\x05\x02\x06\x02\x00\x2b\x00\x09\x08\x03"
|
||||
"\x04\x03\x03\x03\x02\x03\x01\x00\x2d\x00\x02\x01\x01"
|
||||
"\x00\x33\x00\x26\x00\x24\x00\x1d\x00\x20\x11\x8c\xb8"
|
||||
"\x8c\xe8\x8a\x08\x90\x1e\xee\x19\xd9\xdd\xe8\xd4\x06"
|
||||
"\xb1\xd1\xe2\xab\xe0\x16\x63\xd6\xdc\xda\x84\xa4\xb8"
|
||||
"\x4b\xfb\x0e\x00\x15\x00\xac\x00\x00\x00\x00\x00\x00"
|
||||
};
|
||||
|
||||
char http_data[43] = {
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"Host: www.wikipedia.org\r\n\r\n"
|
||||
};
|
||||
|
||||
|
||||
int find_tls_ext_offset(uint16_t type, char *data, size_t size)
|
||||
{
|
||||
if (size < 44) {
|
||||
return 0;
|
||||
}
|
||||
uint8_t sid_len = data[43];
|
||||
if (size < 44 + sid_len + 2) {
|
||||
return 0;
|
||||
}
|
||||
uint16_t cip_len = ANTOHS(data, 44 + sid_len);
|
||||
|
||||
size_t skip = 44 + sid_len + 2 + cip_len + 4;
|
||||
if (size <= skip) {
|
||||
return 0;
|
||||
}
|
||||
uint16_t ext_len = ANTOHS(data, skip - 2);
|
||||
|
||||
if (ext_len < (size - skip)) {
|
||||
size = ext_len + skip;
|
||||
}
|
||||
while ((skip + 4) < size) {
|
||||
uint16_t epyt = ANTOHS(data, skip);
|
||||
if (type == epyt) {
|
||||
return skip;
|
||||
}
|
||||
uint16_t len = ANTOHS(data, skip + 2);
|
||||
skip += (len + 4);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int change_tls_sni(char *host, char *buffer, size_t bsize)
|
||||
{
|
||||
int sni_offs, pad_offs;
|
||||
|
||||
if (!(sni_offs = find_tls_ext_offset(0x00, buffer, bsize))) {
|
||||
return -1;
|
||||
}
|
||||
if (!(pad_offs = find_tls_ext_offset(0x15, buffer, bsize))) {
|
||||
return -1;
|
||||
}
|
||||
char *sni = &buffer[sni_offs];
|
||||
char *pad = &buffer[pad_offs];
|
||||
|
||||
uint16_t old_sz = ANTOHS(buffer, sni_offs + 2) - 5;
|
||||
uint16_t free_sz = ANTOHS(buffer, pad_offs + 2);
|
||||
uint16_t new_sz = strlen(host);
|
||||
|
||||
ssize_t diff = new_sz - old_sz;
|
||||
|
||||
if ((free_sz != (bsize - pad_offs - 4))
|
||||
|| free_sz < diff) {
|
||||
return -1;
|
||||
}
|
||||
*(uint16_t *)(sni + 2) = htons(old_sz + diff + 5);
|
||||
*(uint16_t *)(sni + 4) = htons(old_sz + diff + 3);
|
||||
*(uint16_t *)(sni + 7) = htons(old_sz + diff);
|
||||
*(uint16_t *)(pad + 2) = htons(free_sz - diff);
|
||||
|
||||
char *host_end = sni + 9 + old_sz;
|
||||
int oth_sz = bsize - (sni_offs + 9 + old_sz);
|
||||
|
||||
memmove(host_end + diff, host_end, oth_sz);
|
||||
memcpy(sni + 9, host, new_sz);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int parse_tls(char *buffer, size_t bsize, char **hs)
|
||||
{
|
||||
if (ANTOHS(buffer, 0) != 0x1603) {
|
||||
return 0;
|
||||
}
|
||||
int sni_offs = find_tls_ext_offset(0x00, buffer, bsize);
|
||||
|
||||
if (!sni_offs || (sni_offs + 12) >= bsize) {
|
||||
return 0;
|
||||
}
|
||||
uint16_t len = ANTOHS(buffer, sni_offs + 7);
|
||||
|
||||
if ((sni_offs + 9 + len) > bsize) {
|
||||
return 0;
|
||||
}
|
||||
*hs = &buffer[sni_offs + 9];
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
int parse_http(char *buffer, size_t bsize, char **hs, uint16_t *port)
|
||||
{
|
||||
char *host = buffer, *h_end;
|
||||
size_t osz = bsize;
|
||||
|
||||
if (bsize < 16 || *buffer > 'T' || *buffer < 'C') {
|
||||
return 0;
|
||||
}
|
||||
while (1) {
|
||||
host = memchr(host, '\n', osz);
|
||||
if (!host)
|
||||
return 0;
|
||||
host++;
|
||||
osz = bsize - (host - buffer);
|
||||
if (osz < 6)
|
||||
return 0;
|
||||
if (!strncasecmp(host, "Host:", 5))
|
||||
break;
|
||||
}
|
||||
host += 5; osz -= 5;
|
||||
for (; osz && isblank(*host); host++, osz--) {}
|
||||
|
||||
char *l_end = memchr(host, '\n', osz);
|
||||
if (!l_end) {
|
||||
return 0;
|
||||
}
|
||||
for (; isspace(*(l_end - 1)); l_end--) {}
|
||||
|
||||
if (!(isdigit(*(l_end - 1))))
|
||||
h_end = 0;
|
||||
else
|
||||
h_end = memrchr(host, ':', l_end - host);
|
||||
|
||||
if (!h_end) {
|
||||
if (port) *port = 80;
|
||||
h_end = l_end;
|
||||
}
|
||||
else if (port) {
|
||||
char *end;
|
||||
long i = strtol(h_end + 1, &end, 10);
|
||||
if (i <= 0 || end != l_end || i > 0xffff)
|
||||
return 0;
|
||||
*port = i;
|
||||
}
|
||||
*hs = host;
|
||||
return h_end - host;
|
||||
}
|
||||
|
||||
|
||||
int mod_http(char *buffer, size_t bsize, int m)
|
||||
{
|
||||
char *host = 0, *par;
|
||||
int hlen = parse_http(buffer, bsize, &host, 0);
|
||||
if (!hlen)
|
||||
return -1;
|
||||
for (par = host - 1; *par != ':'; par--) {}
|
||||
par -= 4;
|
||||
if (m & MH_HMIX) {
|
||||
par[0] = tolower(par[0]);
|
||||
par[1] = toupper(par[1]);
|
||||
par[3] = toupper(par[3]);
|
||||
}
|
||||
if (m & MH_DMIX) {
|
||||
for (int i = 0; i < hlen; i += 2) {
|
||||
host[i] = toupper(host[i]);
|
||||
}
|
||||
}
|
||||
if (m & MH_SPACE) {
|
||||
for (; !isspace(*(host + hlen)); hlen++) {}
|
||||
int sc = host - (par + 5);
|
||||
memmove(par + 5, host, hlen);
|
||||
memset(par + 5 + hlen, '\t', sc);
|
||||
}
|
||||
return 0;
|
||||
}
|
21
packets.h
Normal file
21
packets.h
Normal file
@ -0,0 +1,21 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define IS_UNKNOWN 0
|
||||
#define IS_HTTP 1
|
||||
#define IS_HTTPS 2
|
||||
|
||||
#define MH_HMIX 1
|
||||
#define MH_SPACE 2
|
||||
#define MH_DMIX 4
|
||||
|
||||
extern char tls_data[517];
|
||||
extern char http_data[43];
|
||||
|
||||
int change_tls_sni(char *host, char *buffer, size_t bsize);
|
||||
|
||||
int parse_tls(char *buffer, size_t bsize, char **hs);
|
||||
|
||||
int parse_http(char *buffer, size_t bsize, char **hs, uint16_t *port);
|
||||
|
||||
int mod_http(char *buffer, size_t bsize, int m);
|
48
params.h
Normal file
48
params.h
Normal file
@ -0,0 +1,48 @@
|
||||
enum mode {
|
||||
MODE_PROXY_H,
|
||||
MODE_PROXY_S,
|
||||
MODE_TRANSPARENT
|
||||
};
|
||||
|
||||
enum demode {
|
||||
DESYNC_NONE,
|
||||
DESYNC_SPLIT,
|
||||
DESYNC_DISORDER,
|
||||
DESYNC_FAKE
|
||||
};
|
||||
|
||||
struct params {
|
||||
int ttl;
|
||||
int split;
|
||||
size_t sfdelay;
|
||||
enum demode attack;
|
||||
char split_host;
|
||||
int def_ttl;
|
||||
int mod_http;
|
||||
|
||||
enum mode mode;
|
||||
char ipv6;
|
||||
char resolve;
|
||||
char de_known;
|
||||
int max_open;
|
||||
|
||||
int debug;
|
||||
size_t bfsize;
|
||||
size_t nack_max;
|
||||
int send_bfsz;
|
||||
};
|
||||
|
||||
extern struct params params;
|
||||
|
||||
struct packet {
|
||||
size_t size;
|
||||
char *data;
|
||||
};
|
||||
extern struct packet fake_tls;
|
||||
extern struct packet fake_http;
|
||||
|
||||
#define LOG_S 1
|
||||
#define LOG_L 2
|
||||
|
||||
#define LOG(s, str, ...) \
|
||||
if (params.debug >= s) printf(str, ##__VA_ARGS__)
|
692
proxy.c
Normal file
692
proxy.c
Normal file
@ -0,0 +1,692 @@
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <netdb.h>
|
||||
|
||||
#include <proxy.h>
|
||||
#include <packets.h>
|
||||
#include <params.h>
|
||||
#include <conev.h>
|
||||
#include <desync.h>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <linux/netfilter_ipv4.h>
|
||||
#endif
|
||||
|
||||
int NOT_EXIT = 1;
|
||||
|
||||
static void on_cancel(int sig) {
|
||||
NOT_EXIT = 0;
|
||||
}
|
||||
|
||||
|
||||
static inline int is_binded_addr(int fd, struct sockaddr_ina *dst)
|
||||
{
|
||||
struct sockaddr_ina me;
|
||||
socklen_t alen = sizeof(me);
|
||||
|
||||
if (getsockname(fd, &me.sa, &alen)) {
|
||||
perror("getsockname");
|
||||
return -1;
|
||||
}
|
||||
if (dst->sa.sa_family != me.sa.sa_family ||
|
||||
dst->in.sin_port != me.in.sin_port) {
|
||||
return 0;
|
||||
}
|
||||
if (dst->sa.sa_family == AF_INET6 ?
|
||||
!memcmp(&dst->in6.sin6_addr, &me.in6.sin6_addr, 16) :
|
||||
dst->in.sin_addr.s_addr == me.in.sin_addr.s_addr) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int resolve(char *host, int len, struct sockaddr_ina *addr)
|
||||
{
|
||||
struct addrinfo hints = {0}, *res = 0;
|
||||
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_flags = AI_ADDRCONFIG;
|
||||
|
||||
char rchar = host[len];
|
||||
host[len] = '\0';
|
||||
|
||||
if (getaddrinfo(host, 0, &hints, &res) || !res) {
|
||||
host[len] = rchar;
|
||||
return -1;
|
||||
}
|
||||
if (res->ai_addr->sa_family == AF_INET6)
|
||||
addr->in6 = *(struct sockaddr_in6 *)res->ai_addr;
|
||||
else
|
||||
addr->in = *(struct sockaddr_in *)res->ai_addr;
|
||||
freeaddrinfo(res);
|
||||
|
||||
host[len] = rchar;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int auth_socks5(int fd, char *buffer, ssize_t n)
|
||||
{
|
||||
if (n <= 2 || (uint8_t)buffer[1] != (n - 2)) {
|
||||
return -1;
|
||||
}
|
||||
buffer[1] = S_AUTH_BAD;
|
||||
long i = 2;
|
||||
for (; i < n; i++)
|
||||
if (buffer[i] == S_AUTH_NONE) {
|
||||
buffer[1] = S_AUTH_NONE;
|
||||
break;
|
||||
}
|
||||
if (send(fd, buffer, 2, 0) < 0) {
|
||||
perror("send");
|
||||
return -1;
|
||||
}
|
||||
return i < n ? 0 : -1;
|
||||
}
|
||||
|
||||
|
||||
int resp_error(int fd, int e, int flag)
|
||||
{
|
||||
if (flag & FLAG_HTTP) {
|
||||
const char *r;
|
||||
if (e) r = "HTTP/1.1 504\r\n\r\n";
|
||||
else r = "HTTP/1.1 200\r\n\r\n";
|
||||
return send(fd, r, 16, 0);
|
||||
}
|
||||
else if (flag & FLAG_S4) {
|
||||
struct s4_req s4r = {
|
||||
.cmd = e ? S4_ER : S4_OK
|
||||
};
|
||||
return send(fd, &s4r, sizeof(s4r), 0);
|
||||
}
|
||||
else if (flag & FLAG_S5) {
|
||||
uint8_t se;
|
||||
switch (e) {
|
||||
case 0: se = S_ER_OK;
|
||||
break;
|
||||
case ECONNREFUSED:
|
||||
se = S_ER_CONN;
|
||||
break;
|
||||
case EHOSTUNREACH:
|
||||
case ETIMEDOUT:
|
||||
se = S_ER_HOST;
|
||||
break;
|
||||
case ENETUNREACH:
|
||||
se = S_ER_NET;
|
||||
break;
|
||||
default: se = S_ER_GEN;
|
||||
}
|
||||
struct s5_rep s5r = {
|
||||
.ver = 0x05, .code = se,
|
||||
.atp = S_ATP_I4
|
||||
};
|
||||
return send(fd, &s5r, sizeof(s5r), 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int handle_socks4(int fd, char *bf,
|
||||
size_t n, struct sockaddr_ina *dst)
|
||||
{
|
||||
if (n < sizeof(struct s4_req) + 1) {
|
||||
return -1;
|
||||
}
|
||||
struct s4_req *r = (struct s4_req *)bf;
|
||||
char er = 0;
|
||||
|
||||
if (r->cmd != S_CMD_CONN) {
|
||||
er = 1;
|
||||
}
|
||||
else if (ntohl(r->i4.s_addr) <= 255) do {
|
||||
er = 1;
|
||||
if (!params.resolve || bf[n - 1])
|
||||
break;
|
||||
char *ie = strchr(bf + sizeof(*r), 0);
|
||||
if (!ie)
|
||||
break;
|
||||
int len = (bf + n - ie) - 2;
|
||||
if (len > 2) {
|
||||
if (resolve(ie + 1, len, dst)) {
|
||||
fprintf(stderr, "not resolved: %.*s\n", len, ie + 1);
|
||||
break;
|
||||
}
|
||||
er = 0;
|
||||
}
|
||||
} while (0);
|
||||
else {
|
||||
dst->in.sin_family = AF_INET;
|
||||
dst->in.sin_addr = r->i4;
|
||||
}
|
||||
if (er) {
|
||||
struct s4_req s4r = {
|
||||
.cmd = S4_ER
|
||||
};
|
||||
if (send(fd, &s4r, sizeof(s4r), 0) < 0)
|
||||
perror("send");
|
||||
return -1;
|
||||
}
|
||||
dst->in.sin_port = r->port;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int handle_socks5(int fd, char *buffer,
|
||||
size_t n, struct sockaddr_ina *addr)
|
||||
{
|
||||
if (n < sizeof(struct s5_rep)) {
|
||||
return -1;
|
||||
}
|
||||
struct s5_req *r = (struct s5_req *)buffer;
|
||||
uint8_t er = 0;
|
||||
|
||||
if (n != (r->atp == S_ATP_I4 ? 10 :
|
||||
(r->atp == S_ATP_ID ? r->id.len + 7 :
|
||||
(r->atp == S_ATP_I6 ? 22 : 0)))) {
|
||||
fprintf(stderr, "ss: bad request\n");
|
||||
return -1;
|
||||
}
|
||||
else if (r->cmd != S_CMD_CONN) {
|
||||
fprintf(stderr, "ss: unsupported cmd: 0x%x\n", r->cmd);
|
||||
er = S_ER_CMD;
|
||||
}
|
||||
else switch (r->atp) {
|
||||
case S_ATP_I4:
|
||||
addr->in.sin_family = AF_INET;
|
||||
addr->in.sin_addr = r->i4;
|
||||
break;
|
||||
|
||||
case S_ATP_ID:
|
||||
if (!params.resolve) {
|
||||
er = S_ER_ATP;
|
||||
break;
|
||||
}
|
||||
if (resolve(r->id.domain, r->id.len, addr)) {
|
||||
fprintf(stderr, "not resolved: %.*s\n", r->id.len, r->id.domain);
|
||||
er = S_ER_HOST;
|
||||
}
|
||||
break;
|
||||
|
||||
case S_ATP_I6:
|
||||
if (!params.ipv6)
|
||||
er = S_ER_ATP;
|
||||
else {
|
||||
addr->in6.sin6_family = AF_INET6;
|
||||
addr->in6.sin6_addr = r->i6;
|
||||
}
|
||||
}
|
||||
LOG(LOG_L, "s5r: cmd: 0x%x, atp: 0x%x\n", r->cmd, r->atp);
|
||||
|
||||
if (er) {
|
||||
struct s5_rep s5r = {
|
||||
.ver = 0x05, .code = er,
|
||||
.atp = S_ATP_I4
|
||||
};
|
||||
if (send(fd, &s5r, sizeof(s5r), 0) < 0)
|
||||
perror("send");
|
||||
return -1;
|
||||
}
|
||||
addr->in.sin_port = *(uint16_t *)&buffer[n - 2];
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int handle_http(int fd, char *buffer,
|
||||
size_t bfsize, struct sockaddr_ina *dst)
|
||||
{
|
||||
char *host = 0;
|
||||
uint16_t port = 443;
|
||||
int cnt = 0;
|
||||
|
||||
ssize_t n = recv(fd, buffer, bfsize, MSG_PEEK);
|
||||
if (n <= 0) {
|
||||
perror("recv proxy");
|
||||
return -1;
|
||||
}
|
||||
int len = parse_http(buffer, n, &host, &port);
|
||||
if (len <= 2) {
|
||||
fprintf(stderr, "parse error\n");
|
||||
return -1;
|
||||
}
|
||||
if (!memcmp(buffer, "CONNECT", 7)) {
|
||||
if (recv(fd, buffer, n, 0) != n) {
|
||||
perror("recv");
|
||||
return -1;
|
||||
}
|
||||
cnt = 1;
|
||||
}
|
||||
if (*host == '[' && host[len - 1] == ']') {
|
||||
host++; len -= 2;
|
||||
}
|
||||
if (resolve(host, len, dst)) {
|
||||
fprintf(stderr, "not resolved: %.*s\n", len, host);
|
||||
return -1;
|
||||
}
|
||||
dst->in.sin_port = htons(port);
|
||||
return cnt;
|
||||
}
|
||||
|
||||
|
||||
int nsendc(int fd, int *wn)
|
||||
{
|
||||
#ifdef SO_NWRITE
|
||||
socklen_t len = sizeof(*wn);
|
||||
if (getsockopt(fd, SOL_SOCKET, SO_NWRITE, wn, &len) < 0) {
|
||||
perror("getsockopt SO_NWRITE");
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
if (ioctl(fd, TIOCOUTQ, wn) < 0 ) {
|
||||
perror("ioctl");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static inline int create_conn(struct poolhd *pool, struct eval *val,
|
||||
enum eid nxt, struct sockaddr_ina *dst)
|
||||
{
|
||||
int sfd = socket(dst->sa.sa_family, SOCK_STREAM, 0);
|
||||
if (sfd < 0) {
|
||||
perror("socket");
|
||||
return -1;
|
||||
}
|
||||
#ifdef __linux__
|
||||
int syn_count = 1;
|
||||
if (setsockopt(sfd, IPPROTO_TCP,
|
||||
TCP_SYNCNT, &syn_count, sizeof(syn_count))) {
|
||||
perror("setsockopt TCP_SYNCNT");
|
||||
close(sfd);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
int one = 1;
|
||||
if (setsockopt(sfd, IPPROTO_TCP,
|
||||
TCP_NODELAY, (char *)&one, sizeof(one))) {
|
||||
perror("setsockopt TCP_NODELAY");
|
||||
close(sfd);
|
||||
return -1;
|
||||
}
|
||||
if (setsockopt(sfd, SOL_SOCKET, SO_SNDBUF,
|
||||
¶ms.send_bfsz, sizeof(params.send_bfsz)) < 0) {
|
||||
close(sfd);
|
||||
perror("setsockopt SO_SNDBUF");
|
||||
return -1;
|
||||
}
|
||||
if (fcntl(sfd, F_SETFL, O_NONBLOCK) < 0) {
|
||||
perror("fcntl");
|
||||
close(sfd);
|
||||
return -1;
|
||||
}
|
||||
int status = connect(sfd, &dst->sa, sizeof(*dst));
|
||||
if (!status || errno != EINPROGRESS) {
|
||||
perror("connect");
|
||||
close(sfd);
|
||||
return -1;
|
||||
}
|
||||
struct eval *pair = add_event(pool, nxt, sfd, POLLOUT);
|
||||
if (!pair) {
|
||||
close(sfd);
|
||||
return -1;
|
||||
}
|
||||
val->pair = pair;
|
||||
pair->pair = val;
|
||||
pair->flag = FLAG_CONN;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static inline int on_request(struct poolhd *pool, struct eval *val,
|
||||
char *buffer, size_t bfsize)
|
||||
{
|
||||
struct sockaddr_ina dst = {0};
|
||||
|
||||
#ifdef __linux__
|
||||
if (params.mode == MODE_TRANSPARENT) {
|
||||
socklen_t alen = sizeof(dst);
|
||||
|
||||
if (getsockopt(val->fd, SOL_IP, SO_ORIGINAL_DST, &dst, &alen)) {
|
||||
perror("getsockopt SO_ORIGINAL_DST");
|
||||
return -1;
|
||||
}
|
||||
if (is_binded_addr(val->fd, &dst)) {
|
||||
fprintf(stderr, "drop connection to self\n");
|
||||
return -1;
|
||||
}
|
||||
mod_etype(pool, val, POLLIN, 0);
|
||||
} else
|
||||
#endif
|
||||
if (params.mode == MODE_PROXY_H) {
|
||||
int cnt = handle_http(val->fd, buffer, bfsize, &dst);
|
||||
if (cnt < 0)
|
||||
return -1;
|
||||
else if (cnt)
|
||||
val->flag = FLAG_HTTP;
|
||||
else
|
||||
mod_etype(pool, val, POLLIN, 0);
|
||||
}
|
||||
else {
|
||||
ssize_t n = recv(val->fd, buffer, bfsize, 0);
|
||||
if (n < 1) {
|
||||
if (n) perror("ss recv");
|
||||
return -1;
|
||||
}
|
||||
if (*buffer == S_VER5) {
|
||||
if (val->flag != FLAG_S5) {
|
||||
if (auth_socks5(val->fd, buffer, n)) {
|
||||
return -1;
|
||||
}
|
||||
val->flag = FLAG_S5;
|
||||
return 0;
|
||||
}
|
||||
if (handle_socks5(val->fd, buffer, n, &dst)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else if (*buffer == S_VER4) {
|
||||
if (handle_socks4(val->fd, buffer, n, &dst)) {
|
||||
return -1;
|
||||
}
|
||||
val->flag = FLAG_S4;
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "ss: invalid version: 0x%x (%lu)\n", *buffer, n);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (create_conn(pool, val, EV_CONNECT, &dst)) {
|
||||
if (resp_error(val->fd, errno, val->flag) < 0)
|
||||
perror("send");
|
||||
return -1;
|
||||
}
|
||||
val->type = EV_IGNORE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static inline int on_accept(struct poolhd *pool, struct eval *val)
|
||||
{
|
||||
struct sockaddr_ina client;
|
||||
struct eval *rval;
|
||||
|
||||
while (1) {
|
||||
socklen_t len = sizeof(client);
|
||||
int c = accept(val->fd, &client.sa, &len);
|
||||
if (c < 0) {
|
||||
if (errno == EAGAIN)
|
||||
break;
|
||||
perror("accept");
|
||||
return -1;
|
||||
}
|
||||
if (fcntl(c, F_SETFL, O_NONBLOCK) < 0) {
|
||||
perror("fcntl");
|
||||
close(c);
|
||||
continue;
|
||||
}
|
||||
int one = 1;
|
||||
if (setsockopt(c, IPPROTO_TCP,
|
||||
TCP_NODELAY, (char *)&one, sizeof(one))) {
|
||||
perror("setsockopt TCP_NODELAY");
|
||||
close(c);
|
||||
continue;
|
||||
}
|
||||
if (setsockopt(c, SOL_SOCKET, SO_SNDBUF,
|
||||
¶ms.send_bfsz, sizeof(params.send_bfsz)) < 0) {
|
||||
perror("setsockopt SO_SNDBUF");
|
||||
close(c);
|
||||
continue;
|
||||
}
|
||||
if (!(rval = add_event(pool, EV_REQUEST, c, 0))) {
|
||||
close(c);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static inline int on_data(struct eval *val, char *buffer, size_t bfsize)
|
||||
{
|
||||
ssize_t n = recv(val->fd, buffer, bfsize, 0);
|
||||
if (n <= 0) {
|
||||
if (n) perror("recv data");
|
||||
return -1;
|
||||
}
|
||||
if (desync(val->pair->fd, buffer, n)) {
|
||||
return -1;
|
||||
}
|
||||
val->type = EV_TUNNEL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static inline int on_connect(struct poolhd *pool, struct eval *val,
|
||||
char *buffer, size_t bfsize, int e)
|
||||
{
|
||||
if (val->flag & FLAG_CONN) {
|
||||
if (!e) {
|
||||
val->type = EV_TUNNEL;
|
||||
mod_etype(pool, val, POLLOUT, 0);
|
||||
}
|
||||
if (val->pair->flag) {
|
||||
int error = 0;
|
||||
socklen_t len = sizeof(error);
|
||||
if (e) {
|
||||
if (getsockopt(val->fd, SOL_SOCKET,
|
||||
SO_ERROR, (char *)&error, &len)) {
|
||||
perror("getsockopt SO_ERROR");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (resp_error(val->pair->fd,
|
||||
error, val->pair->flag) < 0) {
|
||||
perror("send");
|
||||
return -1;
|
||||
}
|
||||
if (e) return -1;
|
||||
val->pair->type = EV_CONNECT;
|
||||
return 0;
|
||||
}
|
||||
else if (!e) {
|
||||
val = val->pair;
|
||||
mod_etype(pool, val, POLLIN, 1);
|
||||
}
|
||||
}
|
||||
if (e) return -1;
|
||||
return on_data(val, buffer, bfsize);
|
||||
}
|
||||
|
||||
|
||||
static inline int on_tunnel(struct poolhd *pool, struct eval *val,
|
||||
char *buffer, size_t bfsize, int out)
|
||||
{
|
||||
ssize_t n = 0;
|
||||
int peek = 0;
|
||||
struct eval *pair = val->pair;
|
||||
|
||||
if (val->flag & FLAG_NOSEND && out) {
|
||||
val->flag &= ~FLAG_NOSEND;
|
||||
|
||||
mod_etype(pool, val, POLLOUT, 0);
|
||||
mod_etype(pool, val->pair, POLLIN, 1);
|
||||
|
||||
pair = val;
|
||||
val = val->pair;
|
||||
}
|
||||
do {
|
||||
if (pair->send_count >= params.nack_max) {
|
||||
int wn = 0;
|
||||
if (nsendc(pair->fd, &wn)) {
|
||||
return -1;
|
||||
}
|
||||
pair->send_count = wn;
|
||||
if (wn) {
|
||||
LOG(LOG_S, "not ack: %d (fd: %d)\n", wn, pair->fd);
|
||||
}
|
||||
if (wn >= params.nack_max) {
|
||||
peek = MSG_PEEK;
|
||||
}
|
||||
}
|
||||
n = recv(val->fd, buffer, bfsize, peek);
|
||||
|
||||
if (n < 0 && errno == EAGAIN)
|
||||
break;
|
||||
if (n < 1) {
|
||||
if (n) perror("recv server");
|
||||
return -1;
|
||||
}
|
||||
if (send(pair->fd, buffer, n, 0) < 0) {
|
||||
if (errno == EAGAIN && peek) {
|
||||
LOG(LOG_S, "EAGAIN, set POLLOUT (fd: %d)\n", pair->fd);
|
||||
|
||||
mod_etype(pool, val, POLLIN, 0);
|
||||
mod_etype(pool, pair, POLLOUT, 1);
|
||||
|
||||
pair->flag |= FLAG_NOSEND;
|
||||
break;
|
||||
}
|
||||
perror("send");
|
||||
return -1;
|
||||
}
|
||||
if (peek) {
|
||||
if (recv(val->fd, buffer, n, 0) != n) {
|
||||
perror("recv");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
pair->send_count += n;
|
||||
|
||||
} while (n == bfsize);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int big_loop(int srvfd)
|
||||
{
|
||||
size_t bfsize = params.bfsize;
|
||||
|
||||
struct poolhd *pool = init_pool(params.max_open * 2 + 1);
|
||||
if (!pool) {
|
||||
perror("init pool");
|
||||
return -1;
|
||||
}
|
||||
char *buffer = malloc(params.bfsize);
|
||||
if (!buffer) {
|
||||
perror("malloc");
|
||||
return -1;
|
||||
}
|
||||
add_event(pool, EV_ACCEPT, srvfd, 0);
|
||||
|
||||
struct eval *val;
|
||||
int i = -1, etype;
|
||||
|
||||
while (NOT_EXIT) {
|
||||
val = next_event(pool, &i, &etype);
|
||||
if (!val) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
perror("(e)poll");
|
||||
break;
|
||||
}
|
||||
LOG(LOG_L, "new event: fd: %d, evt: %s\n", val->fd, eid_name[val->type]);
|
||||
|
||||
if (!val->fd) {
|
||||
continue;
|
||||
}
|
||||
switch (val->type) {
|
||||
case EV_ACCEPT:
|
||||
if (on_accept(pool, val))
|
||||
NOT_EXIT = 0;
|
||||
continue;
|
||||
|
||||
case EV_REQUEST:
|
||||
if ((etype & POLLHUP) ||
|
||||
on_request(pool, val, buffer, bfsize))
|
||||
del_event(pool, val);
|
||||
continue;
|
||||
|
||||
case EV_TUNNEL:
|
||||
if ((etype & POLLHUP) ||
|
||||
on_tunnel(pool, val, buffer, bfsize, etype & POLLOUT))
|
||||
del_event(pool, val);
|
||||
continue;
|
||||
|
||||
case EV_CONNECT:
|
||||
if (on_connect(pool, val, buffer, bfsize, etype & POLLERR))
|
||||
del_event(pool, val);
|
||||
continue;
|
||||
|
||||
case EV_IGNORE:
|
||||
if (etype & (POLLHUP | POLLERR))
|
||||
del_event(pool, val);
|
||||
continue;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "???\n");
|
||||
NOT_EXIT = 0;
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "exit\n");
|
||||
free(buffer);
|
||||
destroy_pool(pool);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int listener(struct sockaddr_ina srv)
|
||||
{
|
||||
if (signal(SIGPIPE, SIG_IGN))
|
||||
perror("signal SIGPIPE!");
|
||||
|
||||
if (signal(SIGINT, on_cancel))
|
||||
perror("signal SIGINT!");
|
||||
|
||||
int srvfd = socket(srv.sa.sa_family, SOCK_STREAM, 0);
|
||||
if (srvfd < 0) {
|
||||
perror("socket");
|
||||
return -1;
|
||||
}
|
||||
if (fcntl(srvfd, F_SETFL, O_NONBLOCK) < 0) {
|
||||
perror("fcntl");
|
||||
close(srvfd);
|
||||
return -1;
|
||||
}
|
||||
int opt = 1;
|
||||
if (setsockopt(srvfd, SOL_SOCKET,
|
||||
SO_REUSEADDR, (char *)&opt, sizeof(opt)) == -1) {
|
||||
perror("setsockopt");
|
||||
close(srvfd);
|
||||
return -1;
|
||||
}
|
||||
if (bind(srvfd, &srv.sa, sizeof(srv)) < 0) {
|
||||
perror("bind");
|
||||
close(srvfd);
|
||||
return -1;
|
||||
}
|
||||
if (listen(srvfd, 10)) {
|
||||
perror("listen");
|
||||
close(srvfd);
|
||||
return -1;
|
||||
}
|
||||
int status = big_loop(srvfd);
|
||||
close(srvfd);
|
||||
return status;
|
||||
}
|
81
proxy.h
Normal file
81
proxy.h
Normal file
@ -0,0 +1,81 @@
|
||||
#include <stdint.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
struct sockaddr_ina {
|
||||
union {
|
||||
struct sockaddr sa;
|
||||
struct sockaddr_in in;
|
||||
struct sockaddr_in6 in6;
|
||||
};
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
struct s4_req {
|
||||
uint8_t ver, cmd;
|
||||
uint16_t port;
|
||||
struct in_addr i4;
|
||||
};
|
||||
|
||||
struct s5_req {
|
||||
uint8_t ver, cmd, zero, atp;
|
||||
union {
|
||||
struct in_addr i4;
|
||||
struct in6_addr i6;
|
||||
struct {
|
||||
uint8_t len;
|
||||
char domain[];
|
||||
} id;
|
||||
};
|
||||
};
|
||||
|
||||
struct s5_rep {
|
||||
uint8_t ver, code, zero, atp;
|
||||
struct {
|
||||
struct in_addr i4;
|
||||
uint16_t port;
|
||||
};
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
enum s_auth {
|
||||
S_AUTH_NONE = 0x00,
|
||||
S_AUTH_GSSAPI = 0x01,
|
||||
S_AUTH_USPA = 0x02,
|
||||
S_AUTH_BAD = 0xff
|
||||
};
|
||||
|
||||
enum s_atp {
|
||||
S_ATP_I4 = 0x01,
|
||||
S_ATP_ID = 0x03,
|
||||
S_ATP_I6 = 0x04
|
||||
};
|
||||
|
||||
enum s_cmd {
|
||||
S_CMD_CONN = 0x01,
|
||||
S_CMD_BIND = 0x02,
|
||||
S_CMD_AUDP = 0x03
|
||||
};
|
||||
|
||||
enum s_err {
|
||||
S_ER_OK = 0x00,
|
||||
S_ER_GEN = 0x01,
|
||||
S_ER_DENY = 0x02,
|
||||
S_ER_NET = 0x03,
|
||||
S_ER_HOST = 0x04,
|
||||
S_ER_CONN = 0x05,
|
||||
S_ER_TTL = 0x06,
|
||||
S_ER_CMD = 0x07,
|
||||
S_ER_ATP = 0x08
|
||||
};
|
||||
|
||||
enum s4_rep {
|
||||
S4_OK = 0x5a,
|
||||
S4_ER = 0x5b
|
||||
};
|
||||
|
||||
#define S_VER5 0x05
|
||||
#define S_VER4 0x04
|
||||
|
||||
int listener(struct sockaddr_ina srv);
|
37
readme.txt
Normal file
37
readme.txt
Normal file
@ -0,0 +1,37 @@
|
||||
Реализация некоторых способов запутывания DPI на Linux в виде SOCKS прокси
|
||||
|
||||
В дополнение к "--help":
|
||||
-m, --method <s|d|f>
|
||||
Способ десинхронизации, комбинировать нельзя
|
||||
split:
|
||||
Разбить первый запрос на два по указанному смещению
|
||||
Если смещение отрицательное, то считать относительно конца
|
||||
Реализация: два вызова send
|
||||
disorder:
|
||||
Как split, но части отправляются в обратном порядке
|
||||
Реализация: устанавливаем TTL=1, отправляем первую часть, затем восстанавливаем значение TTL и отправляем вторую
|
||||
(из-за низкого TTL первая часть не прибудет получателю и ОС выполнит ретрансмисию и снова отправит первую часть, но уже после второй и с нормальным TTL)
|
||||
fake:
|
||||
Как disorder, только перед первым запросом отправляется поддельный такого же размера
|
||||
Реализация: тут используется возможность перезаписи данных после вызове sendfile
|
||||
помещаем в буффер поддельные данные, вызываем sendfile с таким TTL, чтобы тот не дошел до сервера,
|
||||
затем перезаписываем данные оригинальными, восстанавливаем TTL и отправляем вторую часть с помощью send
|
||||
|
||||
-H, --split-at-host
|
||||
Если найден SNI или Host, то считать смещение относительно позиции домена
|
||||
|
||||
-t, --ttl <num>
|
||||
TTL для поддельного пакета, чтобы тот не дошел до сервера, но был обработан DPI
|
||||
|
||||
-n, --tls-sni <str>
|
||||
В качестве поддельного пакета для TLS используется заранее записанный ClientHello,
|
||||
данный параметр модифицирует его, изменяя SNI на указанный
|
||||
|
||||
-M, --mod-http <h[,d,r]>
|
||||
Всякие манипуляции с HTTP пакетом, можно комбинировать
|
||||
hcsmix:
|
||||
"Host: name" -> "hOsT: name"
|
||||
dcsmix:
|
||||
"Host: name" -> "Host: NaMe"
|
||||
rmspace:
|
||||
"Host: name" -> "Host:name\t"
|
Loading…
Reference in New Issue
Block a user