Packetdrill的简明使用手册
1. Packetdrill 编译与安装
/* Set the offload flags to be like a typical ethernet device */
static void set_device_offload_flags(struct local_netdev *netdev)
{
#ifdef linux
// const u32 offload =
// TUN_F_CSUM | TUN_F_TSO4 | TUN_F_TSO6 | TUN_F_TSO_ECN | TUN_F_UFO;
// if (ioctl(netdev- tun_fd, TUNSETOFFLOAD, offload) != 0)
// die_perror("TUNSETOFFLOAD");
#endif
}
./configure && make
使用办法
./packetdrill test.pkt
test.pkt为按Packetdrill语法编写的测试脚本。
成功:无输出,表示脚本正确,一切都符合预期。
失败:指出脚本的错误地方,以及原因。
2. Packetdrill 执行自带测试用例
- 开启tcpdump -i any tcp port 8080抓包便于分析
- 这里测试快速重传,测试环境centos7.2。
- 简单说明< 表示输入,packetdrill会构造一个真实的数据包。 表示预期协议栈会响应的数据包。(这个包不是由packetdrill构造的,而是由协议栈发出的。)
// Test fast retransmit with 4 packets outstanding, receiver sending SACKs.
// In this variant the receiver supports SACK.
// Establish a connection.
0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0 bind(3, ..., ...) = 0
+0 listen(3, 1) = 0
//三次握手
+0 < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7
+0 S. 0:0(0) ack 1 <...
+.1 < . 1:1(0) ack 1 win 257
+0 accept(3, ..., ...) = 4
//系统调用,让协议栈发出100个字节
// Send 1 data segment and get an ACK, so cwnd is now 4.
+0 write(4, ..., 1000) = 1000
//预期协议栈会发出psh,ack,实际上发出了ack1
//+0 P. 1:1001(1000) ack 2
//向协议栈注入 ack
+.1 < . 1:1(0) ack 1001 win 257
// Write 4 data segments.
//系统调用,让协议栈发出4000个字节
+0 write(4, ..., 4000) = 4000
//预期协议栈会发出psh,ack,实际上发出了seq 1001:2001, ack 1;seq 2001:3001, ack 1;seq 3001:4001, ack 1;[P.], seq 4001:5001, ack 1
//+0 P. 1001:5001(4000) ack 1
// Get 3 SACKs.
//向协议栈连续发出三个ack
+.1 < . 1:1(0) ack 1001 win 257 <sack 2001:3001,nop,nop
+0 < . 1:1(0) ack 1001 win 257 <sack 2001:4001,nop,nop
+0 < . 1:1(0) ack 1001 win 257 <sack 2001:5001,nop,nop
// We've received 3 duplicate ACKs, so we do a fast retransmit.
//预期协议栈会发出一次快速重传 Seq 1001:2001,ack 1
//+0 . 1001:2001(1000) ack 1
// Receiver ACKs all data.
//向协议栈ack,响应所有报文的ack。
+.1 < . 1:1(0) ack 6001 win 257
4. 将fr-4pkt-sack-linux.pkt 中的修改如下。
+0 P. 1:1001(1000) ack 2 ? +0 P. 1:1001(1000) ack 1
//+0 P. 1001:5001(4000) ack 1 ?
+0 . 1001:2001(1000) ack 1
+0 . 2001:3001(1000) ack 1
+0 . 3001:4001(1000) ack 1
+0 P. 4001:5001(1000) ack 1
[注解:如果执行packetdrill自带的用例出错,一般是协议栈发出的包没有达到预期的包,先将预期 那部分干掉,然后再执行测试用例,然后通过抓包分析预期结果。通常是因为三次握手mss 的限制]
- 执行: ../../../packetdrill fr-4pkt-sack-linux.pkt,无出错。
- 抓包可以看到一下结果:三次重复ack,则实施快速重传。达到预期效果。
// 自己构造包实现三次重复的ack 1001.
07:57:36.469280 IP 192.0.2.1.36840 TENCENT64.site.webcache: Flags [.], ack 1001, win 257, options [sack 1 {2001:3001},nop,nop], length 0
07:57:36.469836 IP 192.0.2.1.36840 TENCENT64.site.webcache: Flags [.], ack 1001, win 257, options [sack 1 {2001:4001},nop,nop], length 0
07:57:36.470349 IP 192.0.2.1.36840 TENCENT64.site.webcache: Flags [.], ack 1001, win 257, options [sack 1 {2001:5001},nop,nop], length 0
// 协议栈发起快速重传。Seq 1001:2001,ack 1,1000
07:57:36.470376 IP TENCENT64.site.webcache 192.0.2.1.36840: Flags [.], seq 1001:2001, ack 1, win 229, length 1000
3. Packetdrill 解读自带测试用例说明
这里主要说明packetdrill的基本语法。
脚本中可以包含四种语句:数据包、系统调用、shell命令、python语句。 每条语句都必须以时间戳开头,指明它的执行时间。
- Packets
数据包分为:输入的数据包、输出的数据包,格式类似于tcpdump的, 支持TCP、UDP、ICMP,以及TCP的大部分选项。
输入数据包(<表示输入):packetdrill会构造一个真实的数据包,然后注入协议栈。
例子:
0.100 < S 0:0(0) win 32792 <mss 1000, nop, nop, sackOK, nop, wscale 7
0.250 < [1:1461(1460)] icmp unreachable frag_needed mtu 1200
输出数据包( 表示输出):packetdrill会检查协议栈是不是真的发出了这样一个包。
+0 udp (1472)
- System Calls
系统调用的格式类似于strace。 对于每个系统调用,packetdrill会在指定的时间给予执行,并检查返回值是否和预期的一样。系统调用的主要是应用于场景构造,已经非测试端的数据发送和接收。
常见的系统调用例子: 系统调用
connect(3, ..., ...) = -1 EINPROGRESS (Operation now in progress) //客户端连接服务器
getsockopt(3, SOL_SOCKET, SO_ERROR, [0], [4]) = 0 //获取scoketopt
fcntl(3, F_SETFL, O_RDWR) = 0 //Fcntl设置
ioctl(4, SIOCINQ, [1000]) = 0 //Ioctl设置
read(3, ..., 1024) = 785 //读取数据
write(3, ..., 57) = 57 //写入数据
close(3) = 0 //关闭连接
socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 //Tcp socket
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 //设置地址复用
bind(3, ..., ...) = 0 //绑定端口
listen(3, 1) = 0 //监听端口
accept(3, ..., ...) = 4 //接受连接
- shell脚本用法
常见用法是用shell脚本设置内核参数或者调用shell命令统计tcp信息。设置
例子:
+0 `sysctl -q net.ipv4.tcp_timestamps=0`
+0 `ss -4 -n state SYN-RECV | grep 192.168.0.1:8080 /dev/null`
- python脚本的用法
常见用法是使用python的assert断言tcp_info的里面的信息,是否符合预期。
例子:
0.310 %{
assert tcpi_reordering == 3
assert tcpi_unacked == 10
assert tcpi_sacked == 6
assert tcpi_ca_state == TCP_CA_Recovery
}%
- 时间戳
每条语句都必须以时间戳开头,指明它的执行时间,或者预期事件的发生时间。测试case有可能是timing的问题导致测试case无法通过。
时间戳可以使用多种格式:
Absolute(绝对时间):0.75
Relative(相对时间):+0.2
Wildcard(任意时间):*
Range(绝对时间区间):0.750~0.900
Relative Range(相对时间区间):+0.1~+0.2
Loose(允许误差值):--tolerance_usecs=800
Blocking(阻塞时间区间):0.750...0.900
如果在规定的时间戳,对应的事件并没有发生就会报错,并告知该事件的实际发生时间。
+1.0 S. 0:0(0) ack 1 <mss 1460,nop,nop,sackOK,nop,wscale 6
预期在1s以后TCP应该发送一个SYNACK包。
在实际的使用中,一般指定–tolerance_usecs=405000,也就是允许4ms的时间误差。
4. Packetdrill 实现基本场景构造测试
场景的场景构造是客户端场景或者是服务器场景。具体包怎么构造,具体看packetdrill的自带的测试用例。
1.服务端场景
构造服务器端场景:数据包输入端是客户端。数据包输出端是系统调用,充当服务端。
// Establish a connection.
0.000 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
0.000 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
0.000 bind(3, ..., ...) = 0
0.000 listen(3, 1) = 0
0.000...0.200 accept(3, ..., ...) = 4
0.100 < S 0:0(0) win 32792 <mss 1000,nop,wscale 7
0.100 S. 0:0(0) ack 1 <mss 1460,nop,wscale 6
0.200 < . 1:1(0) ack 1 win 257
//服务器端调用系统调用,预期发出2段数据包。
0.300 write(4, ..., 2000) = 2000
//0.300 P. 1:2001(2000) ack 1
0.300 . 1:1001(1000) ack 1
0.300 P. 1001:2001(1000) ack 1
1.客户端场景构造
构造服务器端场景:数据包输入端是服务端。数据包输出端是系统调用,充当客户端。
// Create a socket and set it to non-blocking.
0.000 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
0.000 fcntl(3, F_GETFL) = 0x2 (flags O_RDWR)
0.000 fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0
// Establish connection and verify that there was no error.
0.100 connect(3, ..., ...) = -1 EINPROGRESS (Operation now in progress)
0.100 S 0:0(0) <mss 1460,sackOK,TS val 100 ecr 0,nop,wscale 6
0.200 < S. 0:0(0) ack 1 win 5792 <mss 1460,sackOK,TS val 700 ecr 100,nop,wscale 7
0.200 . 1:1(0) ack 1 <nop,nop,TS val 200 ecr 700
//客户端调用系统调用,预期发出http请求。
// Send the HTTP request.
0.200 write(3, ..., 57) = 57
0.200 P. 1:58(57) ack 1 <nop,nop,TS val 200 ecr 700
0.300 < . 1:1(0) ack 58 win 92 <nop,nop,TS val 800 ecr 200
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对网站事(zalou.cn)的支持。如果你想了解更多相关内容请查看下面相关链接
- iOS 滚动视图的复用问题解决方案
- 二叉排序树(BSTree)关于查找算法结合
- 使用ES6新特性开发微信小程序(5)——内建对象的变化
- 弗洛伊德(Floyd)算法
- 迪杰斯特拉(Dijkstra)算法求图中最短路径
- 微信小程序实例-摇一摇抽奖
- 普利姆(prim)算法和克鲁斯卡尔(kruskal)算法
- android Handler机制之ThreadLocal详解
- 图(Graph)的常用代码集合
- 我差不多,哦不我已经是个废人了No.4
- 将Gradle项目发布到Jcenter和Maven Central
- FLAnimatedImage -ios gif图片加载框架介绍
- linux学习第八篇:文件或目录权限chmod,更改所有者和所属组chown,umask,隐藏权限lsattr_chattr
- linux学习第九篇:特殊权限set_uid,set_gid,stick_bit以及软连接文件,硬链接文件
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- MongoDB的CRUD操作讲解-1-insert
- MongoDB入门
- 算法—判断字符串是否为IP地址
- 博客整体风格更改
- centos6.6下安装MongoDB3.x
- Goland 中配置 go-lint 代码检查
- JetBrains注册码计算(IntelliJ IDEA 15.0注册码激活)
- Java文本框内文字显示不同颜色、字号等属性【函数调用一键实现】
- PLSQL-sql操作&判断循环
- Python 技术篇-用PIL库实现彩色图片转化为黑白图片
- PLSQL-游标
- 超详细的Java容器、面板及四大布局管理器应用讲解!
- Go各时间字符串的解析
- MongoDB数据分页与排序
- Python 技术篇-用PIL库实现图片剪切、图片粘贴