很久以前,我在开发一个TCP拥塞控制算法模块的时候,由于频繁快速迭代,常常需要将模块卸了再装,装了再卸,由于我更改的Linux的全局内核参数net.ipv4.tcp_congestion_control,我的ssh连接也是要使用同样的CC模块,这导致在我将默认算法切换到reno builtin算法后,必须将所有TCP连接全部断开后才能使得CC模块的引用计数降为0,从而顺利卸载。

以bic为例,我将tcp_bic模块加载后,连入几个TCP连接:

[root@localhost ~]# sysctl net.ipv4.tcp_congestion_control
net.ipv4.tcp_congestion_control = bic
[root@localhost ~]# lsmod |grep bic
tcp_bic                13483  3

你看,tcp_bic的引用计数为3,这意味着有3个连接在使用该算法:

[root@localhost ~]# ss -antip|awk -F ' ' '/ bic/{print a;}{a=$6}'
users:(("sshd",pid=10323,fd=3))
users:(("sshd",pid=10515,fd=3))
users:(("sshd",pid=10348,fd=3))

如何在不杀掉这三个进程的前提下,卸载掉tcp_bic模块呢?

本文就是说这个的,哈哈。

思路很简单,将这三个进程的CC算法切换成reno这个builtin算法不就可以了吗?

谈何容易?虽然socket支持TCP_CONGESTION这个sockopt,但它需要在进程上下文设置,这需要动态hook进程,这三个进程大部分时间都在wait,我们可以随便找一个看一下:

[root@localhost ~]# cat /proc/10348/wchan
poll_schedule_timeout
[root@localhost ~]#

这意味着我们需要hook住其select/poll/epoll等调用,然后插入setsockopt调用。

复杂,且无趣。

下面我来杂耍一种基于systemtap的方法。

完成两个目标即可:

  • 想办法切到目标进程的上下文。
  • 在目标进程上下文完成setsockopt。

第二个目标手到擒来,问题是第一个目标如何实现。

也不难。

我们知道,所有进程被切换出去都是从__schedule进入,而它再次被切换回来则从__schedule出来,因此只需要hook __schedule.return即可,然后wakeup目标进程。

这会导致wait在select/poll/epoll的目标进程被唤醒,然后检查资源未就绪后再次被切换出去,我们的机会正在其中间,即__schedule返回的一刹那。

代码就是下面的样子:

#!/usr/bin/stap -g%{#include <net/sock.h>
%}function alter_cc(fd:long)
%{int err;mm_segment_t fs;char cc[] = "reno";struct socket *socket = NULL;socket = sockfd_lookup(STAP_ARG_fd, &err);if (socket == NULL) {return;}fs = get_fs();set_fs(KERNEL_DS);
#define TCP_CONGESTION  13sock_common_setsockopt(socket, SOL_TCP, TCP_CONGESTION, (void*)cc, strlen(cc));set_fs(fs);sockfd_put(socket);
%}probe kernel.function("__schedule").return
{if (pid() == $1) {alter_cc($2);exit()}
}function wakeup(pid:long)
%{struct task_struct *tsk;tsk = pid_task(find_vpid(STAP_ARG_pid), PIDTYPE_PID);if (tsk)wake_up_process(tsk);
%}probe timer.ms(500)
{wakeup($1)
}

对,就是这么简单。来来来,看效果:

[root@localhost ~]# lsmod |grep tcp_bic
tcp_bic                13483  3
[root@localhost ~]# ss -antip|awk -F ' ' '/ bic/{print a;}{a=$6}'
users:(("sshd",pid=11707,fd=3))
users:(("sshd",pid=11680,fd=3))
users:(("sshd",pid=11654,fd=3))
[root@localhost ~]# ss -antip|awk -F ' ' '/ bic/{print a;}{a=$6}'|egrep -o [0-9]+ |sed 'N;s/\n/ /g' |xargs -L 1 ./alterCC.stp
[root@localhost ~]# ss -antip|awk -F ' ' '/ bic/{print a;}{a=$6}'
[root@localhost ~]# lsmod |grep tcp_bic
tcp_bic                13483  0
[root@localhost ~]# rmmod tcp_bic
[root@localhost ~]# echo $?
0
[root@localhost ~]# lsmod |grep tcp_bic

