通过内核模块按fd强制tcp的quickack方法

一、背景

tcp的quickack功能是为了让ack迅速回发,快速响应,减少网络通讯时延,属于一个优化项,但是tcp的quickack是有配额限制的,配置是16个quick,也就是短时间内quickack了16次以后,这个配额为0了以后,就算当前是quickack模式(非pingpong模式),系统仍然可能不进行quickack发送。为什么用“可能”呢?因为还有一些其他的条件检查,这些条件是或的关系,只要有一条满足了,系统就会quickack,其中,最常满足的是通过设置路由规则的quickack,让相关的tcp连接的fd都能强制进行quickack,就算quickack配额已经为0了,也并不影响quickack的发送。

在下面第二章里,我们贴出了实现的源码,并进行成果展示,同时也会给出路由方式进行设置的方式和结果展示,在第三章里,会讲解第二章里的源码的实现原理。

在后面的博客里,我们会进行setsockopt的定制,增加一个force quickack的选项,避免反复的通过setsockopt来触发增加quickack配额。

另外,如果需求可以通过增加路由规则来满足那也没必要这么麻烦,直接参考 2.4 一节里的方法即可。通过增加路由规则的方式的不足在于它不能按照fd来进行指定fd的quickack强制,要强制就对所有适用于增加的这条路由规则的tcp连接一起起效了。

二、源码及成果展示

2.1 通过内核模块方式捕获setsockopt系统调用进行quickack配额填充的源码

源码如下:

