# 111-2 專題研究筆記 Week 3-4 ## 專題閱讀-ICMP DDoS Mitigation with eBRF XDP ### Background #### ICMP DDos 過去要攻擊某服務的方法很簡單,對其地址傳送大量垃圾封包就可以消耗掉攻擊對象的運算資源、頻寬,不過很快的防火牆開始有了機制,能在偵測到來自某個位址的突然大量垃圾流量時就封鎖來自該位址的所有封包,後來派生出了DDoS,分散式阻斷服務攻擊。攻擊者在攻擊主要目標前會先透過社交網路之類的手段侵入其他防禦脆弱的host,在他們的主機中埋下malisious file,之後該主機就成為殭屍電腦。將被compromise的殭屍電腦們集中統一起來,同時對主要目標進行ICMP洪水攻擊,將導致前述防禦辦法失效,因此名為分散式阻斷服務攻擊。除了ICMP攻擊方法外,也有利用TCP的三向交握的漏洞來進行的方式,因交握的過程中server也會等待ACK,因此若設計程式來發送大量的SYN然後不回應來自server的SYN/ACK,也可達成同樣的效果。 ![](https://i.imgur.com/xgtU4AC.png) #### eBPF Berkeley Packet Filter,是一種能在kernel space裡執行由user space定義的程式的技術。為了實現穩定與安全性,作業系統在核心的功能很難發生太大的改變,畢竟kernel擁有控制整個系統的權限,牽一髮而動全身,而透過eBPF,我們可以在將user space的程式run在作業系統,並透過eBPF map來讓kernel發生的事也可以被user space的application捕捉到,具體方法為撰寫C語言後由BPF編譯為byte code後丟下作業系統來verify與執行,並通過map將統計資料回傳給user space,供可視性的分析。 ![](https://i.imgur.com/1LxfsJ1.png) #### XDP 透過eBPF的技術,在linux kernel中透過使用者定義的規則來提早判斷packets的verdict。為了縮短從判斷到執行的時間,XDP hook在NIC的driver上,在Network stack被複製到記憶體前就已判斷完畢,因為複製到記憶體是非常expensive的行為,也提供了hacker能攻擊成功的間隙。透過在kernel space的分析,XDP可以根據eBPF的定義採取幾種行為: - XDP_PASS,讓packet繼續通過network stack - XDP_DROP,單純Drop packet - XDP_ABORTED,Drop完後將trace point以exception的形式報出 - XDP_TX,將該packet傳回當初他進來的NIC - XDP_REDIRECT,將packet送往其他NIC或user space的socket 此外XDP也支援將eBPF的program run在NIC card的controller,可減輕CPU的負擔。 ### Method 透過eBPF based XDP來將ICMP DDoS mitigation code編譯為BPF code並run在NIC driver上,並透過BPF map將資料傳回user space的process,並將現在執行的情況以real time的形式show出來。當遭受ICMP DDoS攻擊時,對所有source ip紀錄pps(packets-per-second)的數值,因為ICMP為瞬間的暴漲流量,因此偵測到異常pps值時便將該source ip封鎖。 要達成上述的行為,我們需要對封包進行packet parsing,將各個header依序解析來獲取我們需要的資訊。先從ethernet header獲取mac address,再移到network header來獲取IP等的資訊,並通過此層標明的transport protocol來決定transport header的處理方式,如TCP、UDP、ICMP。因為packet的header資料結構都可以在linux的kernel source code裡找到以C撰寫的data struture,因此獲取這些header資訊只是以簡單的pointer就可以取得,另外接下來的header 如application就不用再分析下去,因為那些是由user space定義的,kernel並沒有能力去處理及解析。 ![](https://i.imgur.com/kxkRGzW.png) ### Experiment 在ubuntu 18.04 desktop上重現一次實驗。 ``` $ git clone https://github.com/johnnylord/xdp-icmp-ddos-mitigation.git ``` 將source code載下來 ``` $ sudo apt install clang llvm libelf-dev libpcap-dev gcc-multilib build-essential $ sudo apt install linux-tools-$(uname -r) $ sudo apt install linux-headers-$(uname -r) $ sudo apt install linux-tools-common linux-tools-generic ``` 安裝需要的dependencies ``` $ sudo testenv/testenv.sh setup --name dos --legacy-ip ``` 透過xdp-tutorial提供的setup script將網路環境建立好 ![](https://i.imgur.com/XQvpuey.png) ![](https://i.imgur.com/oOR2p6j.png) host多了一張virtual的網路卡-dos ``` $ make ``` 將source code編譯為BPF bytecode ![](https://i.imgur.com/WHKjYAM.png) 之後多了三個檔案分別是: - xdp_loader,幫助我們將BPF bytecode load進kernel - xdp_stats,run在user space的程式,接收來自kernel的BPF map並將其轉為可視化資料並real time秀出 - xdp_prog_kern.o,我們要load進kernel的目標程式 ``` $ sudo ip link ``` 現在我們觀察是否有XDP程式註冊在NIC上,根據下圖可以發現並沒有 ![](https://i.imgur.com/iZyEWAH.png) ``` $ sudo testenv/testenv.sh load ``` 將bytecode load進kernel後再check一次,發現多了一個XDP program註冊在dos這張網卡上 ![](https://i.imgur.com/FvSwH3N.png) ``` $ sudo testenv/testenv.sh stats ``` 開啟視覺化監控程式,監控dos這張卡的網路狀態 ``` $ sudo ip netns exec dos /bin/bash ``` 透過另一個terminal進入剛剛建立的virtual namespace裡,下ipconfig觀察 ![](https://i.imgur.com/cObtQhR.png) 可以看到只有一張NIC叫veth0,IP為10.11.1.2。 ``` # ping 10.11.1.1 ``` 在該vm以正常的頻率先來ping host, ![](https://i.imgur.com/3EuMPuT.png) 可以看到上面的stats程式端的XDP_PASS有了反應,顯示PPS為1,並且因為是正常行為,並沒有對來自這台機器(10.11.1.2)的封包做出任何特殊處理,直接讓他都pass下去。 ``` # ping -i 0.2 10.11.1.1 ``` 而若加快封包的傳送頻率,可以發現PPS值也會跟著變化。 ![](https://i.imgur.com/XyL4HjC.png) ``` $ sudo ip netns exec dos /bin/bash # hping3 -q -n -d 200 --icmp --flood 10.11.1.1 ``` 再new一個新的terminal來模擬DDoS攻擊,使用的同樣為10.11.1.2,並對host進行ICMP洪水攻擊。 ![](https://i.imgur.com/eZzwXP3.png) 可以看到在開始攻擊後原先正常ping的terminal停止收到回應,最上方的stats程式顯示有非常大量的封包慘遭drop。而停止洪水攻擊後,過一段時間中間的正常ping的process又可以正常收到回應了,可觀察到XDP的運作模式並不會永久BAN掉該source IP,滿符合現代多為NAT的上網環境。 ### Questions #### 'D'DoS? DDos的難解之處在於其是利用殭屍網路造成平行的大量流量,導致server端很難去區分哪些來自殭屍網路、哪些來自正常用戶。此次實驗只模擬了1對1的攻擊,未來想試試模擬出多對1的攻擊下,XDP的效果表現(by mininet? IP namespace?)。 #### kernel version 原本採用ubuntu 20.04 server,kernel版本5.4.0-100-generic,與文中的5.4.0-94-generic基本上十分接近,但將XDP load進kernel時仍出現了問題: ``` libbpf: Error loading BTF: Invalid argument(22) ``` 不知道問題出在哪邊,未來也是值得研究的主題(還是就只是單純版本對不上?)。 ## Software-Defined WANs ### From ATM To MPLS ![](https://i.imgur.com/SI6i6GO.png) 以企業的立場來說,通常會通過ISP建置WAN的服務,將服務公開在都會網路上,又或是方便使用VPN,而以前的技術上是使用ATM,***Asynchronous Transfer Mode***,非同步傳輸模式。 而同步傳輸模式,指的是TDMA(Time division multiple access)分時多工的情況下,雙方溝通時的虛擬通道走的time slot固定,也就是如下圖: ![](https://i.imgur.com/kcwRv82.png) 兩個多工器事先講好哪個通道走哪些slots,因此左邊的多工器只要負責將資料塞進slots裡,另一邊的多工器就照個講好的模式將資料提取出來,送給目的主機。這種方法雖然可靠,但固定slots的方式卻導致若兩個peer之間停止傳送資料,分配給他們的slots就浪費掉了。 因此後來演進成ATM,也就是非同步傳輸模式。在這模式下,虛擬通道不再fix固定的slots,而是只要看到空的slots就可以塞進去,能夠利用到傳輸鏈路的所有頻寬,但這種沒有pattern的編排方式,資料就勢必得加上可供多工器辨認的dist addr等資料。用這種方式還有另外的好處,動態的收到有加上標籤的資料可以幫助多工器控管流量的優先級,以滿足QoS。 不過在現在網路流量動輒以gb在計算的時代來說,傳統的ATM技術也受到了一些瓶頸,因此出現了MPLS(Multi-Protocol Label Switching),主要是在IP header以及Ethenet header間插入一個MPLS header,其標明了一個MPLS label。 ![](https://i.imgur.com/nREVVbS.jpg) router是屬於L3的設備,因此需要解讀到IP header並由router的routing table來判斷該把封包送去哪,但這樣實在是太消耗CPU的資源了,因此想辦法將layer3的功能與layer2整合,透過MPLS label讓routing的行為直接由類似switch的行為來進行,即通過layer2來達成layer3的功能,提高傳輸效率。 具體的方法為建立MPLS網域,封包在從外部進入時會被進行一次L3的routing,決定好route之後LSR會將其打上MPLS label,之後封包在該網域傳送時都不需再次被route,node們直接看label來做交換,因此封包在該網域間的傳送可以說是非常有效率且高速的。 ![](https://i.imgur.com/ULRiKFu.png) 現在MPLS的應用大多為ISP透過MPLS來實現高速的vpn,因MPLS為“連接導向”的網路協定,有些類似於過往撥接的概念,在傳送packet前會先建立好連線線路(當然是透過MPLS建立的虛擬通道),不像IP協定的非連接導向(傳出去就沒我的事),透過ISP提供的MPLS VPN有與其他都會網路的流量隔離、傳輸效率與品質上的優勢。 ### SD-WAN 透過MPLS搭建企業的VPN已經比以往方便許多了,但大部分情況下仍然需要一條專用線路從企業拉出至最近的ISP,且企業端(Customer Edge, CE)與(Provider Edge, PE)若想增加site便要人工configure所有設備,這還是算較為不方便的點。 若能透過SDN的方式來達成的話,理想情境是透過central的controller來實現無接觸的configure方式,加入bare metal machine後將其連接上controller就好了。 ![](https://i.imgur.com/agh6WW8.png) 透過這種方式也可以在幾個按鍵間就搞定某些policy,如“將youtube的流量放到最低層級”、“讓所有公司站點都能存取某個cloud service”等等。 ## Mininet Basic Test 支持SDN的測試環境,可在虛擬化的場景搭建虛擬switch、router及host,且Mininet對於網路行為的code與linux kernel的原始碼是一樣的,不像cisco packet tracer的封包是用軟體模擬出來的。 ![](https://i.imgur.com/d3Cvqa2.png) 在ubuntu server20.04上嘗試操作mininet ``` $ git clone git://github.com/mininet/mininet $ mininet/util/install.sh -a ``` 安裝mininet。 這個時候可以透過 ``` $ mn ``` 這個指令來觀察現在的預設網路拓樸。 ``` root@mininet:~# mn *** Creating network *** Adding controller *** Adding hosts: h1 h2 *** Adding switches: s1 *** Adding links: (h1, s1) (h2, s1) *** Configuring hosts h1 h2 *** Starting controller c0 *** Starting 1 switches s1 ... *** Starting CLI: ``` 可以看到預設給了兩個host:h1以及h2,一個switch s1以及controller c0。而switch已經幫我們開好機了。 ![](https://i.imgur.com/ZiDn2kT.png) 我們可以輸入 ``` mininet> nodes ``` 來觀察現在有哪些節點。 ![](https://i.imgur.com/3JWkoj9.png) 而使用 ``` mininet> net ``` 則可以讓我們看到node間的連接情況。 ![](https://i.imgur.com/AgzlZOk.png) 輸入 ``` mininet> dump ``` 可以讓我們看到關於節點的詳細資訊。 ![](https://i.imgur.com/g97Jy1c.png) 我們可以試試用 ``` mininet> h1 ping -c 1 h2 ``` 來試試用h1 ping h2一個封包。 ![](https://i.imgur.com/fEkphxL.png) ### Lab:flowtable 建立一個3個host、1個switch搭配上一個controller的拓樸,如下圖。 ![](https://i.imgur.com/R2eZbSv.png) ``` $ sudo mn --topo single,3 --mac --switch ovsk --controller remote ``` ![](https://i.imgur.com/TlWTa43.png) - topo,後面接拓樸型與host數量,型態有single,linear,tree - mac,代表讓mininet根據host id自己產生mac address - switch,設置switch,ovsk=Openflow vSwitch - controller,設置controller,remote=外部controller控制(127.0.0.1:6653) ``` mininet> net ``` 觀察連接的情況 ![](https://i.imgur.com/JkbRed9.png) 這裡有一個非常有用的指令 ``` mininet> pingall ``` 可以讓所有host互ping。 ``` $ sudo mn --topo single,3 --mac --switch ovsk mininet> pingall ``` ![](https://i.imgur.com/UwFIdHR.png) 這個時候我們可以重置flow table ``` mininet> sh ovs-ofctl del-flows s1 ``` 然後讓h1多ping h2幾個封包 ``` mininet> h1 ping -c 6 h2 ``` 我們可以看,第一個封包有著明顯的延遲,因為這時候的flow table是空的,packet需要先送給controller做解讀並插入相關的flow rule,因此後續幾個封包的延遲都少上許多。 ![](https://i.imgur.com/vUP7ahb.png) 接著把ovs的flow table dump下來(這裡不知道為什麼只有在機器視窗才能看到dump下來的table而ssh介面看不到) ![](https://i.imgur.com/RifMu34.png) 前三個是關於arp的flow(為什麼會有三個?),後兩個為h1與h2相互ICMP的flow,關於flow的機制與原理預計下次開始研究,這裡先放上參數代表的意義。 ![](https://i.imgur.com/ucoE4HQ.png) 這次遇到很多問題,有關mininet的remote controller設不過,以及對flow單個entry並不是很了解等等,還有幾個實驗: - POX controller,使用自架的POX controller來替代預設的mininet controller - 不同的虛擬網路環境技術,如namespace ### Lab:POX controller mininet安裝時就幫我們也順便安裝好pox了,因此我們只要在pox資料夾 ``` $ ./pox.py forwarding.l2_learning ``` ![](https://i.imgur.com/l1npMhi.png) 先將pox controller開起來,接著我們就可以建立mininet拓墣: ``` mn --topo single,3 --mac --switch ovsk --controller remote ``` 這裡我們不用預設的controller,而是改用run在localhost的pox controller。 ![](https://i.imgur.com/b69xNAU.png) 可以看到他走的port是6633且已連接成功。 接著使用ping all試試,一切連接正常。 接下來試試看使用pos controller做簡易的防火牆,在./pox/pox/forwarding裡面找到我們先前執行的l2_learning程式。 ``` $ cp l2_learning.py l2_testfirewall.py ``` copy一份讓我們來做修改。 在最上面加上: ``` from pox.lib.packet.ipv4 import ipv4 from pox.lib.addresses import IPAddr, EthAddr ``` 來import我們需要的套件,接著在*def_handle_PacketIn(self, event)*裡加上簡單的判斷式: ``` if isinstance(packet.next, ipv4): if str(packet.next.srcip) == IPAddr('10.0.0.1'): actions = [] msg = of.ofp_flow_mod( command=of.OFPFC_ADD, idle_timeout=20, hard_timeout=of.OFP_FLOW_PERMANENT, buffer_id=None, actions=actions, match=of.ofp_match(dl_src = packet.src,) ) event.connection.send(msg.pack()) return ``` 其中: - isinstance(packet.next, ipv4) check是否為ipv4 packet - str(packet.next.srcip) 會return source ip addr - actions = [] 這裡我們給了一個空白的action,代表將此packet drop掉 - of.ofp_flow_mod接著我們可以透過這個來增加一條flow給switch,讓接下來switch遇到類似的事時不用再傳到controller來問路,可以自己做決定: - idle_timeout 當幾秒後都沒有packet符合這個flow就把它移除 - hard_timeout 不管有沒有人符合,過了這段時間照樣移除(of.OFP_FLOW_PERMANENT代表永久存在) - buffer 指定暫存器 - actions 指定行為,此處為空代表drop - match 以mac位址指定觸發情況 - event.connection.send 將這條flow送去switch 接著把他run起來 ![](https://i.imgur.com/5e5awAA.png) 啟動mininet拓樸 ![](https://i.imgur.com/7D9kYqq.png) 先試試用h2 ping h3 ![](https://i.imgur.com/tZI3W4A.png) 成功,接下來換用h2 ping h1: ![](https://i.imgur.com/c05CdJq.png) 發現ping不到了,接著我們檢查ovs的flow table: ![](https://i.imgur.com/pNjVdpJ.png) 可以看到h2向h1的arp與icmp都正常通過,但h1回傳給h2的packet卻被drop了。 ### Code Reading: firewall.py for pox controller