智汇华云 | 通过TProxy实现haproxy 透传用户IP

Posted 华云

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了智汇华云 | 通过TProxy实现haproxy 透传用户IP相关的知识,希望对你有一定的参考价值。


智汇华云 | 通过TProxy实现haproxy 透传用户IP

如今,实现IP透传最常见的技术便是linux TProxy技术,那么在IP透传过程中需要具体做哪些事情?本期华云数据“智汇华云”专栏将为带来“通过TProxy实现haproxy 透传用户IP”


本期讲解人

智汇华云 | 通过TProxy实现haproxy 透传用户IP


廖桥生

华云数据网络开发工程师



1 IP透传


在所有代理类型的应用中都一个共同的问题,就是后端的目标服务器上看到的连接的源IP都不再是客户端原始的IP,而是前端的代理服务器的IP


以haproxy来说,假设你有两台后端Web服务器提供服务,前端使用haproxy作为负载均衡设备。所有客户端的HTTP访问会先到达haproxy,haproxy作为代理,将这些请求按照指定的负载均衡规则分发到后边的两台Web服务器上。这个操作本身没有任何问题,因为haproxy就应该是这么工作的。但是对于某些对于客户端的访问IP有限制的敏感应用,问题来了: 后端服务器上的ACL无法限制哪些IP可以访问,因为在它看来,所有连接的源IP都是haproxy的IP


智汇华云 | 通过TProxy实现haproxy 透传用户IP

图一

(图一例子中后端服务器只知道源IP为haproxy服务器的IP:192.168.1.4,而不知道客户端的真实IP)


启用IP透传后,haproxy转发客户端请求到后端服务时,源IP为客户端原始的IP了。


智汇华云 | 通过TProxy实现haproxy 透传用户IP

图二

(图二例子中后端服务器收到请求的源IP为客户端的真实IP,而不是haproxy服务器的IP:192.168.1.4)


实现IP透传最常见的技术便是linux TProxy技术,haproxy实现IP透传的技术之一便是linux TProxy。


2 TProxy技术


TProxy是linux内核支持透明代理的一种技术,TProxy允许代理"模仿"用户的访问IP,就像代理设备不存在一样,当HTTP请求到达后端的Web服务器时,在后端服务器上用netstat查看连接的时候,看到连接的源IP就是用户的真实IP,而不是haproxy的IP,TProxy名字中的T表示的就是transparent(透明)。从2.6.28以后TProxy已经进入官方内核,因此配置haproxy透传IP时需要注意你的linux内核版本。


2.1 TProxy主要功能


3. 重定向一部分经过路由选择的流量到本地路由进程(类似NAT中的REDIRECT)


2.2 TProxy如何工作?



1. / /* socket设置IP_TRANSPARENT标识 */  

2. setsockopt(fd,SOL_IP, IP_TRANSPARENT,&opt,sizeof(opt))  

3. /* 下句代码中的(struct sockaddr_in *)remote->sin_addr是客户端ip */  

4. ((struct sockaddr_in *)&bind_addr)->sin_addr = ((struct sockaddr_in *)remote)->sin_addr

5. /* 将socket bind到client ip */  

6. bind(fd, (struct sockaddr *)&bind_addr, get_addr_len(&bind_addr))  

8. connect(fd, (struct sockaddr *)&conn->addr.to, get_addr_len(&conn->addr.to)) 


Ø 如何重定向一部分经过路由选择的流量到本地路由进程?


1. 后端server修改路由规则,将目的地为用户IP的回包,路由给haproxy服务器;


2. 由图三所示,haproxy服务器收到后端server的回包后,在IP层, 通过netfilter/iptables将回包路由到本地环路lo,TProxy从本地环路上抓取网络包(skb),从网络包提取出网络包中的源ip/port,目的ip/port,根据这些信息,从内核中查找出对应的套接字句柄sk,然后进行赋值:将该回包的skb->sk = sk(sk为haproxy进程创建的对应套接字),从而让tcp层能够根据skb->sk, 将该回包递交给haproxy进程进行处理,最终返回给客户端

 