#include <linux/module.h>
#include <linux/capability.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/ctype.h>
#include <linux/seq_file.h>
#include <linux/poll.h>
#include <linux/types.h>
#include <linux/ioctl.h>
#include <linux/errno.h>
#include <linux/stddef.h>
#include <linux/lockdep.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/init.h>
#include <asm/atomic.h>
#include <trace/events/workqueue.h>
#include <linux/sched/clock.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/tracepoint.h>
#include <trace/events/osmonitor.h>
#include <trace/events/sched.h>
#include <trace/events/irq.h>
#include <trace/events/kmem.h>
#include <linux/ptrace.h>
#include <linux/uaccess.h>
#include <asm/processor.h>
#include <linux/sched/task_stack.h>
#include <linux/nmi.h>
#include <asm/apic.h>
#include <linux/version.h>
#include <linux/sched/mm.h>
#include <asm/irq_regs.h>
#include <linux/kallsyms.h>
#include <linux/kprobes.h>
#include <linux/stop_machine.h>MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhaoxin");
MODULE_DESCRIPTION("Module for debug quickack.");
MODULE_VERSION("1.0");struct kern_tracepoint {void *callback;struct tracepoint *ptr;bool bregister;
};
static void clear_kern_tracepoint(struct kern_tracepoint *tp)
{if (tp->bregister) {tracepoint_probe_unregister(tp->ptr, tp->callback, NULL);}
}#define INIT_KERN_TRACEPOINT(tracepoint_name) \static struct kern_tracepoint mykern_##tracepoint_name = {.callback = NULL, .ptr = NULL, .bregister = false};#define TRACEPOINT_CHECK_AND_SET(tracepoint_name)                                             \static void tracepoint_name##_tracepoint_check_and_set(struct tracepoint *tp, void *priv) \{                                                                                \if (!strcmp(#tracepoint_name, tp->name))                                     \{                                                                            \((struct kern_tracepoint *)priv)->ptr = tp;                          \return;                                                                  \}                                                                            \}//INIT_KERN_TRACEPOINT(sched_switch)
//TRACEPOINT_CHECK_AND_SET(sched_switch)
//INIT_KERN_TRACEPOINT(sched_waking)
//TRACEPOINT_CHECK_AND_SET(sched_waking)typedef unsigned long (*kallsyms_lookup_name_func)(const char *name);
kallsyms_lookup_name_func _kallsyms_lookup_name_func;void* get_func_by_symbol_name_kallsyms_lookup_name(void)
{int ret;void* pfunc = NULL;struct kprobe kp;memset(&kp, 0, sizeof(kp));kp.symbol_name = "kallsyms_lookup_name";kp.pre_handler = NULL;kp.addr = NULL;	// 作为强调,提示使用symbol_nameret = register_kprobe(&kp);if (ret < 0) {printk("register_kprobe fail!\n");return NULL;}printk("register_kprobe succeed!\n");pfunc = (void*)kp.addr;unregister_kprobe(&kp);return pfunc;
}void* get_func_by_symbol_name(const char* i_symbol)
{if (_kallsyms_lookup_name_func == NULL) {return NULL;}return _kallsyms_lookup_name_func(i_symbol);
}#include <uapi/linux/rtnetlink.h>
#include <net/sock.h>
#include <net/inet_connection_sock.h>#include <linux/netdevice.h>
#include <linux/inetdevice.h>
#include <linux/ip.h>
#include <net/dst.h>
#include <net/route.h>
#include <net/tcp.h>
#include <linux/inet.h>
#include <linux/sockptr.h>// void print_dst_entry(struct dst_entry *dst) {
//     struct rtable *rt = (struct rtable *)dst;
//     struct in_device *in_dev;//     if (!dst)
//         return;//     in_dev = __in_dev_get_rcu(rt->u.dst.dev);//     printk(KERN_INFO "Dst Entry Info:\n");
//     printk(KERN_INFO "  Input Device: %s\n", rt->u.dst.dev->name);
//     printk(KERN_INFO "  Output Device: %s\n", rt->u.dst.dev->name);
//     if (in_dev) {
//         printk(KERN_INFO "  In Device MTU: %d\n", in_dev->mtu);
//         printk(KERN_INFO "  In Device Output MTU: %d\n", in_dev->output_mtu);
//     }
//     printk(KERN_INFO "  Expires: %ld\n", dst->expires);
//     printk(KERN_INFO "  Flags: 0x%lx\n", dst->flags);
//     printk(KERN_INFO "  Last Use: %lu\n", dst->lastuse);
//     printk(KERN_INFO "  Obsolete: %lu\n", dst->obsolete);
//     printk(KERN_INFO "  Hash Chain: %p\n", dst->dn.next);
//     printk(KERN_INFO "  Input Hash: 0x%lx\n", dst->hash);
//     printk(KERN_INFO "  Output Hash: 0x%lx\n", dst->child_mask);
//     printk(KERN_INFO "  Reference Count: %d\n", atomic_read(&dst->__refcnt));
//     printk(KERN_INFO "  Use Count: %d\n", dst->use);
//     printk(KERN_INFO "  Wireless Use Count: %d\n", dst->wireless_ref);
//     printk(KERN_INFO "  Last Metric Update: %lu\n", dst->last_metric_update);
//     printk(KERN_INFO "  Protocol Specific Data: %p\n", dst->input);
//     printk(KERN_INFO "  Optimistic ACK Prediction: %d\n", tcp_hdr(rt->u.dst.xfrm)->ack_seq - 1);
// }void print_rtable_info(struct rtable *rt) {if (!rt) {printk(KERN_ERR "rtable is NULL\n");return;}// 打印 rtable 的基本信息printk(KERN_INFO "Route Entry Information:\n");//printk(KERN_INFO "Destination Address: %pI4\n", &rt->dst.dest.addr);printk(KERN_INFO "Gateway: %pI4\n", &rt->rt_gw4);printk(KERN_INFO "Flags: 0x%x\n", rt->rt_flags);printk(KERN_INFO "Type: %u\n", rt->rt_type);printk(KERN_INFO "Input Interface: %d\n", rt->rt_iif);printk(KERN_INFO "Uses Gateway: %u\n", rt->rt_uses_gateway);printk(KERN_INFO "MTU: %u\n", rt->rt_pmtu);printk(KERN_INFO "MTU Locked: %u\n", rt->rt_mtu_locked);printk(KERN_INFO "Generation ID: %d\n", rt->rt_genid);
}// void print_dst_entry_info(struct dst_entry *dst) {
//     if (!dst) {
//         printk(KERN_ERR "dst_entry is NULL\n");
//         return;
//     }//     // 打印 dst_entry 的基本信息
//     printk(KERN_INFO "Destination Address: %pI4\n", &dst->dest.addr);
//     printk(KERN_INFO "Flags: 0x%x\n", dst->flags);
//     //printk(KERN_INFO "Reference Count: %d\n", atomic_read(&dst->refcnt));//     // 如果是 IPv4 路由表
//     // {
//     //     struct rtable *rt = (struct rtable *)dst; // 转换为 rtable
//     //     printk(KERN_INFO "Gateway: %pI4\n", &rt->rt_gw);
//     //     printk(KERN_INFO "Interface: %s\n", rt->dev->name);
//     //     printk(KERN_INFO "Metric: %u\n", rt->rt_metric);
//     // }//     // 可以添加更多字段的信息
// }int _notquickmodecount = 0;static bool tcp_in_quickack_mode(struct sock *sk)
{struct inet_sock *inet = inet_sk(sk);const struct inet_connection_sock *icsk = inet_csk(sk);const struct dst_entry *dst = __sk_dst_get(sk);u32 dst_metric_ret = 0;u32 dst_v = 0;bool ret;if (dst) {dst_v = 1;dst_metric_ret = dst_metric(dst, RTAX_QUICKACK);}ret = (dst && dst_metric(dst, RTAX_QUICKACK)) ||(icsk->icsk_ack.quick && !inet_csk_in_pingpong_mode(sk));if (ret) {printk("pid[%d]quick mode[%u], dst[%u]dst_metric_ret[%u]icsk->icsk_ack.quick[%u]quickmode[%u]src[%pI4]dst[%pI4]src_port[%u]dst_port[%u]\n", current->pid, ret ? 1 : 0,dst_v, dst_metric_ret, (u32)icsk->icsk_ack.quick, (!inet_csk_in_pingpong_mode(sk))?1:0,&sk->sk_rcv_saddr, &sk->sk_daddr, ntohs(inet->inet_sport), ntohs(inet->inet_dport));}else {if (_notquickmodecount > 10) {}else {_notquickmodecount++;printk("pid[%d]quick mode[%u], dst[%u]dst_metric_ret[%u]icsk->icsk_ack.quick[%u]quickmode[%u]src[%pI4]dst[%pI4]src_port[%u]dst_port[%u]\n", current->pid, ret ? 1 : 0,dst_v, dst_metric_ret, (u32)icsk->icsk_ack.quick, (!inet_csk_in_pingpong_mode(sk))?1:0,&sk->sk_rcv_saddr, &sk->sk_daddr, ntohs(inet->inet_sport), ntohs(inet->inet_dport));}}return ret;
}static int haslog = 0;struct kprobe _kp;
struct kprobe _kp1;// __tcp_ack_snd_check
int kprobecb_tcp_ack_snd_check(struct kprobe* i_k, struct pt_regs* i_p)
{//printk("kprobecb_tcp_ack_snd_check enter");//unsigned long arg1 = regs->di;struct sock *sk = (struct sock *) i_p->di;struct inet_sock *inet = inet_sk(sk);__be32 target_ip, dst_ip;target_ip = in_aton("10.100.130.87");dst_ip = in_aton("10.100.130.103");if (sk->sk_rcv_saddr == target_ip && sk->sk_daddr == dst_ip) {if (tcp_in_quickack_mode(sk)) {//haslog = 1;//printk("quick mode, src[%pI4]dst[%pI4]src_port[%u]dst_port[%u]\n", &sk->sk_rcv_saddr, &sk->sk_daddr, ntohs(inet->inet_sport), ntohs(inet->inet_dport));//printk(KERN_INFO "Destination IP: %pI4\n", &sk->sk_daddr);//print_rtable_info((struct rtable *)__sk_dst_get(sk));}else {//printk("NOT quick mode, src[%pI4]dst[%pI4]src_port[%u]dst_port[%u]\n", &sk->sk_rcv_saddr, &sk->sk_daddr, ntohs(inet->inet_sport), ntohs(inet->inet_dport));//printk("NOT quick mode\n");}}return 0;
}#define MY_KERNEL_KLOG_INFO_HEXDUMP( addr, size) \do {    \print_hex_dump(KERN_INFO, "hex_dump:", DUMP_PREFIX_NONE, 32, 4, addr, size, true);  \} while (0)static void tcp_quickack_config(struct sock *sk)
{struct inet_connection_sock *icsk = inet_csk(sk);if (icsk->icsk_ack.quick != 16) {icsk->icsk_ack.quick = 16;printk("pid[%d]set quick = 16\n", current->pid);}// unsigned int quickacks = tcp_sk(sk)->rcv_wnd / (2 * icsk->icsk_ack.rcv_mss);// if (quickacks == 0)// 	quickacks = 2;// quickacks = min(quickacks, max_quickacks);// if (quickacks > icsk->icsk_ack.quick)// 	icsk->icsk_ack.quick = quickacks;
}#if 0
void kprobecb_tcp_sock_set_quickack(struct kprobe* i_k, struct pt_regs* i_p,unsigned long i_flags)
{struct sock *sk = (struct sock*) i_p->di;int* pval = i_p->r10;int val;int len = i_p->r8;//tcp_enter_quickack_mode(sk, 16);//printk("val=%d\n", val);printk("len[%d]\n", len);if (len == 4) {val = *pval;if ((val & 1) && val != 1) {printk("111 val=%d\n", val);//tcp_enter_quickack_mode(sk, 16);//tcp_incr_quickack(sk, 16u);}}}
#endifstatic int handler_pre(struct pt_regs *regs) {// 打印 pt_regs 中的内容printk(KERN_INFO "pt_regs contents:\n");printk(KERN_INFO "RIP: 0x%lx\n", regs->ip);printk(KERN_INFO "RSP: 0x%lx\n", regs->sp);printk(KERN_INFO "RBP: 0x%lx\n", regs->bp);printk(KERN_INFO "RAX: 0x%lx\n", regs->ax);printk(KERN_INFO "RBX: 0x%lx\n", regs->bx);printk(KERN_INFO "RCX: 0x%lx\n", regs->cx);printk(KERN_INFO "RDX: 0x%lx\n", regs->dx);printk(KERN_INFO "RSI: 0x%lx\n", regs->si);printk(KERN_INFO "RDI: 0x%lx\n", regs->di);printk(KERN_INFO "R8: 0x%lx\n", regs->r8);printk(KERN_INFO "R9: 0x%lx\n", regs->r9);printk(KERN_INFO "R10: 0x%lx\n", regs->r10);printk(KERN_INFO "R11: 0x%lx\n", regs->r11);printk(KERN_INFO "R12: 0x%lx\n", regs->r12);printk(KERN_INFO "R13: 0x%lx\n", regs->r13);printk(KERN_INFO "R14: 0x%lx\n", regs->r14);printk(KERN_INFO "R15: 0x%lx\n", regs->r15);return 0; // 继续执行被探测的函数
}int kprobecb_tcp_sock_set_quickack(struct kprobe* i_k, struct pt_regs* i_p)
{struct sock *sk = (struct sock*) i_p->di;struct inet_connection_sock *icsk = inet_csk(sk);//sockptr_t pval = (sockptr_t)i_p->cx;int val;int len = i_p->r9;//MY_KERNEL_KLOG_INFO_HEXDUMP(icsk->icsk_ca_priv, 104);//tcp_enter_quickack_mode(sk, 16);//printk("val=%d\n", val);// printk("len[%d][0x%llx][0x%llx][0x%llx][0x%llx][0x%llx]\n", len,//     i_p->di, i_p->si, i_p->dx, i_p->cx, i_p->r9);if (len == 4) {copy_from_user(&val, i_p->cx, 4);//memcpy(&val, i_p->cx, 4);// if (copy_from_sockptr(&val, pval, sizeof(val))) {//     return 0;// }if (i_p->dx == 12) {if ((val & 1) && val != 1) {//printk("111 val=%d\n", val);//handler_pre(i_p);//tcp_enter_quickack_mode(sk, 16);tcp_quickack_config(sk);}}}return 0;
}int kprobe_register_func_tcp_ack_snd_check(void)
{int ret;memset(&_kp, 0, sizeof(_kp));_kp.symbol_name = "__tcp_ack_snd_check";_kp.pre_handler = kprobecb_tcp_ack_snd_check;ret = register_kprobe(&_kp);if (ret < 0) {printk("register_kprobe fail!\n");return -1;}return 0;
}int kprobe_register_func_tcp_sock_set_quickack(void)
{int ret;memset(&_kp1, 0, sizeof(_kp1));_kp1.symbol_name = "tcp_setsockopt";_kp1.pre_handler = kprobecb_tcp_sock_set_quickack;ret = register_kprobe(&_kp1);if (ret < 0) {printk("register_kprobe fail!\n");return -1;}return 0;
}void kprobe_unregister_func_tcp_ack_snd_check(void)
{unregister_kprobe(&_kp);
}void kprobe_unregister_func_tcp_sock_set_quickack(void)
{unregister_kprobe(&_kp1);
}static int __init testquickack_init(void)
{_kallsyms_lookup_name_func = get_func_by_symbol_name_kallsyms_lookup_name();kprobe_register_func_tcp_ack_snd_check();kprobe_register_func_tcp_sock_set_quickack();#if 0mykern_sched_waking.callback = cb_sched_waking;for_each_kernel_tracepoint(sched_waking_tracepoint_check_and_set, &mykern_sched_waking);if (!mykern_sched_waking.ptr) {printk(KERN_ERR "mykern_sched_waking register failed!\n");return -1;}else {printk(KERN_INFO "mykern_sched_waking register succeeded!\n");}tracepoint_probe_register(mykern_sched_waking.ptr, mykern_sched_waking.callback, NULL);mykern_sched_waking.bregister = 1;
#endifreturn 0;
}static void __exit testquickack_exit(void)
{//clear_kern_tracepoint(&mykern_sched_waking);//tracepoint_synchronize_unregister();kprobe_unregister_func_tcp_ack_snd_check();kprobe_unregister_func_tcp_sock_set_quickack();
}module_init(testquickack_init);
module_exit(testquickack_exit);

