这个情人节,工程师用阿里云来试着表达不一样的爱意
年輕的時(shí)候談的戀愛就像TCP鏈接,戀愛時(shí)三次握手即可,可分手時(shí)卻分了四次。而常常久久的愛情,更像是icmp協(xié)議,無論對方身在何處,無論是否是可靠連接,無論你何時(shí)去ping她/他,她/他都默默地響應(yīng)你。這篇文章就是說說,如何在內(nèi)核中增加幾行代碼,讓你的女神/男神當(dāng)ping你(的服務(wù)器)的時(shí)候,來傳達(dá)表達(dá)你的愛。效果如下(左邊為ping的結(jié)果,需要破解ascii碼轉(zhuǎn)換為對應(yīng)字符,右邊為使用tcpdump抓包直接讀取的信息):
? ? 對于UNIX_LIKE系統(tǒng)來說,如果ping的發(fā)送內(nèi)容與接收內(nèi)容不同,會顯示不同的部分,那么就讓你的女神或者男神,慢慢將ASCII碼解析成你想告訴她/他的話吧。或者告訴她/他,使用tcpdump來直接抓包隱藏在ping中的悄悄話。(對于windows來說本人沒有充分測試,只是知道不會像unix_like系統(tǒng)一樣直接顯示出請求消息和回顯消息的不同,所以需要大家抓包認(rèn)真提取信息)
一、ICMP協(xié)議這些你需要了解:
? ? 學(xué)過計(jì)算機(jī)網(wǎng)絡(luò)的一定知道,一個(gè)網(wǎng)絡(luò)包的封裝主要由多個(gè)屬于不同網(wǎng)絡(luò)協(xié)議層的報(bào)文頭和用戶數(shù)據(jù)共同組成:鏈路層報(bào)文頭+網(wǎng)絡(luò)層IP報(bào)文頭+傳輸層報(bào)文頭+攜帶的內(nèi)容+幀尾。而ICMP報(bào)文在整個(gè)以太幀位于如下位置:? ? ??
? ? 上圖顯示的是一個(gè)未分片ICMP報(bào)文或者是一個(gè)較長ICMP報(bào)文的第一個(gè)IP分片的報(bào)文(被分片的報(bào)文中不會帶有ICMP報(bào)頭)。RFC792(https://tools.ietf.org/html/rfc792)中定義了11種ICMP報(bào)文類型,通過ICMP報(bào)頭8bit"類型"字段進(jìn)行區(qū)分。并且每種"類型“會和其”代碼"字段以及報(bào)文頭的最后4字節(jié),共同表達(dá)每種報(bào)文類型所表示的信息。這些ICMP報(bào)文類型被主要分為差錯報(bào)文和查詢報(bào)文:
- 查詢報(bào)文主要包括:回送請求(TYPE8),回送應(yīng)答(TYPE0),地址掩碼或時(shí)間戳的請求/應(yīng)答等
- 差錯報(bào)文主要包括:目標(biāo)主機(jī)不可達(dá)(TYPE3),超時(shí),源抑制,路由重定向等
? ? ping作為ICMP協(xié)議最為典型的運(yùn)用,主要和回送請求,和回送應(yīng)答這兩個(gè)類型相關(guān),這也是本文主要關(guān)心的兩個(gè)類型。當(dāng)然,當(dāng)主機(jī)不可達(dá)或者網(wǎng)絡(luò)路由不可達(dá)出現(xiàn)的時(shí)候,ping會收到路由器傳來的TYPE為3的目標(biāo)主機(jī)不可達(dá)的報(bào)文(我們可以通過tcpdump抓包獲取)。對于其他的類型,有興趣的同學(xué)可以自行學(xué)習(xí),如icmp重定向攻擊,洪水攻擊都是利用了ICMP協(xié)議進(jìn)行的網(wǎng)絡(luò)攻擊。
二、動手寫一個(gè)簡單的ping,了解Linux ping
? ? ?作為本文的主角之一ping,有必要動手寫一個(gè)簡單的ping,幫助我們更好的理解整個(gè)請求應(yīng)答的過程。我本人的測試機(jī)器centos 7中使用的是iputils這個(gè)工具進(jìn)行ping操作,所以我們可以從iputils源碼入手學(xué)習(xí)如何寫一個(gè)簡單的ping。
? ? 學(xué)習(xí)過c網(wǎng)絡(luò)編程的一定都了解socket套接字這個(gè)概念。對于ping來說發(fā)送請求和接受應(yīng)答也同樣是通過套接字來完成。只不過,ICMP協(xié)議雖然在內(nèi)核中和TCP、UDP相似屬于L4層協(xié)議,但是本質(zhì)是附屬于IP協(xié)議的網(wǎng)絡(luò)層協(xié)議,所以需要使用原始套接字(SOCK_RAW)構(gòu)建套接字,而非TCP或UDP使用的流式套接字(SOCK_STREAM)和數(shù)據(jù)包式套接字(SOCK_DGRAM)。SOCK_RAW的用途在于用戶可以自定義填充IP報(bào)文頭,并且對于ICMP報(bào)文自定義填充ICMP報(bào)文頭。下面一張圖,展示了代碼中整個(gè)ping的邏輯發(fā)送以及處理應(yīng)答的邏輯。
? ?
具體代碼可以參考這個(gè):https://github.com/xiaobaidemu/myping/blob/master/ping.c?整個(gè)流程非常簡單,需要說明的是,對于ping 127.0.0.1來說,程序極有可能先收到type為0的回顯請求報(bào)文,再收到type為8的回顯應(yīng)答報(bào)文。這是因?yàn)閕cmp報(bào)文可以同時(shí)被內(nèi)核接收處理,也會被原始套接字接收處理,如下為Understanding Linux Network Internals書中所述。
三、添加內(nèi)核代碼前,你只需要知道一個(gè)結(jié)構(gòu)體和icmp.c
? ? 理解了ping的整個(gè)過程,接下來就是需要修改內(nèi)核來傳達(dá)你想說的話。但是最重要的是,需要分析出修改的位置,即回顯應(yīng)答可能發(fā)送的字節(jié)在內(nèi)核代碼中的位置。這里有一個(gè)非常重要的結(jié)構(gòu)體——struct sk_buff,其定義位于<include/linux/skbuff.h>。
? ? 內(nèi)核中sk_buff結(jié)構(gòu)體做到了可以不使用拷貝或刪除的方式,使得數(shù)據(jù)在各層協(xié)議之間傳輸——即移動指針頭的方式,具體為在處理不同的協(xié)議頭時(shí),代表協(xié)議頭的指針,指向的是不同數(shù)據(jù)區(qū)域(如從L2到L4層協(xié)議,分別指向二層mac頭,三層IP頭,四層傳輸頭)。以下是幾個(gè)比較重要和混淆的字段說明,結(jié)合示意圖說明:
| 指針head/end | 從head指針到end指針區(qū)域指向的數(shù)據(jù)塊為真實(shí)存儲以太幀數(shù)據(jù)區(qū)域(包括了鏈路成之上的各層協(xié)議協(xié)議頭和數(shù)據(jù)報(bào)文,且一直不變) |
| 指針data/tail | data指針和tail指針表示當(dāng)前正在處理的協(xié)議層的開始和結(jié)束為止(其隨著處理協(xié)議的向高層/低層推進(jìn)而變化)head<=data<=tail<=end |
| len | data_len和len比較抽象。len表示skbuff中由head到tail指向的數(shù)據(jù)塊的大小+分片fragment(即skb_shared_info結(jié)構(gòu)體中)非線性數(shù)據(jù)大小,其大小會隨著在內(nèi)核各層中移動而變化(去掉或者增加了各層協(xié)議頭) |
| data_len | data_len僅為分片中非線性數(shù)據(jù)大小。 |
? ?上圖簡單說明了四個(gè)指針和指向區(qū)域之間的關(guān)系。另外對于data_len和len的關(guān)系,如果假設(shè)icmp報(bào)文比較小,ip層不會對其分片,那么data_len即為0,而len即為當(dāng)前協(xié)議頭長度+數(shù)據(jù)報(bào)文長度。關(guān)于data_len和len之間的關(guān)系涉及到skb_shared_info這個(gè)結(jié)構(gòu)體的相關(guān)內(nèi)容,因?yàn)楹臀恼轮行年P(guān)系不大,有興趣的同學(xué)可以自行查閱一下文章來學(xué)習(xí)
- http://blog.51cto.com/weiguozhihui/1586777
- https://0x657573.wordpress.com/2010/11/22/the-relation-between-skb-len-and-skb-data_len-and-what-they-represent/
- https://blog.csdn.net/farmwang/article/details/54233975
? ? 上述內(nèi)容中data指針和表征協(xié)議層數(shù)據(jù)長度的len,和后文中修改的sk_buff指向的數(shù)據(jù)直接相關(guān)。另外sk_buff關(guān)聯(lián)了眾多其他結(jié)構(gòu)體,這里只簡要的講解部分重要的字段含義,更為具體詳細(xì)的說明可以參考Understanding Linux Network Internal第二章或者h(yuǎn)ttps://blog.csdn.net/YuZhiHui_No1/article/details/38666589系列文章進(jìn)行更深入學(xué)習(xí)。
? ? 了解了sk_buff結(jié)構(gòu)體,之后需要定位處理icmp協(xié)議的文件在哪里。icmp.c位于內(nèi)核目錄中net/ipv4/icmp.c中,且ICMP協(xié)議通常是靜態(tài)編譯至內(nèi)核中,而非通過模塊配置的。這里我從Understanding Linux Network Internal這本書中摳出來一張Big Picture,來簡要說明一下對于ping發(fā)出的回顯請求,sk_buff結(jié)構(gòu)體對象是如何在icmp中眾多函數(shù)中傳遞。
? ? 首先ip_local_deliver_finish會傳遞ICMP消息到icmp_rcv, icmp_rcv會解析icmp報(bào)頭中類型字段,對于屬于查詢報(bào)文的類型(如type8)會傳遞給icmp_reply, 而對于差錯報(bào)文會傳遞給icmp_send處理,并且ICMP協(xié)議也會和其他諸如TCP/UDP協(xié)議進(jìn)行交互傳遞信息。對于ping進(jìn)程發(fā)出的請求,會先傳遞給icmp_echo函數(shù)進(jìn)行處理。而icmp_echo正是處理ping請求很重要的一步,內(nèi)核會把請求中附帶的數(shù)據(jù)報(bào)文部分原封不動的拷貝并發(fā)送回源主機(jī)。因此我們可以在icmp_echo函數(shù)中,添加進(jìn)我們"愛的語句"。
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;}}//-----------添加結(jié)束------------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結(jié)構(gòu)體包含了在后續(xù)icmp消息傳遞過程中的所有需要的信息,包括icmp報(bào)文頭,sk_buff對象,icmp 報(bào)文payload大小等。需要注意的是,由于icmp_rcv已經(jīng)解析過sk_buff中屬于icmp協(xié)議的報(bào)文頭部分,所以參數(shù)中skb->data指向的是icmp數(shù)據(jù)部分,即不包含報(bào)文頭,而skb->len也只有icmp數(shù)據(jù)部分的長度。假設(shè)ping請求中所帶的數(shù)據(jù)部分為56字節(jié),則此時(shí)skb->len大小為56。由于ping數(shù)據(jù)部分的前16字節(jié)為攜帶的是發(fā)送是struct timeval對象——發(fā)送時(shí)的時(shí)間,所以在真實(shí)替換時(shí),從data指向的數(shù)據(jù)部分的第16個(gè)字節(jié)開始,用memcpy復(fù)制到對應(yīng)區(qū)域,或者如上例子傻傻的循環(huán)賦值即可。上面代碼所表示的就是根據(jù)echo請求中seq_id循環(huán)回復(fù)上述三句話。當(dāng)然有創(chuàng)意的小伙伴可以增加更多表達(dá)難度。
四、創(chuàng)建一個(gè)阿里云ECS服務(wù)器,十分鐘完成所有修改
? ? 分析完了整個(gè)icmp處理流程,和修改方法,我們只需要創(chuàng)建一個(gè)阿里云ECS,并簡單編譯修改后的內(nèi)核即可。具體流程如下:
? ? 至此告訴你的女神/男神,你想說的話都在ping中。
?
原文鏈接
本文為云棲社區(qū)原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。
總結(jié)
以上是生活随笔為你收集整理的这个情人节,工程师用阿里云来试着表达不一样的爱意的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 刚刚,阿里宣布开源Flutter应用框架
- 下一篇: Kubernetes Client-go