当前位置: 首页 > Linux

packetdrill——测试TCP协议栈行为的强大工具

时间:2023-04-06 04:21:57 Linux

摘要:packetdrill是一款非常好用的网络协议栈测试工具,由Google开发,常用于网络协议栈的回归测试确保新功能不会影响原有功能。本文主要介绍其基本原理、安装、入门以及如何编写测试脚本。1.简介packetdrill是一款非常好用的网络协议栈测试工具,由Google开发,常用于网络协议栈的回归测试,确保新功能不会影响原有功能。它支持Linux、FreeBSD、OpenBSD和NetBSD内核。它使用脚本语言编写测试语句并预测协议栈的输出。官方也提供了很多测试脚本的例子。2.原理packetdrill的整体框架如下图所示。packetdrill应用程序在内部模拟连接的远程端和本地端。其中,Remoteend作为远端向本机发送消息的通道。我们可以在packetdrill应用程序中将IP消息写入tun设备。对于内核协议栈来说,这相当于接收到远端的IP报文。路由后,消息将被发送到协议栈。另一方面,内核协议栈发送给Remote端的数据包,会通过tun设备返回给packetdrill应用程序。这时候我们可以通过比较它的输入来验证协议栈的正确性。脚本文件是后缀为.pkt的文件。packetdrill开始读取文件后,脚本解析器将每一行脚本语句解析为一个运行时事件,脚本运行器依次执行每个事件。3、安装packetdrill依赖的包:gcc、python、flex、bison从官方github下载源码后,编译>./configure>make4。开始执行一个测试脚本>./packetdrilltests/linux/fast_retransmit/fr-4pkt-sack-linux.pkt>如果没有输出说明脚本测试通过:),否则会提示哪一行脚本不符合预期,错误原因是什么。例如,在我的机器上(内核版本4.4.0)执行以下脚本时发生错误:>./packetdrilltests/linux/listen/listen-incoming-ack.pkttests/linux/listen/listen-incoming-ack.pkt:17:错误处理数据包:错误值出站TCP选项3脚本数据包:0.200000S.0:0(0)ack1实际数据包:0.201014S.0:0(0)ack1win29200>表示执行到脚本第17行时出错。在脚本中,远端期望收到SYNACK消息中的wscale=6,但实际收到的消息中是wscale=7。出现这个错误的原因是脚本适合的内核版本的协议栈实现和我本地版本不一致!内核版本不一致,协议栈部分实现不一致!发生这种情况时,我们可以简单地修改脚本以适应我们自己的内核版本。5.脚本语言Packetdrill不使用现成的脚本语言。它的脚本有一些tcpdump的踪迹和一些套接字编程的踪迹//当侦听器收到传入数据包时的测试行为//ACK位设置但SYN位未设置。0.000socket(...,SOCK_STREAM,IPPROTO_TCP)=30.000setsockopt(3,SOL_SOCKET,SO_REUSEADDR,[1],4)=00.000bind(3,...,...)=00.000listen(3,1)=00.100<。0:0(0)win327920.100>R0:0(0)win0//现在确保有效的SYN在此后不久到达//(使用相同的地址4元组)我们仍然可以成功建立//连接。0.2000.200>S.0:0(0)ack10.300<.1:1(0)ack1win3200.300accept(3,...,...)=4我认为学习这种脚本最好的方法就是学习官方的例子。按照示例,您可以构建您需要的脚本!有疑问的可以稍微翻一下代码!时间戳脚本以每行为单位,每行都是时间戳+语句的形式。时间戳表示这条语句的执行时间。packetdrill支持绝对时间和相对时间。//相对时间,距离最后一个脚本0.1秒后+.1setsockopt(3,SOL_SOCKET,SO_REUSEADDR,[1],4)=0//绝对时间,0.2秒后脚本开始运行setsockopt(3,SOL_SOCKET,SO_REUSEADDR,[1],4)=0Injectthemessagetimestampintotheprotocolstackfollowed<符号语句表示从远端向协议栈注入报文,后面默认是TCP报文的内容(当然,其他协议也可以连接,但是协议栈的复杂度多在TCP)//注入一个SYN报文(S表示SYN),起始序号和结束序号为0,数据长度为0,通知窗口大小为32792,携带mss、sack、wscale选项。0.200从协议栈收到消息时间戳后跟>符号的一句表示远端希望收到来自协议栈的消息。这里的期望接收时间是一个范围[ts-tolerance,ts+tolerance],tolerance时间tolerance默认为4毫秒(可以通过运行参数改变)//期望收到一个SYNACK报文(.表示ACK)ACK序列number为1,携带mss、sack、wscale选项0.200>S.0:0(0)ack1系统调用上面的消息语句来自远端视角是的,系统调用是从本端来看的,packetdrill支持以下系统调用,syscall_listen},{“接受”,syscall_accept},{“连接”,syscall_connect},{“读取”,syscall_read},{“readv”,syscall_readv},{“recv”,syscall_recv},{“recvfrom”,syscall_recvfrom},{“recvmsg”,syscall_recvmsg},{“写入”,syscall_write},{“writev”,syscall_writev},{“发送”,syscall_send},{“发送”,syscall_sendto},{“sendmsg”,syscall_sendmsg},{"fcntl",syscall_fcntl},{"ioctl",syscall_ioctl},{"关闭",syscall_close},{"关闭",syscall_shutdown},{"getsockopt",syscall_getsockopt},{"setsockopt",syscall_setsockopt},{"poll",syscall_poll},{"cap_set",syscall_cap_set},{"open",syscall_open},{"sendfile",syscall_sendfile},{"epoll_create",syscall_epoll_create},{"epoll_ctl",syscall_epoll_ctl},{"epoll_wait",syscall_epoll_wait},{"pipe",syscall_pipe},{"splice",syscall_splice},};和我们习惯的系统调用不同的是,packetdrill中的系统调用中有些参数是不能改的,需要我们填写...(脚本运行机会会帮我们填写),同时还要设置其返回值//socket系统调用,Returnfd=3,其中3只在脚本作用域内有效,运行时返回的描述符的值由框架维护,框架会维护他们的对应关系0.000socket(...,SOCK_STREAM,IPPROTO_TCP)=3//setsockopt系统调用,第三个参数[1]表示指向值1的指针0.000setsockopt(3,SOL_SOCKET,SO_REUSEADDR,[1],4)=0//listen系统调用,最后两个参数由框架决定0.000bind(3,...,...)=00.000listen(3,1)=0assert有时我们需要窥探更多关于TCPruntime的信息,比如双方协商的MSS,当前窗口大小cwnd,慢启动阈值ssthresh是多少。这时候我们可以使用assert语句来预测它的状态//Expectedstatusinformationthistime0.300%{asserttcpi_reordering==3asserttcpi_unacked==10asserttcpi_sacked==1}%packetdrill支持预期的TCP信息如下:/*packetdrill/gtests/net/packetdrill/tcp.h*/struct_tcp_info{__u8tcpi_state;__u8tcpi_ca_state;__u8tcpi_retransmits;__u8tcpi_probes;__u8tcpi_backoff;__u8tcpi_选项;__u8tcpi_snd_wscale:4,tcpi_rcv_wscale:4;__u8tcpi_delivery_rate_app_limited:1;__u32tcpi_rto;__u32tcpi_ato;__u32tcpi_snd_mss;__u32tcpi_rcv_mss;__u32tcpi_unacked;__u32tcpi_sacked;__u32tcpi_lost;__u32tcpi_retrans;__u32tcpi_fackets;/*次。*/__u32tcpi_last_data_sent;__u32tcpi_last_ack_sent;/*不记得了,抱歉。*/__u32tcpi_last_data_recv;__u32tcpi_last_ack_recv;/*指标。*/__u32tcpi_pmtu;__u32tcpi_rcv_ssthresh;__u32tcpi_rtt;__u32tcpi_rttvar;__u32tcpi_snd_ssthresh;__u32tcpi_snd_cwnd;__u32tcpi_advmss;__u32tcpi_重新排序;__u32tcpi_rcv_rtt;__u32tcpi_rcv_space;__u32tcpi_total_retrans;__u64tcpi_pacing_rate;__u64tcpi_max_pacing_rate;__u64tcpi_bytes_acked;/*RFC4898tcpEStatsAppHCThruOctetsAcked*/__u64tcpi_bytes_received;/*RFC4898tcpEStatsAppHCThruOctetsReceived*/__u32tcpi_segs_out;/*RFC4898tcpEStatsPerfSegsOut*/__u32tcpi_segs_in;/*RFC4898tcpEStatsPerfSegsIn*/__u32tcpi_notsent_bytes;__u32tcpi_min_rtt;__u32tcpi_data_segs_in;/*RFC4898tcpEStatsDataSegsIn*/__u32tcpi_data_segs_out;/*RFC4898tcpEStatsDataSegsOut*/__u64tcpi_delivery_rate;__u64tcpi_busy_time;/*Time(usec)忙于发送数据*/__u64tcpi_rwnd_limited;/*时间(usec)受接收窗口限制*/__u64tcpi_sndbuf_limited;/*时间(usec)受发送缓冲区限制*/};需要注意的是,在使用assert时,一定要保证packetdrill中struct_tcp_info结构体的定义与当前内核中的定义一致,否则会报错!(超过)