通过代理服务器在两个TCP接连之间转发数据是一个常见的需求,然后通常部署的时候涉及到(虚拟)服务器、真实服务器、防护设备。涉及到多个ip地址相关联,改动一个IP就需要修改配置。

比如反向服务器部署的时候, 真实服务器ip 改动就会联动反向代理关系改动,比较麻烦。所以当然是将真实服务器Ip 对外最好, 修改Ip 只会去改动DNS,但是中间的网络设备、安全设备怎样加入呢?

此时就有了透传用户ip的方法。client-----socket1-----haproxy-----socket2---server; 即client 和haproxy建立tcp链接, haproxy和server 建立tcp链接转发client请求以及响应,haproxy完成安全设备等业务处理。client-----socket1-----haproxy-----socket2---server;---根据这个数据流可以看到haproxy实际就是串联在client和server 中的设备, 其负责转发报文--报文的IP PORT等信息不会改变。所以就需要将client的IP透传到Haproxy。技术的话 应该是非常成熟了!

proxy的配置:

/sbin/iptables -F
/sbin/iptables -t mangle -N DIVERT
/sbin/iptables -t mangle -A PREROUTING -p tcp -m socket -j DIVERT
/sbin/iptables -t mangle -A DIVERT -j MARK --set-mark 1
/sbin/iptables -t mangle -A DIVERT -j ACCEPT
/sbin/ip rule add fwmark 1 lookup 100
/sbin/ip route add local 0.0.0.0/0 dev lo table 100

将所有client发往server的tcp包,重定向到本地设备haproxy回环接口(lo)上,由TProxy内核补丁来对这些网络包进行处理,此时client和lo接口上的listen socket三次握手建立tcp隧道流。然后拿到tcp流真实源IP-srcip 、目的IP-dstip 、源端口-srcport 、目的端口-dstport,进而使用srcip srcport 同后端server(dstip dstport)建立tcp链接 。

问题1:porxy设备怎样拦截tcp流将报文送到lo接口,同时怎样建立socket-----socket的三次握手

根据以前的文章TCP/IP协议栈文章可知:

  • 数据包在各层传递过程中, 在linux内核中统一表示为一个结构:struct sk_buff,简写为skb;
  • skb在TCP/IP协议栈处理时, 由skb->sk 标明该数据包对应的应用层socket套接字是哪个;tcp/udp层将根据skb->sk这个信息,将网络包放入到某个应用进程创建的套接字中,供应用层处理该网络包

所以关键就是在处理目的地为非本地ip网络数据包时,将skb中的sk指定为haproxy进程创建的socket套接字。

通过iptables配置ip层的路由规则, 将所有基于tcp的网络包(skb),打上标记(–set-mark 1),并将这些打了标记的包, 重定向到本地环路

/sbin/iptables -F
/sbin/iptables -t mangle -N DIVERT
/sbin/iptables -t mangle -A PREROUTING -p tcp -m socket -j DIVERT
/sbin/iptables -t mangle -A DIVERT -j MARK --set-mark 1
/sbin/iptables -t mangle -A DIVERT -j ACCEPT
/sbin/ip rule add fwmark 1 lookup 100
/sbin/ip route add local 0.0.0.0/0 dev lo table 100

proxy从本地环路上抓取网络包(skb),然后提取出网络包中的源ip/port,目的ip/port,根据这些信息,从内核中查找出对应的套接字句柄sk,然后进行赋值: skb->sk = sk

tproxy处理逻辑代码如下:

