android nat64,dpvs学习笔记: 18 nat64 的实现
这几天 iqiyi 同学做了大更新,dpvs 增加 nat64 功能,这样机房对外暴露 ipv6, 对内还不用修改 ipv4 业务代码。算是重量级武器吧,为 ipv6 升级过渡提供了双栈的保障。
提前想好的问题
如何使用 nat64 配置呢?会不会很繁琐
后端 real server 看到的也是 ipv6 地址?怎么正确的获取源 ip,涉及 toa 的实现
为了兼容 4 to 4 和 6 to 6,现有主逻辑代码做了哪些修改呢?只是将三层的 ip 头来回替换就可以了吗?
ipv4 header
ipv6 header
如何使用 nat64
#!/bin/sh -
# add VIP to WAN interface
./dpip addr add 2001::1/128 dev dpdk1
# route for WAN/LAN access
# add routes for other network or default route if needed.
./dpip route -6 add 2001::/64 dev dpdk1
./dpip route add 10.0.0.0/8 dev dpdk0
# add service to forwarding, scheduling mode is RR.
# use ipvsadm --help for more info.
./ipvsadm -A -t [2001::1]:80 -s rr
# add two RS for service, forwarding mode is FNAT (-b)
./ipvsadm -a -t [2001::1]:80 -r 10.0.0.1 -b
./ipvsadm -a -t [2001::1]:80 -r 10.0.0.2 -b
# add at least one Local-IP (LIP) for FNAT on LAN interface
./ipvsadm --add-laddr -z 10.0.0.3 -t [2001::1]:80 -F dpdk0
上面是官网 ipv6 的 nat64 例子,看得出来,和普通搭建 full-nat 没有任何区别,只是 vip 变成了 ipv6,并且 real server 添加时是 ipv4 而己,运维上保持了一致性,点赞
对于新建连接的请求
完整 tcp4 流程可以参考之前的文章,对于 fullnat 大致流程是一样的。但是由于要做 64 转换,所以 ip 头需要重新填充。
函数 tcp_conn_sched 调度后端 service 并建产 session 结构体 conn, dp_vs_schedule 调用 dp_vs_conn_new 建立连接,将 seq -1,选择 local addr port ,并添加到 hash 表中。这块和 tcp4 逻辑基本一致,区别就在于后续发送到 rs 流程。
int dp_vs_xmit_fnat(struct dp_vs_proto *proto,
struct dp_vs_conn *conn,
struct rte_mbuf *mbuf)
{
int af = conn->af;
assert(af == AF_INET || af == AF_INET6);
if (tuplehash_in(conn).af == AF_INET &&
tuplehash_out(conn).af == AF_INET)
return __dp_vs_xmit_fnat4(proto, conn, mbuf);
if (tuplehash_in(conn).af == AF_INET6 &&
tuplehash_out(conn).af == AF_INET6)
return __dp_vs_xmit_fnat6(proto, conn, mbuf);
if (tuplehash_in(conn).af == AF_INET6 &&
tuplehash_out(conn).af == AF_INET)
return __dp_vs_xmit_fnat64(proto, conn, mbuf);
rte_pktmbuf_free(mbuf);
return EDPVS_NOTSUPP;
}
xmit_inbound 开始发送数据包,由于进来的是 af_inet6,出去的是 af_inet,所以做调用 __dp_vs_xmit_fnat64 做 64 转换发送。
static int __dp_vs_xmit_fnat64(struct dp_vs_proto *proto,
struct dp_vs_conn *conn,
struct rte_mbuf *mbuf)
{
......
/*
* mbuf is from IPv6, icmp should send by icmp6
* ext_hdr and
*/
mtu = rt->mtu;
pkt_len = mbuf_nat6to4_len(mbuf);
if (pkt_len > mtu) {
RTE_LOG(DEBUG, IPVS, "%s: frag needed.\n", __func__);
icmp6_send(mbuf, ICMP6_PACKET_TOO_BIG, 0, mtu);
err = EDPVS_FRAG;
goto errout;
}
/* L3 translation before l4 re-csum */
err = mbuf_6to4(mbuf, &conn->laddr.in, &conn->daddr.in);
if (err)
goto errout;
ip4h = ip4_hdr(mbuf);
ip4h->hdr_checksum = 0;
/* L4 FNAT translation */
if (proto->fnat_in_handler) {
err = proto->fnat_in_handler(proto, conn, mbuf);
if (err != EDPVS_OK)
goto errout;
}
if (likely(mbuf->ol_flags & PKT_TX_IP_CKSUM)) {
ip4h->hdr_checksum = 0;
} else {
ip4_send_csum(ip4h);
}
return INET_HOOK(AF_INET, INET_HOOK_LOCAL_OUT, mbuf,
NULL, rt->port, ipv4_output);
}
省去部分无用代码,先看主逻辑
mbuf_nat6to4_len 重新计算三层 pkt 长度,这里可以看源码除了要减去 ipv6 header,还要减去 next header 长度,最后再加上 ipv4 header length
mbuf_6to4 函数把 ipv6 header 真正的变成 ipv4 header,看了内容就是正确的填充头部字段
然后调用 tcp_fnat_in_handler 填充 toa, 调整 seq
最后再调用 ipv4_output_fin2 走正常发送数据包流程
到这里,重点就是 mbuf_6to4,对于己建立连接的数据包,也是同样的流程
toa dpvs 做了哪些修改
struct tcpopt_ip4_addr {
uint8_t opcode;
uint8_t opsize;
__be16 port;
struct in_addr addr;
} __attribute__((__packed__));
struct tcpopt_ip6_addr {
uint8_t opcode;
uint8_t opsize;
__be16 port;
struct in6_addr addr;
} __attribute__((__packed__));
struct tcpopt_addr {
uint8_t opcode;
uint8_t opsize;
__be16 port;
uint8_t addr[16];
} __attribute__((__packed__));
首先 toa 结构体变了,以前只有一个 tcpopt_addr,并且 addr 字段是 4 字节大小,现在为了兼容变成了 16 字节。
/* insert toa right after TCP basic header */
toa = (struct tcpopt_addr *)(tcph + 1);
toa->opcode = TCP_OPT_ADDR;
toa->opsize = tcp_opt_len;
toa->port = conn->cport;
if (conn->af == AF_INET) {
struct tcpopt_ip4_addr *toa_ip4 = (struct tcpopt_ip4_addr *)(tcph + 1);
toa_ip4->addr = conn->caddr.in;
}
else {
struct tcpopt_ip6_addr *toa_ip6 = (struct tcpopt_ip6_addr *)(tcph + 1);
toa_ip6->addr = conn->caddr.in6;
}
利用结构体进行强转,然后给 tcp opt 赋值,这是填充 toa 操作。
toa kmod 内核做了哪些
首先 toa 是运行在 real server 上的,所以肯定进来的是 ipv4 数据,那么 nat64 的逻辑一定在 tcp_v4_syn_recv_sock_toa 里兼容。
static struct sock *
tcp_v4_syn_recv_sock_toa(struct sock *sk, struct sk_buff *skb,
struct request_sock *req, struct dst_entry *dst)
{
struct sock *newsock = NULL;
int nat64 = 0;
TOA_DBG("tcp_v4_syn_recv_sock_toa called\n");
/* call orginal one */
newsock = tcp_v4_syn_recv_sock(sk, skb, req, dst);
/* set our value if need */
if (NULL != newsock && NULL == newsock->sk_user_data) {
newsock->sk_user_data = get_toa_data(AF_INET, skb, &nat64);
sock_reset_flag(newsock, SOCK_NAT64);
if (NULL != newsock->sk_user_data) {
TOA_INC_STATS(ext_stats, SYN_RECV_SOCK_TOA_CNT);
#ifdef TOA_NAT64_ENABLE
if (nat64) {
struct toa_ip6_entry *ptr_ip6_entry = newsock->sk_user_data;
ptr_ip6_entry->sk = newsock;
toa_ip6_hash(ptr_ip6_entry);
newsock->sk_destruct = tcp_v6_sk_destruct_toa;
sock_set_flag(newsock, SOCK_NAT64);
}
#endif
}
else
TOA_INC_STATS(ext_stats, SYN_RECV_SOCK_NO_TOA_CNT);
TOA_DBG("tcp_v4_syn_recv_sock_toa: set "
"sk->sk_user_data to %p\n",
newsock->sk_user_data);
}
return newsock;
}
get_toa_data 生成 toa 数据,如果有 nat64 逻辑,sk_user_data 会赋值成 ptr_toa_entry
如果没有 nat64 逻辑,那么正常返回 toa_ip4_data
sock_reset_flag(newsock, SOCK_NAT64) 将 ipv4 socket 设置 nat64 标记
real server 如何获取 src ip
if (getsockopt(connfd, IPPROTO_IP, TOA_SO_GET_LOOKUP, &uaddr, &len) == 0) {
inet_ntop(AF_INET6, &uaddr.saddr, from, sizeof(from));
printf(" real client [%s]:%d\n", from, ntohs(uaddr.sport));
} else {
printf("client is %s\n", inet_ntoa(caddr.sin_addr));
}
上面是 real server 获取 src ip 的例子,这里看出来线上如果想用 nat64 还是要修改源码的,除非你不关心,但是话说回来,如果 nginx 入口做了 patch 后端也不需要改的。
toa kmod 调用 inet64_getname_toa 填充真正的 src ip,这里也没啥好说的。
小结
由于工作原因,dpvs 暂时不会再碰了。以后用到了再分析。
android nat64,dpvs学习笔记: 18 nat64 的实现相关推荐
- Hadoop学习笔记—18.Sqoop框架学习
Hadoop学习笔记-18.Sqoop框架学习 一.Sqoop基础:连接关系型数据库与Hadoop的桥梁 1.1 Sqoop的基本概念 Hadoop正成为企业用于大数据分析的最热门选择,但想将你的数据 ...
- Ext.Net学习笔记18:Ext.Net 可编辑的GridPanel
Ext.Net GridPanel 有两种编辑模式:编辑单元格和编辑行. 单元格编辑: 行编辑: 可以看出,单元格编辑的时候,只有单元格会进入编辑模式,而行编辑模式中则对编辑行的所有可编辑字段统一进行 ...
- 华为HCIA-datacom 学习笔记18——SDN与NFV概述
华为HCIA-datacom 学习笔记18--SDN与NFV概述 SDN与NFV概述 1.计算机时代的演进 1.1大型机 专门的硬件 专门的操作系统 专门的应用.(稳定性能好,但封闭) 1.2小型机 ...
- 2020-4-12 深度学习笔记18 - 直面配分函数 5 ( 去噪得分匹配,噪声对比估计NCE--绕开配分函数,估计配分函数)
第十八章 直面配分函数 Confronting the Partition Function 中文 英文 2020-4-8 深度学习笔记18 - 直面配分函数 1 ( 配分函数概念,对数似然梯度) 2 ...
- Python学习笔记18:实操案例十五(记录用户登录日志,模拟淘宝客服自动回复)
Python学习笔记18:实操案例十五(记录用户登录日志,模拟淘宝客服自动回复) 网课传送门:https://www.bilibili.com/video/BV1Sw411Z779?p=168& ...
- Android编译及编译脚本、Android构建基础学习笔记
Android编译及编译脚本.Android构建基础学习笔记 Android编译及编译脚本 概述 Android.mk转换成Android.bp 例子(简单Android.mk文件转Android.b ...
- 【计算机网络学习笔记18】防火墙技术、入侵检测技术
[计算机网络学习笔记18]防火墙技术.入侵检测技术 一.防火墙 防火墙 (firewall) :一种访问控制技术,通过严格控制进出网络边界的分组,禁止任何不必要的通信,从而减少潜在入侵的发生,尽可能降 ...
- Android开发技术学习笔记
目标:全面学习知识点,能理解原理,多实战练习并做笔记与总结学习心得 Android开发技术学习笔记记录如下: Android开发Google的官网https://developer.android.g ...
- Android之内核学习笔记
0.Android系统启动 <Android系统启动流程 -- bootloader> <The Android boot process from power on> < ...
- 影像组学视频学习笔记(18)-使用MRIcroGL软件格式转换、勾画ROI、Li‘s have a solution and plan.
本笔记来源于B站Up主: 有Li 的影像组学系列教学视频 本节(18)主要介绍: 使用MRIcroGL软件进行格式转换.勾画ROI并保存为mask文件 MRIcroGL 是一款免费.开源的轻量级软件: ...
最新文章
- numpy.argwhere 返回的为索引值的array
- server之后安装ssms失败 安装sql_关于SQL安装失败的解决方法
- java thread signal_java — Thread之CountDownLatch的使用
- sqlserver关键字
- 【Java线程池】Java线程池汇总,看这一篇文章就够了
- Linux Shell 中 ()、(())、[]、[[]]、{} 的作用
- Dynamics Axapta的B/S解决之道(一)
- ftp 服务器的主动模式和被动模式
- 一文读懂斗鱼上市:直播江湖风云再起,强行盈利背后的艰难上市路
- 新闻发布系统——网站发布
- 软件测试/测试开发丨Docker 容器技术与常用命令
- python 设置excel单元格式,Python xlwt设置excel单元格字体及格式
- C++编程基础入门知识(最详细建议收藏)
- 被互联网大厂空运到印度的年轻人
- 电路中滤波电容和退耦电容_怎么分清滤波电容、去耦电容、旁路电容
- zabbix监控nginx状态,显示状态active,accepts,handled,requests
- windows 可以禁用的服务盘点
- Access denied for user ''@'localhost' (using password: NO)之idea坑~
- jquery转义html字符串,使用jQuery转义HTML字符串
- Windows11家庭版开启来宾账户/Windows11家庭版显示本地组策略