# SDN期末專題--INT實作 :::info 參考資料: * [P4 learning/example/simple-int](https://github.com/nsg-ethz/p4-learning/tree/master/examples/simple_int) * [什麼是遙測技術](https://www.jamf.com/zh-tw/blog/what-is-telemetry-and-how-it-works/) * [什麼是Telemetry?](https://info.support.huawei.com/info-finder/encyclopedia/zh/Telemetry.html) * [新增packet header(影片)](https://youtu.be/ZSmobANlXoc?si=AOPW33d-JePUwVcs) * [copy to cpu](https://www.dropbox.com/scl/fo/2kmculmnuhlgt6cvp0918/AI8ZN6ZbOO74rftbYslF9_Q/p4/copy-to-cpu?dl=0&rlkey=rzh74xw9vfwzpa8uharedltv7&subfolder_nav_tracking=1) * [scapy與p4](https://hackmd.io/@steven1lung/HkwjItTOv) * [INT 參考](https://github.com/GEANT-DataPlaneProgramming/int-platforms/tree/master) * [simple-int 環境架設](https://hackmd.io/@zhi-xiang/B1DOO000A) 程式碼: * [Google 雲端](https://drive.google.com/file/d/1x-robkvssnVSclhQv26fKKR_F5RDudPC/view?usp=drive_link) ::: ## INT(in-band network telemetry)說明 :::info * telemetry * 針對網路進行監控(蒐集運作狀態數據、偵測網路潛在問題) * 診斷(分析網路效能、瓶頸) * 優化(針對網路問題進行相對應處理) * 傳統網路遙測技術 * SNMP * NetFlow / sFlow / IPFIX * In-Band : * controller和 switch 的控制封包走的路徑是和一般封包一樣 * 使用正常的路徑,所以需要 TLS加密。 * LOCAL port(保留埠)。 * Out-of-Band : * controller和 switch的控制封包走的路徑是和一般封包完全隔開的 * 用特殊路徑 (特殊 port) 來傳送控制封包。 * CONTROLLER port。 ![image](https://hackmd.io/_uploads/rJCy0jPK6.png) ::: ## 實驗說明 :::info 這實驗改良了 1. 修改了拓樸 2. 新增了 queue delay的監測項目 ![image](https://hackmd.io/_uploads/BJh7bqRNye.png) ![image](https://hackmd.io/_uploads/S122BpfSJx.png) 3. 修改了 send.py * 可以套用在每個 host去監控 * 能不用每次都使用 INT監控,降低 overhead 4. 修改了 receive.py能把資料存取到 CSV保留 ::: ## 拓樸 ![image](https://hackmd.io/_uploads/Hy0p4j64kg.png) :::success p4app.json ```json! { "switch": "simple_switch", "compiler": "p4c", "options": "--target bmv2 --arch v1model --std p4-16", "switch_cli": "simple_switch_CLI", "cli": true, "pcap_dump": true, "enable_log": true, "cpu_port": true, "topo_module": { "file_path": "", "module_name": "p4utils.mininetlib.apptopo", "object_name": "AppTopoStrategies" }, "controller_module": null, "topodb_module": { "file_path": "", "module_name": "p4utils.utils.topology", "object_name": "Topology" }, "mininet_module": { "file_path": "", "module_name": "p4utils.mininetlib.p4net", "object_name": "P4Mininet" }, "topology": { "assignment_strategy": "mixed", "auto_arp_tables": "true", "auto_gw_arp": "true", "links": [["h1", "s1"], ["h2", "s2"], ["h3", "s3"], ["h4", "s1"],["s1", "s2", {"bw": 0.5}], ["s1", "s3"] ], "hosts": { "h1": { }, "h2": { }, "h3": { }, "h4": { } }, "switches": { "s1": { "cli_input": "s1-commands.txt", "program": "p4src/simple_int.p4" }, "s2": { "cli_input": "s2-commands.txt", "program": "p4src/simple_int.p4" }, "s3": { "cli_input": "s3-commands.txt", "program": "p4src/simple_int.p4" } } } } ``` ::: :::success s1-commands.txt ```txt! table_set_default ipv4_lpm drop set_queue_depth 0 1000 table_set_default int_table add_int_header 1 table_add ipv4_lpm ipv4_forward 10.0.1.1/32 => 00:00:0a:00:01:01 1 table_add ipv4_lpm ipv4_forward 10.0.1.4/32 => 00:00:0a:00:01:04 2 table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:01:0a:00:02:02 3 table_add ipv4_lpm ipv4_forward 10.0.3.0/24 => 00:00:00:03:01:00 4 ``` ::: :::success s2-commands.txt ```txt! table_set_default ipv4_lpm drop set_queue_depth 0 1000 table_set_default int_table add_int_header 2 table_add ipv4_lpm ipv4_forward 10.0.2.2/32 => 00:00:0a:00:02:02 1 table_add ipv4_lpm ipv4_forward 10.0.0.0/16 => 00:00:00:01:02:00 2 ``` ::: :::success s3-commands.txt ```txt! table_set_default ipv4_lpm drop set_queue_depth 0 1000 table_set_default int_table add_int_header 3 table_add ipv4_lpm ipv4_forward 10.0.3.3/32 => 00:00:0a:00:03:03 1 table_add ipv4_lpm ipv4_forward 10.0.0.0/16 => 00:00:00:01:03:00 2 ``` ::: ## INT :::success headers.p4 ``` /************************************************************************* *********************** H E A D E R S *********************************** *************************************************************************/ #define MAX_INT_HEADERS 9 const bit<16> TYPE_IPV4 = 0x800; const bit<5> IPV4_OPTION_INT = 31; typedef bit<9> egressSpec_t; typedef bit<48> macAddr_t; typedef bit<32> ip4Addr_t; typedef bit<6> switch_id_t; typedef bit<6> output_port_t; typedef bit<10> queue_depth_t; typedef bit<10> deq_timedelta_t; header ethernet_t { macAddr_t dstAddr; macAddr_t srcAddr; bit<16> etherType; } header ipv4_t { bit<4> version; bit<4> ihl; bit<6> dscp; bit<2> ecn; bit<16> totalLen; bit<16> identification; bit<3> flags; bit<13> fragOffset; bit<8> ttl; bit<8> protocol; bit<16> hdrChecksum; ip4Addr_t srcAddr; ip4Addr_t dstAddr; } header ipv4_option_t { bit<1> copyFlag; bit<2> optClass; bit<5> option; bit<8> optionLength; } header int_count_t { bit<16> num_switches; } header int_header_t { switch_id_t switch_id; output_port_t output_port; queue_depth_t queue_depth; deq_timedelta_t queue_delay; } struct parser_metadata_t { bit<16> num_headers_remaining; } struct metadata { parser_metadata_t parser_metadata; } struct headers { ethernet_t ethernet; ipv4_t ipv4; ipv4_option_t ipv4_option; int_count_t int_count; int_header_t[MAX_INT_HEADERS] int_headers; } error { IPHeaderWithoutOptions } ``` ::: :::success parsers.p4 ```txt! /************************************************************************* *********************** P A R S E R ******************************* *************************************************************************/ parser MyParser(packet_in packet, out headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) { state start { packet.extract(hdr.ethernet); transition select(hdr.ethernet.etherType){ TYPE_IPV4: parse_ipv4; default: accept; } } state parse_ipv4 { packet.extract(hdr.ipv4); //Check if ihl is bigger than 5. Packets without ip options set ihl to 5. verify(hdr.ipv4.ihl >= 5, error.IPHeaderWithoutOptions); transition select(hdr.ipv4.ihl) { 5 : accept; default : parse_ipv4_option; } } state parse_ipv4_option { packet.extract(hdr.ipv4_option); transition select(hdr.ipv4_option.option){ IPV4_OPTION_INT: parse_int; default: accept; } } state parse_int { packet.extract(hdr.int_count); meta.parser_metadata.num_headers_remaining = hdr.int_count.num_switches; transition select(meta.parser_metadata.num_headers_remaining){ 0: accept; default: parse_int_headers; } } state parse_int_headers { packet.extract(hdr.int_headers.next); meta.parser_metadata.num_headers_remaining = meta.parser_metadata.num_headers_remaining -1 ; transition select(meta.parser_metadata.num_headers_remaining){ 0: accept; default: parse_int_headers; } } } /************************************************************************* *********************** D E P A R S E R ******************************* *************************************************************************/ control MyDeparser(packet_out packet, in headers hdr) { apply { //parsed headers have to be added again into the packet. packet.emit(hdr.ethernet); packet.emit(hdr.ipv4); packet.emit(hdr.ipv4_option); packet.emit(hdr.int_count); packet.emit(hdr.int_headers); } } ``` ::: :::success simple_int.p4 ```txt! /* -*- P4_16 -*- */ #include <core.p4> #include <v1model.p4> //My includes #include "include/headers.p4" #include "include/parsers.p4" /************************************************************************* ************ C H E C K S U M V E R I F I C A T I O N ************* *************************************************************************/ control MyVerifyChecksum(inout headers hdr, inout metadata meta) { apply { } } /************************************************************************* ************** I N G R E S S P R O C E S S I N G ******************* *************************************************************************/ control MyIngress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) { action drop() { mark_to_drop(standard_metadata); } action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) { //set the src mac address as the previous dst, this is not correct right? hdr.ethernet.srcAddr = hdr.ethernet.dstAddr; //set the destination mac address that we got from the match in the table hdr.ethernet.dstAddr = dstAddr; //set the output port that we also get from the table standard_metadata.egress_spec = port; //decrease ttl by 1 hdr.ipv4.ttl = hdr.ipv4.ttl -1; } table ipv4_lpm { key = { hdr.ipv4.dstAddr: lpm; } actions = { ipv4_forward; drop; NoAction; } size = 1024; default_action = NoAction(); } apply { //only if IPV4 the rule is applied. Therefore other packets will not be forwarded. if (hdr.ipv4.isValid()){ ipv4_lpm.apply(); } } } /************************************************************************* **************** E G R E S S P R O C E S S I N G ******************* *************************************************************************/ control MyEgress(inout headers hdr, inout metadata meta, inout standard_metadata_t standard_metadata) { action add_int_header(switch_id_t swid){ //increase int stack counter by one hdr.int_count.num_switches = hdr.int_count.num_switches + 1; hdr.int_headers.push_front(1); // This was not needed in older specs. Now by default pushed // invalid elements are hdr.int_headers[0].setValid(); hdr.int_headers[0].switch_id = (bit<6>)swid; hdr.int_headers[0].output_port = (bit<6>)standard_metadata.egress_port; hdr.int_headers[0].queue_depth = (bit<10>)standard_metadata.deq_qdepth; hdr.int_headers[0].queue_delay = (bit<10>)standard_metadata.deq_timedelta; //update ip header length hdr.ipv4.ihl = hdr.ipv4.ihl + 1; hdr.ipv4.totalLen = hdr.ipv4.totalLen + 4; hdr.ipv4_option.optionLength = hdr.ipv4_option.optionLength + 4; } table int_table { actions = { add_int_header; NoAction; } default_action = NoAction(); } apply { if (hdr.int_count.isValid()){ int_table.apply(); } } } /************************************************************************* ************* C H E C K S U M C O M P U T A T I O N ************** *************************************************************************/ control MyComputeChecksum(inout headers hdr, inout metadata meta) { apply { update_checksum( hdr.ipv4.isValid(), { hdr.ipv4.version, hdr.ipv4.ihl, hdr.ipv4.dscp, hdr.ipv4.ecn, hdr.ipv4.totalLen, hdr.ipv4.identification, hdr.ipv4.flags, hdr.ipv4.fragOffset, hdr.ipv4.ttl, hdr.ipv4.protocol, hdr.ipv4.srcAddr, hdr.ipv4.dstAddr }, hdr.ipv4.hdrChecksum, HashAlgorithm.csum16); } } /************************************************************************* *********************** S W I T C H ******************************* *************************************************************************/ //switch architecture V1Switch( MyParser(), MyVerifyChecksum(), MyIngress(), MyEgress(), MyComputeChecksum(), MyDeparser() ) main; ``` ::: ## send :::success send.py ```python! #!/usr/bin/env python3 import argparse import sys import socket import random import struct from scapy.all import sendp, send, hexdump, get_if_list, get_if_hwaddr from scapy.all import Packet, IPOption from scapy.all import Ether, IP, UDP from scapy.all import IntField, FieldListField, FieldLenField, ShortField, PacketListField, BitField from scapy.layers.inet import _IPOption_HDR from time import sleep def get_if(): ifs=get_if_list() iface=None for i in get_if_list(): if "eth0" in i: iface=i break if not iface: print("Cannot find eth0 interface") exit(1) return iface class SwitchTrace(Packet): fields_desc = [ BitField("swid", 0, 6), BitField("portid", 0,6), BitField("qdepth", 0,10), BitField("delay", 0,10)] def extract_padding(self, p): return "", p class IPOption_INT(IPOption): name = "INT" option = 31 fields_desc = [ _IPOption_HDR, FieldLenField("length", None, fmt="B", length_of="int_headers", adjust=lambda pkt,l:l*2+4), ShortField("count", 0), PacketListField("int_headers", [], SwitchTrace, count_from=lambda pkt:(pkt.count*1)) ] def main(): if len(sys.argv)<4: print('pass 3 arguments: <destination> "<message>" <number of packets>') exit(1) addr = socket.gethostbyname(sys.argv[1]) iface = get_if() pkt = Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") / IP( dst=addr, options = IPOption_INT(count=0, int_headers=[])) / UDP( dport=1234, sport=4321) / sys.argv[2] try: for i in range(int(sys.argv[3])): sendp(pkt, iface=iface) pkt.show2() except KeyboardInterrupt: raise if __name__ == '__main__': main() ``` ::: :::success 產生背景流量 flow.py ```python! #!/usr/bin/env python3 import argparse import sys import socket import random import struct from scapy.all import sendp, send, hexdump, get_if_list, get_if_hwaddr from scapy.all import Packet, IPOption from scapy.all import Ether, IP, UDP from scapy.all import IntField, FieldListField, FieldLenField, ShortField, PacketListField, BitField from scapy.layers.inet import _IPOption_HDR from time import sleep def get_if(): ifs=get_if_list() iface=None for i in get_if_list(): if "eth0" in i: iface=i break if not iface: print("Cannot find eth0 interface") exit(1) return iface class SwitchTrace(Packet): fields_desc = [ BitField("swid", 0, 6), BitField("portid", 0,6), BitField("qdepth", 0,10), BitField("delay", 0,10)] def extract_padding(self, p): return "", p class IPOption_INT(IPOption): name = "INT" option = 31 fields_desc = [ _IPOption_HDR, FieldLenField("length", None, fmt="B", length_of="int_headers", adjust=lambda pkt,l:l*2+4), ShortField("count", 0), PacketListField("int_headers", [], SwitchTrace, count_from=lambda pkt:(pkt.count*1)) ] def main(): if len(sys.argv)<4: print('pass 3 arguments: <destination> "<message>" <number of packets>') exit(1) addr = socket.gethostbyname(sys.argv[1]) iface = get_if() pkt = Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") / IP( dst=addr) / UDP(dport=1234, sport=4321) / sys.argv[2] try: for i in range(int(sys.argv[3])): sendp(pkt, iface=iface) pkt.show2() except KeyboardInterrupt: raise if __name__ == '__main__': main() ``` ::: :::success 多次封包插入一次 INT header send_feq.py ```python! #!/usr/bin/env python3 import argparse import sys import socket import random import struct from scapy.all import sendp, send, hexdump, get_if_list, get_if_hwaddr from scapy.all import Packet, IPOption from scapy.all import Ether, IP, UDP from scapy.all import IntField, FieldListField, FieldLenField, ShortField, PacketListField, BitField from scapy.layers.inet import _IPOption_HDR from time import sleep # find eth0 interface def get_if(): ifs=get_if_list() iface=None for i in get_if_list(): if "eth0" in i: iface=i break if not iface: print("Cannot find eth0 interface") exit(1) return iface # define INT info class SwitchTrace(Packet): fields_desc = [ BitField("swid", 0, 6), BitField("portid", 0,6), BitField("qdepth", 0,10), BitField("delay", 0,10)] def extract_padding(self, p): return "", p # define INT header # count: Number of headers # PacketListField: Stores multiple SwitchTrace packets, using count_from for dynamic calculation. class IPOption_INT(IPOption): name = "INT" option = 31 fields_desc = [ _IPOption_HDR, FieldLenField("length", None, fmt="B", length_of="int_headers", adjust=lambda pkt,l:l*2+4), ShortField("count", 0), PacketListField("int_headers", [], SwitchTrace, count_from=lambda pkt:(pkt.count*1)) ] def main(): if len(sys.argv)<5: print('pass 3 arguments: <destination> "<message>" <number of packets> <frequency>') exit(1) addr = socket.gethostbyname(sys.argv[1]) iface = get_if() # send pkt's info int_pkt = Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") / IP( dst=addr, options = IPOption_INT(count=0, int_headers=[])) / UDP( dport=1234, sport=4321) / sys.argv[2] normal_pkt = Ether(src=get_if_hwaddr(iface), dst="ff:ff:ff:ff:ff:ff") / IP( dst=addr) / UDP(dport=1234, sport=4321) / sys.argv[2] try: for i in range(int(sys.argv[3])): if i % int(sys.argv[4]) == 0: print("Sending INT packet {}".format(i + 1)) sendp(int_pkt, iface=iface) # show int_pkt .show2() else: print("Sending normal packet {}".format(i + 1)) sendp(normal_pkt, iface=iface) # show normal_pkt.show2() except KeyboardInterrupt: raise if __name__ == '__main__': main() ``` ::: ## receive :::success receive.py ```python! #!/usr/bin/env python3 import sys import struct import csv import os import time from scapy.all import sniff, sendp, hexdump, get_if_list, get_if_hwaddr from scapy.all import Packet, IPOption from scapy.all import PacketListField, ShortField, IntField, LongField, BitField, FieldListField, FieldLenField from scapy.all import IP, UDP, Raw from scapy.layers.inet import _IPOption_HDR DATA_FILE = "INT.csv" # find eth0 interface def get_if(target_host): ifs = get_if_list() iface = None for i in ifs: if target_host + "-eth0" in i: iface = i break if not iface: print("Cannot find interface for {}-eth0".format(target_host)) exit(1) return iface # define INT info class SwitchTrace(Packet): fields_desc = [ BitField("swid", 0, 6), BitField("portid", 0,6), BitField("qdepth", 0,10), BitField("delay", 0,10)] def extract_padding(self, p): return "", p # define INT header # count: Number of headers # PacketListField: Stores multiple SwitchTrace packets, using count_from for dynamic calculation. class IPOption_INT(IPOption): name = "INT" option = 31 fields_desc = [ _IPOption_HDR, FieldLenField("length", None, fmt="B", length_of="int_headers", adjust=lambda pkt,l:l*2+4), ShortField("count", 0), PacketListField("int_headers", [], SwitchTrace, count_from=lambda pkt:(pkt.count*1)) ] # Write INT data to CSV def write_to_csv(data): if not os.path.exists(DATA_FILE): with open(DATA_FILE, "w") as csvfile: writer = csv.writer(csvfile) writer.writerow(["time", "count", "swid", "portid", "qdepth", "delay"]) with open(DATA_FILE, mode='a') as csvfile: writer = csv.writer(csvfile) writer.writerow(data) def handle_pkt(pkt): print("got a packet") # show pkt pkt.show2() local_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) if IPOption_INT in pkt: int_option = pkt[IPOption_INT] for switch_trace in int_option.int_headers: # Extract swid, qdepth, and portid from each SwitchTrace data = [local_time, int_option.count, switch_trace.swid, switch_trace.portid, switch_trace.qdepth, switch_trace.delay] write_to_csv(data) sys.stdout.flush() def main(): # Change it to controller. if len(sys.argv) < 2: print('Usage: python receive.py <iface>') exit(1) else: target_host = sys.argv[1] iface = get_if(target_host) print("sniffing on %s" % iface) sys.stdout.flush() sniff(filter="udp and port 4321", iface = iface, prn = lambda x: handle_pkt(x)) if __name__ == '__main__': main() ``` ::: ## 驗證 INT是否成功監測網路(實驗一) ### 執行 `sudo p4run` ![image](https://hackmd.io/_uploads/Skc2l9GBJg.png) ![image](https://hackmd.io/_uploads/Sy5se9frye.png) `pingall` ![image](https://hackmd.io/_uploads/Sy26ecfSyl.png) `mininet> xterm h1 h2 h3` ### 設定 queue ``` # s1 (new cmd)> simple_switch_CLI --thrift-port 9090 RuntimeCmd: set_queue_rate 50 RuntimeCmd: set_queue_depth 1000 ``` ``` # s2 (new cmd)> simple_switch_CLI --thrift-port 9091 RuntimeCmd: set_queue_rate 50 RuntimeCmd: set_queue_depth 1000 ``` ``` # s3 (new cmd)> simple_switch_CLI --thrift-port 9092 RuntimeCmd: set_queue_rate 50 RuntimeCmd: set_queue_depth 1000 ``` ![image](https://hackmd.io/_uploads/BJClf5zBkg.png) ### 設定接收端 h2 ``` mx h2 python receive.py h2 ``` ![image](https://hackmd.io/_uploads/HJQHM9zSJe.png) ### 設定傳送端 h3(灌流量用,先執行讓網路壅塞) ==目的:使 h1的 qdepth有變化(流量太小,只會為0)== ``` mx h3 python flow.py 10.0.2.2 "hello ncyu" 10000 ``` ![image](https://hackmd.io/_uploads/rkWbX5zB1g.png) ### 設定傳送端 h1(主要的監測來源) ``` mx h1 # 檢查 header是否成功監控 python send.py 10.0.2.2 "test" 50 ``` ![image](https://hackmd.io/_uploads/HylSm5zrJe.png) ### 查看連續的資料 ![image](https://hackmd.io/_uploads/BJTCE5GSJx.png) 整體的flow ![image](https://hackmd.io/_uploads/HJp1L5zSyx.png) 主要監控的flow ![image](https://hackmd.io/_uploads/rJXCScfSJl.png) 灌流量的flow ![image](https://hackmd.io/_uploads/r1u6rcGrkx.png) ### 從EXCEL看監測資料的變化 ![image](https://hackmd.io/_uploads/HJuPUcfHJg.png) ## 變化--減緩 INT每次傳送所造成的網路負擔(實驗二) ### 執行 `sudo p4run` ![image](https://hackmd.io/_uploads/SkL7ucfrkl.png) ![image](https://hackmd.io/_uploads/H1iGuczB1l.png) `pingall` ![image](https://hackmd.io/_uploads/B1tNOqMBkx.png) `mininet> xterm h1 h2` ### 設定 queue ``` # s1 (new cmd)> simple_switch_CLI --thrift-port 9090 RuntimeCmd: set_queue_rate 50 RuntimeCmd: set_queue_depth 1000 ``` ``` # s2 (new cmd)> simple_switch_CLI --thrift-port 9091 RuntimeCmd: set_queue_rate 50 RuntimeCmd: set_queue_depth 1000 ``` ``` # s3 (new cmd)> simple_switch_CLI --thrift-port 9092 RuntimeCmd: set_queue_rate 50 RuntimeCmd: set_queue_depth 1000 ``` ![image](https://hackmd.io/_uploads/ByM9OqzrJe.png) ### 設定接收端 h2 ``` mx h2 python receive.py h2 ``` ![image](https://hackmd.io/_uploads/ryD2d5MSJl.png) ### 設定傳送端 h1(主要的監測來源) ``` mx h1 # 換調整頻率的 python send_feq.py 10.0.2.2 "INT header" 30 3 ``` ![image](https://hackmd.io/_uploads/rJdbY9MH1x.png) ### 查看連續的資料 ![image](https://hackmd.io/_uploads/SkO4Kqzryg.png) 證實每3個封包只會產生一次的 INT header ![image](https://hackmd.io/_uploads/rkH2F9GHyx.png) ### 從EXCEL看監測資料的變化 總共送30次,只有10組的 INT監測資料 ![image](https://hackmd.io/_uploads/rJoMcqMBkl.png) ## [DEMO](https://youtu.be/FdwYWdTs5r0) {%youtube FdwYWdTs5r0%}