• haproxy 思考
  •   通过代理服务器在两个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 ", 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

  • 相关阅读:
    flutter RN taro选型思考
    搜索、引擎、优化、营销(点位思考)
    最新思考的问题
    深度思考:不断逼近问题的本质
    人类的问题是欲望,人类的工具是思考
    “猜画小歌”的一些细节和思考
    关于git pull机制和游戏开发热更新思考
    前端优化带来的思考,浅谈前端工程化
    谢惠民恽自求易法槐钱定边数学分析习题课讲义第1~13章思考题练习题参考解答
    关于方法的几点思考
  • 【推广】 阿里云小站-上云优惠聚集地(新老客户同享)更有每天限时秒杀!
    【推广】 云服务器低至0.95折 1核2G ECS云服务器8.1元/月
    【推广】 阿里云老用户升级四重礼遇享6.5折限时折扣!
  • 原文地址:https://www.cnblogs.com/codestack/p/13938030.html
走看看 - 开发者的网上家园