mirror of
https://github.com/bol-van/zapret2.git
synced 2026-10-29 06:02:05 +00:00
nfqws2: timer rework. remove timer-res
This commit is contained in:
1 parent
eb35bb34f5
commit
7db50c018a
8 files changed
+129
-93
No files matched your search
+37
-22
@@ -20,6 +20,8 @@
|
||||
#include "nfqws.h"
|
||||
|
||||
#ifdef __CYGWIN__
|
||||
#include "timer.h"
|
||||
|
||||
#include <sys/cygwin.h>
|
||||
|
||||
#include <wlanapi.h>
|
||||
@@ -1627,32 +1629,34 @@ static bool windivert_recv_exit(void)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static DWORD win_timer_check(t_timer_callback timer_callback, uint64_t *bt_prev)
|
||||
{
|
||||
uint64_t bt,dbt;
|
||||
|
||||
bt = boottime_ms();
|
||||
dbt = bt-*bt_prev;
|
||||
if (dbt>=params.timer_res)
|
||||
{
|
||||
timer_callback(bt);
|
||||
|
||||
*bt_prev = bt;
|
||||
return params.timer_res;
|
||||
}
|
||||
else
|
||||
return params.timer_res-(int)dbt;
|
||||
}
|
||||
|
||||
static bool windivert_recv_filter(HANDLE hFilter, uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa, unsigned int *wa_count, t_timer_callback timer_callback, uint64_t *bt_prev)
|
||||
static bool windivert_recv_filter(HANDLE hFilter, uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa, unsigned int *wa_count, uint64_t *bt_next)
|
||||
{
|
||||
UINT recv_len;
|
||||
DWORD rd,twait,tmax;
|
||||
unsigned int wac;
|
||||
uint64_t bt;
|
||||
|
||||
if (windivert_recv_exit()) return false;
|
||||
if (windivert_recv_exit())
|
||||
return false;
|
||||
|
||||
tmax = win_timer_check(timer_callback, bt_prev);
|
||||
if (params.timers)
|
||||
{
|
||||
if (!*bt_next) *bt_next = TimerPoolNext(params.timers, ¶ms.timers_dirty);
|
||||
bt = boottime_ms();
|
||||
tmax = *bt_next>bt ? (DWORD)(*bt_next-bt) : 0;
|
||||
if (!tmax || params.timers_dirty)
|
||||
{
|
||||
*bt_next = TimerPoolRun(¶ms.timers, ¶ms.timers_dirty, bt);
|
||||
bt = boottime_ms();
|
||||
tmax = *bt_next>bt ? (DWORD)(*bt_next-bt) : 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
*bt_next = 0;
|
||||
tmax = 0;
|
||||
}
|
||||
|
||||
wac = *wa_count * sizeof(WINDIVERT_ADDRESS);
|
||||
if (WinDivertRecvEx(hFilter, packet, *len, &recv_len, 0, wa, &wac, &ovl))
|
||||
@@ -1668,14 +1672,25 @@ static bool windivert_recv_filter(HANDLE hFilter, uint8_t *packet, size_t *len,
|
||||
case ERROR_IO_PENDING:
|
||||
// need to check for signals periodically
|
||||
for(;;)
|
||||
{
|
||||
if (params.timers)
|
||||
{
|
||||
twait = tmax>50 ? 50 : tmax;
|
||||
tmax -= twait;
|
||||
}
|
||||
else
|
||||
twait = 50;
|
||||
// make signals working
|
||||
if (WaitForSingleObject(ovl.hEvent,twait)!=WAIT_TIMEOUT) break;
|
||||
if (windivert_recv_exit())
|
||||
return false;
|
||||
if (!tmax) tmax = win_timer_check(timer_callback, bt_prev);
|
||||
if (params.timers && !tmax)
|
||||
{
|
||||
bt = boottime_ms();
|
||||
*bt_next = TimerPoolRun(¶ms.timers, ¶ms.timers_dirty, bt);
|
||||
bt = boottime_ms();
|
||||
tmax = *bt_next>bt ? (DWORD)(*bt_next-bt) : 0;
|
||||
}
|
||||
}
|
||||
if (!GetOverlappedResult(hFilter,&ovl,&rd,FALSE))
|
||||
{
|
||||
@@ -1700,9 +1715,9 @@ cancel:
|
||||
GetOverlappedResult(hFilter, &ovl, &rd, TRUE);
|
||||
return false;
|
||||
}
|
||||
bool windivert_recv(uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa, unsigned int *wa_count, t_timer_callback timer_callback, uint64_t *bt_prev)
|
||||
bool windivert_recv(uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa, unsigned int *wa_count, uint64_t *bt_next)
|
||||
{
|
||||
return windivert_recv_filter(w_filter,packet,len,wa,wa_count,timer_callback,bt_prev);
|
||||
return windivert_recv_filter(w_filter,packet,len,wa,wa_count,bt_next);
|
||||
}
|
||||
|
||||
static bool windivert_send_filter(HANDLE hFilter, const uint8_t *packet, size_t len, const WINDIVERT_ADDRESS *wa)
|
||||
|
||||
+1
-2
@@ -135,8 +135,7 @@ bool logical_net_filter_present(void);
|
||||
bool logical_net_filter_match(void);
|
||||
bool nlm_list(bool bAll);
|
||||
bool windivert_init(const char *filter);
|
||||
typedef void (*t_timer_callback)(uint64_t bt);
|
||||
bool windivert_recv(uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa, unsigned int *wa_count, t_timer_callback timer_callback, uint64_t *bt_prev);
|
||||
bool windivert_recv(uint8_t *packet, size_t *len, WINDIVERT_ADDRESS *wa, unsigned int *wa_count, uint64_t *bt_next);
|
||||
bool windivert_send(const uint8_t *packet, size_t len, const WINDIVERT_ADDRESS *wa);
|
||||
#else
|
||||
#define ensure_dir_access(dir) ensure_file_access(dir)
|
||||
|
||||
+3
-1
@@ -3825,6 +3825,7 @@ static int luacall_timer_set(lua_State *L)
|
||||
timer->lua_ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
}
|
||||
DLOG("timer: '%s' %s. function '%s' period %llu oneshot %u\n", timer->str, action, timer->func, timer->period, timer->oneshot);
|
||||
params.timers_dirty = true;
|
||||
|
||||
LUA_STACK_GUARD_RETURN(L,0)
|
||||
}
|
||||
@@ -3841,13 +3842,14 @@ static int luacall_timer_del(lua_State *L)
|
||||
{
|
||||
DLOG("timer: '%s' deleted\n", timer->str);
|
||||
TimerPoolDel(¶ms.timers, timer);
|
||||
params.timers_dirty = true;
|
||||
}
|
||||
else
|
||||
DLOG("timer: '%s' not found\n", name);
|
||||
lua_pushboolean(L, !!timer);
|
||||
LUA_STACK_GUARD_RETURN(L,1)
|
||||
}
|
||||
static int lua_push_timer_info(lua_State *L, const timer_pool *timer)
|
||||
static void lua_push_timer_info(lua_State *L, const timer_pool *timer)
|
||||
{
|
||||
lua_newtable(L);
|
||||
if (timer->str) lua_pushf_str(L, "name", timer->str);
|
||||
|
||||
+47
-58
@@ -242,20 +242,27 @@ static int write_pidfile(FILE **Fpid)
|
||||
|
||||
void NoInterceptLoop(void)
|
||||
{
|
||||
useconds_t usec = params.timer_res * 1000;
|
||||
uint64_t bt, bt_next;
|
||||
useconds_t usec;
|
||||
|
||||
if (params.timers)
|
||||
{
|
||||
DLOG("processing timers\n");
|
||||
|
||||
bt_next = TimerPoolNext(params.timers, ¶ms.timers_dirty);
|
||||
while(params.timers)
|
||||
{
|
||||
if (bQuit) goto quit;
|
||||
bt = boottime_ms();
|
||||
if (bt_next>bt)
|
||||
{
|
||||
usec = (useconds_t)(bt_next-bt)*1000U;
|
||||
usleep(usec);
|
||||
if (bQuit) goto quit;
|
||||
}
|
||||
ReloadCheck();
|
||||
lua_do_gc();
|
||||
TimerPoolRun(¶ms.timers, 0);
|
||||
bt_next = TimerPoolRun(¶ms.timers, ¶ms.timers_dirty, 0);
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -442,8 +449,8 @@ static int nfq_main(void)
|
||||
uint8_t *buf=NULL, *mod=NULL;
|
||||
struct nfq_cb_data cbdata = { .sock = -1, .mod = NULL };
|
||||
fd_set fdset;
|
||||
struct timeval tv = {.tv_sec = params.timer_res/1000, .tv_usec = params.timer_res%1000*1000};
|
||||
unsigned int bt,bt_prev,dbt;
|
||||
struct timeval tv;
|
||||
uint64_t bt,dbt,bt_next;
|
||||
|
||||
if (*params.pidfile && !(Fpid = fopen(params.pidfile, "w")))
|
||||
{
|
||||
@@ -519,7 +526,7 @@ static int nfq_main(void)
|
||||
notify_ready();
|
||||
|
||||
fd = nfq_fd(h);
|
||||
bt_prev=0;
|
||||
bt_next = bt = 0;
|
||||
do
|
||||
{
|
||||
if (bQuit) goto quit;
|
||||
@@ -527,6 +534,12 @@ static int nfq_main(void)
|
||||
{
|
||||
if (params.timers)
|
||||
{
|
||||
if (!bt_next) bt_next = TimerPoolNext(params.timers, ¶ms.timers_dirty);
|
||||
bt = boottime_ms();
|
||||
dbt = bt_next>bt ? bt_next-bt : 0;
|
||||
tv.tv_sec = (time_t)(dbt/1000);
|
||||
tv.tv_usec = (suseconds_t)(dbt%1000*1000);
|
||||
|
||||
FD_ZERO(&fdset);
|
||||
FD_SET(fd, &fdset);
|
||||
res = select(fd+1, &fdset, NULL, NULL, &tv);
|
||||
@@ -540,6 +553,7 @@ static int nfq_main(void)
|
||||
}
|
||||
else
|
||||
{
|
||||
bt_next = bt = 0;
|
||||
if (bQuit) goto quit;
|
||||
res = 1;
|
||||
}
|
||||
@@ -566,21 +580,10 @@ static int nfq_main(void)
|
||||
if (params.timers)
|
||||
{
|
||||
bt = boottime_ms();
|
||||
dbt = bt-bt_prev;
|
||||
if (dbt>=params.timer_res)
|
||||
{
|
||||
TimerPoolRun(¶ms.timers, bt);
|
||||
|
||||
bt_prev = bt;
|
||||
tv.tv_sec = params.timer_res/1000;
|
||||
tv.tv_usec = params.timer_res%1000*1000;
|
||||
}
|
||||
else
|
||||
{
|
||||
dbt = params.timer_res-dbt;
|
||||
tv.tv_sec = (time_t)(dbt/1000);
|
||||
tv.tv_usec = (suseconds_t)(dbt%1000*1000);
|
||||
}
|
||||
if (bt>=bt_next)
|
||||
bt_next = TimerPoolRun(¶ms.timers, ¶ms.timers_dirty, bt);
|
||||
else if (params.timers_dirty)
|
||||
bt_next = 0;
|
||||
}
|
||||
}
|
||||
if (errno==EINTR)
|
||||
@@ -629,8 +632,8 @@ static int dvt_main(void)
|
||||
struct sockaddr_in6 bp6;
|
||||
uint8_t buf[RECONSTRUCT_MAX_SIZE] __attribute__((aligned));
|
||||
fd_set fdset;
|
||||
struct timeval tv = {.tv_sec = params.timer_res/1000, .tv_usec = params.timer_res%1000*1000};
|
||||
unsigned int bt,bt_prev,dbt;
|
||||
struct timeval tv;
|
||||
uint64_t bt,bt_next,dbt;
|
||||
|
||||
if (*params.pidfile && !(Fpid = fopen(params.pidfile, "w")))
|
||||
{
|
||||
@@ -714,16 +717,29 @@ static int dvt_main(void)
|
||||
if (params.daemon) daemonize();
|
||||
if (!write_pidfile(&Fpid)) goto exiterr;
|
||||
|
||||
for (bt_prev=0;;)
|
||||
for (bt=bt_next=0;;)
|
||||
{
|
||||
if (bQuit)
|
||||
{
|
||||
DLOG_CONDUP("quit requested\n");
|
||||
goto exitok;
|
||||
}
|
||||
|
||||
FD_ZERO(&fdset);
|
||||
for (i = 0; i < fdct; i++) FD_SET(fd[i], &fdset);
|
||||
r = select(fdmax, &fdset, NULL, NULL, params.timers ? &tv : NULL);
|
||||
|
||||
if (params.timers)
|
||||
{
|
||||
if (!bt_next) bt_next = TimerPoolNext(params.timers, ¶ms.timers_dirty);
|
||||
bt = boottime_ms();
|
||||
dbt = bt_next>bt ? bt_next-bt : 0;
|
||||
tv.tv_sec = (time_t)(dbt/1000);
|
||||
tv.tv_usec = (suseconds_t)(dbt%1000*1000);
|
||||
r = select(fdmax, &fdset, NULL, NULL, &tv);
|
||||
}
|
||||
else
|
||||
r = select(fdmax, &fdset, NULL, NULL, NULL);
|
||||
|
||||
if (bQuit)
|
||||
{
|
||||
DLOG_CONDUP("quit requested\n");
|
||||
@@ -810,21 +826,10 @@ static int dvt_main(void)
|
||||
if (params.timers)
|
||||
{
|
||||
bt = boottime_ms();
|
||||
dbt = bt-bt_prev;
|
||||
if (dbt>=params.timer_res)
|
||||
{
|
||||
TimerPoolRun(¶ms.timers, bt);
|
||||
|
||||
bt_prev = bt;
|
||||
tv.tv_sec = params.timer_res/1000;
|
||||
tv.tv_usec = params.timer_res%1000*1000;
|
||||
}
|
||||
else
|
||||
{
|
||||
dbt = params.timer_res-dbt;
|
||||
tv.tv_sec = (time_t)(dbt/1000);
|
||||
tv.tv_usec = (suseconds_t)(dbt%1000*1000);
|
||||
}
|
||||
if (bt>=bt_next)
|
||||
bt_next = TimerPoolRun(¶ms.timers, ¶ms.timers_dirty, bt);
|
||||
else if (params.timers_dirty)
|
||||
bt_next = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -846,11 +851,6 @@ exiterr:
|
||||
// do not make it less than 65536 - loopback packets can be up to 64K
|
||||
#define WINDIVERT_PACKET_BUF_SIZE 196608 // 3*64K, 128*1500=192000
|
||||
|
||||
static void win_timer_callback(uint64_t bt)
|
||||
{
|
||||
TimerPoolRun(¶ms.timers, bt);
|
||||
}
|
||||
|
||||
static int win_main(void)
|
||||
{
|
||||
size_t len, packet_len, left, modlen;
|
||||
@@ -863,7 +863,7 @@ static int win_main(void)
|
||||
WINDIVERT_ADDRESS wa[WINDIVERT_BULK_MAX];
|
||||
uint8_t *packets = NULL, *packet, *mod=NULL;
|
||||
unsigned int n,wa_count;
|
||||
uint64_t bt_prev = 0;
|
||||
uint64_t bt_next;
|
||||
|
||||
// windows emulated fork logic does not cover objects outside of cygwin world. have to daemonize before inits
|
||||
if (params.daemon) daemonize();
|
||||
@@ -933,11 +933,12 @@ static int win_main(void)
|
||||
goto ex;
|
||||
}
|
||||
|
||||
bt_next = 0;
|
||||
for (id = 0;;)
|
||||
{
|
||||
len = WINDIVERT_PACKET_BUF_SIZE;
|
||||
wa_count = WINDIVERT_BULK_MAX;
|
||||
if (!windivert_recv(packets, &len, wa, &wa_count, win_timer_callback, &bt_prev))
|
||||
if (!windivert_recv(packets, &len, wa, &wa_count, &bt_next))
|
||||
{
|
||||
if (errno == ENOBUFS)
|
||||
{
|
||||
@@ -1839,7 +1840,6 @@ static void exithelp(void)
|
||||
" --ipcache-lifetime=<int>\t\t\t\t; time in seconds to keep cached hop count and domain name (default %u). 0 = no expiration\n"
|
||||
" --ipcache-hostname=[0|1]\t\t\t\t; 1 or no argument enables ip->hostname caching\n"
|
||||
" --reasm-disable=[type[,type]]\t\t\t\t; disable reasm for these L7 payloads : tls_client_hello quic_initial . if no argument - disable all reasm.\n"
|
||||
" --timer-res=msec\t\t\t\t\t; Lua timer resolution. default %d ms\n"
|
||||
#ifdef __CYGWIN__
|
||||
"\nWINDIVERT FILTER:\n"
|
||||
" --wf-iface=<int>[.<int>]\t\t\t\t; numeric network interface and subinterface indexes\n"
|
||||
@@ -1915,7 +1915,6 @@ static void exithelp(void)
|
||||
#endif
|
||||
CTRACK_T_SYN, CTRACK_T_EST, CTRACK_T_FIN, CTRACK_T_UDP,
|
||||
IPCACHE_LIFETIME,
|
||||
TIMER_RES_DEFAULT,
|
||||
LUA_GC_INTERVAL,
|
||||
all_protos,
|
||||
HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT, HOSTLIST_AUTO_FAIL_TIME_DEFAULT,
|
||||
@@ -1998,7 +1997,6 @@ enum opt_indices {
|
||||
IDX_IPCACHE_LIFETIME,
|
||||
IDX_IPCACHE_HOSTNAME,
|
||||
IDX_REASM_DISABLE,
|
||||
IDX_TIMER_RES,
|
||||
#ifdef __linux__
|
||||
IDX_FWMARK,
|
||||
#elif defined(SO_USER_COOKIE)
|
||||
@@ -2104,7 +2102,6 @@ static const struct option long_options[] = {
|
||||
[IDX_IPCACHE_LIFETIME] = {"ipcache-lifetime", required_argument, 0, 0},
|
||||
[IDX_IPCACHE_HOSTNAME] = {"ipcache-hostname", optional_argument, 0, 0},
|
||||
[IDX_REASM_DISABLE] = {"reasm-disable", optional_argument, 0, 0},
|
||||
[IDX_TIMER_RES] = {"timer-res", required_argument, 0, 0},
|
||||
#ifdef __linux__
|
||||
[IDX_FWMARK] = {"fwmark", required_argument, 0, 0},
|
||||
#elif defined(SO_USER_COOKIE)
|
||||
@@ -2507,14 +2504,6 @@ int main(int argc, char **argv)
|
||||
else
|
||||
params.reasm_payload_disable = L7P_ALL;
|
||||
break;
|
||||
case IDX_TIMER_RES:
|
||||
params.timer_res = atoi(optarg);
|
||||
if (params.timer_res<10)
|
||||
{
|
||||
DLOG_ERR("Invalid timer resolution. must be >=10 ms\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
break;
|
||||
#if defined(__linux__)
|
||||
case IDX_FWMARK:
|
||||
#elif defined(SO_USER_COOKIE)
|
||||
|
||||
@@ -588,7 +588,6 @@ void init_params(struct params_s *params)
|
||||
params->ctrack_t_fin = CTRACK_T_FIN;
|
||||
params->ctrack_t_udp = CTRACK_T_UDP;
|
||||
params->ipcache_lifetime = IPCACHE_LIFETIME;
|
||||
params->timer_res = TIMER_RES_DEFAULT;
|
||||
params->lua_gc = LUA_GC_INTERVAL;
|
||||
|
||||
LIST_INIT(¶ms->hostlists);
|
||||
|
||||
+1
-3
@@ -37,8 +37,6 @@
|
||||
|
||||
#define IPCACHE_LIFETIME 7200
|
||||
|
||||
#define TIMER_RES_DEFAULT 50
|
||||
|
||||
#define MAX_GIDS 64
|
||||
|
||||
#define MAX_BLOB_SIZE (16*1024)
|
||||
@@ -195,8 +193,8 @@ struct params_s
|
||||
bool writeable_dir_enable;
|
||||
char writeable_dir[PATH_MAX];
|
||||
|
||||
int timer_res; // timer resolution in msec
|
||||
timer_pool *timers;
|
||||
bool timers_dirty; // changed something in timers
|
||||
|
||||
int lua_gc;
|
||||
int ref_desync_ctx; // desync ctx userdata registry ref
|
||||
|
||||
+37
-5
@@ -77,22 +77,40 @@ static bool TimerPoolRunTimer(timer_pool *p)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void TimerPoolRun(timer_pool **pp, uint64_t bt)
|
||||
uint64_t TimerPoolNext(const timer_pool *p, bool *dirty)
|
||||
{
|
||||
if (!pp) return; // no timers
|
||||
const timer_pool *elem, *tmp;
|
||||
uint64_t nexttime, mintime=0x7FFFFFFFFFFFFFFF;
|
||||
|
||||
if (!bt) bt = boottime_ms();
|
||||
if (!p) return 0;
|
||||
|
||||
HASH_ITER(hh, p, elem, tmp)
|
||||
{
|
||||
nexttime = elem->bt_prev + elem->period;
|
||||
if (nexttime<mintime) mintime = nexttime;
|
||||
}
|
||||
*dirty = false;
|
||||
return mintime;
|
||||
}
|
||||
uint64_t TimerPoolRun(timer_pool **pp, bool *dirty, uint64_t bt)
|
||||
{
|
||||
*dirty = false;
|
||||
if (!*pp) return 0; // no timers
|
||||
|
||||
timer_pool *elem, *tmp, *p;
|
||||
char *name;
|
||||
const char *del;
|
||||
unsigned int n;
|
||||
uint64_t nexttime, mintime=0x7FFFFFFFFFFFFFFF;
|
||||
|
||||
if (!bt) bt = boottime_ms();
|
||||
HASH_ITER(hh, *pp, elem, tmp)
|
||||
{
|
||||
if (bt >= (elem->bt_prev + elem->period))
|
||||
nexttime = elem->bt_prev + elem->period;
|
||||
if (bt >= nexttime)
|
||||
{
|
||||
if (!(name = strdup(elem->str)))
|
||||
return;
|
||||
return 0;
|
||||
n = elem->n;
|
||||
|
||||
del = NULL;
|
||||
@@ -110,12 +128,26 @@ void TimerPoolRun(timer_pool **pp, uint64_t bt)
|
||||
{
|
||||
DLOG(del,name);
|
||||
TimerPoolDel(pp, elem);
|
||||
elem = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
elem->bt_prev = bt;
|
||||
nexttime = elem->bt_prev + elem->period;
|
||||
}
|
||||
}
|
||||
|
||||
free(name);
|
||||
}
|
||||
if (elem)
|
||||
{
|
||||
if (nexttime<mintime) mintime = nexttime;
|
||||
}
|
||||
}
|
||||
if (!*pp) return 0; // no timers
|
||||
if (*dirty)
|
||||
// something changed in the timer pool (deleted or added in the lua timer code)
|
||||
// rescan all timers for mintime
|
||||
mintime = TimerPoolNext(*pp, dirty);
|
||||
return mintime;
|
||||
}
|
||||
+3
-1
@@ -20,4 +20,6 @@ void TimerPoolDestroy(timer_pool **pp);
|
||||
struct timer_pool *TimerPoolSearch(timer_pool *p, const char *str);
|
||||
struct timer_pool *TimerPoolAdd(timer_pool **pp, const char *str, const char *func, uint64_t period, bool oneshot);
|
||||
void TimerPoolDel(timer_pool **pp, timer_pool *p);
|
||||
void TimerPoolRun(timer_pool **pp, uint64_t bt);
|
||||
// return next timer fire time
|
||||
uint64_t TimerPoolRun(timer_pool **pp, bool *dirty, uint64_t bt);
|
||||
uint64_t TimerPoolNext(const timer_pool *p, bool *dirty);
|
||||
Reference in new issue
Block a user