• 不错的网络协议栈測试工具 — Packetdrill


    Packetdrill - A network stack testing tool developed by Google.

    项目:https://code.google.com/p/packetdrill/

    本文:zhangskd @ csdn blog

    简单介绍

    The packetdrill scripting tool enables quick, precise tests for entire TCP/UDP/IPv4/IPv6 network stacks,

    from the system call layer down to the NIC hardware.

    截至2013年开源时。Packetdrill已经在Google内部使用了18个月,主要用于下面几个用途:

    (1) Regression testing of network stack

    "we have a suite of hundreds of packetdrill scripts that are run by all developers on our team before

    submitting a patch for review."

    对网络协议栈进行回归測试。确保新的功能不会影响网络协议栈的可用性。

    总共包括657个test cases。

    (2) Test-driven development of network protocols

    "we have developed several new features for Linux TCP using packetdrill."

    在下面几个TCP新特性的开发中发挥重要作用:

    Early Retransmit

    Fast Open

    Loss Probes

    Rewrite of F-RTO

    (3) Reproduction of bugs seen in production network traces

    "we have used packetdrill to isolate hard-to-reproduce bugs seen in complex real traces."

    使用它发现了Linux内核的10个bug。

    安装和使用

    (1) 安装

    首先安装flex和bison。用于构建词法和语法分析器。

    然后编译就可以:

    cd packetdrill

    ./configure

    make


    (2) 使用

    ./packetdrill test.pkt

    test.pkt为按Packetdrill语法编写的測试脚本。

    成功:无输出,表示脚本正确。一切都符合预期。

    失败:指出脚本的错误地方,以及原因。

    语法

    The tool supports four types of statements: packets, system calls, shell commands, and Python scripts.

    Each statement is timestamped and is executed by the interpreter in real time, verifying that events

    proceed as the script expects.

    脚本中能够包括四种语句:数据包、系统调用、shell命令、python语句。

    每条语句都必须以时间戳开头。指明它的运行时间。

    (1) Packets

    数据包分为:输入的数据包、输出的数据包。格式类似于tcpdump的,

    支持TCP、UDP、ICMP,以及TCP的大部分选项。

    输入的数据包(input packets)

    对于输入的数据包(<表示输入),packetdrill会构造一个真实的数据包,然后注入协议栈。

    < denotes an input packet to construct and inject into the system under test.

    Here's an example of a TCP SYN packet, which packetdrill creates and injects into the

    network stack under test 100ms after the start of the test:

    0.100 < S 0:0(0) win 32792 <mss 1000, nop, nop, sackOK, nop, wscale 6>

    输出的数据包(outbound packets)

    对于输出的数据包(>表示输出),packetdrill会检查协议栈是不是真的发出了这样一个包。

    > denotes an output packet to sniff and verify, to expect the system to send.

    Here's an example of an outbound UDP packet expected to be sent immediately after

    a prior event(denoted by +0), which packetdrill sniffs and then verifies for matching

    specification:

    +0 > udp (1472)

    (2) System Calls

    系统调用的格式类似于strace。

    对于每一个系统调用,packetdrill会在指定的时间给予运行,并检查返回值是否和预期的一样。

    Here's an example of a bind() system call invocation in packetdrill notation:

    +0 bind(3, ..., ...) = 0

    In this example, 3 denotes the file descriptor number to pass in, and the = 0 denotes the expected

    return value (i.e.., the user expects the system call to succeed).

    The ellipsis (...) allows scripts to omit irrelevant details.
     

    (3) Shell Commands

    同意在脚本中使用shell命令,用反引號括起来。

    +0 `sysctl -q net.ipv4.tcp_timestamps=0`

    (4) Python Commands

    同意在脚本中使用Python命令,用%{和}%括起来。

    Packetdrill allows inline Python code snippets to print information and to make assertions about the

    internal state of a TCP socket using the TCP_INFO getsockopt() option.

    The following Linux-based example asserts that the sender's congestion window is 10 packets:

    +0 %{ assert tcpi_snd_cwnd == 10 }%

    (5) 时间戳

    每条语句都必须以时间戳开头,指明它的运行时间。或者预期事件的发生时间。

    时间戳能够使用多种格式:

    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的时间误差。

    (6) 完整样例

    验证TCP的高速重传功能,fast retransmit说白了就是收到3个反复的ACK或SACK后立即重传一个数据包

    (对于FACK来说仅仅要孔>=3个包就可以)。

    脚本中server端的协议栈是要观測的对象,相应的是输出的数据包(outbound packet)。

    脚本中client相应的是输入的数据包(inbound packet),用于注入协议栈。

    完整样例例如以下:

    // Establish a connection. 服务端socket函数调用
    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
    
    // client的socket函数调用不用显式指出
    // client构造SYN包,注入协议栈
    +0 < S 0:0(0) win 32792 <mss 1000, sackOK, nop, nop, nop, wscale 7>
    
    // 预期协议栈发送SYNACK包
    +0 > S. 0:0(0) ack 1 <...>
    
    // client构造ACK包。注入协议栈,完毕三次握手
    +.1 < . 1:1(0) ack 1 win 257
    
    // 服务端接受连接
    +0 accept(3, ..., ...) = 4
    
    // Send 1 data segment and get an ACK,构造收发包场景
    +0 write(4, ..., 1000) = 1000
    +0 > P. 1:1001(1000) ack 1
    +.1 < . 1:1(0) ack 1001 win 257
    +0 %{ print tcpi_snd_cwnd }%
    
    // Write 4 data segments
    +0 write(4, ..., 4000) = 4000
    +0 > P. 1001:5001(4000) ack 1
    
    // Get 3 SACKs。构造高速重传场景
    +.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.
    // 收到3个SACK后。预期协议栈会高速重传
    +0 > . 10001:2001(1000) ack 1
    
    // Receiver ACKs all data.
    +.1 < . 1:1(0) ack 6001 win 257

     

    实现

     

    Packetdrill是一个用户态应用程序,主要用C语言编写。

    使用flex构造词法分析器,使用bison构造语法分析器。

    脚本解释器包含一个主线程和一个用于运行堵塞的系统调用的线程。

    使用packet socket来验证输出的数据包,使用TUN device来注入输入的数据包。

    详细代码可见项目。

    測试案例

    一些用于測试详细场景的測试案例:

    fast_retransmit // 高速重传

    early_retransmit // ER补丁測试

    blocking // 堵塞的系统调用

    fast_recovery // PRR补丁測试

    initial_window // 初始cwnd

    init_rto // SYNACK包的RTO

    close

    connect

    icmp

    inet_diag

    ioctl

    listen

    mss

    pmtu_discovery

    receiver_rtt

    sack

    shutdown

    undo

    run_tests.h为一个測试脚本:

    #!/bin/bash
    for f in `find . -name "*.pkt" | sort`; do
        echo "Running $f ..."
        ip tcp_metrics flush all &> /dev/null
        ../../packetdrill $f
    done

    注意:Due to TCP metrics caching in recent kernels, a second run of all tests can result in failures.

    The script run_tests.sh in this directory uses the iproute tool to flush the TCP metrics cache

    before each test.

    这些測试脚本在3.11.0-12-generic中都能通过。

    偶尔有timing error,是正常现象,可用--tolerance_usecs=405000指定同意的时间误差。

    我的体验

    測试一个简单的场景:连接建立后,服务端发送10个包。

    这时候处于慢启动阶段,cwnd是指数增长的。

    按理来说每收到1个ACK,cwnd++;每收到1个delayed ACK,cwnd+=2。终于cwnd应该为20。

    但測试结果表明,终于cwnd为12。

    进一步分析发现这是受到拥塞窗体有效性验证机制的影响,当发送是受到应用程序的限制(没有新数据可供发送),

    而不是受到cwnd的限制时,不同意添加cwnd。

    当然。这仅仅是一个小样例,说明Packetdrill有助于网络协议栈的分析。

    优缺点

    不论什么一个工具都有长处和限制,Packetdrill也不例外。

    (1) 长处

    属于脚本測试工具,可以高速和方便的測试网络协议栈,自由的构造測试场景。

    由于是用脚本測试。所以高速方便,不用大动干戈。

    能够在产品机上直接測试,因此測结果是真实的。

    场景可重现,測试可自己主动运行。

    比較通用,支持IPv4和IPv6,支持多种操作系统。

    (2) 缺点

    属于黑盒測试工具,尽管它能通过TCP_INFO选项从内核中获取一些信息。可是这些信息毕竟有限。

    当预期结果不符时。缺少信息来做进一步推断。

    编写測试脚本时。须要对要构造的场景十分了解,知道协议栈是怎样详细处理的(对每一步了如指掌)。

    所以,当场景比較复杂时(比方涉及到较多的数据包、往返时延)。编写脚本的难度大大添加了。

    另外眼下仅仅支持測试单条连接,不同意同一时候測试多条连接。

    Reference

    [1] packetdrill: Scriptable Network Stack Testing, from Sockets to Packets

    [2] Drilling Network Stacks with packetdrill

    [3] https://code.google.com/p/packetdrill/

  • 相关阅读:
    TCP/IP
    logging模块
    HttpClient当HTTP连接的时候出现大量CLOSE_WAIT连接(转)
    三 os模块
    一 time与datetime模块
    (转)HTTPS到底是个啥玩意儿?
    Python
    Codeforces Round #374 (Div. 2)
    Codeforces Round #373 (Div. 2)
    bzoj3527: [Zjoi2014]力
  • 原文地址:https://www.cnblogs.com/jhcelue/p/6754030.html
Copyright © 2020-2023  润新知