Byeo

6. P4 - mri 본문

프로그래밍 (Programming)/P4

6. P4 - mri

BKlee 2024. 6. 29. 16:27
반응형

 이번 예제는 MRI (Multi-hop Route Inspection)입니다. 튜토리얼에 작성된 내용을 확인하면, sender가 보내는 packet에 매 hop마다 switch의 정보와 switch의 queue 길이가 기록되어 receiver에게 전달된다고 합니다. 이를 통해서 user는 packet의 경로와 각 queue의 상태를 추적할 수 있게 됩니다.

 

tutorial: https://github.com/p4lang/tutorials/tree/master/exercises/mri

 

tutorials/exercises/mri at master · p4lang/tutorials

P4 language tutorials. Contribute to p4lang/tutorials development by creating an account on GitHub.

github.com

 

 

0. topology

 

topology는 ECN tutorial과 동일합니다.

  •  switch 1에는 h1과 h11이, switch 2에는 h2와 h22가, 그리고 switch 3에는 h3이 연결되어 있습니다.
  • 각 host는 10.0.<switchid>.<hostid>의 IP를 갖고 있습니다.
  • h11에서 h22는 iperf를 통해서 많은 양의 traffic을 주고받습니다. 그리고 h1과 h2 사이에서는 낮은 양의 traffic을 주고 받습니다. topology.json에서 s1과 s2 사이에서는 512kbps로 제한하였기 때문에, h2에서는 적절하게 설정된 ECN value를 봐야합니다.

 

1. Test Run

1-1. h1, h11, h2, h22 xterm 열기

mininet> xterm h1 h11 h2 h22

 

1-2. 각 터미널에 명령어 입력

h2> ./receive.py

h22> iperf -s -u

h1> ./send.py 10.0.2.2 "P4 is cool" 30

h11> iperf -c 10.0.2.22 -t 15 -u

 

 

1-3. 결과 확인

 

###[ Ethernet ]### 
  dst       = 08:00:00:00:02:02
  src       = 08:00:00:00:02:00
  type      = IPv4
###[ IP ]### 
     version   = 4
     ihl       = 6
     tos       = 0x0
     len       = 42
     id        = 1
     flags     = 
     frag      = 0
     ttl       = 62
     proto     = udp
     chksum    = 0x64c0
     src       = 10.0.1.1
     dst       = 10.0.2.2
     \options   \
      |###[ MRI ]### 
      |  copy_flag = 0
      |  optclass  = control
      |  option    = 31
      |  length    = 4
      |  count     = 0
      |  \swtraces  \
###[ UDP ]### 
        sport     = 1234
        dport     = 4321
        len       = 18
        chksum    = 0x1c7b
###[ Raw ]### 
           load      = 'P4 is cool'

 

현재 sender는 option field에 데이터를 채워서 보내고 있습니다. 확인 보면 IP option에서 count = 0임을 확인할 수 있습니다.

 

2. Implementation - Header 분석

이번에도 역시 mri.p4 파일을 열어서 확인해봅니다.

const bit<8>  UDP_PROTOCOL = 0x11;
const bit<16> TYPE_IPV4 = 0x800;
const bit<5>  IPV4_OPTION_MRI = 31;

#define MAX_HOPS 9

typedef bit<9>  egressSpec_t;
typedef bit<48> macAddr_t;
typedef bit<32> ip4Addr_t;
typedef bit<32> switchID_t;
typedef bit<32> qdepth_t;

header ethernet_t {
    macAddr_t dstAddr;
    macAddr_t srcAddr;
    bit<16>   etherType;
}