一气呵成,不多说。

可以确认下当前这些进程的CC算法是不是已经改成了reno,于是我用reno来正则匹配:

[root@localhost ~]# ss -antip|awk -F ' ' '/reno/{print a;}{a=$6}'
users:(("sshd",pid=11707,fd=3))
users:(("sshd",pid=11680,fd=3))
users:(("sshd",pid=11654,fd=3))

好吧,我承认这里的ss/egrep/sed/xargs写的有点low,如果有谁能帮我写个优雅的,我感激不尽。

那么,再杂耍一个crash extension插件来遍历所有TCP连接的CC算法,可以的,代码如下:


static int get_field(unsigned long addr, char *name, char *field, void* buf)
{unsigned long off = MEMBER_OFFSET(name, field);if (!readmem(addr + off, KVADDR, buf, MEMBER_SIZE(name, field), name, FAULT_ON_ERROR))return 0;return 1;
}struct dummy_list {struct dummy_list *stub;
};struct dummy_list *iter;
void do_cmd(void)
{unsigned long _addr, sock_addr, cc_addr, socket_addr, inode_addr, next;unsigned long ehash_mask = 0, inode;char name[64];int i;optind++;iter = (struct dummy_list *)htol(args[optind], FAULT_ON_ERROR, NULL);optind++;ehash_mask = atoi(args[optind]);for (i = 0; i <= ehash_mask; i++) {next = (unsigned long)iter + i*sizeof(unsigned long);do {inode = 0;get_field(next, "hlist_nulls_node", "next", &_addr);if (_addr & 0x1) {break;}sock_addr = _addr - MEMBER_OFFSET("sock_common", "skc_nulls_node");get_field(sock_addr, "inet_connection_sock", "icsk_ca_ops", &cc_addr);get_field(cc_addr, "tcp_congestion_ops", "name", &name[0]);get_field(sock_addr, "sock", "sk_socket", &socket_addr);if (socket_addr) {inode_addr = socket_addr + MEMBER_OFFSET("socket_alloc", "vfs_inode");get_field(inode_addr, "inode", "i_ino", &inode);}fprintf(fp, "  ----%s   %d\n", name, inode);} while(get_field(next, "hlist_nulls_node", "next", &next));}
}static struct command_table_entry command_table[] = {{ "tcpcc", do_cmd, NULL, 0},{ NULL },
};void __attribute__((constructor)) tcpcc_init(void)
{register_extension(command_table);
}void __attribute__((destructor)) tcpcc_fini(void) { }

编译之:

[root@localhost ext]# gcc -fPIC -shared tcpcc.c -o tcpcc.so

在crash命令行载入,执行之:

crash>
crash> extend  tcpcc.so
./tcpcc.so: shared object loaded
crash> px tcp_hashinfo.ehash
$5 = (struct inet_ehash_bucket *) 0xffffc90000188000
crash> pd tcp_hashinfo.ehash_mask
$6 = 8191
crash>
crash> tcpcc 0xffffc90000188000 8191----reno   27670----reno   27547----reno   27407----reno   36423----reno   28717----reno   36282----reno   36138
crash>

以上输出的第二列是socket的inode号,这个用来和procfs里task的fd来匹配,最终的交集配合pid来执行alterCC.stp脚本。

唉,又将是一堆可怕的命令组合。


浙江温州皮鞋湿,下雨进水不会胖。

