IT评测·应用市场-qidao123.com

标题: 通过内核模块按fd强制tcp的quickack方法 [打印本页]

作者: 何小豆儿在此    时间: 2025-1-18 16:33
标题: 通过内核模块按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配额填充的源码

源码如下:
  1. #include <linux/module.h>
  2. #include <linux/capability.h>
  3. #include <linux/sched.h>
  4. #include <linux/uaccess.h>
  5. #include <linux/proc_fs.h>
  6. #include <linux/ctype.h>
  7. #include <linux/seq_file.h>
  8. #include <linux/poll.h>
  9. #include <linux/types.h>
  10. #include <linux/ioctl.h>
  11. #include <linux/errno.h>
  12. #include <linux/stddef.h>
  13. #include <linux/lockdep.h>
  14. #include <linux/kthread.h>
  15. #include <linux/sched.h>
  16. #include <linux/delay.h>
  17. #include <linux/wait.h>
  18. #include <linux/init.h>
  19. #include <asm/atomic.h>
  20. #include <trace/events/workqueue.h>
  21. #include <linux/sched/clock.h>
  22. #include <linux/string.h>
  23. #include <linux/mm.h>
  24. #include <linux/interrupt.h>
  25. #include <linux/tracepoint.h>
  26. #include <trace/events/osmonitor.h>
  27. #include <trace/events/sched.h>
  28. #include <trace/events/irq.h>
  29. #include <trace/events/kmem.h>
  30. #include <linux/ptrace.h>
  31. #include <linux/uaccess.h>
  32. #include <asm/processor.h>
  33. #include <linux/sched/task_stack.h>
  34. #include <linux/nmi.h>
  35. #include <asm/apic.h>
  36. #include <linux/version.h>
  37. #include <linux/sched/mm.h>
  38. #include <asm/irq_regs.h>
  39. #include <linux/kallsyms.h>
  40. #include <linux/kprobes.h>
  41. #include <linux/stop_machine.h>
  42. MODULE_LICENSE("GPL");
  43. MODULE_AUTHOR("zhaoxin");
  44. MODULE_DESCRIPTION("Module for debug quickack.");
  45. MODULE_VERSION("1.0");
  46. struct kern_tracepoint {
  47.     void *callback;
  48.     struct tracepoint *ptr;
  49.     bool bregister;
  50. };
  51. static void clear_kern_tracepoint(struct kern_tracepoint *tp)
  52. {
  53.     if (tp->bregister) {
  54.         tracepoint_probe_unregister(tp->ptr, tp->callback, NULL);
  55.     }
  56. }
  57. #define INIT_KERN_TRACEPOINT(tracepoint_name) \
  58.     static struct kern_tracepoint mykern_##tracepoint_name = {.callback = NULL, .ptr = NULL, .bregister = false};
  59. #define TRACEPOINT_CHECK_AND_SET(tracepoint_name)                                             \
  60.     static void tracepoint_name##_tracepoint_check_and_set(struct tracepoint *tp, void *priv) \
  61.     {                                                                                \
  62.         if (!strcmp(#tracepoint_name, tp->name))                                     \
  63.         {                                                                            \
  64.             ((struct kern_tracepoint *)priv)->ptr = tp;                          \
  65.             return;                                                                  \
  66.         }                                                                            \
  67.     }
  68. //INIT_KERN_TRACEPOINT(sched_switch)
  69. //TRACEPOINT_CHECK_AND_SET(sched_switch)
  70. //INIT_KERN_TRACEPOINT(sched_waking)
  71. //TRACEPOINT_CHECK_AND_SET(sched_waking)
  72. typedef unsigned long (*kallsyms_lookup_name_func)(const char *name);
  73. kallsyms_lookup_name_func _kallsyms_lookup_name_func;
  74. void* get_func_by_symbol_name_kallsyms_lookup_name(void)
  75. {
  76.     int ret;
  77.     void* pfunc = NULL;
  78.         struct kprobe kp;
  79.         memset(&kp, 0, sizeof(kp));
  80.         kp.symbol_name = "kallsyms_lookup_name";
  81.         kp.pre_handler = NULL;
  82.         kp.addr = NULL;        // 作为强调,提示使用symbol_name
  83.         ret = register_kprobe(&kp);
  84.         if (ret < 0) {
  85.                 printk("register_kprobe fail!\n");
  86.                 return NULL;
  87.         }
  88.         printk("register_kprobe succeed!\n");
  89.     pfunc = (void*)kp.addr;
  90.         unregister_kprobe(&kp);
  91.     return pfunc;
  92. }
  93. void* get_func_by_symbol_name(const char* i_symbol)
  94. {
  95.     if (_kallsyms_lookup_name_func == NULL) {
  96.         return NULL;
  97.     }
  98.     return _kallsyms_lookup_name_func(i_symbol);
  99. }
  100. #include <uapi/linux/rtnetlink.h>
  101. #include <net/sock.h>
  102. #include <net/inet_connection_sock.h>
  103. #include <linux/netdevice.h>
  104. #include <linux/inetdevice.h>
  105. #include <linux/ip.h>
  106. #include <net/dst.h>
  107. #include <net/route.h>
  108. #include <net/tcp.h>
  109. #include <linux/inet.h>
  110. #include <linux/sockptr.h>
  111. // void print_dst_entry(struct dst_entry *dst) {
  112. //     struct rtable *rt = (struct rtable *)dst;
  113. //     struct in_device *in_dev;
  114. //     if (!dst)
  115. //         return;
  116. //     in_dev = __in_dev_get_rcu(rt->u.dst.dev);
  117. //     printk(KERN_INFO "Dst Entry Info:\n");
  118. //     printk(KERN_INFO "  Input Device: %s\n", rt->u.dst.dev->name);
  119. //     printk(KERN_INFO "  Output Device: %s\n", rt->u.dst.dev->name);
  120. //     if (in_dev) {
  121. //         printk(KERN_INFO "  In Device MTU: %d\n", in_dev->mtu);
  122. //         printk(KERN_INFO "  In Device Output MTU: %d\n", in_dev->output_mtu);
  123. //     }
  124. //     printk(KERN_INFO "  Expires: %ld\n", dst->expires);
  125. //     printk(KERN_INFO "  Flags: 0x%lx\n", dst->flags);
  126. //     printk(KERN_INFO "  Last Use: %lu\n", dst->lastuse);
  127. //     printk(KERN_INFO "  Obsolete: %lu\n", dst->obsolete);
  128. //     printk(KERN_INFO "  Hash Chain: %p\n", dst->dn.next);
  129. //     printk(KERN_INFO "  Input Hash: 0x%lx\n", dst->hash);
  130. //     printk(KERN_INFO "  Output Hash: 0x%lx\n", dst->child_mask);
  131. //     printk(KERN_INFO "  Reference Count: %d\n", atomic_read(&dst->__refcnt));
  132. //     printk(KERN_INFO "  Use Count: %d\n", dst->use);
  133. //     printk(KERN_INFO "  Wireless Use Count: %d\n", dst->wireless_ref);
  134. //     printk(KERN_INFO "  Last Metric Update: %lu\n", dst->last_metric_update);
  135. //     printk(KERN_INFO "  Protocol Specific Data: %p\n", dst->input);
  136. //     printk(KERN_INFO "  Optimistic ACK Prediction: %d\n", tcp_hdr(rt->u.dst.xfrm)->ack_seq - 1);
  137. // }
  138. void print_rtable_info(struct rtable *rt) {
  139.     if (!rt) {
  140.         printk(KERN_ERR "rtable is NULL\n");
  141.         return;
  142.     }
  143.     // 打印 rtable 的基本信息
  144.     printk(KERN_INFO "Route Entry Information:\n");
  145.     //printk(KERN_INFO "Destination Address: %pI4\n", &rt->dst.dest.addr);
  146.     printk(KERN_INFO "Gateway: %pI4\n", &rt->rt_gw4);
  147.     printk(KERN_INFO "Flags: 0x%x\n", rt->rt_flags);
  148.     printk(KERN_INFO "Type: %u\n", rt->rt_type);
  149.     printk(KERN_INFO "Input Interface: %d\n", rt->rt_iif);
  150.     printk(KERN_INFO "Uses Gateway: %u\n", rt->rt_uses_gateway);
  151.     printk(KERN_INFO "MTU: %u\n", rt->rt_pmtu);
  152.     printk(KERN_INFO "MTU Locked: %u\n", rt->rt_mtu_locked);
  153.     printk(KERN_INFO "Generation ID: %d\n", rt->rt_genid);
  154. }
  155. // void print_dst_entry_info(struct dst_entry *dst) {
  156. //     if (!dst) {
  157. //         printk(KERN_ERR "dst_entry is NULL\n");
  158. //         return;
  159. //     }
  160. //     // 打印 dst_entry 的基本信息
  161. //     printk(KERN_INFO "Destination Address: %pI4\n", &dst->dest.addr);
  162. //     printk(KERN_INFO "Flags: 0x%x\n", dst->flags);
  163. //     //printk(KERN_INFO "Reference Count: %d\n", atomic_read(&dst->refcnt));
  164.    
  165. //     // 如果是 IPv4 路由表
  166. //     // {
  167. //     //     struct rtable *rt = (struct rtable *)dst; // 转换为 rtable
  168. //     //     printk(KERN_INFO "Gateway: %pI4\n", &rt->rt_gw);
  169. //     //     printk(KERN_INFO "Interface: %s\n", rt->dev->name);
  170. //     //     printk(KERN_INFO "Metric: %u\n", rt->rt_metric);
  171. //     // }
  172. //     // 可以添加更多字段的信息
  173. // }
  174. int _notquickmodecount = 0;
  175. static bool tcp_in_quickack_mode(struct sock *sk)
  176. {
  177.     struct inet_sock *inet = inet_sk(sk);
  178.         const struct inet_connection_sock *icsk = inet_csk(sk);
  179.         const struct dst_entry *dst = __sk_dst_get(sk);
  180.     u32 dst_metric_ret = 0;
  181.     u32 dst_v = 0;
  182.     bool ret;
  183.     if (dst) {
  184.         dst_v = 1;
  185.         dst_metric_ret = dst_metric(dst, RTAX_QUICKACK);
  186.     }
  187.         ret = (dst && dst_metric(dst, RTAX_QUICKACK)) ||
  188.                 (icsk->icsk_ack.quick && !inet_csk_in_pingpong_mode(sk));
  189.     if (ret) {
  190.         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",
  191.             current->pid, ret ? 1 : 0,
  192.             dst_v, dst_metric_ret, (u32)icsk->icsk_ack.quick, (!inet_csk_in_pingpong_mode(sk))?1:0,
  193.             &sk->sk_rcv_saddr, &sk->sk_daddr, ntohs(inet->inet_sport), ntohs(inet->inet_dport));
  194.     }
  195.     else {
  196.         if (_notquickmodecount > 10) {
  197.         }
  198.         else {
  199.             _notquickmodecount++;
  200.             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",
  201.                 current->pid, ret ? 1 : 0,
  202.                 dst_v, dst_metric_ret, (u32)icsk->icsk_ack.quick, (!inet_csk_in_pingpong_mode(sk))?1:0,
  203.                 &sk->sk_rcv_saddr, &sk->sk_daddr, ntohs(inet->inet_sport), ntohs(inet->inet_dport));
  204.         }
  205.     }
  206.     return ret;
  207. }
  208. static int haslog = 0;
  209. struct kprobe _kp;
  210. struct kprobe _kp1;
  211. // __tcp_ack_snd_check
  212. int kprobecb_tcp_ack_snd_check(struct kprobe* i_k, struct pt_regs* i_p)
  213. {
  214.     //printk("kprobecb_tcp_ack_snd_check enter");
  215.     //unsigned long arg1 = regs->di;
  216.     struct sock *sk = (struct sock *) i_p->di;
  217.     struct inet_sock *inet = inet_sk(sk);
  218.     __be32 target_ip, dst_ip;
  219.     target_ip = in_aton("10.100.130.87");
  220.     dst_ip = in_aton("10.100.130.103");
  221.     if (sk->sk_rcv_saddr == target_ip
  222.         && sk->sk_daddr == dst_ip) {
  223.         if (tcp_in_quickack_mode(sk)) {
  224.             //haslog = 1;
  225.             //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));
  226.             //printk(KERN_INFO "Destination IP: %pI4\n", &sk->sk_daddr);
  227.             //print_rtable_info((struct rtable *)__sk_dst_get(sk));
  228.         }
  229.         else {
  230.             //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));
  231.             //printk("NOT quick mode\n");
  232.         }
  233.     }
  234.     return 0;
  235. }
  236. #define MY_KERNEL_KLOG_INFO_HEXDUMP( addr, size) \
  237.     do {    \
  238.         print_hex_dump(KERN_INFO, "hex_dump:", DUMP_PREFIX_NONE, 32, 4, addr, size, true);  \
  239.     } while (0)
  240. static void tcp_quickack_config(struct sock *sk)
  241. {
  242.         struct inet_connection_sock *icsk = inet_csk(sk);
  243.    
  244.     if (icsk->icsk_ack.quick != 16) {
  245.         icsk->icsk_ack.quick = 16;
  246.         printk("pid[%d]set quick = 16\n", current->pid);
  247.         
  248.     }
  249.         // unsigned int quickacks = tcp_sk(sk)->rcv_wnd / (2 * icsk->icsk_ack.rcv_mss);
  250.         // if (quickacks == 0)
  251.         //         quickacks = 2;
  252.         // quickacks = min(quickacks, max_quickacks);
  253.         // if (quickacks > icsk->icsk_ack.quick)
  254.         //         icsk->icsk_ack.quick = quickacks;
  255. }
  256. #if 0
  257. void kprobecb_tcp_sock_set_quickack(struct kprobe* i_k, struct pt_regs* i_p,
  258.     unsigned long i_flags)
  259. {
  260.     struct sock *sk = (struct sock*) i_p->di;
  261.     int* pval = i_p->r10;
  262.     int val;
  263.     int len = i_p->r8;
  264.     //tcp_enter_quickack_mode(sk, 16);
  265.     //printk("val=%d\n", val);
  266.     printk("len[%d]\n", len);
  267.     if (len == 4) {
  268.         val = *pval;
  269.         if ((val & 1) && val != 1) {
  270.             printk("111 val=%d\n", val);
  271.             //tcp_enter_quickack_mode(sk, 16);
  272.             //tcp_incr_quickack(sk, 16u);
  273.         }
  274.     }
  275. }
  276. #endif
  277. static int handler_pre(struct pt_regs *regs) {
  278.     // 打印 pt_regs 中的内容
  279.     printk(KERN_INFO "pt_regs contents:\n");
  280.     printk(KERN_INFO "RIP: 0x%lx\n", regs->ip);
  281.     printk(KERN_INFO "RSP: 0x%lx\n", regs->sp);
  282.     printk(KERN_INFO "RBP: 0x%lx\n", regs->bp);
  283.     printk(KERN_INFO "RAX: 0x%lx\n", regs->ax);
  284.     printk(KERN_INFO "RBX: 0x%lx\n", regs->bx);
  285.     printk(KERN_INFO "RCX: 0x%lx\n", regs->cx);
  286.     printk(KERN_INFO "RDX: 0x%lx\n", regs->dx);
  287.     printk(KERN_INFO "RSI: 0x%lx\n", regs->si);
  288.     printk(KERN_INFO "RDI: 0x%lx\n", regs->di);
  289.     printk(KERN_INFO "R8: 0x%lx\n", regs->r8);
  290.     printk(KERN_INFO "R9: 0x%lx\n", regs->r9);
  291.     printk(KERN_INFO "R10: 0x%lx\n", regs->r10);
  292.     printk(KERN_INFO "R11: 0x%lx\n", regs->r11);
  293.     printk(KERN_INFO "R12: 0x%lx\n", regs->r12);
  294.     printk(KERN_INFO "R13: 0x%lx\n", regs->r13);
  295.     printk(KERN_INFO "R14: 0x%lx\n", regs->r14);
  296.     printk(KERN_INFO "R15: 0x%lx\n", regs->r15);
  297.     return 0; // 继续执行被探测的函数
  298. }
  299. int kprobecb_tcp_sock_set_quickack(struct kprobe* i_k, struct pt_regs* i_p)
  300. {
  301.     struct sock *sk = (struct sock*) i_p->di;
  302.     struct inet_connection_sock *icsk = inet_csk(sk);
  303.     //sockptr_t pval = (sockptr_t)i_p->cx;
  304.     int val;
  305.     int len = i_p->r9;
  306.     //MY_KERNEL_KLOG_INFO_HEXDUMP(icsk->icsk_ca_priv, 104);
  307.     //tcp_enter_quickack_mode(sk, 16);
  308.     //printk("val=%d\n", val);
  309.     // printk("len[%d][0x%llx][0x%llx][0x%llx][0x%llx][0x%llx]\n", len,
  310.     //     i_p->di, i_p->si, i_p->dx, i_p->cx, i_p->r9);
  311.    
  312.     if (len == 4) {
  313.         copy_from_user(&val, i_p->cx, 4);
  314.         //memcpy(&val, i_p->cx, 4);
  315.         // if (copy_from_sockptr(&val, pval, sizeof(val))) {
  316.         //     return 0;
  317.         // }
  318.         if (i_p->dx == 12) {
  319.             if ((val & 1) && val != 1) {
  320.                 //printk("111 val=%d\n", val);
  321.                 //handler_pre(i_p);
  322.                 //tcp_enter_quickack_mode(sk, 16);
  323.                 tcp_quickack_config(sk);
  324.                
  325.             }
  326.         }
  327.         
  328.     }
  329.     return 0;
  330. }
  331. int kprobe_register_func_tcp_ack_snd_check(void)
  332. {
  333.     int ret;
  334.     memset(&_kp, 0, sizeof(_kp));
  335.     _kp.symbol_name = "__tcp_ack_snd_check";
  336.     _kp.pre_handler = kprobecb_tcp_ack_snd_check;
  337.     ret = register_kprobe(&_kp);
  338.         if (ret < 0) {
  339.                 printk("register_kprobe fail!\n");
  340.                 return -1;
  341.         }
  342.     return 0;
  343. }
  344. int kprobe_register_func_tcp_sock_set_quickack(void)
  345. {
  346.     int ret;
  347.     memset(&_kp1, 0, sizeof(_kp1));
  348.     _kp1.symbol_name = "tcp_setsockopt";
  349.     _kp1.pre_handler = kprobecb_tcp_sock_set_quickack;
  350.     ret = register_kprobe(&_kp1);
  351.         if (ret < 0) {
  352.                 printk("register_kprobe fail!\n");
  353.                 return -1;
  354.         }
  355.     return 0;
  356. }
  357. void kprobe_unregister_func_tcp_ack_snd_check(void)
  358. {
  359.     unregister_kprobe(&_kp);
  360. }
  361. void kprobe_unregister_func_tcp_sock_set_quickack(void)
  362. {
  363.     unregister_kprobe(&_kp1);
  364. }
  365. static int __init testquickack_init(void)
  366. {
  367.     _kallsyms_lookup_name_func = get_func_by_symbol_name_kallsyms_lookup_name();
  368.     kprobe_register_func_tcp_ack_snd_check();
  369.     kprobe_register_func_tcp_sock_set_quickack();
  370.    
  371. #if 0
  372.     mykern_sched_waking.callback = cb_sched_waking;
  373.     for_each_kernel_tracepoint(sched_waking_tracepoint_check_and_set, &mykern_sched_waking);
  374.     if (!mykern_sched_waking.ptr) {
  375.         printk(KERN_ERR "mykern_sched_waking register failed!\n");
  376.         return -1;
  377.     }
  378.     else {
  379.         printk(KERN_INFO "mykern_sched_waking register succeeded!\n");
  380.     }
  381.     tracepoint_probe_register(mykern_sched_waking.ptr, mykern_sched_waking.callback, NULL);
  382.     mykern_sched_waking.bregister = 1;
  383. #endif
  384.     return 0;
  385. }
  386. static void __exit testquickack_exit(void)
  387. {
  388.     //clear_kern_tracepoint(&mykern_sched_waking);
  389.     //tracepoint_synchronize_unregister();
  390.    
  391.     kprobe_unregister_func_tcp_ack_snd_check();
  392.     kprobe_unregister_func_tcp_sock_set_quickack();
  393. }
  394. module_init(testquickack_init);
  395. module_exit(testquickack_exit);