header ipv4_t {
    bit<4>    version;
    bit<4>    ihl;
    bit<8>    diffserv;
    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 mri_t {
    bit<16>  count;
}

header switch_t {
    switchID_t  swid;
    qdepth_t    qdepth;
}

struct ingress_metadata_t {
    bit<16>  count;
}

struct parser_metadata_t {
    bit<16>  remaining;
}

struct metadata {
    ingress_metadata_t   ingress_metadata;
    parser_metadata_t   parser_metadata;
}

struct headers {
    ethernet_t         ethernet;
    ipv4_t             ipv4;
    ipv4_option_t      ipv4_option;
    mri_t              mri;
    switch_t[MAX_HOPS] swtraces;
}

 

header를 보면 추가로 ipv4_option_t structure와 mri_t, 그리고 switch_t[9] 가 추가된 것을 확인할 수 있습니다.

 

2-1. ipv4_option

http://www.ktword.co.kr/test/view/view.php?no=1900

 

ipv4_option structure는 기본적으로 알려져있는 ipv4_option 프로토콜 (https://datatracker.ietf.org/doc/html/rfc791)을 따라갑니다.

 

2-2. mri_t

mri_t는 count 라는 2 bytes의 field를 포함하고 있습니다. 이는 switch ID의 개수 (유효한 swtraces의 길이)를 나타냅니다. 

 

2-3. switch_t

switch_t는 32 bits의 switch ID와 32 bits의 qdepth field를 포함하고 있습니다.

 

 

3. Parser 작성

/*************************************************************************
*********************** 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 {
        transition parse_ethernet;
    }

    state parse_ethernet {
        packet.extract(hdr.ethernet);
        transition select(hdr.ethernet.etherType) {
            TYPE_IPV4: parse_ipv4;
            default: accept;
        }
    }

    state parse_ipv4 {
        packet.extract(hdr.ipv4);
        verify(hdr.ipv4.ihl >= 5, error.IPHeaderTooShort);
        transition select(hdr.ipv4.ihl) {
            5             : accept;
            default       : parse_ipv4_option;
        }
    }

    state parse_ipv4_option {
        /*
        * TODO: Add logic to:
        * - Extract the ipv4_option header.
        *   - If value is equal to IPV4_OPTION_MRI, transition to parse_mri.
        *   - Otherwise, accept.
        */
        transition accept;
    }

    state parse_mri {
        /*
        * TODO: Add logic to:
        * - Extract hdr.mri.
        * - Set meta.parser_metadata.remaining to hdr.mri.count
        * - Select on the value of meta.parser_metadata.remaining
        *   - If the value is equal to 0, accept.
        *   - Otherwise, transition to parse_swtrace.
        */
        transition accept;
    }

    state parse_swtrace {
        /*
        * TODO: Add logic to:
        * - Extract hdr.swtraces.next.
        * - Decrement meta.parser_metadata.remaining by 1
        * - Select on the value of meta.parser_metadata.remaining
        *   - If the value is equal to 0, accept.
        *   - Otherwise, transition to parse_swtrace.
        */
        transition accept;
    }
}

 

IPv4 field에 상당히 많은 양의 field가 추가되었습니다. 따라서 이들을 parsing할 수 있도록 parser를 적절히 작성해줘야 하는데요, 각 주석에 맞게 작성해줍시다!

 

3-1. ipv4_option 존재 체크

    state parse_ipv4 {
        packet.extract(hdr.ipv4);
        verify(hdr.ipv4.ihl >= 5, error.IPHeaderTooShort);
        transition select(hdr.ipv4.ihl) {
            5             : accept;
            default       : parse_ipv4_option;
        }
    }

 

위에서부터 차례로 보면, hdr.ipv4.ihl의 값을 확인합니다. 먼저 5 이상인지 한 번 검증을 하고 (최소 5), 5라면 accept, 6 이상인 경우에는 parse_ipv4_option state에 진입합니다.

 

3-2. ipv4_option parser

    state parse_ipv4_option {
        packet.extract(hdr.ipv4_option);
        
        transition select(hdr.ipv4_option.option) {
            IPV4_OPTION_MRI     : parse_mri;
            default             : accept;
        }
    }

 

parse_ipv4_option parser는 ipv4_option을 추출하고, ipv4_option.option field의 값이 IPV4_OPTION_MRI (31)인 경우에 parse_mri state로 진입하도록 합니다. 그 외에는 accept합니다.

 

3-3. parse_mri

    state parse_mri {
        packet.extract(hdr.mri);
        meta.parser_metadata.remaining = hdr.mri.count;

        transition select(meta.parser_metadata.remaining) {
            0           : accept;
            default     : parse_swtrace;
        }
    }

 