static unsigned int
tproxy_tg4(struct net *net, struct sk_buff *skb, __be32 laddr, __be16 lport,
u_int32_t mark_mask, u_int32_t mark_value)
{
const struct iphdr *iph = ip_hdr(skb);
struct udphdr _hdr, *hp;
struct sock *sk;
////获得传输头
hp = skb_header_pointer(skb, ip_hdrlen(skb), sizeof(_hdr), &_hdr);
if (hp == NULL)
return NF_DROP; /* check if there's an ongoing connection on the packet
* addresses, this happens if the redirect already happened
* and the current packet belongs to an already established
* connection */
//根据数据包的内容,向tcp已建立的队列查找skb属于的struct sock
//如果客户端与代理服务器已经建立连接,该数据包属于的sock将存在
sk = nf_tproxy_get_sock_v4(net, iph->protocol,
iph->saddr, iph->daddr,
hp->source, hp->dest,
skb->dev, NFT_LOOKUP_ESTABLISHED); laddr = tproxy_laddr4(skb, laddr, iph->daddr);
if (!lport)
lport = hp->dest; /* UDP has no TCP_TIME_WAIT state, so we never enter here */
if (sk && sk->sk_state == TCP_TIME_WAIT)
/* reopening a TIME_WAIT connection needs special handling */
sk = tproxy_handle_time_wait4(net, skb, laddr, lport, sk);
else if (!sk) {
/* no, there's no established connection, check if
* there's a listener on the redirected addr/port
laddr lport 指向hapoxy 进程bind的ip以及port 也就是为了找到其listen socket 比如为
·······127。0.0.1 80 端口
    使用此socket建立三次握手, 但是建立新的socket的时候 会用 ip->saddr ip->daddr 以及hp->source hp->dest 去new 一个新的socket并加入tcp_established 链表
*/
sk = nf_tproxy_get_sock_v4(net, iph->protocol,
iph->saddr, laddr,
hp->source, lport,
skb->dev, NFT_LOOKUP_LISTENER);
}
/* NOTE: assign_sock consumes our sk reference */
if (sk && tproxy_sk_is_transparent(sk)) {
//运行至此,说明客户端已经与服务器端建立了三次握手,即sk存在;
//则通过nf_tproxy_assign_sock函数,将当前数据包的skb与代理服务器的监听socket建立联系,即skb->sk = sk
//最后,将数据包打上比较,待策略路由转发到loobackshang
/* This should be in a separate target, but we don't do multiple
targets on the same rule yet */
skb->mark = (skb->mark & ~mark_mask) ^ mark_value; pr_debug("redirecting: proto %hhu %pI4:%hu -> %pI4:%hu, mark: %x\n",
iph->protocol, &iph->daddr, ntohs(hp->dest),
&laddr, ntohs(lport), skb->mark); nf_tproxy_assign_sock(skb, sk);
return NF_ACCEPT;
}

问题2: haproxy 如何和真实server 建立TCP链接流

  • 既然要建立tcp流,那肯定需要知道client server 两端ip 端口
  • client端ip 不是本机IP,怎样bind一个socket

通过:

  • getpeername(fd, sa, &salen)  
  • getsockname(fd, sa, &salen); 
  • getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, sa, &salen)

获取client 以及server 的ip port

get using getsockname() and getpeername() :
- address family (AF_INET for IPv4, AF_INET6 for IPv6, AF_UNIX)
- socket protocol (SOCK_STREAM for TCP, SOCK_DGRAM for UDP)
- layer 3 source and destination addresses
- layer 4 source and destination ports if any

通过: 将socket设置为IP_TRANSPARENT或IP_FREEBIND 即可bind 非本地ip port到socket---

  • setsockopt(fd, SOL_IP, IP_TRANSPARENT, (char *) &one, sizeof(one))
  • setsockopt(fd, SOL_IP, IP_FREEBIND, (char *) &one, sizeof(one))

--开启IP_TRANSPARENT选项, 并绑定用户ip为源ip

参考:

https://www.nginx.com/blog/ip-transparency-direct-server-return-nginx-plus-transparent-proxy/

http://people.netfilter.org/hidden/nfws/nfws-2008-tproxy_slides.pdf

对于patch可以参考:

https://files.cnblogs.com/files/codestack/tproxy4-2.6.23-200709262209.tar

思考:ip_rcv_finish 中,是怎样将数据包投递到上层协议以及指定接口 ;而不是forwarding-转发

- ip_route_input_noref
- ip_route_input_rcu
- ip_route_input_slow
- fib_lookup
- fib_table_lookup
- res->type = fa->fa_type;
- if (res->type == RTN_LOCAL) {
...
goto local_input;
}
- skb_dst_set_noref(skb, &rth->dst);
- rth = rt_dst_alloc(l3mdev_master_dev_rcu(dev) ? : net->loopback_dev,
flags | RTCF_LOCAL, res->type,
IN_DEV_CONF_GET(in_dev, NOPOLICY), false, do_cache);
- if (flags & RTCF_LOCAL)
rt->dst.input = ip_local_deliver;

通过查找路由表确定 res-type 的类型为 RTN_LOCAL,goto 到 local_input,进而调用 rt_dst_alloc,形参参数 (flag & RTCF_LOCAL) == true,设置了 rt->dst.input 是 ip_local_deliver

参考:

https://patchwork.ozlabs.org/project/netdev/patch/1226572624.7164.11.camel@bzorp.balabit/

https://blog.chionlab.moe/2018/03/31/full-cone-nat-with-linux-2/

https://patchwork.ozlabs.org/project/netdev/patch/1226572624.7164.11.camel@bzorp.balabit/

http://blog.chinaunix.net/uid-20786208-id-5145525.html

