当前位置: 首页 > news >正文

通过内核模块按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可以避免这样的问题,详细原因见后面的博客

相关文章:

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

一、背景 tcp的quickack功能是为了让ack迅速回发&#xff0c;快速响应&#xff0c;减少网络通讯时延&#xff0c;属于一个优化项&#xff0c;但是tcp的quickack是有配额限制的&#xff0c;配置是16个quick&#xff0c;也就是短时间内quickack了16次以后&#xff0c;这个配额为…...

Wi-Fi 7、Wi-Fi 6 与 5G、4G 的全方位对比

随着无线通信技术的飞速发展&#xff0c;Wi-Fi 7、Wi-Fi 6&#xff0c;以及5G、4G 已经成为人们生活和工作中不可或缺的网络技术。无论是家庭网络、高速移动通信&#xff0c;还是工业物联网&#xff0c;这些技术都在发挥各自的作用。那么&#xff0c;它们之间有什么区别&#x…...

【例43.3】 转二进制

目录 描述 输入描述 输出描述 用例输入 1 用例输出 1 来源 类型 知识补充站 代码 C Python3 C 描述 请你把一个整数n转化为二进制并从低位到高位输出。 输入描述 一行&#xff0c;仅含一个正整数 n (1≤n≤109)。 输出描述 从低位到高位输出一个二进制数&a…...

qt vs ios开发应用环境搭建和上架商店的记录

qt 下载链接如下 https://download.qt.io/new_archive/qt/5.14/5.14.2/qt-opensource-mac-x64-5.14.2.dmg 安装选项全勾选就行&#xff0c;这里特别说明下qt5.14.2/qml qt5.14.2对qml支持还算成熟&#xff0c;但很多特性还得qt6才行&#xff0c;这里用qt5.14.2主要是考虑到服…...

安全测评主要标准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 安全测评的主要标准‌包括多个国际和国内的标准&#xff0c;这些标准为信息系统和产品的安全评估提供了基础和指导。 一、安全测评的主要标准 1.1、国际标准 ‌可信计算机系统评估准则&#xff08;TC…...

如何学习数学 | 数学家如何思考

学习数学的关键在哪里&#xff1f; 原创 遇见数学 不少人面对数学都会觉得高深莫测&#xff0c;甚至非常枯燥乏味。 只有当你真正走入它的世界&#xff0c;才会发现里面蕴含着无尽的智慧和美感。要想推开这座数学的大门&#xff0c;需要的不仅仅是背公式&#xff0c;或者做一…...

职场沟通与行为

职场沟通与行为 引言 在职场上&#xff0c;你是否曾遇到过困惑的沟通&#xff1f;是否对同事的行为有过疑虑&#xff1f;这不仅是个别现象&#xff0c;而是我们这个时代工作文化中的普遍问题。许多职场的摩擦&#xff0c;来自沟通不畅或是行为不当。那么&#xff0c;如何才能…...

IIO(Industrial I/O)驱动介绍

文章目录 IIO&#xff08;Industrial I/O&#xff09;驱动是Linux内核中用于工业I/O设备的子系统&#xff0c;主要用于处理传感器数据采集和转换。以下是其关键点&#xff1a; 功能 数据采集&#xff1a;从传感器读取数据。数据处理&#xff1a;对原始数据进行滤波、校准等操作…...

关于vite+vue3+ts项目中env.d.ts 文件详解

env.d.ts 文件是 Vite 项目中用于定义全局类型声明的 TypeScript 文件。它帮助开发者向 TypeScript提供全局的类型提示&#xff0c;特别是在使用一些特定于 Vite 的功能时&#xff08;如 import.meta.env&#xff09;。以下是详细讲解及代码示例 文章目录 **1. env.d.ts 文件的…...

32单片机综合应用案例——物联网(IoT)环境监测站(四)(内附详细代码讲解!!!)

无论你身处何种困境&#xff0c;都要坚持下去&#xff0c;因为勇气和毅力是成功的基石。不要害怕失败&#xff0c;因为失败并不代表终结&#xff0c;而是为了成长和进步。相信自己的能力&#xff0c;相信自己的潜力&#xff0c;相信自己可以克服一切困难。成功需要付出努力和坚…...

