#2338·EasyTier

[feat] 建议给bind-device参数添加更详细的解释,并相应改善日志输出

Author: NyankoSongCreated Jun 10, 2026Updated Sep 17, 2026

功能类别 / Feature Category

监控和日志 / Monitoring & Logging

使用场景 / Use Case

开发者们好!

我在使用过程中遇到了完全无法辨认的报错,最后不得不依靠Agent工具解决,建议官方文档能相应记录,并改善日志输出。

由于我没有相应开发经验,此处我将AI出具的报告附在最后,以供参考。

当前限制 / Current Limitations

Docker 容器启动后,无法连接到远端 peer,日志每约 3 秒重复以下错误:

INFO CORE::INSTANCE::CONNECTION: connect to peer error
  dst="tcp://sample.net:11010" ip_version="V4"
  error=AnyhowError("connect timeout after 1.997675847s")

该报错内容完全没有包含有效信息;

--bind-device | 将连接器的套接字绑定到物理设备以避免路由问题。 [env: ET_BIND_DEVICE=]

我也无法从文档中得知这个参数修改为false可以解决此问题。

建议的解决方案 / Proposed Solution

A、如果此报错是预期行为,我建议将其记录在文档常见问题中;

B、如果此报错是非预期的,我建议改善日志输出可读性。

预期收益 / Expected Benefits

降低排错难度。

技术考虑 / Technical Considerations

No response

备选方案 / Alternative Solutions

No response

实现优先级 / Implementation Priority

我认为是中等优先级:这个错误看起来接近随机出现(相同的Ubuntu系统、Docker镜像、config.toml配置,但是此问题仅在某一台机器上出现),受到影响时很难确认;且改善文档和日志内容并不过多触及代码逻辑。

补充信息 / Additional Context

AI给出的具体报告如下:

点击展开:Easytier-Server Docker 连接超时排查报告

Easytier-Server Docker 容器连接超时问题排查报告

一、问题表现

Docker 容器 easytier-server(镜像 easytier/easytier:latest,版本 2.6.4-8428a89d)启动后,无法连接到远端 peer tcp://sample.net:11010,日志每约 3 秒重复以下错误:

INFO CORE::INSTANCE::CONNECTION: connect to peer error
  dst="tcp://sample.net:11010" ip_version="V4"
  error=AnyhowError("connect timeout after 1.997675847s")

容器本身监听 tcp://0.0.0.0:11010udp://0.0.0.0:11010 正常,但出站 TCP 连接始终无法完成。同样的配置文件在其他机器上正常运行。

二、环境信息

项目
操作系统 Ubuntu, Linux 6.8.0-117-generic x86_64
Docker 29.5.1
easytier 版本 easytier-core 2.6.4-8428a89d
容器网络模式 host
容器能力 CAP_NET_ADMIN, CAP_NET_RAW
远端 peer tcp://sample.net:11010 → sample-ip:11010
本地物理 IP 192.168.235.58/24 (eno1)
easytier TUN IP 10.114.154.191/24 (tun0)
rp_filter 2 (loose mode),所有接口均为 2
nf_conntrack 已加载,含 xt_REDIRECT、xt_MASQUERADE 模块

主机网络接口

eno1:            192.168.235.58/24   (物理网卡)
enxsamplemacadd: 169.254.3.1/24      (USB 网卡,link-local)
docker0:         172.17.0.1/16       (Docker 网桥)
edge0:           10.216.243.105/24   (n2n 虚拟网卡)
tun0:            10.114.154.191/24   (easytier TUN)

三、排查过程

步骤 1:分析日志

日志显示持续的 connect timeout after ~2s,且从容器启动就开始。排除了偶然网络抖动。

步骤 2:验证网络连通性(排除基础网络问题)

测试 结果
ping sample.net ✅ ~2ms 延迟
nc -zv sample.net 11010(宿主机) ✅ 成功
nc -zv sample.net 11010(容器内) ✅ 成功
nc -s 192.168.235.58 -zv ...(指定源 IP) ✅ 成功
nc -s 169.254.3.1 -zv ...(指定源 IP) ✅ 成功
nc -s 172.17.0.1 -zv ...(指定源 IP) ✅ 成功
DNS 解析(宿主机 + 容器内) ✅ 均正确解析到 sample-ip

结论:基础网络连通性、DNS、TCP 握手均正常,问题出在 easytier 自身的连接方式上。

步骤 3:排除 smoltcp

用户曾尝试禁用 use_smoltcp(从 true 改为 false),但问题仍然存在。日志中的错误完全相同。

步骤 4:阅读 easytier 源码,理解连接机制

分析 easytier/src/connector/manual.rseasytier/src/tunnel/tcp.rs

  • easytier 对 TCP peer 的总超时时间为 2 秒Duration::from_secs(2)
  • 连接流程:DNS 解析 → TCP 连接 → 握手(三阶段共享 2s 预算)
  • TCP 连接时,如果存在多个本地 IP(bind_addrs),会对每个 IP 各创建一个 TcpSocket,通过 FuturesUnordered 并发尝试连接

日志确认:

INFO easytier::tunnel::tcp: bind addr bind_addr=192.168.235.58:0 addr=sample-ip:11010
INFO easytier::tunnel::tcp: bind addr bind_addr=169.254.3.1:0     addr=sample-ip:11010
INFO easytier::tunnel::tcp: bind addr bind_addr=172.17.0.1:0      addr=sample-ip:11010

三个源 IP 各创建了一个 TCP 连接。

步骤 5:实时监控 TCP 状态 —— 关键突破

在 easytier 尝试连接时,使用 ss -tan state syn-sent 监控:

