工作中经常使用netcat
这个小工具来测试某些端口的连通性,比如5060,5080之类的。于是自己萌生了造轮子的想法,并尝试着给`netcat`加个心跳检测的小功能。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUF_SIZE 256
int main(){
int fd, connect_fd;
char buffer[BUF_SIZE] = {0};
struct sockaddr_in cliaddr, servaddr;
int cli_addr_len = sizeof(cliaddr);
int n;
fd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
bzero((char *)&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(10000);
int ret = bind(fd, (struct sockaddr *)&servaddr, sizeof(servaddr));
if (ret < 0){
printf("error %d\n", ret);
exit(1);
}
listen(fd, 5);
connect_fd = accept(fd, (struct sockaddr *)&cliaddr, &cli_addr_len);
while (1) {
char buffer[BUF_SIZE] = {0};
n = read(connect_fd, buffer, BUF_SIZE);
//write(connect_fd, buffer, n);
printf("%s", buffer);
}
close(connect_fd);
close(fd);
return 0;
}
这样一个简单的轮子就造好了,他看起来和netcat
实现了类似的功能。 gcc server.c
编译。 ./a.out
运行
在客户端使用nc ip port连过来,就可以发消息了。抓包查看,有数据就会发没数据就停哪里等着,即使对方死了可能这个链接还会一直在。
于是我又想改造下这个小程序,让他可以发心跳检测,弄个通用的补丁。在没有源码情况下替换已知的一些函数,加入想要的keepalive的功能。使用strace ./a.out
查看系统调用
可以看到只调用 setsockopt
设置了 SO_REUSEADDR
允许端口复用,并没有设置 TCP_KEEPALIVE
,那我们 hook
一下 setsockopt
函数调用,让它在设置端口复用的同时设置 TCP_KEEPALIVE
。
恰好,LD_PRELOAD可以满足需求,他运行在程序运行前优先加载动态链接库,通过这个环境变量,我们可以修改覆盖真正的系统调用,达到我们的目的。代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
static int (*new)(int , int , int , void *, socklen_t) = NULL;
__attribute__((constructor)) void init() {
new = dlsym(RTLD_NEXT, "setsockopt");
}
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) {
new(sockfd, level, optname, &optval, optlen);
// 判断是否是 SO_REUSEADDR
if (level == SOL_SOCKET && optname == SO_REUSEADDR) {
int val = 1; // 设置 SO_KEEPALIVE
int keepidle = 10; // 如该连接在60秒内没有任何数据往来,则进行探测
int keepinterval = 2; // 探测时发包的时间间隔为5 秒
int keepcount = 3;// 探测尝试的次数.如果第1次探测包就收到响应了,则后2次的不再发.
new(sockfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&val , sizeof(val));
new(sockfd, SOL_TCP, TCP_KEEPIDLE, (void*)&keepidle , sizeof(keepidle ));
new(sockfd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepinterval , sizeof(keepinterval ));
new(sockfd, SOL_TCP, TCP_KEEPCNT, (void *)&keepcount , sizeof(keepcount ));
return 0;
}
return 0;
}
编译上面的 pach.c 文件为 .so 文件: gcc pach.c -fPIC -D_GNU_SOURCE -shared -ldl -o pach.so
如下命令运行 LD_PRELOAD=./pach.so ./a.out
抓包可以看到已经是每10秒发个包,查看对方是否还活着,完美解决了我的需求
即使把他挂在别的有setsockopt
函数的程序中,也可以轻松加上心跳功能。
是不是有些心动,自己也动手尝试下吧。