parse_mri는 mri를 추출하고 meta.parser_metadata.remaining에 hdr.mri.count를 저장합니다. 이 개수만큼 parse_swtrace에서 루프를 돌 예정입니다.

 

만약 count가 0이면 단순히 accept하고, 0이 아니라면 parse_swtrace state에 진입합니다.

 

3-4. parse_swtrace

    state parse_swtrace {
        packet.extract(hdr.swtraces.next);
        meta.parser_metadata.remaining = meta.parser_metadata.remaining - 1;

        transition select(meta.parser_metadata.remaining) {
            0           : accept;
            default     : parse_swtrace;
        }
    }

 

parse_swtrace state는 mri state와 유사합니다. 단, 재귀적으로 계속 돈다는 차이만 있습니다.

 

여기서 hdr.swtraces.next라는 것을 사용하라고 힌트를 주는데, 배열을 어떻게 받아들이는지 배울 수 있는 부분입니다.

 

 

이렇게 작성하면 parser가 끝납니다.

 

4. Ingress Control

/*************************************************************************
**************  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) {
        standard_metadata.egress_spec = port;
        hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
        hdr.ethernet.dstAddr = dstAddr;
        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 {
        if (hdr.ipv4.isValid()) {
            ipv4_lpm.apply();
        }
    }
}

 

ingress control은 내용이 많지는 않습니다. dst address 기준으로 lpm matching을 실행하며, matching되면 ipv4_forward를 실행합니다.

 

5. Egress Control

/*************************************************************************
****************  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_swtrace(switchID_t swid) {
        /*
        * TODO: add logic to:
        - Increment hdr.mri.count by 1
        - Add a new swtrace header by calling push_front(1) on hdr.swtraces.
        - Set hdr.swtraces[0].swid to the id parameter
        - Set hdr.swtraces[0].qdepth to (qdepth_t)standard_metadata.deq_qdepth
        - Increment hdr.ipv4.ihl by 2
        - Increment hdr.ipv4.totalLen by 8
        - Increment hdr.ipv4_option.optionLength by 8
        */
    }

    table swtrace {
        actions        = {
            add_swtrace;
            NoAction;
        }

        default_action =  NoAction();
    }

    apply {
        /*
        * TODO: add logic to:
        * - If hdr.mri is valid:
        *   - Apply table swtrace
        */
        swtrace.apply();
    }
}

 

egress control에서는 이제 switch에 대한 정보를 작성해야 합니다. 각 주석에 맞게 add_swtrace와 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_swtrace(switchID_t swid) {
        hdr.mri.count = hdr.mri.count + 1;
        hdr.swtraces.push_front(1);
        hdr.swtraces[0].setValid();
        hdr.swtraces[0].swid = swid;
        hdr.swtraces[0].qdepth = (qdepth_t)standard_metadata.deq_qdepth;
        hdr.ipv4.ihl = hdr.ipv4.ihl + 2;
        hdr.ipv4.totalLen = hdr.ipv4.totalLen + 8;
        hdr.ipv4_option.optionLength = hdr.ipv4_option.optionLength + 8;
    }

    table swtrace {
        actions        = {
            add_swtrace;
            NoAction;
        }

        default_action =  NoAction();
    }

    apply {
        if (hdr.mri.isValid()) {
            swtrace.apply();
        }
    }
}

 