2.2 服务端和客户端的源码

server端的代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/tcp.h>#define PORT 80
#define BUFFER_SIZE 1024int main() {int server_fd, new_socket;struct sockaddr_in address;int addrlen = sizeof(address);char buffer[BUFFER_SIZE] = {0};// 创建 socketif ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("Socket failed");exit(EXIT_FAILURE);}{// int optval = 1; // 启用 QUICKACK 选项// socklen_t optlen = sizeof(optval);// // 设置 TCP_QUICKACK 选项// if (setsockopt(server_fd, IPPROTO_TCP, TCP_QUICKACK, &optval, optlen) < 0) {//     perror("setsockopt");//     close(server_fd);//     exit(EXIT_FAILURE);// }}// 设置地址结构address.sin_family = AF_INET;address.sin_addr.s_addr = inet_addr("10.100.130.87");address.sin_port = htons(PORT);// 绑定 socketif (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("Bind failed");close(server_fd);exit(EXIT_FAILURE);}// 开始监听if (listen(server_fd, 3) < 0) {perror("Listen failed");close(server_fd);exit(EXIT_FAILURE);}printf("Server is listening on %s:%d\n", "10.100.130.87", PORT);// 循环接受信息while (1) {if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("Accept failed");continue;}printf("Connected to client\n");// 接受信息while (1) {// {//     int optval = 1; // 启用 QUICKACK 选项//     socklen_t optlen = sizeof(optval);//     // 设置 TCP_QUICKACK 选项//     if (setsockopt(server_fd, IPPROTO_TCP, TCP_NODELAY, &optval, optlen) < 0) {//         perror("setsockopt");//         close(server_fd);//         exit(EXIT_FAILURE);//     }// }{int optval = 3; // 启用 QUICKACK 选项socklen_t optlen = sizeof(optval);// 设置 TCP_QUICKACK 选项if (setsockopt(new_socket, IPPROTO_TCP, TCP_QUICKACK, &optval, optlen) < 0) {perror("setsockopt");close(new_socket);exit(EXIT_FAILURE);}}int valread = read(new_socket, buffer, BUFFER_SIZE);if (valread > 0) {buffer[valread] = '\0';  // 确保字符串以 null 结束printf("Received: %s\n", buffer);} else {break; // 客户端关闭连接}}printf("Client disconnected\n");close(new_socket);}close(server_fd);return 0;
}

