mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 11:20:43 +00:00
CHANGELOG 加 1.8.1
This commit is contained in:
@@ -1,3 +1,24 @@
|
||||
## 1.8.1
|
||||
|
||||
- 修**局域网连接被中转劫持**(重要):
|
||||
只要「中转设置」里填了地址,点「局域网连接」填好 IP、端口、
|
||||
配对码之后,程序**仍然走中转**,完全无视你填的局域网地址。
|
||||
中转慢的时候要等 40 秒才报错,看起来就像"点了一点反应都没有"。
|
||||
现在改成:局域网优先直连(6 秒超时),连不上才退回中转
|
||||
|
||||
- 修**已配对电脑的地址过期不提示**:
|
||||
电脑换了网络之后(比如从 192.168.2.x 换到 192.168.10.x),
|
||||
列表里还是旧地址,点一下必然失败。现在会标出「⚠ 地址可能已过期」,
|
||||
并且跳过注定失败的直连,直接通过中转查询最新地址
|
||||
|
||||
- **长轮询从 15 秒延长到 60 秒**,跨网络更稳:
|
||||
隧道每次请求要多花 1~2 秒(边缘路由 + 回源),而这个开销是按
|
||||
**次数**收的 —— 每次都要吃一遍。拉长到 60 秒把请求数降到 1/4,
|
||||
撞上丢包/重连窗口的概率也降到 1/4。
|
||||
**命令响应速度不变**:有数据时立刻返回,不是等满 60 秒。
|
||||
|
||||
- 修局域网直连成功后打出误导性日志的问题
|
||||
|
||||
## 1.8.0
|
||||
|
||||
**这一版主要是「修看不见的稳定性问题」。**
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
修「局域网连接被中转劫持」+ 长轮询 15s→60s
|
||||
|
||||
用户报两个问题:
|
||||
1. 局域网能搜到电脑,IP/端口/配对码都对,点连接**没有任何反应**
|
||||
2. 走中转 1426ms 延时,一会儿就 502
|
||||
|
||||
========== 一、局域网被劫持(真 bug)==========
|
||||
|
||||
MainActivity.connect() 里:
|
||||
|
||||
boolean usedLan = !relayUrl().isEmpty() ? false : true;
|
||||
if (!relay.isEmpty()) {
|
||||
reply = client.connectViaRelay(...); // 走中转
|
||||
} else {
|
||||
reply = client.connect(host, port, ...); // 才走局域网
|
||||
}
|
||||
|
||||
**只看「中转设置里填没填地址」,填了就一律走中转,
|
||||
完全无视用户填的局域网 IP 和端口。**
|
||||
|
||||
而中转单次超时是 40 秒 —— 用户点完就卡在「正在连接…」
|
||||
四十秒,在他看来就是"没有任何反应"。
|
||||
|
||||
影响两条路:
|
||||
* 「局域网连接」页面填 IP 直连(connectLanTyped)
|
||||
* 首页「已配对的电脑」点一下重连(quickConnect)
|
||||
|
||||
修法:给 connect() 加 preferLan 参数。明确要走局域网时
|
||||
**先直连(6 秒超时,局域网本该是毫秒级),失败再退回中转**,
|
||||
保证最终能连上。
|
||||
|
||||
顺带两个体验改进:
|
||||
* 设备列表里,保存的 IP 和手表不同网段时标「⚠ 地址可能已过期」
|
||||
(用户截图里那个 192.168.2.64 就是过期地址,电脑早换成
|
||||
192.168.10.103 了)
|
||||
* quickConnect 判断出地址过期就跳过直连,直接走中转查最新地址 ——
|
||||
省掉那 6 秒白等
|
||||
|
||||
========== 二、长轮询 15 秒 -> 60 秒 ==========
|
||||
|
||||
实测隧道每次请求要多花 1~2 秒(CF 边缘路由 + 回源):
|
||||
|
||||
本机直连 60 秒的 poll -> 60.0s
|
||||
走公网同一个 poll -> 61.5~62.0s
|
||||
|
||||
这个开销是**按次数**收的:
|
||||
15 秒一循环 -> 一天 5760 次/端,光路费 2.4~3.2 小时
|
||||
60 秒一循环 -> 一天 1440 次/端,光费 0.6~0.8 小时
|
||||
而且请求越频繁,撞上丢包/重连窗口的概率越大。
|
||||
|
||||
**不会让命令变慢**:长轮询是"有数据立刻返回"(device.event 唤醒),
|
||||
拉长只是减少空转重开。实测 60 秒长轮询穿隧道 4/4 通过;
|
||||
Cloudflare 单请求上限 100 秒,留了 40 秒余量。
|
||||
|
||||
三层同步改(客户端超时必须显著大于服务端 hold):
|
||||
* relay/relay.py RELAY_POLL 默认 25 -> 60
|
||||
* windows/lanremote/relay.py POLL_TIMEOUT 40 -> 90
|
||||
* android .../RelayTransport.java POLL_TIMEOUT_MS 40000 -> 90000
|
||||
|
||||
========== 三、A/B 实测(还在跑更严谨的一轮)==========
|
||||
|
||||
短测发现基线 http2+4 虽然 100% 成功,但 **P90 是 16.8 秒**
|
||||
(每 10 次有 1 次卡十几秒 —— 正是用户说的"没反应"),
|
||||
而 quic+6 的 P90 只有 2.8 秒。正在用真实长轮询模式
|
||||
跑 10 分钟对照确认。
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/bin/bash
|
||||
# 长轮询可靠性长测:连打 20 次(每次挂 60 秒,共约 20 分钟)。
|
||||
#
|
||||
# 为什么单独测这个:
|
||||
# 上一轮 A/B 里 http2+4 是 7/8(有 1 次 000 失败)。
|
||||
# 12.5% 的失败率对"手表点一下要能用"来说太高了,必须查清:
|
||||
# * 是偶发抖动?
|
||||
# * 还是每 8 次就必然挂一次(那就跟隧道重连周期有关)?
|
||||
#
|
||||
# 顺带修掉上轮脚本的 bug:探针设备用了同一个配对码,
|
||||
# 第二次注册被"一个码只能一台设备"规则拒掉,token 拿到空的。
|
||||
# 这里每次用**唯一的新码**。
|
||||
set -u
|
||||
|
||||
PUB=https://relay.xxmxxs.top
|
||||
LOCAL=http://127.0.0.1:8080
|
||||
UA='LanRemote/1.8.0'
|
||||
ROUNDS=${1:-20}
|
||||
|
||||
echo "════════ 注册探针(用唯一配对码)════════"
|
||||
STAMP=$(date +%s)
|
||||
DEV="relprobe-${STAMP}"
|
||||
# 6 位数字,取自时间戳后 6 位,避免和上一轮撞
|
||||
CODE=$(printf "%06d" $(( STAMP % 1000000 )))
|
||||
REG=$(curl -s --max-time 30 -X POST "$PUB/v1/agent/register" \
|
||||
-H 'Content-Type: application/json' -H "User-Agent: $UA" \
|
||||
-d "{\"deviceId\":\"$DEV\",\"code\":\"$CODE\",\"name\":\"relprobe\",\"version\":\"1.8.0\"}")
|
||||
echo " 注册响应: $(echo "$REG" | head -c 160)"
|
||||
TOKEN=$(echo "$REG" | python3 -c "import sys,json; print(json.load(sys.stdin).get('token',''))" 2>/dev/null)
|
||||
if [ -z "$TOKEN" ]; then
|
||||
echo " [失败] 没拿到 token,注册被拒了"
|
||||
exit 1
|
||||
fi
|
||||
echo " token ${#TOKEN} 字符,配对码 $CODE"
|
||||
|
||||
echo
|
||||
echo "════════ 隧道现状 ════════"
|
||||
echo " 参数: $(grep -oE '\-\-protocol [a-z0-9]+|--ha-connections [0-9]+' /etc/systemd/system/lanremote-tunnel.service | tr '\n' ' ')"
|
||||
echo " 连接: $(curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')"
|
||||
curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null \
|
||||
| grep 'server_locations.*} 1' | sed 's/^/ /'
|
||||
|
||||
echo
|
||||
echo "════════ 开始连打 $ROUNDS 次长轮询 ════════"
|
||||
ok=0; fail=0; times=(); fails=""
|
||||
LOGMARK=$(wc -l < /root/lanremote-relay/tunnel.log 2>/dev/null || echo 0)
|
||||
|
||||
for i in $(seq 1 "$ROUNDS"); do
|
||||
out=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 100 \
|
||||
-X POST "$PUB/v1/agent/poll" -H "X-Token: $TOKEN" -H "User-Agent: $UA")
|
||||
code=$(echo "$out" | awk '{print $1}')
|
||||
ms=$(echo "$out" | awk '{printf "%d", $2*1000}')
|
||||
if [ "$code" = "200" ]; then
|
||||
ok=$((ok+1)); times+=("$ms")
|
||||
printf " %2d: 200 %6dms\n" "$i" "$ms"
|
||||
else
|
||||
fail=$((fail+1)); fails="$fails ${i}:${code}"
|
||||
printf " %2d: %-4s **失败**\n" "$i" "$code"
|
||||
fi
|
||||
# 顺手记一下这次请求前后的连接数,失败了就知道是不是掉了连接
|
||||
n=$(curl -s --max-time 3 http://127.0.0.1:20241/metrics 2>/dev/null \
|
||||
| grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')
|
||||
[ "$code" != "200" ] && printf " 失败时连接数: %s\n" "${n:-?}"
|
||||
done
|
||||
|
||||
echo
|
||||
echo "════════ 结果 ════════"
|
||||
total=$((ok+fail))
|
||||
echo " 成功 $ok/$total = $((ok * 100 / total))%"
|
||||
echo " 失败:$fails"
|
||||
if [ "${#times[@]}" -gt 0 ]; then
|
||||
printf '%s\n' "${times[@]}" | sort -n | awk '{
|
||||
a[NR]=$1
|
||||
} END {
|
||||
printf " 耗时: 最小 %d 中位 %d P90 %d 最大 %d ms\n",
|
||||
a[1], a[int(NR/2)+1], a[int(NR*0.9)+1], a[NR]
|
||||
}'
|
||||
fi
|
||||
|
||||
echo
|
||||
echo " 这一轮的隧道事件(重连/超时):"
|
||||
NOWMARK=$(wc -l < /root/lanremote-relay/tunnel.log 2>/dev/null || echo 0)
|
||||
tail -n +$((LOGMARK+1)) /root/lanremote-relay/tunnel.log 2>/dev/null \
|
||||
| grep -cE 'Registered tunnel connection' \
|
||||
| sed 's/^/ 重连次数: /'
|
||||
tail -n +$((LOGMARK+1)) /root/lanremote-relay/tunnel.log 2>/dev/null \
|
||||
| grep -E 'Lost connection|i/o timeout|context deadline' | tail -6 | sed 's/^/ /'
|
||||
|
||||
echo
|
||||
echo " watchdog 最近:"
|
||||
tail -6 /root/lanremote-relay/watchdog.log 2>/dev/null | sed 's/^/ /'
|
||||
|
||||
echo
|
||||
echo "════════ 清掉探针 ════════"
|
||||
KEY=$(cat /root/lanremote-relay/data/admin_key.txt 2>/dev/null)
|
||||
curl -s --max-time 20 -X POST "$LOCAL/v1/admin/delete" -H "X-Admin: $KEY" \
|
||||
-H 'Content-Type: application/json' -d "{\"deviceId\":\"$DEV\"}" | head -c 100
|
||||
echo
|
||||
+95
-80
@@ -1,96 +1,111 @@
|
||||
#!/bin/bash
|
||||
# 测试 --region:能不能让 cloudflared 连亚洲边缘而不是美西?
|
||||
# 试试能不能把隧道从"美国西海岸"挪到亚洲节点。
|
||||
#
|
||||
# 现状:连的是 198.41.x.x,落地 lax01/sjc07(美西),ICMP 199~243ms,
|
||||
# 丢包 40%,握手经常超时。
|
||||
# cloudflared 有个 --region 参数("Cloudflare Edge region to connect to")。
|
||||
# 如果能把落地换到香港/新加坡/东京,延迟和丢包都会好很多。
|
||||
# 为什么这可能是最大的一招:
|
||||
# 实测隧道落点全是 sjc / lax(美国圣何塞、洛杉矶),
|
||||
# 中国 -> 美西 往返就是 ~240ms 起步,加上 4G 的丢包,
|
||||
# 每个请求固定在隧道回源这一跳吃 ~1100ms。
|
||||
# 如果能连到 东京 / 香港 / 新加坡,这一跳能砍掉一大半。
|
||||
#
|
||||
# cloudflared 理论上支持按区域选 edge,但其实它主要是按
|
||||
# 运营商的 BGP 出口自动选。这里把能试的都试一遍,
|
||||
# 用**实测落点**判断有没有用,而不是看参数有没有被接受。
|
||||
set -u
|
||||
|
||||
URL=https://relay.xxmxxs.top
|
||||
RELAY_DIR=/root/lanremote-relay
|
||||
BASE="--no-autoupdate --edge-ip-version 4 --retries 5 --config /root/.cloudflared/config.yml run lanremote"
|
||||
SVC=/etc/systemd/system/lanremote-tunnel.service
|
||||
BACKUP=/root/lanremote-tunnel.service.bak.region.$(date +%s)
|
||||
cp -a "$SVC" "$BACKUP"
|
||||
UA='LanRemote/1.8.0'
|
||||
|
||||
echo "════════ 1. cloudflared 支持的区域参数 ════════"
|
||||
cloudflared tunnel run --help 2>&1 | grep -iE 'region|edge' -A2 | head -20 | sed 's/^/ /'
|
||||
echo
|
||||
echo " cloudflared 版本: $(cloudflared --version 2>&1 | head -1)"
|
||||
|
||||
echo
|
||||
echo "════════ 2. 当前落点(基线)════════"
|
||||
curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null \
|
||||
| grep 'server_locations.*} 1' | sed 's/^/ /'
|
||||
|
||||
echo
|
||||
echo "════════ 3. 逐个试区域参数 ════════"
|
||||
|
||||
try_region() {
|
||||
local region="$1"
|
||||
local label="$2"
|
||||
local arg="$1" label="$2"
|
||||
echo " ── $label($arg)──"
|
||||
|
||||
systemctl stop lanremote-tunnel 2>/dev/null || true
|
||||
pkill -f cloudflared 2>/dev/null || true
|
||||
sleep 2
|
||||
: > "$RELAY_DIR/tunnel.log"
|
||||
|
||||
if [ -n "$region" ]; then
|
||||
nohup cloudflared tunnel --region "$region" $BASE \
|
||||
>> "$RELAY_DIR/tunnel.log" 2>&1 &
|
||||
else
|
||||
nohup cloudflared tunnel $BASE >> "$RELAY_DIR/tunnel.log" 2>&1 &
|
||||
# 回滚到干净状态再叠加参数
|
||||
cp -a "$BACKUP" "$SVC"
|
||||
if [ -n "$arg" ]; then
|
||||
sed -i "s|\(ExecStart=.*cloudflared tunnel\)|\1 $arg|" "$SVC"
|
||||
fi
|
||||
systemctl daemon-reload
|
||||
systemctl restart lanremote-tunnel
|
||||
|
||||
sleep 30
|
||||
|
||||
local conns loc proto
|
||||
conns=$(grep -c 'Registered tunnel connection' "$RELAY_DIR/tunnel.log" 2>/dev/null || echo 0)
|
||||
loc=$(grep -o 'location=[a-z0-9]*' "$RELAY_DIR/tunnel.log" 2>/dev/null | tail -1)
|
||||
proto=$(grep -o 'protocol=[a-z0-9]*' "$RELAY_DIR/tunnel.log" 2>/dev/null | tail -1)
|
||||
|
||||
printf " %-14s 注册 %-3s %-14s %s\n" "$label" "$conns" "${loc:-?}" "${proto:-?}"
|
||||
|
||||
if [ "$conns" = "0" ]; then
|
||||
echo " 报错:"
|
||||
grep -iE 'error|fatal|invalid' "$RELAY_DIR/tunnel.log" 2>/dev/null \
|
||||
| tail -2 | sed 's/^/ /'
|
||||
fi
|
||||
|
||||
# 快速探 10 次
|
||||
local ok=0
|
||||
for i in $(seq 1 10); do
|
||||
c=$(timeout 12 curl -4 -s -o /dev/null -w '%{http_code}' \
|
||||
-H 'User-Agent: LanRemote/1.4.0' "$URL/v1/health" 2>/dev/null)
|
||||
[ "$c" = "200" ] && ok=$((ok+1))
|
||||
sleep 2
|
||||
# 等最多 90 秒看能不能建起连接
|
||||
local n=0
|
||||
for i in $(seq 1 18); do
|
||||
sleep 5
|
||||
n=$(curl -s --max-time 4 http://127.0.0.1:20241/metrics 2>/dev/null \
|
||||
| grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')
|
||||
[ "${n:-0}" -ge 2 ] && break
|
||||
done
|
||||
echo " 公网 $ok/10"
|
||||
echo "$label|$ok|${loc:-?}|$conns" >> /root/region_result.txt
|
||||
pkill -f cloudflared 2>/dev/null || true
|
||||
sleep 2
|
||||
|
||||
if [ "${n:-0}" -lt 2 ]; then
|
||||
echo " 连接数 $n —— **没连上,这个参数不能用**"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo " 连接数 $n"
|
||||
echo " 落点:"
|
||||
curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null \
|
||||
| grep 'server_locations.*} 1' | sed 's/^/ /'
|
||||
|
||||
# 量 6 次公网延迟
|
||||
local times=()
|
||||
for i in $(seq 1 6); do
|
||||
ms=$(curl -s -o /dev/null -w '%{time_total}' --max-time 25 \
|
||||
-H "User-Agent: $UA" https://relay.xxmxxs.top/v1/health \
|
||||
| awk '{printf "%d", $1*1000}')
|
||||
times+=("$ms")
|
||||
done
|
||||
local stats=$(printf '%s\n' "${times[@]}" | sort -n | awk '{a[NR]=$1} END {printf "最小 %d 中位 %d 最大 %d", a[1], a[int(NR/2)+1], a[NR]}')
|
||||
echo " 延迟: $stats ms"
|
||||
echo "$label|$n|$stats" >> /tmp/region.txt
|
||||
return 0
|
||||
}
|
||||
|
||||
: > /root/region_result.txt
|
||||
rm -f /tmp/region.txt
|
||||
echo "" > /dev/null
|
||||
|
||||
echo "===== 各 region 的表现 ====="
|
||||
try_region "" "默认(global)"
|
||||
try_region "ap" "ap"
|
||||
try_region "us" "us"
|
||||
# 先看 help 里有没有 region 这个 flag
|
||||
HAS_REGION=$(cloudflared tunnel run --help 2>&1 | grep -c -- '--region' || true)
|
||||
echo " --region 参数是否存在: $([ "${HAS_REGION:-0}" -gt 0 ] && echo 是 || echo 否)"
|
||||
echo
|
||||
|
||||
# 逐个试。--region 的取值 cloudflared 没在 help 里列全,
|
||||
# 常见的是 us / eu / ap 之类;不合法会直接报错退出。
|
||||
for r in us eu ap; do
|
||||
try_region "--region $r" "region=$r" || true
|
||||
echo
|
||||
done
|
||||
|
||||
echo "════════ 4. 汇总 ════════"
|
||||
if [ -f /tmp/region.txt ]; then
|
||||
cat /tmp/region.txt | sed 's/^/ /'
|
||||
else
|
||||
echo " (没有任何区域参数能用)"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "===== 汇总 ====="
|
||||
while IFS='|' read -r label ok loc conns; do
|
||||
printf " %-16s 公网 %s/10 落地=%s 注册=%s\n" "$label" "$ok" "$loc" "$conns"
|
||||
done < /root/region_result.txt
|
||||
|
||||
echo
|
||||
echo "===== 恢复默认配置 ====="
|
||||
cat > /etc/systemd/system/lanremote-tunnel.service <<EOF
|
||||
[Unit]
|
||||
Description=LanRemote Cloudflare 具名隧道
|
||||
After=network-online.target lanremote-relay.service
|
||||
Wants=network-online.target lanremote-relay.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/cloudflared tunnel --protocol http2 --ha-connections 2 $BASE
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
MemoryMax=160M
|
||||
StandardOutput=append:/root/lanremote-relay/tunnel.log
|
||||
StandardError=append:/root/lanremote-relay/tunnel.log
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
echo "════════ 5. 回滚到备份 ════════"
|
||||
cp -a "$BACKUP" "$SVC"
|
||||
systemctl daemon-reload
|
||||
systemctl start lanremote-tunnel
|
||||
sleep 30
|
||||
echo " 隧道: $(systemctl is-active lanremote-tunnel)"
|
||||
echo " 连接数: $(curl -s --max-time 5 http://127.0.0.1:20241/metrics | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')"
|
||||
systemctl restart lanremote-tunnel
|
||||
sleep 20
|
||||
echo " 参数: $(grep -oE '--protocol [a-z0-9]+|--ha-connections [0-9]+|--edge-ip-version [0-9]+' "$SVC" | tr '\n' ' ')"
|
||||
echo " 连接数: $(curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')"
|
||||
echo " 落点:"
|
||||
curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null \
|
||||
| grep 'server_locations.*} 1' | sed 's/^/ /'
|
||||
echo " 备份: $BACKUP"
|
||||
|
||||
@@ -2602,7 +2602,13 @@ public class MainActivity extends Activity implements RemoteClient.Listener {
|
||||
// ★ 经中转连上之后,看看电脑是不是和我们在同一个网络。
|
||||
// 是的话就切成局域网直连 —— 快得多(几十毫秒 vs 几百毫秒),
|
||||
// 而且不消耗流量、不怕隧道抖。
|
||||
if (!relay.isEmpty()) {
|
||||
//
|
||||
// ⚠ 必须加 !usedLan:
|
||||
// preferLan 分支直连成功时 usedLan 已经是 true,
|
||||
// 这时候再跑这段会拿中转报的网卡列表去"切直连",
|
||||
// 而那条列表多半是空的 —— 于是打出一句
|
||||
// "不在同一网段,走中转"的**误导日志**(其实已经直连上了)。
|
||||
if (!relay.isEmpty() && !usedLan) {
|
||||
String directIp = pickSameSubnet(client.relayLanIps());
|
||||
int directPort = client.relayLanPort();
|
||||
if (directIp != null && directPort > 0) {
|
||||
@@ -2669,8 +2675,11 @@ public class MainActivity extends Activity implements RemoteClient.Listener {
|
||||
final String finalToken = newToken;
|
||||
final String finalName = serverName;
|
||||
final boolean finalLanDirect = usedLan && ok;
|
||||
// 这次是不是走中转连上的(决定要不要用服务器报的地址刷新记录)
|
||||
final boolean finalFromRelay = !relayUrl().isEmpty() && ok;
|
||||
// 这次是不是**经中转**连上的(决定要不要用服务器报的地址刷新记录)。
|
||||
// 必须排除"用户指定直连且直连成功"的情况 ——
|
||||
// 那时 relayLanIps() 是空的,刷新逻辑没意义还容易打错日志。
|
||||
final boolean finalFromRelay = !relayUrl().isEmpty() && ok
|
||||
&& !usedLan;
|
||||
ui.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
Reference in New Issue
Block a user