【Flink系列】6. Flink中的时间和窗口

6. Flink中的时间和窗口 在批处理统计中&#xff0c;我们可以等待一批数据都到齐后&#xff0c;统一处理。但是在实时处理统计中&#xff0c;我们是来一条就得处理一条&#xff0c;那么我们怎么统计最近一段时间内的数据呢&#xff1f;引入“窗口”。 所谓的“窗口”&#xff…...

代码随想录算法训练营第三十五天-动态规划-01背包(二维)

动规五部曲 dp数组的含义&#xff0c;注意这是一个二维数组。dp[i][j] 第一维度代表“从0到第i个物品&#xff0c;而且包括选或不选的情况&#xff0c;即这一维度代表物品编号第二维度代表代表背包容量合在一起的意思是当背包容量是j时&#xff0c;从0到i个物品中选择任意物品…...

快速开发:用AI构造AI —— 打造属于个人的Copilot(M-聪明AI)

作品简介&#xff1a; 当今快速发展的AI时代&#xff0c;学会使用AI的同时&#xff0c;也可以融入AI&#xff0c;来打造自己的产品&#xff0c;我给我这个取名M-聪明&#xff0c; 是基于VUE 3 Spring Boot -Redis ChatGML RxJava SSE 的AI 服务平台。然后这款工具旨在为用户…...

Elasticsearch容器启动报错:AccessDeniedException[/usr/share/elasticsearch/data/nodes];

AccessDeniedException 表明 Elasticsearch 容器无法访问或写入数据目录 /usr/share/elasticsearch/data/nodes。这是一个权限问题。 问题原因&#xff1a; 1、宿主机目录权限不足&#xff1a;映射到容器的数据目录 /data/es/data 在宿主机上可能没有足够的权限供容器访问。 …...

用公网服务器实现内网穿透

首先需要一个公网服务器 下载frp 搜索github下载到frp&#xff0c;服务端frps/客户端frpc。。下载的时候要注意自己本地内网机的cpu版本和服务端cpu架构 我的电脑是mac M1PRO版本 下载的是&#xff1a;darwinarm64 比如 服务端一般是Linux&#xff08;Intel 64位CPU&#xf…...

Jmeter如何进行多服务器远程测试

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 JMeter是Apache软件基金会的开源项目&#xff0c;主要来做功能和性能测试&#xff0c;用Java编写。 我们一般都会用JMeter在本地进行测试&#xff0c;但是受到单…...

前端实习第二个月小结

时间飞快&#xff0c;第一次实习已经过去两个多月&#xff0c;作一些简单的总结和分享。 注&#xff1a;文章整体会比较轻松&#xff0c;提及的经历、经验仅作参考。 一、关于实习/工作内容 1、工作内容 近期做的是管理后台方面的业务&#xff0c;技术栈&#xff1a;前端re…...

C# 并发和并行的区别--16

目录 并发和并行 一.并发 定义 特点 代码示例 代码解释 二.并行 定义 特点 在C#中的体现 代码示例 代码解释 三.并发和并行的区别 四 .如何在C#中选择并发还是并行 1.考虑任务类型 2.代码示例 3.注意事项 五.总结 并发和并行 在编程领域,并发和并行是两个密切…...

Python编程与在线医疗平台数据挖掘与数据应用交互性研究

一、引言 1.1 研究背景与意义 在互联网技术飞速发展的当下,在线医疗平台如雨后春笋般涌现,为人们的就医方式带来了重大变革。这些平台打破了传统医疗服务在时间和空间上的限制,使患者能够更加便捷地获取医疗资源。据相关报告显示,中国基于互联网的医疗保健行业已进入新的…...

HBase实训:纸币冠字号查询任务

一、实验目的 1. 理解分布式数据存储系统HBase的架构和工作原理。 2. 掌握HBase表的设计原则&#xff0c;能够根据实际业务需求设计合理的表结构。 3. 学习使用HBase Java API进行数据的插入、查询和管理。 4. 实践分布式数据存储系统在大数据环境下的应用&#xff0c;…...

