use std::sync::Arc;
use std::time::Duration;
use anyhow::{bail, Context, Result};
use nfrp_common::{
config::VisitorConfig,
frp::{
conn,
conn::FrpConn,
msg::{FrpMessage, Ping, UdpPacket},
},
util,
};
use tokio::{
io::AsyncWriteExt,
net::{TcpListener, TcpStream, UdpSocket},
sync::watch,
};
use tracing::{debug, info, warn};
use crate::{p2p, ClientSession};
async fn wait_session(
session_rx: &mut watch::Receiver<Option<Arc<ClientSession>>>,
) -> Result<Arc<ClientSession>> {
let deadline = tokio::time::Instant::now() + Duration::from_secs(15);
loop {
if let Some(s) = (*session_rx.borrow()).clone() {
return Ok(s);
}
let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
if remaining.is_zero() {
bail!("等待控制会话就绪超时(客户端还在重连?)");
}
if tokio::time::timeout(remaining, session_rx.changed())
.await
.is_err()
{
bail!("等待控制会话就绪超时(客户端还在重连?)");
}
}
}
pub async fn run(
session_rx: watch::Receiver<Option<Arc<ClientSession>>>,
cfg: VisitorConfig,
user: Arc<String>,
) -> Result<()> {
if cfg.server_name.is_empty() {
bail!("visitor [{}] 未配置 server_name(目标代理名)", cfg.name);
}
if cfg.secret_key.is_empty() {
bail!("visitor [{}] 未配置 secret_key", cfg.name);
}
if cfg.bind_port <= 0 {
info!(
visitor = %cfg.name,
bind_port = cfg.bind_port,
"bind_port <= 0,visitor 不监听本地端口"
);
return Ok(());
}
let prefix_user = if cfg.server_user.is_empty() {
user.as_str()
} else {
cfg.server_user.as_str()
};
let target = util::add_user_prefix(prefix_user, &cfg.server_name);
let listen = format!("{}:{}", cfg.bind_addr, cfg.bind_port);
let addr = util::resolve_addr(&listen)
.await
.with_context(|| format!("解析 visitor 监听地址 {listen} 失败"))?;
if !nfrp_common::util::is_loopback_addr(&cfg.bind_addr) {
warn!(
visitor = %cfg.name,
bind_addr = %cfg.bind_addr,
%addr,
"visitor 监听在非回环地址上 —— 该端口对所有能访问到它的主机开放。\
虽然 secret_key 强制且每条连接都过服务端签名校验,\
但仍建议改回 127.0.0.1,除非你确实需要从别的机器接入"
);
}
if cfg.visitor_type == "sudp" {
let mut rx = session_rx;
return run_sudp(&mut rx, cfg, target, addr).await;
}
let listener = TcpListener::bind(addr)
.await
.with_context(|| format!("visitor [{}] 监听 {addr} 失败(端口可能被占用)", cfg.name))?;
info!(
visitor = %cfg.name,
%addr,
server_name = %target,
kind = %cfg.visitor_type,
"visitor 已开始监听"
);
loop {
let (user_conn, peer) = match listener.accept().await {
Ok(v) => v,
Err(e) => {
warn!(visitor = %cfg.name, "visitor 监听中断:{e}");
break;
}
};
user_conn.set_nodelay(true).ok();
let mut rx = session_rx.clone();
let cfg = cfg.clone();
let target = target.clone();
tokio::spawn(async move {
if let Err(e) = handle_user(&mut rx, cfg, target, user_conn, peer).await {
debug!(%peer, "visitor 连接结束:{e:#}");
}
});
}
Ok(())
}
async fn run_sudp(
session_rx: &mut watch::Receiver<Option<Arc<ClientSession>>>,
cfg: VisitorConfig,
target: String,
addr: std::net::SocketAddr,
) -> Result<()> {
let sock = UdpSocket::bind(addr).await.with_context(|| {
format!(
"SUDP visitor [{}] 绑定本地 UDP {addr} 失败(端口可能被占用)",
cfg.name
)
})?;
info!(
visitor = %cfg.name,
%addr,
server_name = %target,
kind = "sudp",
"SUDP visitor 已开始监听"
);
let mut buf = vec![0u8; 65507];
loop {
let session = wait_session(session_rx).await?;
let mut udp_conn = match open_sudp_work_conn(&session, &cfg, &target).await {
Ok(c) => c,
Err(e) => {
warn!(visitor = %cfg.name, "SUDP 通道建立失败,2 秒后重试:{e:#}");
tokio::time::sleep(Duration::from_secs(2)).await;
continue;
}
};
debug!(visitor = %cfg.name, server_name = %target, "SUDP 通道已建立");
let mut keepalive = tokio::time::interval(Duration::from_secs(30));
keepalive.tick().await;
loop {
tokio::select! {
res = sock.recv_from(&mut buf) => {
match res {
Ok((n, remote)) => {
if let Err(e) = udp_conn.send_msg(&FrpMessage::UdpPacket(UdpPacket::new(&buf[..n], &remote))).await {
warn!(visitor = %cfg.name, "SUDP 报文写往工作连接失败:{e}");
break;
}
}
Err(e) => {
warn!(visitor = %cfg.name, "SUDP 本地读取失败:{e}");
break;
}
}
}
msg = udp_conn.recv_msg() => {
match msg {
Ok(Some(FrpMessage::UdpPacket(pkt))) => {
let Some(dst) = pkt.remote_addr.as_ref().and_then(|a| a.to_socket()) else {
debug!(visitor = %cfg.name, "SUDP 回包缺少访客地址,丢弃");
continue;
};
if let Err(e) = sock.send_to(pkt.payload(), dst).await {
debug!(%dst, "SUDP 回写访客失败:{e}");
}
}
Ok(Some(_)) => {} _ => break,
}
}
_ = keepalive.tick() => {
let _ = udp_conn.send_msg(&FrpMessage::Ping(Ping::default())).await;
}
}
}
warn!(visitor = %cfg.name, "SUDP 通道断开,准备重建");
tokio::time::sleep(Duration::from_millis(500)).await;
}
}
async fn open_sudp_work_conn(
session: &ClientSession,
cfg: &VisitorConfig,
target: &str,
) -> Result<FrpConn> {
let (tunnel, leftover) = conn::client_visitor_conn(
session.link.connect().await?,
session.link.wire,
&session.run_id,
target,
&cfg.secret_key,
)
.await
.with_context(|| {
format!(
"SUDP visitor [{}] 接入 {target} 失败(密钥不对、user 不在 allow_users、或 provider 未注册?)",
cfg.name
)
})?;
let mut udp_conn = FrpConn::new(tunnel, session.link.wire);
if !leftover.is_empty() {
udp_conn.push_leftover(leftover, false);
}
udp_conn.set_udp_codec(
session
.link
.udp_binary
.load(std::sync::atomic::Ordering::Relaxed),
);
Ok(udp_conn)
}
async fn handle_user(
session_rx: &mut watch::Receiver<Option<Arc<ClientSession>>>,
cfg: VisitorConfig,
target: String,
mut user: TcpStream,
peer: std::net::SocketAddr,
) -> Result<()> {
debug!(visitor = %cfg.name, %peer, "收到本地连接,等待控制会话就绪");
let session = wait_session(session_rx).await?;
if cfg.visitor_type == "xtcp" {
if let Some(route) = &session.p2p {
debug!(visitor = %cfg.name, %peer, "xtcp 尝试 P2P 直连");
match p2p::try_punch_as_visitor(route, &session.run_id, &target, &cfg.secret_key).await
{
Ok(mut stream) => {
match util::relay_between(&mut user, &mut stream).await {
Ok((up, down)) => {
debug!(visitor = %cfg.name, "P2P 转发结束:上行 {up}B / 下行 {down}B")
}
Err(e) => debug!(visitor = %cfg.name, "P2P 转发中断:{e}"),
}
return Ok(());
}
Err(e) => {
debug!(visitor = %cfg.name, "P2P 打洞失败,回退中继:{e:#}");
}
}
}
}
debug!(visitor = %cfg.name, %peer, "建立 visitor 通道 target={target}");
let (mut tunnel, leftover) = conn::client_visitor_conn(
session.link.connect().await?,
session.link.wire,
&session.run_id,
&target,
&cfg.secret_key,
)
.await
.with_context(|| {
format!(
"visitor [{}] 接入 {target} 失败(密钥不对、user 不在 allow_users、或 provider 未注册?)",
cfg.name
)
})?;
if !leftover.is_empty() {
user.write_all(&leftover).await?;
}
match util::relay_between(&mut user, &mut tunnel).await {
Ok((up, down)) => {
debug!(visitor = %cfg.name, "visitor 转发结束:上行 {up}B / 下行 {down}B")
}
Err(e) => debug!(visitor = %cfg.name, "visitor 转发中断:{e}"),
}
Ok(())
}