linux下TOA组件开发

2020-12-18 16:27:56 浏览数 (1)

TOA从哪里来?

我们知道LVS有三种负载均衡模式:DR,NAT和Tunnel,但是这三种模式都有各自不同的缺陷,比如DR和NAT要求virtual server和real server位于同一子网内,Tunnel搭建繁杂。因此阿里的大牛为了解决灵活部署的问题,开发出了第四种模式FULLNAT模式。

如图所示,FULLNAT模式是NAT的一种扩展,不仅仅将目的IP地址进行替换,同时还将源IP地址进行了替换,这样做的好处是将real server从virtual server的后端网络中解放出来,不再要求与virtual server位于同一子网内。但是这种模式也带了一个问题,那就是客户端的真实IP地址无法获取,这对一些需要知道客户端真实IP的后台服务带来了麻烦。这时候TOA应运而生。

TOA实际上是TCP的一个option字段,占8个字节,其中包括code字段0xC8,长度字段,和value字段:4字节的客户端的IP地址和两字节的端口号。服务器端打上FULLNAT补丁后,通过调用系统函数getsockopt取到。

怎么带上TOA呢?

为了支持TOA,FULLNAT直接修改了内核代码,因此我们需要打上内核补丁,重新编译内核,这会消耗不少时间和精力。有时候有一些业务,比如TCP的转发代理服务,并不需要使用FULLNAT,但是希望使用TOA的特性来透传客户端IP,而不得不重新编译内核。因此我们希望开发一个TOA的组件,直接以内核模块的形式提供给业务方进行配置。

基本原理及实现

如上图所示:

1. 通过系统调用函数setsockopt将客户端的IP和端口号设置到IP首部的选项字段中去临时存放。为了方便使用者调用,我们将这个步骤简单封装成了一个调用接口,即settoaopt。

static int settcptoa(int sockfd, unsigned short port, struct in_addr* addrptr)

2. 设置钩子函数,netfilter有五个hook点,如下图所示,这里选取local_out点埋下钩子,该hook点位于IP报文封装之后,分片之前,具有完整的IP报文。

3. 在钩子函数里实现IP首部选项字段的解析,拿到我们第一步设置的TOA选项,进行移位操作,将后面TCP首部前移,然后将TOA插入空出来的空间中,这里要求第一步里面设置option时按照标准的TOA格式进行设置,防止移动过程中出现内存空隙。具体的流程如下图所示

这里面有两个点需要注意一下:

1. 根据IP首部的协议字段过滤出TCP的报文,再解析IP首部,不用针对每个IP报文都进行解析;

2. TCP的首部长度是有限的,最大60个字节,需要先判断TCP的长度够不够再进行移动。如果不够,则直接透传。

其中IP首部校验和接口函数:

__inline__ void ip_send_check(struct iphdr *iph)

TCP首部校验和接口函数:

__wsum csum_partial(const void *buff, int len, __wsum sum)

__sum16 csum_tcpudp_magic(__be32 saddr, __be32 daddr, unsigned short len, unsigned short proto, __wsum sum)

至此TOA组件的主要步骤全部完成,最终落地的是一个内核模块,插入内核后,通过接口settoaopt完成设置。

0 人点赞