Java 读取 Windows 设备的唯一性标识及定位

在 Windows 系统中&#xff0c;获取设备唯一性标识及定位信息对设备管理、安全监控等场景意义重大。本文介绍 Java 中几种实现方法&#xff0c;如 JNA 库、WMI4Java 库及通过 JNI 结合 Windows API。 1. 使用 JNA 库读取 DEVPKEY_Device_ContainerId 在 Windows 系统中&…...

UE控件学习

ListView&#xff1a; item设置&#xff1a;使能在list设置为Entry类 Grid Panel&#xff1a; 常用作背包&#xff0c;每个格子大小可不相同 WidgetSwitcher&#xff1a; 用于切换页签 Wrap_Box&#xff1a; 自动横向排版子节点&#xff0c;超过一定范围则自动换行…...

1.Spring AI 从入门到实践

​Spring AI 从入门到实践 1.什么是Spring AI 2.使用Spring Boot&Spring AI快速构建AI应用程序 3.ChatClient&Chat Model简化与AI模型的交互 4.Spring AI Prompt:与大模型进行有效沟通 5.结构化输出大模型响应 6.实战:AI聊天机器人 Ben技术站关注Java技术&#x…...

2025年01月蓝桥杯Scratch1月stema选拔赛真题—美丽的图形

美丽的图形 编程实现美丽的图形具体要求: 1)点击绿旗&#xff0c;角色在舞台中心&#xff0c;如图所示&#xff1b; 2)1秒后&#xff0c;绘制一个边长为 140的红色大正方形&#xff0c;线条粗细为 3&#xff0c;正方形的中心为舞台中心&#xff0c;如图所示; 完整题目可点击下…...

FLASK创建下载

html用a标签 <!-- Button to download the image --> <a href"{{ url_for(download_file, filenameimage.png) }}"><button>Download Image</button> </a> 后端&#xff1a;url_for双大括号即是用来插入变量到模板中的语法。也就是绑…...

LDD3学习7--硬件接口I/O端口(以short为例)

1 理论 1.1 基本概念 目前对外设的操作&#xff0c;都是通过寄存器。寄存器的概念&#xff0c;其实就是接口&#xff0c;访问硬件接口&#xff0c;有I/O端口通信和内存映射I/O (Memory-Mapped I/O)&#xff0c;I/O端口通信是比较老的那种&#xff0c;都是老的串口并口设备&am…...

MySQL(高级特性篇) 06 章——索引的数据结构

一、为什么使用索引 索引是存储引擎用于快速找到数据记录的一种数据结构&#xff0c;就好比一本教科书的目录部分&#xff0c;通过目录找到对应文章的页码&#xff0c;便可快速定位到需要的文章。MySQL中也是一样的道理&#xff0c;进行数据查找时&#xff0c;首先查看查询条件…...

【FlutterDart】MVVM(Model-View-ViewModel)架构模式例子-http版本(30 /100)

动图更精彩 MVVM&#xff08;Model-View-ViewModel&#xff09; 特点 Model&#xff1a;负责数据管理和业务逻辑。 View&#xff1a;负责显示数据&#xff0c;通常是一个UI组件。 ViewModel&#xff1a;负责处理用户交互&#xff0c;更新Model&#xff0c;并将数据转换为View可…...

光谱相机的光谱分辨率可以达到多少?

多光谱相机 多光谱相机的光谱分辨率相对较低&#xff0c;波段数一般在 10 到 20 个左右&#xff0c;光谱分辨率通常在几十纳米到几百纳米之间&#xff0c;如常见的多光谱相机光谱分辨率为 100nm 左右。 高光谱相机 一般的高光谱相机光谱分辨率可达 2.5nm 到 10nm 左右&#x…...

.Net8 Avalonia跨平台UI框架——<vlc:VideoView>控件播放海康监控、摄像机视频(Windows / Linux)

