# 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。

:::
## 實驗說明
:::info
這實驗改良了
1. 修改了拓樸
2. 新增了 queue delay的監測項目


3. 修改了 send.py
* 可以套用在每個 host去監控
* 能不用每次都使用 INT監控,降低 overhead
4. 修改了 receive.py能把資料存取到 CSV保留
:::
## 拓樸

:::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`


`pingall`

`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
```

### 設定接收端 h2
```
mx h2
python receive.py h2
```

### 設定傳送端 h3(灌流量用,先執行讓網路壅塞)
==目的:使 h1的 qdepth有變化(流量太小,只會為0)==
```
mx h3
python flow.py 10.0.2.2 "hello ncyu" 10000
```

### 設定傳送端 h1(主要的監測來源)
```
mx h1
# 檢查 header是否成功監控
python send.py 10.0.2.2 "test" 50
```

### 查看連續的資料

整體的flow

主要監控的flow

灌流量的flow

### 從EXCEL看監測資料的變化

## 變化--減緩 INT每次傳送所造成的網路負擔(實驗二)
### 執行
`sudo p4run`


`pingall`

`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
```

### 設定接收端 h2
```
mx h2
python receive.py h2
```

### 設定傳送端 h1(主要的監測來源)
```
mx h1
# 換調整頻率的
python send_feq.py 10.0.2.2 "INT header" 30 3
```

### 查看連續的資料

證實每3個封包只會產生一次的 INT header

### 從EXCEL看監測資料的變化
總共送30次,只有10組的 INT監測資料

## [DEMO](https://youtu.be/FdwYWdTs5r0)
{%youtube FdwYWdTs5r0%}