SYN-SENT  0  1  192.168.235.58%eno1:33561     sample-ip:11010
SYN-SENT  0  1  169.254.3.1%enxsamplemacadd:36841  sample-ip:11010
SYN-SENT  0  1  172.17.0.1%docker0:39747      sample-ip:11010

所有三个连接都卡在 SYN-SENT 状态,SYN 已发出但 SYN-ACK 从未到达。

但同时,在容器内执行 nc -zv sample.net 11010 却能成功连接。这证明了 SYN-ACK 是可以到达这台机器的,只是 easytier 创建的那些连接无法收到。

步骤 6:定位根因 —— SO_BINDTODEVICE

分析 easytier 源码 easytier/src/tunnel/common.rs 中的 bind 函数:

rust
// bind() 函数默认使用 BindDev::Auto
let dev = match dev {
    BindDev::Auto => get_interface_name_by_ip(&addr.ip()),  // 根据 IP 自动查找网卡名
    ...
};
// 然后通过 socket2 设置 SO_BINDTODEVICE
socket2_socket.bind_device_by_index_v4(dev_idx)?;

easytier 默认会为每个绑定到指定源 IP 的 socket 设置 SO_BINDTODEVICE,强制该 socket 的所有流量(包括收包)只能通过该网卡。

用 Python 验证:

python
# 测试 1:无 SO_BINDTODEVICE → 成功
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('192.168.235.58', 0))
s.connect(('sample-ip', 11010))  # SUCCESS

# 测试 2:设置 SO_BINDTODEVICE → 超时
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, 25, b'eno1\x00')  # SO_BINDTODEVICE
s.bind(('192.168.235.58', 0))
s.connect(('sample-ip', 11010))  # TIMEOUT - 与 easytier 行为一致!

SO_BINDTODEVICE 导致 SYN 能发出但 SYN-ACK 无法被 socket 接收,造成 TCP 连接永远无法完成握手,触发 easytier 的 2 秒超时。

为什么 nc 和 ping 能通

ncping 不设置 SO_BINDTODEVICE,内核可以自由选择收包路径,因此 SYN-ACK 能正常到达。

四、解决方案

修复方法

在 easytier 配置文件(/home/username/EasyTier/config.toml)的 [flags] 段添加:

toml
bind_device = false

完整配置示例(仅列出关键部分):

toml
[flags]
accept_dns = true
enable_kcp_proxy = true
enable_quic_proxy = true
private_mode = true
use_smoltcp = true
bind_device = false    # ← 关键修复

此选项对应的命令行参数为 --bind-device <BIND_DEVICE>,环境变量为 ET_BIND_DEVICE

验证

修复后日志:

INFO easytier::connector::manual: reconnect succ: 1255628362 ... tcp://sample.net:11010

peer 连接成功,且多个 peer 通过 TCP 直连加入:

INFO ... peer_node_id=584772333 is_directly_connected=true
INFO ... peer_node_id=1117003951 is_directly_connected=true
INFO ... peer_node_id=2433151760 ...

不再出现 connect timeout 错误。

五、对开发者(EasyTier 项目)的建议

  1. bind_device 默认行为可能过于激进BindDev::Auto 作为默认值意味着对所有出站连接都启用 SO_BINDTODEVICE。在多网卡环境中(尤其是同时运行多个 VPN 软件时),这可能导致回程 SYN-ACK 被内核丢弃。建议考虑默认关闭。

  2. 错误信息不够明确connect timeout after 1.997s 无法区分是 TCP 握手失败还是应用层握手失败。建议增加更细粒度的超时报告(如区分 "TCP connect timeout" 和 "handshake timeout")。

  3. SO_BINDTODEVICErp_filter 交互:此问题发生在 rp_filter=2(loose mode)的 Linux 6.8 内核上,可能与内核版本或 conntrack 配置有关,值得深入调查。

  4. 建议的代码改动方向

    • 为 TCP connector 单独提供禁用 SO_BINDTODEVICE 的配置选项
    • 或在 connect_with_custom_bind 中仅对 listeners 使用 SO_BINDTODEVICE,对出站连接使用 BindDev::Disabled

六、附录

配置文件(修复前)

toml
hostname = "sample-hostname"
instance_name = "sample-instance-name"
instance_id = "sample-instance-id"
ipv6_public_addr_auto = true
ipv4 = "10.114.154.191"
dhcp = false
listeners = [
    "tcp://0.0.0.0:11010",
    "udp://0.0.0.0:11010",
]

[network_identity]
network_name = "sample-instance-name"
network_secret = "sample-secret"

[[peer]]
uri = "tcp://sample.net:11010"

[flags]
accept_dns = true
enable_kcp_proxy = true
enable_quic_proxy = true
private_mode = true
use_smoltcp = true   # 改为 false 不解决问题

Docker 容器启动参数

--network host
--cap-add CAP_NET_ADMIN
--cap-add CAP_NET_RAW
-v /home/username/EasyTier/config.toml:/app/config.toml:ro
-v /home/username/EasyTier/log:/var/log/easytier:rw
-v /etc/machine-id:/etc/machine-id:ro

关键源码位置

功能 文件 关键函数
连接重试与超时 easytier/src/connector/manual.rs conn_reconnect(), reconnect_timeout()
TCP 连接器 easytier/src/tunnel/tcp.rs connect_with_custom_bind()
Socket 绑定(SO_BINDTODEVICE) easytier/src/tunnel/common.rs bind(), setup_socket2_ext()
并发连接等待 easytier/src/tunnel/common.rs wait_for_connect_futures()

报告生成时间:2026-06-09T11:15 CST