一、UI效果 二、新建用户控件&#xff1a;VideoViewControl.axaml 需引用&#xff1a;VideoLAN.LibVLC.Windows包 Linux平台需安装&#xff1a;VLC 和 LibVLC &#xff08;sudo apt-get update、sudo apt-get install vlc libvlccore-dev libvlc-dev&#xff09; .axaml 代码 注…...

【论文阅读】基于空间相关性与Stacking集成学习的风电功率预测方法

文章目录 摘要0. 引言1. 空间相关性分析2. 风电功率预测模型2.1 Stacking 集成策略2.2 基学习器2.2.1 基于机器学习算法的基学习器2.2.2 基于神经网络的基学习器2.2.3 基于粒子群优化算法的超参数优化 2.3 元学习器2.4 基于空间相关性与Stacking集成学习的风电功率预测方法 3 算…...

什么是Spring Boot 应用开发?

一、引言 在当今的软件开发领域&#xff0c;Java 依然占据着重要的地位&#xff0c;而 Spring Boot 作为 Java 生态系统中极具影响力的框架&#xff0c;极大地简化了企业级应用的开发流程&#xff0c;提升了开发效率和应用的可维护性。它基于 Spring 框架构建&#xff0c;通过…...

选择saas 还是源码主要考虑

公司业务规模&#xff1a;小型企业可能会发现SaaS提供的即用型解决方案更符合其需求&#xff0c;而大型企业可能需要源码以实现更高的定制性和控制权。 公司技术专长&#xff1a;缺乏技术团队的企业可能会倾向于使用SaaS&#xff0c;而那些拥有强大IT部门的企业可能更适合管理…...

【JAVA 基础 第(19)课】Hashtable 类用法和注意细节,是Map接口的实现类

Map接口&#xff1a;存放的是具有映射关系的键值对&#xff0c;键映射到值&#xff0c;键必须是唯一的 Hashtable 类&#xff0c;Map接口的实现类,键和值都不能为nullHashtable 是同步的&#xff0c;是线程安全的 public class MapTest {public static void main(String[] arg…...

AI时代下 | 通义灵码冲刺备战求职季

AI时代下 | 通义灵码冲刺备战求职季 什么是通义灵码使用智能编程助手备战求职靠谱吗体验心得 AI时代下&#xff0c;备战求职季有了不一样的方法&#xff0c;使用通义灵码冲刺备战求职季&#xff0c;会有什么样的体验&#xff1f; 什么是通义灵码 在开始话题之前&#xff0c;首…...

如何将 session 共享存储到 redis 中

文章目录 一. 分布式 session 登录1.1 什么是分布式&#xff1f;1.2 Session 共享1.3 为什么服务器 A 登录后&#xff0c;请求发到服务器 B&#xff0c;不认识该用户&#xff1f;1.4 共享存储 二. Session 共享实现Redis三. 测试session共享四. cookie设置4.1 前端4.2 后端 一.…...

智能科技与共情能力加持,哈曼重新定义驾乘体验

2025年1月6日&#xff0c;拉斯维加斯&#xff0c;2025年国际消费电子展——想象一下&#xff0c;当您步入一辆汽车&#xff0c;它不仅能响应您的指令&#xff0c;更能理解您的需求、适应您的偏好&#xff0c;并为您创造一个独特且专属的交互环境。作为汽车科技领域的知名企业和…...

第4章 Kafka核心API——Kafka客户端操作

Kafka客户端操作 一. 客户端操作1. AdminClient API 一. 客户端操作 1. AdminClient API...

Debian 设定 tomcat 定时重启

目录 背景 过程记录 1、编辑sh文件&#xff0c;完成重启功能 2、设置sh的可执行权限 ​编辑 3、设置定时任务 背景 在Debian 12系统中&#xff0c;原本部署了两个tomcat&#xff0c;结果总是遇到CPU飙升到影响应用正常使用的程度&#xff0c;找了很久原因还是没有找到。 …...

mysql8.0 重要指标参数介绍

MySQL 8.0 引入了许多新的功能和优化&#xff0c;针对性能、可扩展性、可靠性以及安全性方面做出了显著改进。为了确保 MySQL 的高效运行&#xff0c;了解和配置 MySQL 的一些关键指标参数非常重要。以下是 MySQL 8.0 中的一些重要参数和指标&#xff0c;帮助你优化数据库性能。…...

