摘要:packetdrill
是一個很是有用的用於測試網絡協議棧的工具,由
packetdrill
是一個很是有用的用於測試網絡協議棧的工具,由Google
開發,它經常使用於對網絡協議棧進行迴歸測試,確保新的功能不會影響原有功能。它支持Linux, FreeBSD, OpenBSD與NetBSD內核。它使用腳本化的語言編寫測試語句,預測協議棧輸出,官方也提供了許多測試腳本的例子。python
packetdrill
的總體框架以下圖所示linux
packetdrill
應用內部模擬了一個鏈接的Remote端
和Local端
。其中Remote端
用做遠端發送到本機報文的通道,咱們能夠在packetdrill
應用內向tun
設備寫入IP
報文,對內核協議棧來來,這至關於從遠端收到了這個IP
報文,再通過路由,這個報文會上送協議棧。反過來講,內核協議棧的向Remote端
發送的報文會經過這個tun
設備回到packetdrill
應用,這時,咱們能夠經過比對其輸入,驗證協議棧的功能正確性。git
腳本文件是以.pkt
爲後綴的文件,packetdrill
啓動後讀取該文件,腳本解析器
將每一行腳本語句其解析爲運行時event
,腳本運行機
依次執行每一個event
。github
packetdrill
依賴的 package: gcc
、python
、flex
、bison
編程
從官方github下載源代碼後,編譯便可bash
> ./configure > make
> ./packetdrill tests/linux/fast_retransmit/fr-4pkt-sack-linux.pkt >
若是沒有任何輸出,就表示腳本測試經過了:),不然,它會提示哪一行腳本不知足預期以及錯誤緣由分別是什麼網絡
好比在個人機器上(內核版本4.4.0)執行下面腳本的時候出現了錯誤:app
> ./packetdrill tests/linux/listen/listen-incoming-ack.pkt tests/linux/listen/listen-incoming-ack.pkt:17: error handling packet: bad value outbound TCP option 3 script packet: 0.200000 S. 0:0(0) ack 1 <mss 1460,nop,nop,sackOK,nop,wscale 6> actual packet: 0.201014 S. 0:0(0) ack 1 win 29200 <mss 1460,nop,nop,sackOK,nop,wscale 7> >
它表示在執行到腳本第17
行的時候出現了錯誤,腳本中remote端
預期收到的SYNACK
報文中wscale=6
,但實際收到的報文中wscale=7
。框架
出現這種錯誤的緣由是腳本適合的內核版本的協議棧實現與我本機版本中的不一致!內核版本不一致,協議棧的某些實現就不一致!遇到這種狀況時,能夠簡單的修改腳本以適應咱們本身使用的內核版本。socket
packetdrill
並無使用某一種現成的腳本語言,它的腳本有一些tcpdump
影子,又有一些socket
編程的蹤影
// Test behavior when a listener gets an incoming packet that has // the ACK bit set but not the SYN bit set. 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.100 < . 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7> 0.100 > R 0:0(0) win 0 // Now make sure that when a valid SYN arrives shortly thereafter // (with the same address 4-tuple) we can still successfully establish // a connection. 0.200 < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7> 0.200 > S. 0:0(0) ack 1 <mss 1460,nop,nop,sackOK,nop,wscale 6> 0.300 < . 1:1(0) ack 1 win 320 0.300 accept(3, ..., ...) = 4
我以爲這種腳本最好的學習方法就是學習官方的例子了,在例子上依葫蘆畫瓢就能夠構造出本身須要的腳本了!實在有疑惑還能夠稍微翻翻代碼!
腳本以每行爲單位,每一行都是時間戳 + 語句
的形式。時間戳表示這條語句執行的時間,packetdrill
支持絕對時間
和相對時間
兩種格式.
// 相對時間,上一條腳本0.1秒後 +.1 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 // 絕對時間,腳本開始運行後0.2秒後 0.200 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
時間戳後跟着<
符號的語句表示從remote端
向協議棧注入報文,默認後面跟的是TCP
報文的內容(固然也能夠接其餘協議,但協議棧的複雜之處大多在TCP
)
//注入一個SYN報文(S表示SYN),起始序號和結束序號爲0,數據長度爲0,通告窗口大小爲32792,攜帶了mss、sack和wscale的選項 0.200 < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
時間戳後跟着>
符號的語句表示remote端
預期從協議棧接收報文。這裏的預期接收時間是一個範圍[ts-tolerance
,ts+tolerance
],容忍時間tolerance
默認是4
毫秒(能夠經過運行參數改變)
// 預期收到一個SYNACK報文(.表示ACK) ACK序號是1 ,攜帶了mss、sack和wscale的選項 0.200 > S. 0:0(0) ack 1 <mss 1460,nop,nop,sackOK,nop,wscale 6>
上面的報文語句是站在remote端
的角度的,系統調用是站在local端
看的,packetdrill
支持如下的系統調用
struct system_call_entry system_call_table[] = { {"socket", syscall_socket}, {"bind", syscall_bind}, {"listen", syscall_listen}, {"accept", syscall_accept}, {"connect", syscall_connect}, {"read", syscall_read}, {"readv", syscall_readv}, {"recv", syscall_recv}, {"recvfrom", syscall_recvfrom}, {"recvmsg", syscall_recvmsg}, {"write", syscall_write}, {"writev", syscall_writev}, {"send", syscall_send}, {"sendto", syscall_sendto}, {"sendmsg", syscall_sendmsg}, {"fcntl", syscall_fcntl}, {"ioctl", syscall_ioctl}, {"close", syscall_close}, {"shutdown", 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系統調用,返回 fd = 3,這裏的3只在腳本範圍中有效,運行時返回的描述符的值由框架內部維護,框架會維護它們的對應關係 0.000 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 // setsockopt系統調用,第三個參數[1]表示一個指向數值1的指針 0.000 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 // listen系統調用,後兩個參數由框架肯定 0.000 bind(3, ..., ...) = 0 0.000 listen(3, 1) = 0
有時咱們還須要窺測TCP
運行時的更多信息,好比雙方協商的MSS
是多少,當前的窗口大小cwnd
是多少,慢啓動閾值ssthresh
是多少。這時咱們可使用assert
語句來預期其狀態
// 預期此時的狀態信息 0.300 %{ assert tcpi_reordering == 3 assert tcpi_unacked == 10 assert tcpi_sacked == 1 }%
packetdrill
支持預期TCP
信息以下:
/* packetdrill/gtests/net/packetdrill/tcp.h */ struct _tcp_info { __u8 tcpi_state; __u8 tcpi_ca_state; __u8 tcpi_retransmits; __u8 tcpi_probes; __u8 tcpi_backoff; __u8 tcpi_options; __u8 tcpi_snd_wscale:4, tcpi_rcv_wscale:4; __u8 tcpi_delivery_rate_app_limited:1; __u32 tcpi_rto; __u32 tcpi_ato; __u32 tcpi_snd_mss; __u32 tcpi_rcv_mss; __u32 tcpi_unacked; __u32 tcpi_sacked; __u32 tcpi_lost; __u32 tcpi_retrans; __u32 tcpi_fackets; /* Times. */ __u32 tcpi_last_data_sent; __u32 tcpi_last_ack_sent; /* Not remembered, sorry. */ __u32 tcpi_last_data_recv; __u32 tcpi_last_ack_recv; /* Metrics. */ __u32 tcpi_pmtu; __u32 tcpi_rcv_ssthresh; __u32 tcpi_rtt; __u32 tcpi_rttvar; __u32 tcpi_snd_ssthresh; __u32 tcpi_snd_cwnd; __u32 tcpi_advmss; __u32 tcpi_reordering; __u32 tcpi_rcv_rtt; __u32 tcpi_rcv_space; __u32 tcpi_total_retrans; __u64 tcpi_pacing_rate; __u64 tcpi_max_pacing_rate; __u64 tcpi_bytes_acked; /* RFC4898 tcpEStatsAppHCThruOctetsAcked */ __u64 tcpi_bytes_received; /* RFC4898 tcpEStatsAppHCThruOctetsReceived */ __u32 tcpi_segs_out; /* RFC4898 tcpEStatsPerfSegsOut */ __u32 tcpi_segs_in; /* RFC4898 tcpEStatsPerfSegsIn */ __u32 tcpi_notsent_bytes; __u32 tcpi_min_rtt; __u32 tcpi_data_segs_in; /* RFC4898 tcpEStatsDataSegsIn */ __u32 tcpi_data_segs_out; /* RFC4898 tcpEStatsDataSegsOut */ __u64 tcpi_delivery_rate; __u64 tcpi_busy_time; /* Time (usec) busy sending data */ __u64 tcpi_rwnd_limited; /* Time (usec) limited by receive window */ __u64 tcpi_sndbuf_limited; /* Time (usec) limited by send buffer */ };
須要特別注意是,在使用assert
時,咱們要肯定struct _tcp_info
結構在packetdrill
中和當前內核中的定義一致,不然也會報錯!
(完)