- Today
- Total
Byeo
3. P4 - calc 본문
이번 예제는 계산 연습입니다. 오가는 packet의 header와 operand를 파싱해서 결과를 적는 미션인데요, 자세한 설명은 다음 링크를 참조 바랍니다.: https://github.com/p4lang/tutorials/tree/master/exercises/calc
시작하기 전에
calc.py를 실행하려 했으나.. google.protobuf.text_format.ParseError: 40:3 : Message type "p4.config.v1.Table" has no field named "has_initial_entries". 에러가 발생하여 진행할 수 없었습니다. protobuf와 관련된 이슈로 보이는데요. (https://github.com/p4lang/tutorials/issues/608, https://forum.p4.org/t/compilation-error-upon-doing-calc-exercise/1074/4)
2시간이나 소요되는 재설치가 너무 부담스러워서 이 calc 예제는 virtualbox에서 진행합니다.
겸사겸사 X11을!
ssh option중에서 -X 혹은 -Y를 활성화하면 X11 server를 사용할 수 있습니다. 이는 ssh가 연결중인 서버에서 x11을 지원하는 응용을 실행 시, 내 컴퓨터에서 GUI를 지원받을 수 있는 엄청난 도구입니다.
Virtualbox calc.p4 test
휴.. 다행히 컴파일이 잘 되어서 calc.p4 예제를 시작할 수 있게 되었습니다.
0. Topology
topology는 위와 같습니다.
1. 실험 테스트
mininet> h1 python3 calc.py
> 1+1
mininet이 실행됐다면 위처럼 입력해봅니다. 이 때, 응답은 다음과 같이 돌아옵니다.
Didn't receive response
2. packet format
0 1 2 3
+----------------+----------------+----------------+---------------+
| P | 4 | Version | Op |
+----------------+----------------+----------------+---------------+
| Operand A |
+----------------+----------------+----------------+---------------+
| Operand B |
+----------------+----------------+----------------+---------------+
| Result |
+----------------+----------------+----------------+---------------+
해당 packet format은 L3 계층으로, L2는 ethernet src/dst를 갖고 있고, ethertype이 0x1234인 경우에만 처리하면 된다고 설명하고 있습니다.
2. Parser
ethertype이 0x1234인 것은 이미 스켈레톤 코드에서 구분해주고 있습니다.
state start {
packet.extract(hdr.ethernet);
transition select(hdr.ethernet.etherType) {
P4CALC_ETYPE : check_p4calc;
default : accept;
}
}
check_p4calc state를 볼까요?
state check_p4calc {
/* TODO: just uncomment the following parse block */
transition select(packet.lookahead<p4calc_t>().p,
packet.lookahead<p4calc_t>().four,
packet.lookahead<p4calc_t>().ver) {
(P4CALC_P, P4CALC_4, P4CALC_VER) : parse_p4calc;
default : accept;
}
}
check_p4calc state는 p4 header에 대해서 p4calc_t의 (p, four, ver) 값이 각각 (0x50, 0x34, 0x01)인지를 확인합니다. 이러면 parse_p4calc state로 전환합니다. 이 함수는 단순히 uncomment만 시켜주면 됩니다.
여기서 다음 두 가지를 추가로 알 수 있습니다.
- extract와 다르게 lookahead를 통해서 packet_in packet의 pointer를 옮기지 않고 데이터를 읽어올 수 있다. ( The lookahead method provided by the packet_in packet abstraction evaluates to a set of bits from the input packet without advancing the nextBitIndex pointer.)
- select문은 tuple 형태로도 검증이 가능하다.
state parse_p4calc {
packet.extract(hdr.p4calc);
transition accept;
}
parse_p4calc함수는 실제로 input packet을 읽어서 hdr.p4calc에 기록해주는 역할을 수행합니다.
3. TODO: p4calc_t structure
그러면 p4calc_t 구조체는 어떻게 생겼을까요?
header p4calc_t {
bit<8> op;
/* TODO
* fill p4calc_t header with P, four, ver, op, operand_a, operand_b, and res
entries based on above protocol header definition.
*/
}
비어있네요. 우리가 채워야 할 것 같습니다. 위에서 제시된 packet format에 따라서 모두 채워줍니다.
header p4calc_t {
bit<8> p;
bit<8> four;
bit<8> ver;
bit<8> op;
bit<32> operand_a;
bit<32> operand_b;
bit<32> res;
}
* 설명은 대문자 P로 되어있으나, parser에서는 소문자 p를 사용하기 때문에 소문자 p를 사용해야 합니다.
4. Ingress control
parser를 다 작성했으니, 이제 Ingress control을 살펴봅니다.
table calculate {
key = {
hdr.p4calc.op : exact;
}
actions = {
operation_add;
operation_sub;
operation_and;
operation_or;
operation_xor;
operation_drop;
}
const default_action = operation_drop();
const entries = {
P4CALC_PLUS : operation_add();
P4CALC_MINUS: operation_sub();
P4CALC_AND : operation_and();
P4CALC_OR : operation_or();
P4CALC_CARET: operation_xor();
}
}
apply {
if (hdr.p4calc.isValid()) {
calculate.apply();
} else {
operation_drop();
}
}
table은 hdr.p4calc.op의 값 (조건: exact)에 따라 action이 결정됩니다. 각각의 값은 build/calc.json의 table에 정의되어 있습니다.
...
{
"source_info" : {
"filename" : "calc.p4",
"line" : 200,
"column" : 12,
"source_fragment" : "P4CALC_PLUS : operation_add()"
},
"match_key" : [
{
"match_type" : "exact",
"key" : "0x2b"
}
],
"action_entry" : {
"action_id" : 0,
"action_data" : []
},
"priority" : 1
}
...
5. operand 채우기
각각의 operation에 적절한 코드를 작성해줍니다. 주석에 적혀있는 설명에 맞게 send_back을 호출합니다.
action operation_add() {
/* TODO call send_back with operand_a + operand_b */
send_back(hdr.p4calc.operand_a + hdr.p4calc.operand_b);
}
action operation_sub() {
/* TODO call send_back with operand_a - operand_b */
send_back(hdr.p4calc.operand_a - hdr.p4calc.operand_b);
}
action operation_and() {
/* TODO call send_back with operand_a & operand_b */
send_back(hdr.p4calc.operand_a & hdr.p4calc.operand_b);
}
action operation_or() {
/* TODO call send_back with operand_a | operand_b */
send_back(hdr.p4calc.operand_a | hdr.p4calc.operand_b);
}
action operation_xor() {
/* TODO call send_back with operand_a ^ operand_b */
send_back(hdr.p4calc.operand_a ^ hdr.p4calc.operand_b);
}
https://p4.org/p4-spec/docs/P4-16-v1.2.0.html#sec-int-ops의 8.5, 8.6 section을 참고하면 됩니다.
6. send_back
이제 마지막으로 send_back action을 작성해줍니다. 설명에 적힌대로 작성하면 쉽게 완수할 수 있습니다.
action send_back(bit<32> result) {
/* TODO
* - put the result back in hdr.p4calc.res
* - swap MAC addresses in hdr.ethernet.dstAddr and
* hdr.ethernet.srcAddr using a temp variable
* - Send the packet back to the port it came from
by saving standard_metadata.ingress_port into
standard_metadata.egress_spec
*/
// put the result back in hdr.p4calc.res
hdr.p4calc.res = result;
// swap MAC addresses in hdr.ethernet.dstAddr and hdr.ethernet.srcAddr using a temp variable
bit<48> ethsrc_tmp = hdr.ethernet.srcAddr;
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
hdr.ethernet.dstAddr = ethsrc_tmp;
// Send the packet back to the port it came from by saving standard_metadata.ingress_port into standard_metadata.egress_spec
standard_metdata.egress_spec = standard_metadata.ingress_port;
}
테스트
잘 된 것을 확인할 수 있습니다!
'프로그래밍 (Programming) > P4' 카테고리의 다른 글
5. P4 - ecn (0) | 2024.06.29 |
---|---|
4. P4 - P4runtime (0) | 2024.06.25 |
2. P4 - basic_tunnel (0) | 2024.06.19 |
1. P4 - Basic (0) | 2024.06.17 |
0. P4 tutorial 환경 구성 (1) | 2024.06.16 |