SpringMVC (2)

目录 1. RequestMapping 注解介绍 2. RequestMapping 使用 3. RequestMapping与请求方式 3.1 RequestMapping 支持Get和Post类型的请求 3.2 RequestMapping 指定接收某种请求 3.3 GetMapping和PostMapping 4. 传参 4.1 通过查询字符串传参 4.2 在 Body 中传参 4.2.1 …...

【全面解析】深入解析 TCP/IP 协议:网络通信的基石

深入解析 TCP/IP 协议&#xff1a;网络通信的基石 导语 你是否曾好奇&#xff0c;现代互联网是如何实现全球设备之间的高速、稳定和可靠通信的&#xff1f;无论是浏览网页、发送电子邮件&#xff0c;还是进行视频通话&#xff0c;背后都离不开 TCP/IP 协议 的支撑。作为互联网…...

图数据库 | 19、高可用分布式设计(下)

相信大家对分布式系统设计与实现的复杂性已经有了一定的了解&#xff0c;本篇文章对分布式图数据库系统中最复杂的一类系统架构设计进行探索&#xff0c;即水平分布式图数据库系统&#xff08;这个挑战也可以泛化为水平分布式图数据仓库、图湖泊、图中台或任何其他依赖图存储、…...

【2024年华为OD机试】 (C卷,200分)- 反射计数(Java JS PythonC/C++)

一、问题描述 题目解析 题目描述 给定一个包含 0 和 1 的二维矩阵&#xff0c;一个物体从给定的初始位置出发&#xff0c;在给定的速度下进行移动。遇到矩阵的边缘时会发生镜面反射。无论物体经过 0 还是 1&#xff0c;都不影响其速度。请计算并给出经过 t 时间单位后&#…...

【微服务】SpringCloud 1-9章

1从Boot和Cloud版本选型开始说起 1.1Springboot版本选择 1.1.1git源码地址 https://github.com/spring-projects/spring-boot/releases/ 1.1.2官网看Boot版本 1.1.3SpringBoot3.0崛起 https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Release-Notes …...

Jmeter进行http接口并发测试

目录&#xff1a; 1、Jmeter设置&#xff08;1&#xff09;设置请求并发数&#xff08;2&#xff09;设置请求地址以及参数&#xff08;3&#xff09;添加结果数 2、启动看结果 1、Jmeter设置 &#xff08;1&#xff09;设置请求并发数 &#xff08;2&#xff09;设置请求地址…...

JavaScript语言的数据结构

JavaScript中的数据结构 引言 在编程的世界里&#xff0c;数据结构是处理和组织数据的重要方式。数据结构的选择往往直接影响到程序的性能和可维护性。JavaScript作为一门广泛使用的编程语言&#xff0c;在数据结构的设计和使用上也有其独特的特点。本文将深入探讨JavaScript…...

【数据分享】1929-2024年全球站点的逐日平均气温数据(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、湿度等指标&#xff0c;其中又以气温指标最为常用&#xff01;说到气温数据&#xff0c;最详细的气温数据是具体到气象监测站点的气温数据&#xff01;本次我们为大家带来的就是具体到气象监…...

DETRs with Collaborative Hybrid Assignments Training论文阅读与代码

关键词:协作混合分配训练 【目标检测】Co-DETR:ATSS+Faster RCNN+DETR协作的先进检测器(ICCV 2023)-CSDN博客 摘要: 在这篇论文中,作者观察到在DETR中将过少的 Query 分配为正样本,采用一对一的集合匹配,会导致对编码器输出的监督稀疏,严重损害编码器的区分特征学习…...

某国际大型超市电商销售数据分析和可视化

完整源码项目包获取→点击文章末尾名片&#xff01; 本作品将从人、货、场三个维度&#xff0c;即客户维度、产品维度、区域维度&#xff08;补充时间维度与其他维度&#xff09;对某国际大型超市的销售情况进行数据分析和可视化报告展示&#xff0c;从而为该超市在弄清用户消费…...