client端的代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>#define PORT 80
#define BUFFER_SIZE 1024int main() {int sock = 0;struct sockaddr_in serv_addr;char *message = "Hello from client";// 创建 socketif ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {printf("\n Socket creation error \n");return -1;}serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(PORT);// 转换 IPv4 和 IPv6 地址从文本到二进制if (inet_pton(AF_INET, "10.100.130.87", &serv_addr.sin_addr) <= 0) {printf("\nInvalid address/ Address not supported \n");return -1;}// 连接到服务器if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {printf("\nConnection Failed \n");return -1;}// 循环发送信息while (1) {send(sock, message, strlen(message), 0);printf("Message sent: %s\n", message);//sleep(1); // 每秒发送一次usleep(1);}close(sock);return 0;
}

2.3 成果展示

先在server的机器上,insmod 2.1一节里的源码编出的ko后,运行server程序,然后,在client的机器上运行client程序,注意上面 2.1 一节和 2.2 一节里的ip地址需要根据实际情况来改变。

server刚运行起来以后的打印:

运行client后,client的打印:

server这边的打印:

再看dmesg里的打印:

可以从上图中看到,server这边每次调用recv接受client这边发来的消息前,会进行依次setsockopt的系统调用从而触发了dmesg里的kprobe的逻辑,对应的内核的代码逻辑是:

这样,在判断是否进行quickack时,就会得到quickack的mode是1的结果,因为quickack被我们反复的设置回16了:

2.4 通过路由配置的方法和成果展示

首先强调一下,通过增加路由规则的方式的不足在于它不能按照fd来进行指定fd的quickack强制,要强制就对所有适用于增加的这条路由规则的tcp连接一起起效了。

反过来说,如果不需要细粒度的针对路由规则适用的tcp连接里部分使用quickack部分不适用quickack进行针对性配置的话,那么用本节的方法也是更加规范和更加通用。

适用本节的方法,还需要考虑网卡link down/up的变化的情况,link up以后,需要重新设置路由规则,避免路由规则丢失导致不能quickack。

2.4.1 路由配置方式

根据当前测试环境的网络,如下方式配置:

ip route add 10.100.130.0/24 quickack 1 dev enp0s31f6

设置后可以通过ip -d route来查看是否设置进去了,如下图,成功设置了quickack:

2.4.2 路由配置后可以看到quickack的检查一直都是1的

在做验证前,把服务端代码里的setsockopt的逻辑去掉:

重新启动server,并启动client后,可以如下图的dmesg里的打印看到,quickack mode一直是检查是1的:

三、实现原理

这一章我们讲的是 2.1 一节里展示的源码里的实现原理。

