VPP: NAT
本系列文章仅为学习使用 VPP 过程中的一些自用笔记
VPP 的 NAT 功能是针对网关设备设计的
VPP 的 NAT 功能是针对网关设备设计的, 它的其中一端连接 local 网络(这一侧的接口称为 inside 口),另一端连接 external (这一个侧的接口称为 outside 口)。
根据报文的方向,流量可分为左图中的 in2out 和右图中的 out2in ,in2out 做 SNAT, out2in 做 DNAT
VPP NAT 的位置
VPP 中 SNAT 和 DNAT 的位置在报文输入到达 IP 层查找路由前,类比 Netfilter 模型的话,就是在 PREROUTING 阶段,
VPP没有类似于 Netfilter 中 LOCAL_IN 、FORWARD、OUTPUT 用于做 NAT,它只能在 PREROUTING 阶段做 NAT。
POSTROUTING 处可能可以,但是我暂时没有看懂output feature的用法。以下只考虑在 PREROUTING 处的 NAT
VPP NAT 以 plugin 的形式插入到进 node graph 的 node 之间的。在 ip4-input 到 ip4-lookup 的路径上,可以 enable nat44 feature,这样单播IP报文就会绕道 ip4-unicast 这个 arc,在这个 arc 中,VPP 可以进行 NAT 变换。feature 的开关是以 interface 为粒度的。如果我们想 enable 某个 interface 的 NAT 功能,那么需要做的就是将这个 interface
vpp# set int nat44 in G1
vpp# set int nat44 out G0
VPP NAT 的 conntrack 机制
VPP NAT 也有类似于 Netfilter conntrack 机制,它的表项称为 snat session,报文在 NAT 前总是尝试先搜索是否满足条件的 snat session,若有,则直接做 NAT。
具体实现
数据结构
snat_main_t
snat_main_t snat_main;
typedef struct snat_main_s
{
/* Per thread data */
snat_main_per_thread_data_t *per_thread_data;
/* Find a static mapping by local */
clib_bihash_8_8_t static_mapping_by_local;
/* Find a static mapping by external */
clib_bihash_8_8_t static_mapping_by_external;
/* Static mapping pool */
snat_static_mapping_t *static_mappings;
/* Interface pool */
snat_interface_t *interfaces;
/* vector of outside fibs */
nat_outside_fib_t *outside_fibs;
// other filed..
} snat_main_t;
- per_thread_data: 每线程的数据,后面详述
-
static_mapping_by_local:以 local 侧的地址端口信息为 key 存储的静态NAT规则哈希表,value 为静态映射表项 snat_static_mapping_t 在 static_mappings 池中的索引。
-
static_mapping_by_external:以 external 侧的地址端口信息为 key 存储的静态NAT规则哈希表,value 为 静态映射表项 snat_static_mapping_t 在 static_mappings 池中的索引
- static_mappings:静态NAT规则池
- interfaces: 使能了 NAT 特性的 interface 集合。 ref. snat_interface_add_del
- outside_fibs:当使能了一个 interface 的 outside 侧 NAT 特性时,这个 interface 关联到的 fib 会以加入到该 vector。 ref. snat_interface_add_del.
typedef struct
{
/* Main lookup tables */
clib_bihash_8_8_t out2in;
clib_bihash_8_8_t in2out;
/* Session pool */
snat_session_t *sessions;
// ...
} snat_main_per_thread_data_t;
过程
使能 interface 的 inside NAT feature
假设使用命令行使能 interface G1 的 nat 功能,将其配置为 external 侧的接口
本文不考虑使能 output_feature 的情况,且worker线程数目为 1.
vpp# set interface nat44 out G1
调用命令行处理函数,sw_if_index 为 G1 的 index,is_inside 为 0,is_del 为 0
int snat_interface_add_del (u32 sw_if_index, u8 is_inside, int is_del)
{
// code omitted...
// 找到该 interface 关联的 fib
u32 fib_index = fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4, sw_if_index);
// 由于我们添加的是 outside interface,所以这里得到的是 "nat44-out2in"
feature_name = is_inside ? "nat44-in2out" : "nat44-out2in";
// 由于我们添加的是 outside interface,这里将 interface 对应的 fib 添加到 outside_fibs vector 中
if (!is_inside)
{
// code omitted
if (!is_del)
{
vec_add2 (sm->outside_fibs, outside_fib, 1);
outside_fib->refcount = 1;
outside_fib->fib_index = fib_index;
}
}
// 接下来进行 feature set,我们假设 G1 之前没有使能过 NAT 功能
// 这里分配一个 snat 的 interface
pool_get (sm->interfaces, i); // allocate a snat_interface_t
i->sw_if_index = sw_if_index; //
i->flags = 0;
// 启动 "ip4-unicast" 这个 arc 上的 "nat44-out2in" 特性.
vnet_feature_enable_disable ("ip4-unicast", feature_name, sw_if_index, 1, 0, 0);
}
接下来启动 G0 的 inside nat 功能
vpp# set interface nat44 in G0
与 G1 类似,这里会开启 G0 的 “nat44-in2out” 特性。
local 访问 external
此时 ip4-input node 会将报文传递给 snat_in2out_node,调用该 node 的处理函数,is_slow_path 为 0, is_output_feature 为0
static inline uword
snat_in2out_node_fn_inline (vlib_main_t * vm,
vlib_node_runtime_t * node,
vlib_frame_t * frame, int is_slow_path,
int is_output_feature)
{
// code omitted
while (n_left_from > 0)
{
// 解析出输入的报文的 IP 头和 transport header 以及输入的 interface 和关联的 fib
ip0 = (ip4_header_t *) ((u8 *) vlib_buffer_get_current (b0) +
iph_offset0);
udp0 = ip4_next_header (ip0);
tcp0 = (tcp_header_t *) udp0;
icmp0 = (icmp46_header_t *) udp0;
sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
rx_fib_index0 = vec_elt (sm->ip4_main->fib_index_by_sw_if_index, sw_if_index0);
proto0 = ip_proto_to_nat_proto (ip0->protocol);
// 根据 sip+sport+fib+proto 计算一个 key
init_nat_k (&kv0, ip0->src_address, vnet_buffer (b0)->ip.reass.l4_src_port, rx_fib_index0,
proto0);
// 用该 key 搜索是否存在现成的 snat_session,
if (clib_bihash_search_8_8(&sm->per_thread_data[thread_index].in2out, &kv0, &value0))
{
// 如果没有找到snat session,由于当前我们不是处于 slow path,则这里我们将报文传递给 slow path
if (is_slow_path)
{
next0 = slow_path (sm, b0, ip0, ip0->src_address,
vnet_buffer (b0)->ip.reass.l4_src_port,
rx_fib_index0, proto0, &s0, node, next0,
thread_index, now);
}
else
{
next0 = SNAT_IN2OUT_NEXT_SLOW_PATH;
goto trace0;
}
}
else
// 如果找到,则得到现成的 snat_session
s0 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions, value0.value);
}
// code omitted...
}
没有找到线程 snat_session 的报文会走 slow path。在这里会优先尝试静态映射,如果不成功再尝试动态映射
static u32
slow_path (snat_main_t * sm, vlib_buffer_t * b0,
ip4_header_t * ip0,
ip4_address_t i2o_addr,
u16 i2o_port,
u32 rx_fib_index0,
nat_protocol_t nat_proto,
snat_session_t ** sessionp, // slow path 的结果
vlib_node_runtime_t * node, u32 next0, u32 thread_index, f64 now)
{
//
// static mapping 高于 dynamic translation,如果我们配置了满足条件的静态 mapping 规则,则这里会有限使用
if (snat_static_mapping_match
(sm, i2o_addr, i2o_port, rx_fib_index0, nat_proto, &sm_addr,
&sm_port, &sm_fib_index, 0, 0, 0, 0, 0, &identity_nat, 0))
{
// 如果没有的话 就会尝试 dynamic translation
if (snat_alloc_outside_address_and_port (sm->addresses, rx_fib_index0,
thread_index,
nat_proto,
&sm_addr, &sm_port,
sm->port_per_thread,
sm->per_thread_data
[thread_index].snat_thread_index))
{
b0->error = node->errors[SNAT_IN2OUT_ERROR_OUT_OF_PORTS];
return SNAT_IN2OUT_NEXT_DROP;
}
}
else
{
// code omitted...
is_sm = 1;
}
// 无论是静态还是动态,这里得到映射的结果 转换后的源IP:sm_addr 源端口sm_port outside接口关联的fib:sm_fib_index
}
static mapping NAT
该功能用作 1:1 SNAT.
涉及命令
vpp# set interface nat44 in G0
vpp# set interface nat44 out G1
vpp# nat44 add address 10.100.1.1
vpp# nat44 add static mapping local 192.168.1.1 external 10.100.1.1
开启 interface 的 NAT44 功能
命令行触发
VLIB_CLI_COMMAND (set_interface_snat_command, static) = {
.path = "set interface nat44",
.function = snat_feature_command_fn,
.short_help = "set interface nat44 in <intfc> out <intfc> [output-feature] "
"[del]",
};
(完)