智汇华云 | 通过TProxy实现haproxy 透传用户IP

图三


一些背景知识:


1. linux内核分层处理后端server返回的网络包,网络包经由链路层(网卡驱动)、IP层、tcp层,最终递交到应用层的haproxy


2. 数据包在各层传递过程中, 在linux内核中,统一表示为一个结构:struct sk_buff,或称之为socket buffer,简写为skb;


3. skb在递交tcp层时, 由skb->sk 标明该网络包对应的应用层socket套接字是哪个,tcp层将根据skb->sk这个信息,将网络包放入到某个应用进程创建的套接字中,供应用层处理该网络包


4. 为此,处理“目的地为非本地IP网络包”的关键, 在于在IP层,将skb中的sk,指定为haproxy进程创建的socket套接字


3 haproxy IP透传源码解析


3.1 确认haproxy的版本


通过haproxy -vv确认所运行的haproxy编译时指定USE_LINUX_TPROXY=1,如图四所示。

 

图四


如果OPTIONS中没有USE_LINUX_TPROXY=1,需要重新编译haproxy打开透传用户IP的代码,haproxy编译步骤如下:


1. wget http://www.haproxy.org/download/1.5/src/haproxy-1.5.14.tar.gz  

2. tar zxvf haproxy-1.5.14.tar.gz  

3. cd haproxy-1.5.14  

4. yum install gcc gcc-c++ autoconf automake -y  

5. make TARGET=linux2628 arch=x86_64 USE_LINUX_TPROXY=1  

6. make install 


3.2 haproxy IP透传如何透传用户IP


这一步其实非常简单。haproxy进程只需要拿到用户IP,然后在创建到后端server的tcp连接时,做三件件事情:


1. 将要启用 IP透传的后端服务器组对应的 backend 字段部分增加配置 "source 0.0.0.0 usesrc clientip",这样会将flags设置为1,使能IP透传,详细配置如下:


1. backend XXXX  

2.     mode tcp  

3.     balance XXX  

4.     source 0.0.0.0 usesrc clientip  

5.     . . .  


2. 创建和后端server通信的socket,并调用setsockopt函数, 将socket设置为IP_TRANSPARENT或IP_FREEBIND


3. 调用bind函数,将用户IP绑定到该socket,绑定后后端server看到的该tcp连接IP,即为用户源IP。


haproxy相关代码:


1. int tcp_bind_socket(int fd, int flags, struct sockaddr_storage *local, struct sockaddr_storage *remote)  

2. {  

3. struct sockaddr_storage bind_addr;  

4. int foreign_ok = 0;  

5. int ret;  

6. static int ip_transp_working = 1;  

7. static int ip6_transp_working = 1;  

8.    

9.  switch (local->ss_family) {  

10. case AF_INET:  

11.      /* backend 字段部分增加配置 "source 0.0.0.0 usesrc clientip"后,flags变量值为1 */  

12.         if (flags && ip_transp_working) {  

13.             /* 调用setsockopt函数, 将socket设置IP_TRANSPARENT或IP_FREEBIND标识 */  

14.             if (0  

15. #if defined(IP_TRANSPARENT)  

16.                 || (setsockopt(fd, SOL_IP, IP_TRANSPARENT, &one, sizeof(one)) == 0)  

17. #endif  

18. #if defined(IP_FREEBIND)  

19.                 || (setsockopt(fd, SOL_IP, IP_FREEBIND, &one, sizeof(one)) == 0)  

20. #endif  

21. #if defined(IP_BINDANY)  

22.                 || (setsockopt(fd, IPPROTO_IP, IP_BINDANY, &one, sizeof(one)) == 0)  

23. #endif  

24. #if defined(SO_BINDANY)  

25.                 || (setsockopt(fd, SOL_SOCKET, SO_BINDANY, &one, sizeof(one)) == 0)  

26. #endif  

27.                 )  

28.                 foreign_ok = 1;  

29.             else  

30.                 ip_transp_working = 0;  

31.         }  

32.         break;  

33.     }  

34.     if (flags) {  

35.         memset(&bind_addr, 0, sizeof(bind_addr));  

36.         bind_addr.ss_family = remote->ss_family;  

37.         switch (remote->ss_family) {  

38.         case AF_INET:  

39.             if (flags & 1)  

40.             /* 因为flags变量值为1,这儿只会将客户端IP赋值给bind_addr */  

41.                 ((struct sockaddr_in *)&bind_addr)->sin_addr = ((struct sockaddr_in *)remote)->sin_addr;  

42.             if (flags & 2)  

43.                 ((struct sockaddr_in *)&bind_addr)->sin_port = ((struct sockaddr_in *)remote)->sin_port;  

44.             break;  

45.  default:  

46.             /* we don't want to try to bind to an unknown address family */  

47.             foreign_ok = 0;  

48.         }  

49.     }  

50.     setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));  