事实上,它是用kprobe方式来hook setsockopt系统调用,过滤出如果设置的是TCP_QUICKACK的type的话,根据设置指定一个一般不会去设置的值,去做额外的逻辑,补充quickack配额,但是这是需要客户端不断地进行setsockopt系统调用的。

下面的实现的原理介绍省去了之前的博客多次提到的tracepoint及kprobe获取函数指针的原理说明,有关这两个话题,参考之前的 内核tracepoint的注册回调及添加的方法_tracepoint 自定义回调-CSDN博客 和 内核模块里访问struct rq及获取rq_clock_task时间的方法-CSDN博客 博客。

3.1 内核里是通过__tcp_ack_snd_check函数来判断是否需要quickack

内核里判断tcp是否需要quickack,是在tcp_input.c里的__tcp_ack_snd_check函数里,如下图:

上图里的关键的函数是tcp_in_quickack_mode这个函数:

这个函数有两种情况回返回进行quickack:

1)当前的sock连接有使能quickack的路由规则与之对应,RTAX_QUICKACK这个标志位,我们可以通过strace来跟踪 2.4 一节里介绍的路由设置命令,可以跟踪到这个标志位设置。

2)当前的sock连接是设置的quickack模式的且quick计数不是0,实测发现,目前默认是适用quickack模式的,并不需要额外去做这样的设置,pingpong mode是false也就是使用quickack mode。除了使用quickack模式,还需要满足quick计数不是0的条件,而quick计数会在每次recv收后减一,减到0以后,上图中的(icsk->icsk_ack.quick && !inet_csk_in_pingpong_mode(sk))就是0了

另外,需要说明的是,为什么我们不直接kprobe tcp_in_quickack_mode这个函数,而是kprobe __tcp_ack_snd_check函数,是因为tcp_in_quickack_mode这个函数在kallsyms里是没有的:

而__tcp_ack_snd_check在kallsyms里是有的:

3.2 通过kprobe捕获tcp_setsockopt,进行quick计数的特殊处理

用户态是通过如下方式进行的设置:

这样,内核态在捕获tcp_setsockopt时,可以根据传下来的数值来进行特殊处理,一般来说,我们设置quickack的on/off时,就传0或者1的,我们设置3就是为了区分这种常规的设置,这样我们可以发现是我们的特殊设置,如下逻辑就可以判断出来(另外,下图中的i_p-dx == 12的12表示的是TCP_QUICKACK):

要注意,内核里是默认的以最后一个bit来判断是否使用quickack模式的:

所以,我们特殊的设置值也遵守这个基本规则,最后一个bit仍然是1,让bit1以上部分非0,来做特殊处理的判断条件。

