【DPDK】DPDK实现NAT

1、前言

        DPDK源码提供了不少的用户程序示例,在源码的 ./examples/ 目录下可以看到这些示例,有诸如2层转发、三层转发、ACL之类的常见网络应用,我注意到没有NAT的内容,于是打算基于已有的示例程序编写一个。

        编写一个通用的内容需要花费不少时间,并且成品也更加复杂,这并不符合DPDK的使用场景。我认为,在特定的网络环境下,DPDK程序应该在满足网络需求的条件下尽量精简,毕竟,如果不是为了提高网络性能而是追求通用性,那为什么不选择Linux网络内核呢?

2、网络规划

        因此,在给出程序代码之前,有必要了解到将要应用的场景。我预想中的简单网络可以由下图描述:

        可以看到,上图详细表明了各个网络接口的MAC及IP,并表明这三台虚拟机的网口都是桥接模式。在网络访问过程中,client 将直接访问其网关 192.168.31.100 ,DPDK NAT 用户程序将数据包的源IP地址、目的IP地址修改,并修改源MAC、目的MAC之后发送给server,server发送给client的回复报文也是类似处理。

        另外,可以看到这里给出的各个接口都位于 192.168.31.x 网段,因此,需要在client和server上分别添加他们的网关的ARP,否则访问将会异常。对于一个简单示例而言,再添加ARP功能似乎不是那么好的选择,因此,先这样。

3、代码及解析

        针对此模型,我在 dpdk-19.05 源码示例 ./examples/l3fwd-vf 的基础上完成了NAT应用程序的编写,具体修改的是 l3fwd_simple_forward 函数,代码如下:

#define NAT
#define PORT2CLIENT 0
#define PORT2SERVER 1
#define CLIENT_MAC 0x38cf85239c30
#define SERVER_MAC 0xb29e29290c00
#define CLIENT_IP "192.168.31.214"
#define SERVER_IP "192.168.31.197"
#define CLIENT_GW "192.168.31.100"
#define SERVER_GW "192.168.31.200"