复制代码

2.2 服务端和客户端的源码

server端的代码如下:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <arpa/inet.h>
  6. #include <sys/types.h>
  7. #include <sys/socket.h>
  8. #include <netinet/tcp.h>
  9. #define PORT 80
  10. #define BUFFER_SIZE 1024
  11. int main() {
  12.     int server_fd, new_socket;
  13.     struct sockaddr_in address;
  14.     int addrlen = sizeof(address);
  15.     char buffer[BUFFER_SIZE] = {0};
  16.     // 创建 socket
  17.     if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
  18.         perror("Socket failed");
  19.         exit(EXIT_FAILURE);
  20.     }
  21.     {
  22.         // int optval = 1; // 启用 QUICKACK 选项
  23.         // socklen_t optlen = sizeof(optval);
  24.         // // 设置 TCP_QUICKACK 选项
  25.         // if (setsockopt(server_fd, IPPROTO_TCP, TCP_QUICKACK, &optval, optlen) < 0) {
  26.         //     perror("setsockopt");
  27.         //     close(server_fd);
  28.         //     exit(EXIT_FAILURE);
  29.         // }
  30.     }
  31.     // 设置地址结构
  32.     address.sin_family = AF_INET;
  33.     address.sin_addr.s_addr = inet_addr("10.100.130.87");
  34.     address.sin_port = htons(PORT);
  35.     // 绑定 socket
  36.     if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
  37.         perror("Bind failed");
  38.         close(server_fd);
  39.         exit(EXIT_FAILURE);
  40.     }
  41.     // 开始监听
  42.     if (listen(server_fd, 3) < 0) {
  43.         perror("Listen failed");
  44.         close(server_fd);
  45.         exit(EXIT_FAILURE);
  46.     }
  47.     printf("Server is listening on %s:%d\n", "10.100.130.87", PORT);
  48.     // 循环接受信息
  49.     while (1) {
  50.         if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
  51.             perror("Accept failed");
  52.             continue;
  53.         }
  54.         printf("Connected to client\n");
  55.         // 接受信息
  56.         while (1) {
  57.             // {
  58.             //     int optval = 1; // 启用 QUICKACK 选项
  59.             //     socklen_t optlen = sizeof(optval);
  60.             //     // 设置 TCP_QUICKACK 选项
  61.             //     if (setsockopt(server_fd, IPPROTO_TCP, TCP_NODELAY, &optval, optlen) < 0) {
  62.             //         perror("setsockopt");
  63.             //         close(server_fd);
  64.             //         exit(EXIT_FAILURE);
  65.             //     }
  66.             // }
  67.             {
  68.                 int optval = 3; // 启用 QUICKACK 选项
  69.                 socklen_t optlen = sizeof(optval);
  70.                 // 设置 TCP_QUICKACK 选项
  71.                 if (setsockopt(new_socket, IPPROTO_TCP, TCP_QUICKACK, &optval, optlen) < 0) {
  72.                     perror("setsockopt");
  73.                     close(new_socket);
  74.                     exit(EXIT_FAILURE);
  75.                 }
  76.             }
  77.             int valread = read(new_socket, buffer, BUFFER_SIZE);
  78.             if (valread > 0) {
  79.                 buffer[valread] = '\0';  // 确保字符串以 null 结束
  80.                 printf("Received: %s\n", buffer);
  81.             } else {
  82.                 break; // 客户端关闭连接
  83.             }
  84.         }
  85.         printf("Client disconnected\n");
  86.         close(new_socket);
  87.     }
  88.     close(server_fd);
  89.     return 0;
  90. }
