图四
如果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
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
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
其中10.10.0.0/16替换成client的网段,10.10.46.198替换成LB的IP地址
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