上面逻辑里如何通过pt_regs拿到各个入参,有两个注意事项,我们会择机在后面的博客里详细展开:

1)当入参的大小大于了64bit后,要根据情况,后移传递参数用的寄存器,为了强调,我们在后面的博客里展开

2)在使用用户态数据时,需要考虑用户态的数据可能还没有缺页异常(还未映射),在内核态里直接使用(比如用memcpy)的话,就会出现内核态错误,使用copy_from_user可以避免这样的问题,详细原因见后面的博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/4592.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大模型GUI系列论文阅读 DAY2续:《一个具备规划、长上下文理解和程序合成能力的真实世界Web代理》

摘要 预训练的大语言模型&#xff08;LLMs&#xff09;近年来在自主网页自动化方面实现了更好的泛化能力和样本效率。然而&#xff0c;在真实世界的网站上&#xff0c;其性能仍然受到以下问题的影响&#xff1a;(1) 开放领域的复杂性&#xff0c;(2) 有限的上下文长度&#xff…

C++实现Point2D类 有限元基础类

基础类表示的是在有限元计算过程中必要通用的类。 1、Point2D 表示的是二维节点&#xff0c;是组成二维有限元单元的节点。 1.1、public function 1.1.1、构造函数与析构函数 构造函数用来初始化point2D的基本信息&#xff0c;析构函数用来释放内存 Point2D.h声明函数 //…

51c自动驾驶~合集48

我自己的原文哦~ https://blog.51cto.com/whaosoft/13133866 #UDMC 考虑轨迹预测的统一决策控制框架 论文&#xff1a;https://arxiv.org/pdf/2501.02530 代码&#xff1a;​​https://github.com/henryhcliu/udmc_carla.git​​ 1. 摘要 当前的自动驾驶系统常常在确…

LeetCode hot 力扣热题100 排序链表

归并忘了 直接抄&#xff01; class Solution { // 定义一个 Solution 类&#xff0c;包含链表排序的相关方法。// 使用快慢指针找到链表的中间节点&#xff0c;并断开链表为两部分ListNode* middleNode(ListNode* head) { ListNode* slow head; // 慢指针 slow 初始化为链表…

ChromeOS 132 版本更新

ChromeOS 132 版本更新 1. 企业定制化 Chrome Web Store 管理员现在可以使用新设置定制 Chrome Web Store 以适应他们管理的用户&#xff0c;包括以下功能&#xff1a; 添加公司标志添加首页横幅和自定义公告策划扩展集合实施基于类别的控制 这些设置可以通过管理员控制台进…

力扣动态规划-5【算法学习day.99】

前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;建议灵神的题单和代码随想录&#xff09;和记录自己的学习过程&#xff0c;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关…

国内汽车法规政策标准解读:GB 44495-2024《汽车整车信息安全技术要求》

目录 背景 概述 标准适用范围 汽车信息安全管理体系要求&#xff08;第5章&#xff09; 信息安全基本要求&#xff08;第6章&#xff09; 信息安全技术要求&#xff08;第7章&#xff09; ◆ 外部连接安全要求&#xff1a; ◆通信安全要求&#xff1a; ◆软件升级安全…

Apache SeaTunnel 2.3.9 正式发布:多项新特性与优化全面提升数据集成能力

近日&#xff0c;Apache SeaTunnel 社区正式发布了最新版本 2.3.9。本次更新新增了Helm 集群部署、Transform 支持多表、Zeta新API、表结构转换、任务提交队列、分库分表合并、列转多行 等多个功能更新&#xff01; 作为一款开源、分布式的数据集成平台&#xff0c;本次版本通过…

EasyControl:首个登陆AWS Marketplace的中国MDM先锋

在全球数字化与移动化浪潮中&#xff0c;企业对安全、高效的移动设备管理&#xff08;MDM&#xff09;需求日益增长。EasyControl作为国内MDM领域的佼佼者&#xff0c;凭借成熟的技术和创新的解决方案&#xff0c;成为国内首个成功上线亚马逊AWS Marketplace的MDM产品&#xff…

OpenCV简介、OpenCV安装

OpenCV简介、OpenCV安装 本文目录&#xff1a; 零、时光宝盒 一、OpenCV简介 二、OpenCV图像处理基础知识 三、OpenCV-Python环境安装 2.1、纯python环境下安装OpenCV 2.2、Anaconda管理环境下安装 OpenCV 四、如何用OpenCV 中进行读取展示图像 五、OpenCV读取图像、显…