haproxy 思考的更多相关文章

  1. haproxy内存管理-free_list原理

    haproxy的内存管理中,通过pool_head->free_list,存储空闲内存块,free_list是个二级指针,却把空闲内存块都串了起来,没有用next,pre之类的指针.怎么实现的? ...

  2. Redis+Twemproxy+HAProxy集群(转) 干货

    原文地址:Redis+Twemproxy+HAProxy集群  干货 Redis主从模式 Redis数据库与传统数据库属于并行关系,也就是说传统的关系型数据库保存的是结构化数据,而Redis保存的是一 ...

  3. HAProxy的独门武器:ebtree

    1. HAProxy和ebtree简介 HAProxy是法国人Willy Tarreau个人开发的一个开源软件,目标是应对客户端10000以上的同时连接,为后端应用服务器.数据库服务器提供高性能的负载 ...

  4. HAProxy+Varnish+LNMP实现高可用负载均衡动静分离集群部署

    HAProxy高可用负载均衡集群部署 基本信息: 系统平台:VMware WorkStation 系统版本: CentOS Linux release 7.2.1511 (Core) 内核版本: 3. ...

  5. 领域驱动和MVVM应用于UWP开发的一些思考

    领域驱动和MVVM应用于UWP开发的一些思考 0x00 起因 有段时间没写博客了,其实最近本来是根据梳理的MSDN上的资料(UWP开发目录整理)有条不紊的进行UWP学习的.学习中有了心得体会或遇到了问 ...

  6. 关于面试题 Array.indexof() 方法的实现及思考

    这是我在面试大公司时碰到的一个笔试题,当时自己云里雾里的胡写了一番,回头也曾思考过,最终没实现也就不了了之了. 昨天看到有网友说面试中也碰到过这个问题,我就重新思考了这个问题的实现方法. 对于想进大公 ...

  7. 关于 CSS 反射倒影的研究思考

    原文地址:https://css-tricks.com/state-css-reflections 译者:nzbin 友情提示:由于演示 demo 的兼容性,推荐火狐浏览.该文章篇幅较长,内容庞杂,有 ...

  8. 关于.NET参数传递方式的思考

    年关将近,整个人已经没有了工作和写作的激情,估计这个时候很多人跟我差不多,该相亲的相亲,该聚会喝酒的聚会喝酒,总之就是没有了干活的心思(我有很多想法,但就是叫不动我的手脚,所以我只能看着别人在做我想做 ...

  9. 使用NUnit为游戏项目编写高质量单元测试的思考

    0x00 单元测试Pro & Con 最近尝试在我参与的游戏项目中引入TDD(测试驱动开发)的开发模式,因此单元测试便变得十分必要.这篇博客就来聊一聊这段时间的感悟和想法.由于游戏开发和传统软 ...

随机推荐

  1. android init.rc语法

    转自:http://www.cnblogs.com/nokiaguy/p/3164799.html init.rc由如下4部分组成. 动作(Actions) 命令(Commands) 3. 服务(Se ...

  2. 多测师讲解接口测试 —jmeter接数据库(004)_高级讲师肖sir

    1.连接数据库jar包 2. 3. jdbc:mysql://192.168.153.131:3306/baoan?zeroDateTimeBehavior=convertToNull&all ...

  3. Jmeter之参数化函数助手__CSVRead

    1.在Tool->函数对话框中选择__CSVRead,2处填写测试用例的文档地址(测试用例要以csv格式保存),3处是测试用例中参数的位置,第一栏参数的CSV文件列号填0,第二栏参数的CSV文件 ...

  4. spring boot:实现图片文件上传并生成缩略图(spring boot 2.3.1)

    一,为什么要给图片生成缩略图? 1, 用户上传的原始图片如果太大,不能直接展示在网站页面上, 因为不但流费server的流量,而且用户打开时非常费时间, 所以要生成缩略图. 2,服务端管理图片要注意的 ...

  5. Storm入门教程汇总

    http://www.aboutyun.com/thread-8059-1-1.html

  6. 【Azure云服务 Cloud Service】Cloud Service的实例(VM)中的服务描述Software Protection 与 Windows Defender, 如何设置Windows Defender Antivirus服务

    1)Software Protection 与 Windows Defender是两个独立的服务.在Windows 服务中他们的描述分别为 Software Protection Enables th ...

  7. 使用pyenv实现python多版本共存

    背景 如果是Ubuntu等桌面系统,都已经更新到了Python较新的版本.但多数生产环境使用的还是红帽系统. CentOS7默认还是Python2.7,而开发环境如果是高版本Python就带来了问题. ...

  8. Linux入门到放弃之六《磁盘和文件系统管理三》

    设置磁盘配额 对之前创建的逻辑卷设置磁盘配额,要求用户student对该逻辑卷 容量的软限制是:5G,硬限制是7G,文件个数软限制为:25个,硬限制为30个. (1)首先对/etc/fstab文件进行 ...

  9. dd 在度娘上看到的一个大牛的《背包九讲》 (:

    P01: 01背包问题 题目 有N件物品和一个容量为V的背包.第i件物品的费用是c[i],价值是w[i].求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大. 基本思路 这是最 ...

  10. MacOs/Liunx主机搭建windows平台双机调试环境

    0x00 前言 本文的主要试用对象是Mac OS/Linux用户,对于想调试windows内核相关的一些东西时,需要搭建双机调试环境的一些记录.另外对于本机是windows的用户也完全试用,windo ...