51.     if (foreign_ok) {  

52.         if (is_inet_addr(&bind_addr)) {  

53.         /* 这儿会将客户端IP绑定到该socket */  

54.             ret = bind(fd, (struct sockaddr *)&bind_addr, get_addr_len(&bind_addr));  

55.             if (ret < 0)  

56.                 return 2;  

57.         }  

58.     }  

59.     else {  

60.         if (is_inet_addr(local)) {  

61.             ret = bind(fd, (struct sockaddr *)local, get_addr_len(local));  

62.             if (ret < 0)  

63.                 return 1;  

64.         }  

65.     }  

66.     if (!flags)  

67.         return 0;  


3.2 后端server如何返回给haproxy服务器


由于后端收到的包源IP为client IP,默认无法返回给haproxy服务器,因此需要配置haproxy服务器的IP设置client IP端的网关IP,后端server才能将回包返回给haproxy服务器


1. route add -net 10.10.0.0/16 gw 10.10.46.198  


3.3 haproxy 服务器将非本地IP的IP包路由到本地环路


配置iptable规则,将发往非haproxy服务器本地(目的IP为客户端源IP)的tcp网络包打上标记(–set-mark 1)


1. iptables -t mangle -F  

2. iptables -t mangle -N DIVERT,  

3. iptables -t mangle -A PREROUTING -p tcp -m socket -j DIVERT  

4. iptables -t mangle -A DIVERT -j MARK --set-mark 1,  

5. iptables -t mangle -A DIVERT -j ACCEPT


haproxy 上配置路由将标记(mark 1)的包返回到本地环路lo


1. ip rule del from all fwmark 0x1 lookup 100  

2. ip rule add fwmark 1 lookup 100  

3. ip route add local 0.0.0.0/0 dev lo table 100 


4 总结


综上所述,通过TProxy实现haproxy IP透传,需要做如下四件事情:

1. 确认linux内核版本大于2.6.28,确认haproxy编译时配置了USE_LINUX_TPROXY=1选项

2. 修改haproxy配置,需要启用IP透传的后端服务器组对应的backend 字段部分增加配置 "source 0.0.0.0 usesrc clientip"

3. 后端服务器配置路由规则,将客户端IP端的网关配置haproxy服务器IP

4. haproxy服务器上添加iptables规则和路由策略,将后端服务器的回包路由给本地环路lo


5 参考文献:

http://www.360doc.com/content/13/0821/17/13047933_308812287.shtml

https://blog.csdn.net/frockee/article/details/78641188


相关阅读



点击原文链接,了解华云数据更多信息


以上是关于智汇华云 | 通过TProxy实现haproxy 透传用户IP的主要内容,如果未能解决你的问题,请参考以下文章

智汇华云 | 浅谈微服务架构下的服务发现机制

智汇华云 | Ceph的正确玩法之SSD作为HDD的缓存池

智汇华云 | 如何利用Jenkins+ansibe+maven(java)方式自动化更新微服务

智汇华云 | ArSDN之分布式路由及浮动IP简介

智汇华云 | ArSDN之分布式路由及浮动IP简介

智汇华云 | ArSDN之分布式路由及浮动IP简介