这几天 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 的实现相关推荐

  1. Hadoop学习笔记—18.Sqoop框架学习

    Hadoop学习笔记-18.Sqoop框架学习 一.Sqoop基础:连接关系型数据库与Hadoop的桥梁 1.1 Sqoop的基本概念 Hadoop正成为企业用于大数据分析的最热门选择,但想将你的数据 ...

  2. Ext.Net学习笔记18:Ext.Net 可编辑的GridPanel

    Ext.Net GridPanel 有两种编辑模式:编辑单元格和编辑行. 单元格编辑: 行编辑: 可以看出,单元格编辑的时候,只有单元格会进入编辑模式,而行编辑模式中则对编辑行的所有可编辑字段统一进行 ...

  3. 华为HCIA-datacom 学习笔记18——SDN与NFV概述

    华为HCIA-datacom 学习笔记18--SDN与NFV概述 SDN与NFV概述 1.计算机时代的演进 1.1大型机 专门的硬件 专门的操作系统 专门的应用.(稳定性能好,但封闭) 1.2小型机 ...

  4. 2020-4-12 深度学习笔记18 - 直面配分函数 5 ( 去噪得分匹配,噪声对比估计NCE--绕开配分函数,估计配分函数)

    第十八章 直面配分函数 Confronting the Partition Function 中文 英文 2020-4-8 深度学习笔记18 - 直面配分函数 1 ( 配分函数概念,对数似然梯度) 2 ...

  5. Python学习笔记18:实操案例十五(记录用户登录日志,模拟淘宝客服自动回复)

    Python学习笔记18:实操案例十五(记录用户登录日志,模拟淘宝客服自动回复) 网课传送门:https://www.bilibili.com/video/BV1Sw411Z779?p=168& ...

  6. Android编译及编译脚本、Android构建基础学习笔记

    Android编译及编译脚本.Android构建基础学习笔记 Android编译及编译脚本 概述 Android.mk转换成Android.bp 例子(简单Android.mk文件转Android.b ...

  7. 【计算机网络学习笔记18】防火墙技术、入侵检测技术

    [计算机网络学习笔记18]防火墙技术.入侵检测技术 一.防火墙 防火墙 (firewall) :一种访问控制技术,通过严格控制进出网络边界的分组,禁止任何不必要的通信,从而减少潜在入侵的发生,尽可能降 ...

  8. Android开发技术学习笔记

    目标:全面学习知识点,能理解原理,多实战练习并做笔记与总结学习心得 Android开发技术学习笔记记录如下: Android开发Google的官网https://developer.android.g ...

  9. Android之内核学习笔记

    0.Android系统启动 <Android系统启动流程 -- bootloader> <The Android boot process from power on> < ...

  10. 影像组学视频学习笔记(18)-使用MRIcroGL软件格式转换、勾画ROI、Li‘s have a solution and plan.

    本笔记来源于B站Up主: 有Li 的影像组学系列教学视频 本节(18)主要介绍: 使用MRIcroGL软件进行格式转换.勾画ROI并保存为mask文件 MRIcroGL 是一款免费.开源的轻量级软件: ...

最新文章

  1. numpy.argwhere 返回的为索引值的array
  2. server之后安装ssms失败 安装sql_关于SQL安装失败的解决方法
  3. java thread signal_java — Thread之CountDownLatch的使用
  4. sqlserver关键字
  5. 【Java线程池】Java线程池汇总,看这一篇文章就够了
  6. Linux Shell 中 ()、(())、[]、[[]]、{} 的作用
  7. Dynamics Axapta的B/S解决之道(一)
  8. ftp 服务器的主动模式和被动模式
  9. 一文读懂斗鱼上市:直播江湖风云再起,强行盈利背后的艰难上市路
  10. 新闻发布系统——网站发布
  11. 软件测试/测试开发丨Docker 容器技术与常用命令
  12. python 设置excel单元格式,Python xlwt设置excel单元格字体及格式
  13. C++编程基础入门知识(最详细建议收藏)
  14. 被互联网大厂空运到印度的年轻人
  15. 电路中滤波电容和退耦电容_怎么分清滤波电容、去耦电容、旁路电容
  16. zabbix监控nginx状态,显示状态active,accepts,handled,requests
  17. windows 可以禁用的服务盘点
  18. Access denied for user ''@'localhost' (using password: NO)之idea坑~
  19. jquery转义html字符串,使用jQuery转义HTML字符串
  20. Windows11家庭版开启来宾账户/Windows11家庭版显示本地组策略

热门文章

  1. opencv studay3
  2. Datax-HdfsWriter如何实现支持decimal类型数据写入
  3. 芯片春秋: ARM前世今生
  4. 不同时区时间换算_世界时区划分时差在线查询计算_时间换算器
  5. VSCode搭建STM32开发环境
  6. win7安装java环境(详细步骤)
  7. 书_阿朱_好好看书[转]
  8. 高性能计算服务器计算费价格,高性能计算云服务器价格表
  9. 内存卡数据恢复,如何从内存卡恢复数据
  10. oracle实时备份归档日志实现异机恢复丢失数据最小化