原创 通用工程 2025-06-24 12:03 上海
最近在哔哩哔哩,我们开发了一种改进的 BBR 拥塞控制算法,需要在真实环境中进行测试。
🚀 **内核模块热交换与套接字控制**:在迭代开发过程中,直接加载新版本内核模块时遇到同名冲突。通过`setsockopt`配合`pidfd_getfd`系统调用,实现了“套接字窃取”,允许在不重新编译或等待套接字关闭的情况下,动态修改正在使用的套接字的拥塞控制算法,提高了开发效率。
⚠️ **孤立套接字问题与`ss -K`的局限性**:文章详细阐述了因TCP窗口机制(如零窗口)可能导致进程退出后产生无法正常清理的孤立套接字。虽然`ss -K`命令可用于终止套接字,但在特定内核错误下,即使`ss -K`报告成功,孤立套接字仍可能残留,因为它可能已被标记为`SOCK_DEAD`,且`tcp_write_queue_purge`清除了`packets_out`计数器,阻止了后续的超时机制。
💡 **内核错误定位与修复**:通过`bpftrace`追踪`tcp_abort`函数,发现`SOCK_DEAD`标志导致套接字未被完全关闭。分析内核代码后,确认问题根源在于`tcp_abort`中未正确处理`SOCK_DEAD`状态下的套接字清理。最终通过移除`SOCK_DEAD`检查的补丁解决了此问题,该补丁已被内核社区接受并向后移植。
🛠️ **调试工具的有效运用**:文章强调了`ss`(特别是`-K`选项)、`bpftrace`和`virtme-ng`在调试网络内核问题中的重要作用。`ss`用于查看和管理套接字状态,`bpftrace`用于动态追踪内核函数执行和变量状态,而`virtme-ng`则提供了快速的内核测试环境,极大地提升了问题排查和验证的效率。
原创 通用工程 2025-06-24 12:03 上海
最近在哔哩哔哩,我们开发了一种改进的 BBR 拥塞控制算法,需要在真实环境中进行测试。
ip netns add ns
ip link add ve_o type veth peer name ve_i
ip link set ve_i netns ns
ip link set ve_o up
ip addr add dev ve_o 192.168.0.2/24
ip -n ns link set ve_i up
ip -n ns addr add dev ve_i 192.168.0.1/24
加载模块
insmod tcp_bbr_bili.ko
使其成为默认的拥塞控制算法
sysctl -w net/ipv4/tcp_congestion_control=bbr_bili
$ ip netns exec ns ss -npti
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
ESTAB 0 0 192.168.0.1:1000 192.168.0.2:50916 users:(("socat",pid=692883,fd=6))
bbr_bili ...
$ insmod tcp_bbr_bili.ko
insmod: ERROR: could not insert module tcp_bbr_bili.ko: File exists
rmmod tcp_bbr_bili
rmmod: ERROR: Module tcp_bbr_bili is in use
lsmod | grep bili
tcp_bbr_bili 20480 2
setsockopt(sockfd, IPPROTO_TCP, TCP_CONGESTION, "bbr_bili", strlen("bbr_bili"));
$ ip netns exec ns ss -npt
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
ESTAB 0 0 192.168.0.1:1000 192.168.0.2:50916 users:(("socat",pid=692883,fd=6))
// 获取目标进程的 PIDFD
pidfd = syscall(SYS_pidfd_open, pid, 0);
// 复制套接字 fd
fd = syscall(SYS_pidfd_getfd, pidfd, targetfd, 0);
// 设置拥塞控制算法
setsockopt(fd, IPPROTO_TCP, TCP_CONGESTION, "bbr_bili", strlen("bbr_bili"));
$ ./changeling 6928836 cubic
setsockopt success
$ ip netns exec ns ss -npti
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
ESTAB 0 0 192.168.0.1:1000 192.168.0.2:50916 users:(("socat",pid=692883,fd=6))
cubic ...
$ ip netns exec ns ss -np
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
tcp FIN-WAIT-1 0 20481 192.168.0.1:58732 192.168.0.2:65432
# 终端 1 - 服务器
$ socat -u \ # 使用单向模式。第一个地址仅用于读取,第二个地址仅用于写入。
- \ # 第一个地址,即 STDIO (-)。
"TCP-LISTEN:65432,fork" # 第二个地址,我们的侦听服务器。
终端 2 - 客户端
exec ns socat \ ip netns
"/dev/zero" \
"TCP:192.168.0.2:65432"
等待几秒钟后使用 Ctrl+C 终止客户端
^C
$ ip netns exec ns ss -n4tpe
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
FIN-WAIT-1 0 883585 192.168.0.1:60820 192.168.0.2:65432 timer:(persist,1min50sec,0) ...
$ ss -n4tpe '( sport = :65432 )'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
ESTAB 124032 0 192.168.0.2:65432 192.168.0.1:60820 users:(("socat",pid=1509536,fd=6)) ...
# 在此处添加过滤器以确保。
$ ip netns exec ns ss -n4tpe -K '( dport = :65432 )'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
FIN-WAIT-1 0 883585 192.168.0.1:60820 192.168.0.2:65432
$ ip netns exec ns ss -n4tpe -K '( dport = :65432 )'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
FIN-WAIT-1 0 883585 192.168.0.1:60820 192.168.0.2:65432 ino:0 sk:531a ---
$ ip netns exec ns ss -n4tpe -K '( dport = :65432 )'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
FIN-WAIT-1 0 883585 192.168.0.1:60820 192.168.0.2:65432 ino:0 sk:531a ---
$ ip netns exec ns ss -n4tpe -K '( dport = :65432 )'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
FIN-WAIT-1 0 883585 192.168.0.1:60820 192.168.0.2:65432 ino:0 sk:531a ---
staticintkill_inet_sock(struct nlmsghdr *h, void *arg, struct sockstat *s)
{
...
DIAG_REQUEST(req, struct inet_diag_req_v2 r);
req.nlh.nlmsg_type = SOCK_DESTROY;
...
return rtnl_talk(rth, &req.nlh, NULL);
}
staticintshow_one_inet_sock(struct nlmsghdr *h, void *arg)
{
...
if (diag_arg->f->kill && kill_inet_sock(h, arg, &s) != 0) {
if (errno == EOPNOTSUPP || errno == ENOENT) {
/* Socket can't be closed, or is already closed. */
return0;
} else {
perror("SOCK_DESTROY answers");
return-1;
}
}
...
err = inet_show_sock(h, &s);
if (err < 0)
return err;
return0;
}
// net/ipv4/inet_diag.c
staticintinet_diag_cmd_exact(){
err = handler->destroy(in_skb, req);
}
// net/ipv4/tcp_diag.c
staticconststructinet_diag_handlertcp_diag_handler = {
.destroy = tcp_diag_destroy,
};
// net/ipv4/tcp_diag.c
staticinttcp_diag_destroy(struct sk_buff *in_skb,
const struct inet_diag_req_v2 *req) {
err = sock_diag_destroy(sk, ECONNABORTED);
}
// net/core/sock_diag.c
intsock_diag_destroy(struct sock *sk, int err){
return sk->sk_prot->diag_destroy(sk, err);
}
// net/ipv4/tcp_ipv4.c
structprototcp_prot = {
.diag_destroy = tcp_abort,
};
// net/ipv4/tcp.c
inttcp_abort(struct sock *sk, int err)
{
...
if (!sock_flag(sk, SOCK_DEAD)) {
...
if (tcp_need_reset(sk->sk_state))
tcp_send_active_reset(sk, GFP_ATOMIC);
tcp_done(sk);
}
...
tcp_write_queue_purge(sk);
release_sock(sk);
return0;
}
EXPORT_SYMBOL_GPL(tcp_abort);
// net/ipv4/tcp.c
voidtcp_done(struct sock *sk)
{
...
if (!sock_flag(sk, SOCK_DEAD))
sk->sk_state_change(sk);
else
inet_csk_destroy_sock(sk);
}
EXPORT_SYMBOL_GPL(tcp_done);
// 完整代码在 github 上
kprobe:tcp_abort{
printf("aborting: %x\n", ((struct sock *)arg0)->sk_flags);
}
附加 bpftrace 后尝试终止孤立套接字
bpftrace tcp_abort.bt
Attaching 1 probe...
aborting: 0x301
// net/ipv4/tcp_timer.c
staticvoidtcp_probe_timer(struct sock *sk)
{
...
if (tp->packets_out || !skb) {
icsk->icsk_probes_out = 0;
return;
}
...
if (icsk->icsk_probes_out >= max_probes) {
// tcp_write_err() - 关闭套接字并保存错误信息
abort: tcp_write_err(sk);
} else {
/* 仅当我们没有关闭连接时才发送另一个探测。*/
tcp_send_probe0(sk);
}
}
// net/ipv4/tcp.c
voidtcp_write_queue_purge(struct sock *sk)
{
...
tcp_sk(sk)->packets_out = 0;
inet_csk(sk)->icsk_backoff = 0;
}
AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。
鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