复制代码
client端的代码如下:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <arpa/inet.h>
  6. #define PORT 80
  7. #define BUFFER_SIZE 1024
  8. int main() {
  9.     int sock = 0;
  10.     struct sockaddr_in serv_addr;
  11.     char *message = "Hello from client";
  12.     // 创建 socket
  13.     if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
  14.         printf("\n Socket creation error \n");
  15.         return -1;
  16.     }
  17.     serv_addr.sin_family = AF_INET;
  18.     serv_addr.sin_port = htons(PORT);
  19.     // 转换 IPv4 和 IPv6 地址从文本到二进制
  20.     if (inet_pton(AF_INET, "10.100.130.87", &serv_addr.sin_addr) <= 0) {
  21.         printf("\nInvalid address/ Address not supported \n");
  22.         return -1;
  23.     }
  24.     // 连接到服务器
  25.     if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
  26.         printf("\nConnection Failed \n");
  27.         return -1;
  28.     }
  29.     // 循环发送信息
  30.     while (1) {
  31.         send(sock, message, strlen(message), 0);
  32.         printf("Message sent: %s\n", message);
  33.         //sleep(1); // 每秒发送一次
  34.         usleep(1);
  35.     }
  36.     close(sock);
  37.     return 0;
  38. }
复制代码

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 路由配置方式

根据当前测试环境的网络,如下方式配置:
  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可以制止如许的问题,详细缘故起因见后面的博客


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/) Powered by Discuz! X3.4