【语言处理和机器学习】概述篇(基础小白入门篇)

前言 自学笔记&#xff0c;分享给语言学/语言教育学方向的&#xff0c;但对语言数据处理感兴趣但是尚未入门&#xff0c;却需要在论文中用到的小伙伴&#xff0c;欢迎大佬们补充或绕道。ps&#xff1a;本文不涉及公式讲解&#xff08;文科生小白友好体质&#xff09;&#xff…

ARP 表、MAC 表、路由表、跨网段 ARP

文章目录 一、ARP 表1、PC2、路由器 - AR22203、交换机 - S57004、什么样的设备会有 ARP 表&#xff1f; 二、MAC 表什么样的设备会有 MAC 表&#xff1f; 三、路由表什么样的设备会有路由表&#xff1f; 四、抓取跨网段 ARP 包 所谓 “透明” 就是指不用做任何配置 一、ARP 表…

Spring的IoC、Bean、DI的简单实现,难度:※※※

目录 场景描述 第一步&#xff1a;初始化Maven项目 第二步&#xff1a;Maven导入Spring包&#xff08;给代码&#xff09; 第三步&#xff1a;创建Spring配置文件 第四步 创建Bean 第五步 简单使用Bean &#xff08;有代码&#xff09; 第六步 通过依赖注入使用Bean&…

wireshark工具简介

目录 1 wireshark介绍 2 wireshark抓包流程 2.1 选择网卡 2.2 停止抓包 2.3 保存数据 3 wireshark过滤器设置 3.1 显示过滤器的设置 3.2 抓包过滤器 4 wireshark的封包列表与封包详情 4.1 封包列表 4.2 封包详情 参考文献 1 wireshark介绍 wireshark是非常流行的网络…

C# OpenCvSharp 部署文档矫正,包括文档扭曲/模糊/阴影等情况

目录 说明 效果 模型 项目 代码 下载 参考 C# OpenCvSharp 部署文档矫正&#xff0c;包括文档扭曲/模糊/阴影等情况 说明 地址&#xff1a;https://github.com/RapidAI/RapidUnDistort 修正文档扭曲/模糊/阴影等情况&#xff0c;使用onnx模型简单轻量部署&#xff0c…

编辑器Vim基本模式和指令 --【Linux基础开发工具】

文章目录 一、编辑器Vim 键盘布局二、Linux编辑器-vim使用三、vim的基本概念正常/普通/命令模式(Normal mode)插入模式(Insert mode)末行模式(last line mode) 四、vim的基本操作五、vim正常模式命令集插入模式从插入模式切换为命令模式移动光标删除文字复制替换撤销上一次操作…

LeetCode 110.平衡二叉树

题目描述 给定一个二叉树&#xff0c;判断它是否是平衡二叉树。 示例 1&#xff1a; 示例 2&#xff1a; 输入&#xff1a;root [1,2,2,3,3,null,null,4,4] 输出&#xff1a;false 示例 3&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;true 提示&#xff1a; …

Asp .Net Core 实现微服务:集成 Ocelot+Nacos+Swagger+Cors实现网关、服务注册、服务发现

什么是 Ocelot ? Ocelot是一个开源的ASP.NET Core微服务网关&#xff0c;它提供了API网关所需的所有功能&#xff0c;如路由、认证、限流、监控等。 Ocelot是一个简单、灵活且功能强大的API网关&#xff0c;它可以与现有的服务集成&#xff0c;并帮助您保护、监控和扩展您的…

Express中间件

目录 Express中间件 中间件的概念 next函数 全局中间与局部中间件 多个中间件 中间的5个注意事项 中间的分类 应用级中间件 路由级中间件 错误级中间件 Express内置中间件 express.json express.urlencoded 第三方中间件​编辑 自定义中间件 Express中间件 中间…

Linux 高级路由与流量控制-用 tc qdisc 管理 Linux 网络带宽

大家读完记得觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 此分享内容比较专业&#xff0c;很多与硬件和通讯规则及队列&#xff0c;比较底层需要有技术功底人员深入解读。 Linux 的带宽管理能力 足以媲美许多高端、专用的带宽管理系统。 1 队列&#xff0…