static inline void
l3fwd_simple_forward(struct rte_mbuf *m, uint16_t portid,
		      lookup_struct_t *l3fwd_lookup_struct)
{
	struct ether_hdr *eth_hdr;
	struct ipv4_hdr *ipv4_hdr;
	void *tmp;
	uint16_t dst_port;

	eth_hdr = rte_pktmbuf_mtod(m, struct ether_hdr *);

	ipv4_hdr = rte_pktmbuf_mtod_offset(m, struct ipv4_hdr *,
					   sizeof(struct ether_hdr));

#ifdef NAT //打印收到的报文的信息
    char mac[30] = {0};
	struct in_addr addr;
    ether_format_addr(mac, 30, &eth_hdr->s_addr);
	printf("recieve packet, src_mac[%s],", mac);
    ether_format_addr(mac, 30, &eth_hdr->d_addr);
    printf("dst_mac[%s];", mac);
	addr.s_addr = ipv4_hdr->src_addr;
    printf("src_ip:%s;", inet_ntoa(addr));
    addr.s_addr = ipv4_hdr->dst_addr;
    printf("dst_ip:%s; ", inet_ntoa(addr));
	printf("portid:%d\n", portid);
#endif

#ifdef DO_RFC_1812_CHECKS
	/* Check to make sure the packet is valid (RFC1812) */
	if (is_valid_ipv4_pkt(ipv4_hdr, m->pkt_len) < 0) {
		rte_pktmbuf_free(m);
		return;
	}
#endif

	dst_port = get_dst_port(ipv4_hdr, portid, l3fwd_lookup_struct);
	if (dst_port >= RTE_MAX_ETHPORTS || (enabled_port_mask & 1 << dst_port) == 0)
		dst_port = portid;

#ifdef NAT
	if(PORT2CLIENT == portid){
		dst_port = PORT2SERVER;
	}
	else{
		dst_port = PORT2CLIENT;
	}
#endif

	/* 02:00:00:00:00:xx */
	tmp = &eth_hdr->d_addr.addr_bytes[0];
#ifndef NAT
	*((uint64_t *)tmp) = 0x000000000002 + ((uint64_t)dst_port << 40);
#endif

#ifdef DO_RFC_1812_CHECKS
	/* Update time to live and header checksum */
	--(ipv4_hdr->time_to_live);
	++(ipv4_hdr->hdr_checksum);
#endif

	/* src addr */
	ether_addr_copy(&ports_eth_addr[dst_port], &eth_hdr->s_addr);

#ifdef NAT //修改以太网头及ip头
	if(PORT2SERVER == dst_port){
    	*((uint64_t *)tmp) = SERVER_MAC;
	}
	else{
		*((uint64_t *)tmp) = CLIENT_MAC;
	}

	printf("Nat and foward packet, ");
    ether_format_addr(mac, 30, &eth_hdr->s_addr);
    printf("src_mac[%s],", mac);
    ether_format_addr(mac, 30, &eth_hdr->d_addr);
    printf("dst_mac[%s];", mac);

	if(PORT2SERVER == dst_port){
		inet_aton(SERVER_IP, &addr);
		ipv4_hdr->dst_addr = addr.s_addr;
		inet_aton(SERVER_GW, &addr);
		ipv4_hdr->src_addr = addr.s_addr;
	}
	else{
		inet_aton(CLIENT_IP, &addr);
		ipv4_hdr->dst_addr = addr.s_addr;
		inet_aton(CLIENT_GW, &addr);
		ipv4_hdr->src_addr = addr.s_addr;
	}

    addr.s_addr = ipv4_hdr->src_addr;
    printf("src:%s,",inet_ntoa(addr));
    addr.s_addr = ipv4_hdr->dst_addr;
    printf("dst:%s; ",inet_ntoa(addr));

	ipv4_hdr->hdr_checksum = 0;
	ipv4_hdr->hdr_checksum = rte_ipv4_cksum(ipv4_hdr);

	printf("dst_port=%d\n", dst_port);
#endif

	send_single_packet(m, dst_port);

}

        正如一开始所说,这个程序做的主要工作就是修改源IP、目的IP以实现NAT,并修改源MAC、目的MAC以完成二层转发,除此之外,我并未如何修改及使用原示例程序中的内容,如本身三层路由功能我就并未使用,只是简单修改了目的端口,这对于示例程序来说足够了,并且,这可以更直观地体现出DPDK的灵活性。事实上,修改TTL也是没有必要的,因为本实例给出的所有相关网络接口都位于同一网段。

        构建DPDK框架,编译并运行此用户程序,client发出的所有流向其网关192.168.31.100的报文都会NAT并转给server,这一切都可以由程序本身打印出来,以下是一个实例:

$ sudo ./run.sh
......
recieve packet, src_mac[30:9C:23:85:CF:38],dst_mac[00:0C:29:53:03:27];src_ip:192.168.31.214;dst_ip:192.168.31.100; portid:0
Nat and foward packet, src_mac[00:00:29:53:03:31],dst_mac[00:0C:29:29:9E:B2];src:192.168.31.200,dst:192.168.31.197; dst_port=1
recieve packet, src_mac[00:0C:29:29:9E:B2],dst_mac[00:0C:29:53:03:31];src_ip:192.168.31.197;dst_ip:192.168.31.200; portid:1
Nat and foward packet, src_mac[00:00:29:53:03:27],dst_mac[30:9C:23:85:CF:38];src:192.168.31.100,dst:192.168.31.214; dst_port=0

        在这里,我编写了一个脚本./run.sh来构建DPDK框架及编译、运行NAT用户程序,并且我省略了用户程序运行时的打印(用省略号表示)。以上打印是当client ping其网关192.168.31.100时的实时打印,它清楚地给出了icmp报文的处理过程,以便大家更好地理解DPDK的工作方式。

3、总结及展望

        我编写了一个十分简单的DPDK用户程序以实现NAT功能,它工作的环境略显苛刻,但正因此此程序足够简单明了,可以足够清晰地展示dpdk中是如何一层层剥开数据报文,并根据需求修改各个字段的值。

        在程序中我仅解析、修改了二层及三层的报头,实际上,可以进一步分析传输层头部,因此把源端口、目的端口,再加上三层报头可以获取到的源IP地址、目的IP地址、传输层协议,凑齐五元组,并用哈希表存储NAT规则,以实现跟iptables提供的NAT功能一样的效果。

  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值