mirror of
https://github.com/wangyu-/udp2raw.git
synced 2025-09-15 19:54:28 +08:00
Compare commits
21 Commits
20230206.0
...
20171103.0
Author | SHA1 | Date | |
---|---|---|---|
|
4c92c29948 | ||
|
cadb142455 | ||
|
fcb418f5ea | ||
|
63eb7f1f7f | ||
|
66b2bb87c5 | ||
|
6b27f8624d | ||
|
ce7b89f6b6 | ||
|
a0bfa86699 | ||
|
e332cabfed | ||
|
9798bf496a | ||
|
7aa4b8745e | ||
|
ea6a41ebb3 | ||
|
77ba1161f5 | ||
|
7858c4d832 | ||
|
71152ffbe3 | ||
|
b06e979dc8 | ||
|
5300f98b0a | ||
|
063d133463 | ||
|
4c88df9c0e | ||
|
52cb20ec20 | ||
|
7aede9edd0 |
12
README.md
12
README.md
@@ -1,9 +1,15 @@
|
||||
# Udp2raw-tunnel
|
||||

|
||||
|
||||
|
||||
A Tunnel which turns UDP Traffic into Encrypted FakeTCP/UDP/ICMP Traffic by using Raw Socket, helps you Bypass UDP FireWalls(or Unstable UDP Environment). It can defend Replay-Attack and supports Multiplexing. It also acts as a Connection Stabilizer.
|
||||
|
||||
It can tunnel any traffic when used together with a UDP-based VPN(such as OpenVPN).Check [this link](https://github.com/wangyu-/udp2raw-tunnel#tunneling-any-traffic-via-raw-traffic-by-using-udp2raw-openvpn) for more info.
|
||||
When used alone,udp2raw tunnels only UDP traffic. Nevertheless,if you used udp2raw + any UDP-based VPN together,you can tunnel any traffic(include TCP/UDP/ICMP),currently OpenVPN/L2TP/ShadowVPN and [tinyFecVPN](https://github.com/wangyu-/tinyFecVPN) are confirmed to be supported。
|
||||
|
||||

|
||||
|
||||
or
|
||||
|
||||

|
||||
|
||||
[简体中文](/doc/README.zh-cn.md)
|
||||
|
||||
@@ -226,7 +232,7 @@ raw_mode: faketcp cipher_mode: aes128cbc auth_mode: md5
|
||||
|
||||
# Application
|
||||
## Tunneling any traffic via raw traffic by using udp2raw +openvpn
|
||||

|
||||

|
||||
1. Bypasses UDP block/UDP QOS
|
||||
|
||||
2. No TCP over TCP problem (TCP over TCP problem http://sites.inka.de/bigred/devel/tcp-tcp.html ,https://community.openvpn.net/openvpn/ticket/2 )
|
||||
|
5
common.h
5
common.h
@@ -80,10 +80,7 @@ struct ip_port_t
|
||||
char * to_s();
|
||||
};
|
||||
|
||||
struct fd_info_t
|
||||
{
|
||||
ip_port_t ip_port;
|
||||
};
|
||||
|
||||
|
||||
|
||||
typedef u64_t fd64_t;
|
||||
|
@@ -297,6 +297,7 @@ conv_manager_t::~conv_manager_t()
|
||||
assert(oppsite_const_id==0);
|
||||
}
|
||||
}
|
||||
assert(timer_fd64==0);
|
||||
//if(oppsite_const_id!=0) //do this at conn_manager 's deconstuction function
|
||||
//conn_manager.const_id_mp.erase(oppsite_const_id);
|
||||
if(blob!=0)
|
||||
@@ -388,6 +389,7 @@ conv_manager_t::~conv_manager_t()
|
||||
|
||||
fd_manager.fd64_close(erase_it->second->timer_fd64);
|
||||
|
||||
erase_it->second->timer_fd64=0;
|
||||
//timer_fd_mp.erase(erase_it->second->timer_fd);
|
||||
//close(erase_it->second->timer_fd);// close will auto delte it from epoll
|
||||
delete(erase_it->second);
|
||||
@@ -732,9 +734,9 @@ int recv_safer(conn_info_t &conn_info,char &type,char* &data,int &len)///safer t
|
||||
void server_clear_function(u64_t u64)//used in conv_manager in server mode.for server we have to use one udp fd for one conv(udp connection),
|
||||
//so we have to close the fd when conv expires
|
||||
{
|
||||
int fd=int(u64);
|
||||
int ret;
|
||||
assert(fd!=0);
|
||||
//int fd=int(u64);
|
||||
// int ret;
|
||||
//assert(fd!=0);
|
||||
/*
|
||||
epoll_event ev;
|
||||
|
||||
@@ -747,16 +749,18 @@ void server_clear_function(u64_t u64)//used in conv_manager in server mode.for s
|
||||
mylog(log_fatal,"fd:%d epoll delete failed!!!!\n",fd);
|
||||
myexit(-1); //this shouldnt happen
|
||||
}*/ //no need
|
||||
ret= close(fd); //closed fd should be auto removed from epoll
|
||||
|
||||
/*ret= close(fd); //closed fd should be auto removed from epoll
|
||||
|
||||
if (ret!=0)
|
||||
{
|
||||
mylog(log_fatal,"close fd %d failed !!!!\n",fd);
|
||||
myexit(-1); //this shouldnt happen
|
||||
}
|
||||
}*/
|
||||
//mylog(log_fatal,"size:%d !!!!\n",conn_manager.udp_fd_mp.size());
|
||||
assert(fd_manager.exist(u64));
|
||||
fd_manager.fd64_close(u64);
|
||||
fd64_t fd64=u64;
|
||||
assert(fd_manager.exist(fd64));
|
||||
fd_manager.fd64_close(fd64);
|
||||
|
||||
//assert(conn_manager.udp_fd_mp.find(fd)!=conn_manager.udp_fd_mp.end());
|
||||
//conn_manager.udp_fd_mp.erase(fd);
|
||||
|
@@ -95,7 +95,7 @@ struct conn_info_t //stores info for a raw connection.for client ,there is o
|
||||
uint8_t oppsite_roller;
|
||||
u64_t last_oppsite_roller_time;
|
||||
|
||||
ip_port_t ip_port;
|
||||
// ip_port_t ip_port;
|
||||
|
||||
/*
|
||||
const uint32_t &ip=raw_info.recv_info.src_ip;
|
||||
|
@@ -4,15 +4,17 @@ udp2raw tunnel,通过raw socket给UDP包加上TCP或ICMP header,进而绕过
|
||||
|
||||
支持心跳保活、自动重连,重连后会恢复上次连接,在底层掉线的情况下可以保持上层不掉线。同时有加密、防重放攻击、信道复用的功能。
|
||||
|
||||
**欢迎任何形式的转载**
|
||||
|
||||
[English](/README.md)
|
||||
|
||||
[udp2raw+kcptun step_by_step教程](kcptun_step_by_step.md)
|
||||
|
||||
[udp2raw+finalspeed step_by_step教程](finalspeed_step_by_step.md)
|
||||
|
||||
如果你需要加速跨国网游、网页浏览,解决方案在另一个repo:
|
||||
**提示:**
|
||||
|
||||
udp2raw不是加速器,只是一个帮助你绕过UDP限制的工具。如果你需要UDP加速器,请看UDPspeeder。
|
||||
|
||||
UDPspeeder的repo:
|
||||
|
||||
https://github.com/wangyu-/UDPspeeder
|
||||
# 支持的平台
|
||||
|
63
fd_manager.cpp
Normal file
63
fd_manager.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* fd_manager.cpp
|
||||
*
|
||||
* Created on: Sep 25, 2017
|
||||
* Author: root
|
||||
*/
|
||||
|
||||
|
||||
#include "fd_manager.h"
|
||||
int fd_manager_t::fd_exist(int fd)
|
||||
{
|
||||
return fd_to_fd64_mp.find(fd)!=fd_to_fd64_mp.end();
|
||||
}
|
||||
int fd_manager_t::exist(fd64_t fd64)
|
||||
{
|
||||
return fd64_to_fd_mp.find(fd64)!=fd64_to_fd_mp.end();
|
||||
}
|
||||
int fd_manager_t::to_fd(fd64_t fd64)
|
||||
{
|
||||
assert(exist(fd64));
|
||||
return fd64_to_fd_mp[fd64];
|
||||
}
|
||||
void fd_manager_t::fd64_close(fd64_t fd64)
|
||||
{
|
||||
assert(exist(fd64));
|
||||
int fd=fd64_to_fd_mp[fd64];
|
||||
fd64_to_fd_mp.erase(fd64);
|
||||
fd_to_fd64_mp.erase(fd);
|
||||
if(exist_info(fd64))
|
||||
{
|
||||
fd_info_mp.erase(fd64);
|
||||
}
|
||||
assert(close(fd)==0);
|
||||
}
|
||||
void fd_manager_t::reserve(int n)
|
||||
{
|
||||
fd_to_fd64_mp.reserve(n);
|
||||
fd64_to_fd_mp.reserve(n);
|
||||
fd_info_mp.reserve(n);
|
||||
}
|
||||
u64_t fd_manager_t::create(int fd)
|
||||
{
|
||||
assert(!fd_exist(fd));
|
||||
fd64_t fd64=counter++;
|
||||
fd_to_fd64_mp[fd]=fd64;
|
||||
fd64_to_fd_mp[fd64]=fd;
|
||||
return fd64;
|
||||
}
|
||||
fd_manager_t::fd_manager_t()
|
||||
{
|
||||
counter=u32_t(-1);
|
||||
counter+=100;
|
||||
reserve(10007);
|
||||
}
|
||||
fd_info_t & fd_manager_t::get_info(fd64_t fd64)
|
||||
{
|
||||
assert(exist(fd64));
|
||||
return fd_info_mp[fd64];
|
||||
}
|
||||
int fd_manager_t::exist_info(fd64_t fd64)
|
||||
{
|
||||
return fd_info_mp.find(fd64)!=fd_info_mp.end();
|
||||
}
|
43
fd_manager.h
Normal file
43
fd_manager.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* fd_manager.h
|
||||
*
|
||||
* Created on: Sep 25, 2017
|
||||
* Author: root
|
||||
*/
|
||||
|
||||
#ifndef FD_MANAGER_H_
|
||||
#define FD_MANAGER_H_
|
||||
|
||||
#include "common.h"
|
||||
//#include "packet.h"
|
||||
#include "connection.h"
|
||||
|
||||
struct fd_info_t
|
||||
{
|
||||
//ip_port_t ip_port;
|
||||
conn_info_t *p_conn_info;
|
||||
};
|
||||
|
||||
struct fd_manager_t //conver fd to a uniq 64bit number,avoid fd value conflict caused by close and re-create
|
||||
//this class is not strictly necessary,it just makes epoll fd handling easier
|
||||
{
|
||||
fd_info_t & get_info(fd64_t fd64);
|
||||
int exist_info(fd64_t);
|
||||
int exist(fd64_t fd64);
|
||||
int to_fd(fd64_t);
|
||||
void fd64_close(fd64_t fd64);
|
||||
void reserve(int n);
|
||||
u64_t create(int fd);
|
||||
fd_manager_t();
|
||||
private:
|
||||
u64_t counter;
|
||||
unordered_map<int,fd64_t> fd_to_fd64_mp;
|
||||
unordered_map<fd64_t,int> fd64_to_fd_mp;
|
||||
unordered_map<fd64_t,fd_info_t> fd_info_mp;
|
||||
int fd_exist(int fd);
|
||||
//void remove_fd(int fd);
|
||||
//fd64_t fd_to_fd64(int fd);
|
||||
};
|
||||
|
||||
extern fd_manager_t fd_manager;
|
||||
#endif /* FD_MANAGER_H_ */
|
BIN
images/udp2rawopenvpn.PNG
Normal file
BIN
images/udp2rawopenvpn.PNG
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
36
main.cpp
36
main.cpp
@@ -27,8 +27,6 @@ int client_on_timer(conn_info_t &conn_info) //for client. called when a timer is
|
||||
mylog(log_trace,"<client_on_timer,send_info.ts_ack= %u>\n",send_info.ts_ack);
|
||||
|
||||
|
||||
|
||||
|
||||
if(conn_info.state.client_current_state==client_idle)
|
||||
{
|
||||
fail_time_counter++;
|
||||
@@ -603,8 +601,8 @@ int server_on_raw_recv_multi() //called when server received an raw packet
|
||||
conn_info_t &conn_info=conn_manager.find_insert(ip,port);
|
||||
conn_info.raw_info=tmp_raw_info;
|
||||
|
||||
conn_info.ip_port.ip=ip;
|
||||
conn_info.ip_port.port=port;
|
||||
//conn_info.ip_port.ip=ip;
|
||||
//conn_info.ip_port.port=port;
|
||||
|
||||
packet_info_t &send_info=conn_info.raw_info.send_info;
|
||||
packet_info_t &recv_info=conn_info.raw_info.recv_info;
|
||||
@@ -826,6 +824,7 @@ int server_on_raw_recv_ready(conn_info_t &conn_info,char * ip_port,char type,cha
|
||||
struct epoll_event ev;
|
||||
|
||||
fd64_t new_udp_fd64 = fd_manager.create(new_udp_fd);
|
||||
fd_manager.get_info(new_udp_fd64).p_conn_info=&conn_info;
|
||||
|
||||
mylog(log_trace, "[%s]u64: %lld\n",ip_port, new_udp_fd64);
|
||||
ev.events = EPOLLIN;
|
||||
@@ -842,7 +841,7 @@ int server_on_raw_recv_ready(conn_info_t &conn_info,char * ip_port,char type,cha
|
||||
|
||||
conn_info.blob->conv_manager.insert_conv(tmp_conv_id, new_udp_fd64);
|
||||
|
||||
fd_manager.get_info(new_udp_fd64).ip_port=conn_info.ip_port;
|
||||
|
||||
|
||||
//assert(conn_manager.udp_fd_mp.find(new_udp_fd)==conn_manager.udp_fd_mp.end());
|
||||
|
||||
@@ -857,11 +856,11 @@ int server_on_raw_recv_ready(conn_info_t &conn_info,char * ip_port,char type,cha
|
||||
|
||||
}
|
||||
|
||||
u64_t u64 = conn_info.blob->conv_manager.find_u64_by_conv(tmp_conv_id);
|
||||
fd64_t fd64 = conn_info.blob->conv_manager.find_u64_by_conv(tmp_conv_id);
|
||||
|
||||
conn_info.blob->conv_manager.update_active_time(tmp_conv_id);
|
||||
|
||||
int fd = int((u64 << 32u) >> 32u);
|
||||
int fd = fd_manager.to_fd(fd64);
|
||||
|
||||
mylog(log_trace, "[%s]received a data from fake tcp,len:%d\n",ip_port, data_len);
|
||||
int ret = send(fd, data + sizeof(u32_t),
|
||||
@@ -925,10 +924,9 @@ int server_on_raw_recv_pre_ready(conn_info_t &conn_info,char * ip_port,u32_t tmp
|
||||
|
||||
//g_conn_info=conn_info;
|
||||
int new_timer_fd;
|
||||
set_timer_server(epollfd, new_timer_fd);
|
||||
conn_info.timer_fd64=fd_manager.create(new_timer_fd);
|
||||
set_timer_server(epollfd, new_timer_fd,conn_info.timer_fd64);
|
||||
|
||||
fd_manager.get_info(conn_info.timer_fd64).ip_port=conn_info.ip_port;
|
||||
fd_manager.get_info(conn_info.timer_fd64).p_conn_info=&conn_info;
|
||||
//assert(conn_manager.timer_fd_mp.find(new_timer_fd)==conn_manager.timer_fd_mp.end());
|
||||
//conn_manager.timer_fd_mp[new_timer_fd] = &conn_info;//pack_u64(ip,port);
|
||||
|
||||
@@ -1484,7 +1482,7 @@ int server_event_loop()
|
||||
|
||||
fd64_t fd64=events[idx].data.u64;
|
||||
|
||||
if(fd_manager.exist(fd64))
|
||||
if(!fd_manager.exist(fd64))
|
||||
{
|
||||
mylog(log_trace ,"fd64 no longer exist\n");
|
||||
continue;
|
||||
@@ -1492,23 +1490,21 @@ int server_event_loop()
|
||||
|
||||
assert(fd_manager.exist_info(fd64));
|
||||
|
||||
ip_port_t ip_port=fd_manager.get_info(fd64).ip_port;
|
||||
u32_t ip=ip_port.ip;
|
||||
u32_t port=ip_port.port;
|
||||
|
||||
assert(conn_manager.exist(ip,port));
|
||||
|
||||
conn_info_t* p_conn_info=conn_manager.find_insert_p(ip,port);
|
||||
|
||||
conn_info_t* p_conn_info=fd_manager.get_info(fd64).p_conn_info;
|
||||
u32_t ip=p_conn_info->raw_info.send_info.dst_ip;
|
||||
u32_t port=p_conn_info->raw_info.send_info.dst_port;
|
||||
|
||||
//assert(conn_manager.exist(ip,port));
|
||||
|
||||
///conn_info_t* p_conn_info=conn_manager.find_insert_p(ip,port);
|
||||
|
||||
|
||||
if(fd64==p_conn_info->timer_fd64)//////////timer_fd64
|
||||
{
|
||||
|
||||
if(debug_flag)begin_time=get_current_time();
|
||||
int fd=get_u64_l(events[idx].data.u64);
|
||||
//int fd=get_u64_l(events[idx].data.u64);
|
||||
int fd=fd_manager.to_fd(fd64);
|
||||
u64_t dummy;
|
||||
read(fd, &dummy, 8);
|
||||
|
||||
|
3
makefile
3
makefile
@@ -15,7 +15,7 @@ SOURCES_AES_ACC=$(COMMON) $(wildcard lib/aes_acc/aes*.c)
|
||||
|
||||
NAME=udp2raw
|
||||
TARGETS=amd64 arm amd64_hw_aes arm_asm_aes mips24kc_be mips24kc_be_asm_aes x86 x86_asm_aes mips24kc_le mips24kc_le_asm_aes
|
||||
TAR=${NAME}_binaries.tar.gz `echo ${TARGETS}|sed -r 's/([^ ]+)/udp2raw_\1/g'`
|
||||
TAR=${NAME}_binaries.tar.gz `echo ${TARGETS}|sed -r 's/([^ ]+)/udp2raw_\1/g'` version.txt
|
||||
|
||||
all:git_version
|
||||
rm -f ${NAME}
|
||||
@@ -69,6 +69,7 @@ cross3:git_version
|
||||
${cc_cross} -o ${NAME}_cross -I. ${SOURCES} ${FLAGS} -lrt -static -O3
|
||||
|
||||
release: ${TARGETS}
|
||||
cp git_version.h version.txt
|
||||
tar -zcvf ${TAR}
|
||||
|
||||
clean:
|
||||
|
6
misc.cpp
6
misc.cpp
@@ -951,7 +951,7 @@ int set_timer(int epollfd,int &timer_fd)//put a timer_fd into epoll,general func
|
||||
}
|
||||
|
||||
|
||||
int set_timer_server(int epollfd,int &timer_fd)//only for server
|
||||
int set_timer_server(int epollfd,int &timer_fd,fd64_t &fd64)//only for server
|
||||
{
|
||||
int ret;
|
||||
epoll_event ev;
|
||||
@@ -969,9 +969,11 @@ int set_timer_server(int epollfd,int &timer_fd)//only for server
|
||||
its.it_value.tv_nsec=1; //imidiately
|
||||
timerfd_settime(timer_fd,0,&its,0);
|
||||
|
||||
fd64=fd_manager.create(timer_fd);
|
||||
|
||||
|
||||
ev.events = EPOLLIN;
|
||||
ev.data.u64 = pack_u64(2,timer_fd);////difference
|
||||
ev.data.u64 = fd64;////difference
|
||||
|
||||
ret=epoll_ctl(epollfd, EPOLL_CTL_ADD, timer_fd, &ev);
|
||||
if (ret < 0) {
|
||||
|
2
misc.h
2
misc.h
@@ -112,7 +112,7 @@ void iptables_rule();
|
||||
void pre_process_arg(int argc, char *argv[]);//mainly for load conf file;
|
||||
int unit_test();
|
||||
int set_timer(int epollfd,int &timer_fd);
|
||||
int set_timer_server(int epollfd,int &timer_fd);
|
||||
int set_timer_server(int epollfd,int &timer_fd,fd64_t &fd64);
|
||||
int handle_lower_level(raw_info_t &raw_info);
|
||||
|
||||
int add_iptables_rule(const char *);
|
||||
|
16
network.cpp
16
network.cpp
@@ -1820,17 +1820,17 @@ int get_src_adress(u32_t &ip,u32_t remote_ip_uint32,int remote_port) //a trick
|
||||
return 0;
|
||||
}
|
||||
|
||||
int try_to_list_and_bind(int bind_fd,u32_t local_ip_uint32,int port) //try to bind to a port,may fail.
|
||||
int try_to_list_and_bind(int &fd,u32_t local_ip_uint32,int port) //try to bind to a port,may fail.
|
||||
{
|
||||
int old_bind_fd=bind_fd;
|
||||
int old_bind_fd=fd;
|
||||
|
||||
if(raw_mode==mode_faketcp)
|
||||
{
|
||||
bind_fd=socket(AF_INET,SOCK_STREAM,0);
|
||||
fd=socket(AF_INET,SOCK_STREAM,0);
|
||||
}
|
||||
else if(raw_mode==mode_udp||raw_mode==mode_icmp)
|
||||
{
|
||||
bind_fd=socket(AF_INET,SOCK_DGRAM,0);
|
||||
fd=socket(AF_INET,SOCK_DGRAM,0);
|
||||
}
|
||||
if(old_bind_fd!=-1)
|
||||
{
|
||||
@@ -1844,7 +1844,7 @@ int try_to_list_and_bind(int bind_fd,u32_t local_ip_uint32,int port) //try to b
|
||||
temp_bind_addr.sin_port = htons(port);
|
||||
temp_bind_addr.sin_addr.s_addr = local_ip_uint32;
|
||||
|
||||
if (bind(bind_fd, (struct sockaddr*)&temp_bind_addr, sizeof(temp_bind_addr)) !=0)
|
||||
if (bind(fd, (struct sockaddr*)&temp_bind_addr, sizeof(temp_bind_addr)) !=0)
|
||||
{
|
||||
mylog(log_debug,"bind fail\n");
|
||||
return -1;
|
||||
@@ -1852,19 +1852,19 @@ int try_to_list_and_bind(int bind_fd,u32_t local_ip_uint32,int port) //try to b
|
||||
if(raw_mode==mode_faketcp)
|
||||
{
|
||||
|
||||
if (listen(bind_fd, SOMAXCONN) != 0) {
|
||||
if (listen(fd, SOMAXCONN) != 0) {
|
||||
mylog(log_warn,"listen fail\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int client_bind_to_a_new_port(int bind_fd,u32_t local_ip_uint32)//find a free port and bind to it.
|
||||
int client_bind_to_a_new_port(int &fd,u32_t local_ip_uint32)//find a free port and bind to it.
|
||||
{
|
||||
int raw_send_port=10000+get_true_random_number()%(65535-10000);
|
||||
for(int i=0;i<1000;i++)//try 1000 times at max,this should be enough
|
||||
{
|
||||
if (try_to_list_and_bind(bind_fd,local_ip_uint32,raw_send_port)==0)
|
||||
if (try_to_list_and_bind(fd,local_ip_uint32,raw_send_port)==0)
|
||||
{
|
||||
return raw_send_port;
|
||||
}
|
||||
|
@@ -99,9 +99,9 @@ int find_lower_level_info(u32_t ip,u32_t &dest_ip,string &if_name,string &hw);
|
||||
|
||||
int get_src_adress(u32_t &ip,u32_t remote_ip_uint32,int remote_port); //a trick to get src adress for a dest adress,so that we can use the src address in raw socket as source ip
|
||||
|
||||
int try_to_list_and_bind(int bind_fd,u32_t local_ip_uint32,int port); //try to bind to a port,may fail.
|
||||
int try_to_list_and_bind(int & bind_fd,u32_t local_ip_uint32,int port); //try to bind to a port,may fail.
|
||||
|
||||
int client_bind_to_a_new_port(int bind_fd,u32_t local_ip_uint32);//find a free port and bind to it.
|
||||
int client_bind_to_a_new_port(int & bind_fd,u32_t local_ip_uint32);//find a free port and bind to it.
|
||||
|
||||
int send_raw_ip(raw_info_t &raw_info,const char * payload,int payloadlen);
|
||||
|
||||
|
Reference in New Issue
Block a user