여기서 swtraces.push_front(1) 함수는 배열을 하나씩 밀어주는 역할을 수행합니다.

  • hs.push_front(int count): shifts hs “right” by count. The first count elements become invalid. The last count elements in the stack are discarded. The hs.nextIndex counter is incremented by count. The count argument must be a positive integer that is a compile-time known value. The return type is void. (https://p4.org/p4-spec/docs/P4-16-v1.2.0.html#sec-expr-hs)

 

6. Deparser

/*************************************************************************
***********************  D E P A R S E R  *******************************
*************************************************************************/

control MyDeparser(packet_out packet, in headers hdr) {
    apply {
        packet.emit(hdr.ethernet);
        packet.emit(hdr.ipv4);

        /* TODO: emit ipv4_option, mri and swtraces headers */
        packet.emit(hdr.ipv4_option);
        packet.emit(hdr.mri);
        packet.emit(hdr.swtraces);
    }
}

 

deparser를 작성해줍니다. valid한 field만 emit이 되기때문에 hdr.swtraces의 size (9)보다 유효 데이터 사이즈가 훨씬 작더라도 상관 없습니다.

 

Troubleshooting

한 번 코드를 작성하고 테스트를 해보니 통신이 되지 않았습니다. 왜 통신이 안되는지는 pcap 파일을 분석해보고 확인할 수 있었습니다.

 

switch 1에서 switch 2로 가는 packet은 s1의 p3을 타고 나갑니다. 이는 topology.json에서 확인할 수 있습니다.

    "links": [
        ["h1", "s1-p2"], ["h11", "s1-p1"], ["s1-p3", "s2-p3", "0", 0.5], ["s1-p4", "s3-p2"],
        ["s3-p3", "s2-p4"], ["h2", "s2-p2"], ["h22", "s2-p1"], ["h3", "s3-p1"]
    ]

 

그러면 의심스러운 포인트인 s1-eth3_out.pcap을 확인해봅시다. (h1에서부터 차례차례 확인해보면 좋습니다.)

bklee@bklee:~/p4/tutorials/exercises/mri/pcaps$ tcpdump -r s1-eth3_out.pcap
reading from file s1-eth3_out.pcap, link-type EN10MB (Ethernet), snapshot length 262144
18:30:19.598856 IP 10.0.1.1 > 10.0.2.2: ICMP echo request, id 32529, seq 1, length 64
18:30:20.600977 IP 10.0.1.1 > 10.0.2.2: ICMP echo request, id 32529, seq 2, length 64
18:30:21.602703 IP 10.0.1.1 > 10.0.2.2: ICMP echo request, id 32529, seq 3, length 64
18:30:42.726583 IP truncated-ip - 8 bytes missing! 10.0.1.1.20532 > 10.0.2.2.8297: UDP, bad length 29464 > 10
18:30:43.763355 IP truncated-ip - 8 bytes missing! 10.0.1.1.20532 > 10.0.2.2.8297: UDP, bad length 29464 > 10
18:30:44.807404 IP truncated-ip - 8 bytes missing! 10.0.1.1.20532 > 10.0.2.2.8297: UDP, bad length 29464 > 10
18:30:45.847315 IP truncated-ip - 8 bytes missing! 10.0.1.1.20532 > 10.0.2.2.8297: UDP, bad length 29464 > 10
18:30:46.895277 IP truncated-ip - 8 bytes missing! 10.0.1.1.20532 > 10.0.2.2.8297: UDP, bad length 29464 > 10

 

크기가 안맞다고 나오는군요.

 

확인해보니 이유로는 egress control에서 swtrace[0].setValid()를 하지 않아서였습니다.

 

 

Run

receiver.py와 send.py만 실행했을 때, receive의 switchTrace에 swid가 잘 들어간 것을 확인할 수 있었습니다.

 

congestion

 

동시에 다른 host (h11, h22)에서 iperf를 실행하면 qdepth가 차는 것을 확인할 수 있었습니다.

 

끝!

 

 

 

 

Update Switch Performance

switch의 성능을 바꿀수도 있다고 합니다!

 

우리가 사용중인 리눅스에서 다음 명령어를 실행하면 됩니다.

simple_switch_CLI --thrift-port 9090

set_queue_rate 50
set_queue_depth 100

 

 

 

 

 

queue depth가 99까지 차는 것을 확인할 수 있었습니다.

반응형

'프로그래밍 (Programming) > P4' 카테고리의 다른 글

8. P4 - load balancing  (0) 2024.07.06
7. P4 - source routing  (0) 2024.07.04
5. P4 - ecn  (0) 2024.06.29
4. P4 - P4runtime  (0) 2024.06.25
3. P4 - calc  (0) 2024.06.21
Comments