SWITCH.P4的nat.p4代码分析
当前该开源项目对NAT的支持还不完善。
基本元数据
/*
* NAT processing
*/
header_type nat_metadata_t {
fields {
ingress_nat_mode : 2; /* 0: none, 1: inside, 2: outside */
egress_nat_mode : 2; /* nat mode of egress_bd */
nat_nexthop : 16; /* next hop from nat */
nat_nexthop_type : 2; /* ecmp or nexthop */
nat_hit : 1; /* fwd and rewrite info from nat */
nat_rewrite_index : 14; /* NAT rewrite index */
update_checksum : 1; /* update tcp/udp checksum */
update_inner_checksum : 1; /* update inner tcp/udp checksum */
l4_len : 16; /* l4 length */
}
}
metadata nat_metadata_t nat_metadata;
输入流水线
SNAT
通过精确匹配源IP和源端口决定源NAT。
/*
* packet has matched source nat binding, provide rewrite index for source
* ip/port rewrite
*/
action set_src_nat_rewrite_index(nat_rewrite_index) {
modify_field(nat_metadata.nat_rewrite_index, nat_rewrite_index);
}
table nat_src {
reads {
l3_metadata.vrf : exact;
ipv4_metadata.lkp_ipv4_sa : exact;
l3_metadata.lkp_ip_proto : exact;
l3_metadata.lkp_l4_sport : exact;
}
actions {
on_miss;
set_src_nat_rewrite_index;
}
size : IP_NAT_TABLE_SIZE;
}
源NAT必须在路由之后,因为不能与uRPF功能冲突。在这里只是设置一下nat的重写索引。
DNAT
根据目的IP和端口进行nat。目的NAT在原则上来说必须在路由之前进行,因为进行目的NAT修改了报文的目的IP。需要重新路由,在硬件上实现时,将目的NAT和路由查找合二为一了,直接在软件层面做好了路由,给目的NAT一个下一跳索引即可。
/*
* packet has matched destination nat binding, provide nexthop index for
* forwarding and rewrite index for destination ip/port rewrite
*/
action set_dst_nat_nexthop_index(nexthop_index, nexthop_type,
nat_rewrite_index) {
modify_field(nat_metadata.nat_nexthop, nexthop_index);
modify_field(nat_metadata.nat_nexthop_type, nexthop_type);
modify_field(nat_metadata.nat_rewrite_index, nat_rewrite_index);
modify_field(nat_metadata.nat_hit, TRUE);
}
table nat_dst {
reads {
l3_metadata.vrf : exact;
ipv4_metadata.lkp_ipv4_da : exact;
l3_metadata.lkp_ip_proto : exact;
l3_metadata.lkp_l4_dport : exact;
}
actions {
on_miss;
set_dst_nat_nexthop_index;
}
size : IP_NAT_TABLE_SIZE;
}
nat_twice
根据五元组精确匹配进行源目的nat。
/*
* packet has matched twice nat binding, provide nexthop index for forwarding,
* and rewrite index for source and destination ip/port rewrite
*/
action set_twice_nat_nexthop_index(nexthop_index, nexthop_type,
nat_rewrite_index) {
modify_field(nat_metadata.nat_nexthop, nexthop_index);
modify_field(nat_metadata.nat_nexthop_type, nexthop_type);
modify_field(nat_metadata.nat_rewrite_index, nat_rewrite_index);
modify_field(nat_metadata.nat_hit, TRUE);
}
table nat_twice {
reads {
l3_metadata.vrf : exact;
ipv4_metadata.lkp_ipv4_sa : exact;
ipv4_metadata.lkp_ipv4_da : exact;
l3_metadata.lkp_ip_proto : exact;
l3_metadata.lkp_l4_sport : exact;
l3_metadata.lkp_l4_dport : exact;
}
actions {
on_miss;
set_twice_nat_nexthop_index;
}
size : IP_NAT_TABLE_SIZE;
}
nat_flow
根据五元组掩码匹配决定nat。
table nat_flow {
reads {
l3_metadata.vrf : ternary;
ipv4_metadata.lkp_ipv4_sa : ternary;
ipv4_metadata.lkp_ipv4_da : ternary;
l3_metadata.lkp_ip_proto : ternary;
l3_metadata.lkp_l4_sport : ternary;
l3_metadata.lkp_l4_dport : ternary;
}
actions {
nop;
set_src_nat_rewrite_index;
set_dst_nat_nexthop_index;
set_twice_nat_nexthop_index;
}
size : IP_NAT_FLOW_TABLE_SIZE;
}
从上面的四个nat匹配表来看,差异在于匹配的域。nat_flow表是一个通用的表,可以匹配多种多样的字段。实现网段匹配,端口范围匹配等,缺陷是使用的是TCAM作为匹配域,资源有限。可以作为静态NAT表。对于动态NAT表,可以使用精确五元组匹配进行,因为在CPU中会进行session管理,生成五元组下发。
control process_ingress_nat {
#ifndef NAT_DISABLE
apply(nat_twice) {
on_miss {
apply(nat_dst) {
on_miss {
apply(nat_src) {
on_miss {
apply(nat_flow);
}
}
}
}
}
}
#endif /* NAT DISABLE */
}
从功能角度来说,使用nat_flow可以满足所有需求。这里采用多个表,猜测目的是为了节约资源或者是为了展示功能。
出流水线
出流水线主要是根据入流水线进行的NAT决策进行报文重写。
//nat在这个开源代码中还不完善。
/*****************************************************************************/
/* Egress NAT rewrite */
/*****************************************************************************/
#ifndef NAT_DISABLE
action nat_update_l4_checksum() {
modify_field(nat_metadata.update_checksum, 1);//设置nat校验码标志
add(nat_metadata.l4_len, ipv4.totalLen, -20);//目前没有看到这些字段的用处
}
action set_nat_src_rewrite(src_ip) {
modify_field(ipv4.srcAddr, src_ip);
nat_update_l4_checksum();
}
action set_nat_dst_rewrite(dst_ip) {
modify_field(ipv4.dstAddr, dst_ip);
nat_update_l4_checksum();
}
action set_nat_src_dst_rewrite(src_ip, dst_ip) {
modify_field(ipv4.srcAddr, src_ip);
modify_field(ipv4.dstAddr, dst_ip);
nat_update_l4_checksum();
}
action set_nat_src_udp_rewrite(src_ip, src_port) {
modify_field(ipv4.srcAddr, src_ip);
modify_field(udp.srcPort, src_port);
nat_update_l4_checksum();
}
action set_nat_dst_udp_rewrite(dst_ip, dst_port) {
modify_field(ipv4.dstAddr, dst_ip);
modify_field(udp.dstPort, dst_port);
nat_update_l4_checksum();
}
action set_nat_src_dst_udp_rewrite(src_ip, dst_ip, src_port, dst_port) {
modify_field(ipv4.srcAddr, src_ip);
modify_field(ipv4.dstAddr, dst_ip);
modify_field(udp.srcPort, src_port);
modify_field(udp.dstPort, dst_port);
nat_update_l4_checksum();
}
//tcp源nat
action set_nat_src_tcp_rewrite(src_ip, src_port) {
modify_field(ipv4.srcAddr, src_ip);
modify_field(tcp.srcPort, src_port);
nat_update_l4_checksum();
}
//tcp目的nat
action set_nat_dst_tcp_rewrite(dst_ip, dst_port) {
modify_field(ipv4.dstAddr, dst_ip);
modify_field(tcp.dstPort, dst_port);
nat_update_l4_checksum();
}
//tcp twice nat
action set_nat_src_dst_tcp_rewrite(src_ip, dst_ip, src_port, dst_port) {
modify_field(ipv4.srcAddr, src_ip);
modify_field(ipv4.dstAddr, dst_ip);
modify_field(tcp.srcPort, src_port);
modify_field(tcp.dstPort, dst_port);
nat_update_l4_checksum();
}
table egress_nat {
reads {
nat_metadata.nat_rewrite_index : exact;
}
actions {
nop;
set_nat_src_rewrite;
set_nat_dst_rewrite;
set_nat_src_dst_rewrite;
set_nat_src_udp_rewrite;
set_nat_dst_udp_rewrite;
set_nat_src_dst_udp_rewrite;
set_nat_src_tcp_rewrite;
set_nat_dst_tcp_rewrite;
set_nat_src_dst_tcp_rewrite;
}
size : EGRESS_NAT_TABLE_SIZE;
}
#endif /* NAT_DISABLE */
control process_egress_nat {
#ifndef NAT_DISABLE
if ((nat_metadata.ingress_nat_mode != NAT_MODE_NONE) and
(nat_metadata.ingress_nat_mode != nat_metadata.egress_nat_mode)) {
apply(egress_nat);
}
#endif /* NAT_DISABLE */
}

浙公网安备 33010602011771号