年輕的時候談的戀愛就像TCP連接,戀愛時三次握手便可,可分手時卻分了四次。而經常久久的愛情,更像是icmp協議,不管對方身在何處,不管是不是可靠鏈接,不管你什麼時候去ping她/他,她/他都默默地響應你。這篇文章就是說說,如何在內核中增長几行代碼,讓你的女神/男神當ping你(的服務器)的時候,來傳達表達你的愛。效果以下(左邊爲ping的結果,須要破解ascii碼轉換爲對應字符,右邊爲使用tcpdump抓包直接讀取的信息):html
對於UNIX_LIKE系統來講,若是ping的發送內容與接收內容不一樣,會顯示不一樣的部分,那麼就讓你的女神或者男神,慢慢將ASCII碼解析成你想告訴她/他的話吧。或者告訴她/他,使用tcpdump來直接抓包隱藏在ping中的悄悄話。(對於windows來講本人沒有充分測試,只是知道不會像unix_like系統同樣直接顯示出請求消息和回顯消息的不一樣,因此須要你們抓包認真提取信息)linux
學過計算機網絡的必定知道,一個網絡包的封裝主要由多個屬於不一樣網絡協議層的報文頭和用戶數據共同組成:鏈路層報文頭+網絡層IP報文頭+傳輸層報文頭+攜帶的內容+幀尾。而ICMP報文在整個以太幀位於以下位置: git
上圖顯示的是一個未分片ICMP報文或者是一個較長ICMP報文的第一個IP分片的報文(被分片的報文中不會帶有ICMP報頭)。RFC792(https://tools.ietf.org/html/r...)中定義了11種ICMP報文類型,經過ICMP報頭8bit"類型"字段進行區分。而且每種"類型「會和其」代碼"字段以及報文頭的最後4字節,共同表達每種報文類型所表示的信息。這些ICMP報文類型被主要分爲差錯報文和查詢報文:github
ping做爲ICMP協議最爲典型的運用,主要和回送請求,和回送應答這兩個類型相關,這也是本文主要關心的兩個類型。固然,當主機不可達或者網絡路由不可達出現的時候,ping會收到路由器傳來的TYPE爲3的目標主機不可達的報文(咱們能夠經過tcpdump抓包獲取)。對於其餘的類型,有興趣的同窗能夠自行學習,如icmp重定向攻擊,洪水攻擊都是利用了ICMP協議進行的網絡攻擊。編程
做爲本文的主角之一ping,有必要動手寫一個簡單的ping,幫助咱們更好的理解整個請求應答的過程。我本人的測試機器centos 7中使用的是iputils這個工具進行ping操做,因此咱們能夠從iputils源碼入手學習如何寫一個簡單的ping。windows
學習過c網絡編程的必定都瞭解socket套接字這個概念。對於ping來講發送請求和接受應答也一樣是經過套接字來完成。只不過,ICMP協議雖然在內核中和TCP、UDP類似屬於L4層協議,可是本質是附屬於IP協議的網絡層協議,因此須要使用原始套接字(SOCK_RAW)構建套接字,而非TCP或UDP使用的流式套接字(SOCK_STREAM)和數據包式套接字(SOCK_DGRAM)。SOCK_RAW的用途在於用戶能夠自定義填充IP報文頭,而且對於ICMP報文自定義填充ICMP報文頭。下面一張圖,展現了代碼中整個ping的邏輯發送以及處理應答的邏輯。centos
具體代碼能夠參考這個:https://github.com/xiaobaidemu/myping/blob/master/ping.c 整個流程很是簡單,須要說明的是,對於ping 127.0.0.1來講,程序極有可能先收到type爲0的回顯請求報文,再收到type爲8的回顯應答報文。這是由於icmp報文能夠同時被內核接收處理,也會被原始套接字接收處理,以下爲Understanding Linux Network Internals書中所述。服務器
理解了ping的整個過程,接下來就是須要修改內核來傳達你想說的話。可是最重要的是,須要分析出修改的位置,即回顯應答可能發送的字節在內核代碼中的位置。這裏有一個很是重要的結構體——struct sk_buff,其定義位於<include/linux/skbuff.h>。網絡
內核中sk_buff結構體作到了能夠不使用拷貝或刪除的方式,使得數據在各層協議之間傳輸——即移動指針頭的方式,具體爲在處理不一樣的協議頭時,表明協議頭的指針,指向的是不一樣數據區域(如從L2到L4層協議,分別指向二層mac頭,三層IP頭,四層傳輸頭)。如下是幾個比較重要和混淆的字段說明,結合示意圖說明:併發
上圖簡單說明了四個指針和指向區域之間的關係。另外對於data_len和len的關係,若是假設icmp報文比較小,ip層不會對其分片,那麼data_len即爲0,而len即爲當前協議頭長度+數據報文長度。關於data_len和len之間的關係涉及到skb_shared_info這個結構體的相關內容,由於和文章中心關係不大,有興趣的同窗能夠自行查閱一下文章來學習
上述內容中data指針和表徵協議層數據長度的len,和後文中修改的sk_buff指向的數據直接相關。另外sk_buff關聯了衆多其餘結構體,這裏只簡要的講解部分重要的字段含義,更爲具體詳細的說明能夠參考Understanding Linux Network Internal第二章或者https://blog.csdn.net/YuZhiHui_No1/article/details/38666589系列文章進行更深刻學習。
瞭解了sk_buff結構體,以後須要定位處理icmp協議的文件在哪裏。icmp.c位於內核目錄中net/ipv4/icmp.c中,且ICMP協議一般是靜態編譯至內核中,而非經過模塊配置的。這裏我從Understanding Linux Network Internal這本書中摳出來一張Big Picture,來簡要說明一下對於ping發出的回顯請求,sk_buff結構體對象是如何在icmp中衆多函數中傳遞。
首先ip_local_deliver_finish會傳遞ICMP消息到icmp_rcv, icmp_rcv會解析icmp報頭中類型字段,對於屬於查詢報文的類型(如type8)會傳遞給icmp_reply, 而對於差錯報文會傳遞給icmp_send處理,而且ICMP協議也會和其餘諸如TCP/UDP協議進行交互傳遞信息。對於ping進程發出的請求,會先傳遞給icmp_echo函數進行處理。而icmp_echo正是處理ping請求很重要的一步,內核會把請求中附帶的數據報文部分原封不動的拷貝併發送回源主機。所以咱們能夠在icmp_echo函數中,添加進咱們"愛的語句"。
static bool icmp_echo(struct sk_buff *skb) { struct net *net; net = dev_net(skb_dst(skb)->dev); if (!net->ipv4.sysctl_icmp_echo_ignore_all) { struct icmp_bxm icmp_param; icmp_param.data.icmph = *icmp_hdr(skb); icmp_param.data.icmph.type = ICMP_ECHOREPLY; icmp_param.skb = skb; //-----------添加開始----------- char sentence1[] = "I LOVE U, xxxx."; char sentence2[] = "I MISS U, xxxx."; char sentence3[] = "Happy Valentine's Day!"; int sentence_len_list[] = {sizeof(sentence1), sizeof(sentence2), sizeof(sentence3)}; char* sentence_list[] = {sentence1, sentence2, sentence3}; int sentence_index = icmp_param.data.icmph.un.echo.sequence % 3; if(skb->len >= 16 + sentence_len_list[sentence_index]) { char* tmp = (char*)(skb->data+16); char* target_sentence = sentence_list[sentence_index]; int i=0; for(;i<sentence_len_list[sentence_index];++i) { tmp[i] = target_sentence[i]; } for(;i < skb->len-16;++i) { tmp[i] = 0; } } //-----------添加結束------------ icmp_param.offset = 0; icmp_param.data_len = skb->len; icmp_param.head_len = sizeof(struct icmphdr); icmp_reply(&icmp_param, skb); } /* should there be an ICMP stat for ignored echos? */ return true; }
上述代碼中icmp_bxm結構體包含了在後續icmp消息傳遞過程當中的全部須要的信息,包括icmp報文頭,sk_buff對象,icmp 報文payload大小等。須要注意的是,因爲icmp_rcv已經解析過sk_buff中屬於icmp協議的報文頭部分,因此參數中skb->data指向的是icmp數據部分,即不包含報文頭,而skb->len也只有icmp數據部分的長度。假設ping請求中所帶的數據部分爲56字節,則此時skb->len大小爲56。因爲ping數據部分的前16字節爲攜帶的是發送是struct timeval對象——發送時的時間,因此在真實替換時,從data指向的數據部分的第16個字節開始,用memcpy複製到對應區域,或者如上例子傻傻的循環賦值便可。上面代碼所表示的就是根據echo請求中seq_id循環回覆上述三句話。固然有創意的小夥伴能夠增長更多表達難度。
分析完了整個icmp處理流程,和修改方法,咱們只須要建立一個阿里雲ECS,並簡單編譯修改後的內核便可。具體流程以下:
至此告訴你的女神/男神,你想說的話都在ping中。
部分參考文章:
本文爲雲棲社區原創內容,未經容許不得轉載。