卸载正在使用中的tcp_congestion_control模块相关推荐

  1. 计算机管理找不到指定模块,卸载时找不到指定模块怎么办_电脑卸载找不到指定模块处理方法-win7之家...

    我们在使用电脑的过程中,对于系统中安装的大不多数软件有些是不需要,因此就需要卸载掉,以此保证电脑的内存充足,但是近日有的用户发现自己的电脑在卸载软件时总是会有找不到指定模块的提示,那么卸载时找不到指定 ...

  2. idea如何删除java里面工程,Java开发工具IntelliJ IDEA配置项目系列教程(七):卸载模块...

    IntelliJ IDEA是Java语言开发的集成环境,IntelliJ在业界被公认为优秀的Java开发工具之一,尤其在智能代码助手.代码自动提示.重构.J2EE支持.Ant.JUnit.CVS整合. ...

  3. python卸载模块的方法汇总_Python卸载模块的方法汇总

    本文给大家汇总介绍了3种卸载Python已安装模块的方法,推荐大家使用easy_install或者pip的方式来进行,简单方便 easy_install 卸载 通过easy_install 安装的模块 ...

  4. 学习笔记之卸载远程目标进程中的DLL模块(转)

    学习笔记之卸载远程目标进程中的DLL模块 (2007-07-23 23:51:02) 转载▼ 学习笔记之卸载远程目标进程中的DLL模块2007/7/23 1.首先得把DLL模块中的线程结束 使用Cre ...

  5. [运维笔记] PowerShell (模块).模块的查找、安装、卸载、更新、保存、发布

    [运维笔记] PowerShell (库/模块).库的查找.安装.卸载.更新.保存.发布 作者主页:jcLee95:https://blog.csdn.net/qq_28550263?spm=3001 ...

  6. python卸载包很慢_Python卸载模块的方法汇总

    本文给大家汇总介绍了3种卸载Python已安装模块的方法,推荐大家使用easy_install或者pip的方式来进行,简单方便 easy_install 卸载 通过easy_install 安装的模块 ...

  7. python卸载模块的方法汇总_Python卸载模块的方法

    easy_install 卸载 通过easy_install 安装的模块可以直接通过 easy_install -m PackageName 卸载,然后删除\Python27\Lib\site-pac ...

  8. 基于PsSetLoadImageNotifyRoutine实现监控模块加载并卸载已加载模块(卸载DLL、EXE和sys等加载)

    背景 对于内核层实现监控模块的加载,包括加载DLL模块.内核模块等.你也许会想到 HOOK 各种内核函数来实现.确定,在内核层中的 HOOK 已经给人留下太多深刻的印象了,有 SSDT HOOK.In ...

  9. python函数模块概念_python中模块和包的概念

    1.模块 一个.py文件就是一个模块.这个文件的名字是:模块名.py.由此可见在python中,文件名和模块名的差别只是有没有后缀.有后缀是文件名,没有后缀是模块名. 每个文件(每个模块)都是一个独立 ...

  10. linux 内核可装载模块 版本检查机制

    为保持 Linux 内核的稳定与可持续发展,内核在发展过程中引进了可装载模块这一特性.内核可装载模块就是可在内核运行时加载到内核的一组代码.通常 , 我们会在两个版本不同的内核上装载同一模块失败,即使 ...

最新文章

  1. FGMap学习之--加载百度地图
  2. HU 3496 Watch The Movie---二维费用
  3. Ajax和JSON-学习笔记02【JQuery方式实现Ajax】
  4. 电路邱关源第五版pdf_硬件狗教你学电路【二】:电路分析方法
  5. 大剑无锋之TCP和HTTP的区别【面试推荐】
  6. Fiori Elements value help selection pass back
  7. 玩转C#控件-常用控件属性
  8. 使用带有注释和JQuery的Spring MVC 3的Ajax
  9. 面试准备每日五题:C++(二)——mallocnew、宏、volatile、constvolatile、(a)和(a)
  10. freeswitch添加tls加密
  11. 【运输量预测】基于matlab多种算法公路运输量预测【含Matlab源码 041期】
  12. 经典c语言基础代码大全,经典基础实例编程C语言代码.doc
  13. CodeBlocks下载及安装教程(小白图解)
  14. android矢量地图画法_Android 矢量室内地图开发实例
  15. win11电脑快捷键
  16. led点阵c语言程序,51单片机驱动LED点阵扫描显示C语言程序
  17. SOFTICE之后继有人——Syser
  18. if语句与switch语句
  19. MSP430F149实现超声波测距并通过串口和PC机通信进行显示
  20. R3.6.3下载 Rstudio下载及安装,网盘链接永久有效

热门文章

  1. 服务器网卡支持25G,25G服务器网卡光模块解决方案
  2. 配对碱基链(C语言)
  3. 图特摩斯三世厚积薄发
  4. linu {,} 使用
  5. linux学习记录(二)
  6. 动环监控设备维护与故障处理,动环监控系统调试
  7. kali安装网卡驱动
  8. 一个app的流程分析
  9. 学习Nginx这一篇就够了(非本人原创文章)
  10. oneos组件系列02:ws2812全彩LED