ip_vs初识

概述


LVS是章文嵩博士十几年前的开源项目,已经被何如linux kernel 目录十几年了,可以说是国内最成功的kernle 开源项目, 在10多年后的今天,因为互联网的高速发展LVS得到了极大的应用, 是当今国内互联网公司,尤其是大型互联网公司的必备武器之一, 从这一点上来说,LVS名副其实。

搞了这么多年linux 网络开发维护, 由于一直偏通信方向,自己竟然从来没有读过ipvs的代码,不能不说是一个遗憾。这两天花时间研究了一下LVS,阅读LVS的kernel与ipvsadm的代码,终于搞清楚了其工作原理与细节问题,感觉队LVS的认识提高了一个等级,再次感谢章博。

LVS架构以及代码分析

LVS是非常典型的【控制面 + 数据面】的网络体系架构。

控制面


ipvsadm 作为控制面的工具运行在用户空间,其本身是一个非常简单的linux命令。ipvsadm可以使用两种方式和内核进行通信 : netlink 与 raw socket,这两种方式我们在这里不做详细介绍。现在基本上默认都使用netlink的方式,这也是现在绝大多数的用户空间与内核空间通信所选择的方式。ipvsadm的工作原理和代码都非常简单 : 分析命令行,将命令行信息打包进nl_msg,即netlink与内核通信的数据结构,然后发给内核即可。

数据面


LVS的数据面完全在linux kernel中实现, 并且是实现在netfilter的框架中, 对netfilter的介绍并不在本文范围之内。

LVS本身以内核核心模块的方式存在,我们首先来看其初始化函数【static int __init _vs_init(void)】做了些什么 : 

    ---> ip_vs_control_init()    :    初始化virtual server HASH表与virtual server firewall HASH表,注册netdevice 的notification 处理相应事件 ip_vs_dst_notifier
    ---> ip_vs_protocol_init()  :    注册协议处理结构,目前支持tcp,udp,sctp,ah,esp
    ---> ip_vs_conn_init()        :   初始化connection HASH表,默认HASH表头4096个
    ---> register_pernet_subsys()    :   注册namespace子系统
    ---> register_pernet_device()     :   注册namespace device 子系统
    ---> nf_register_hooks()    :    注册LVS处理函数到netfilter框架
    ---> ip_vs_register_nl_ioctl()    :    注册netlink处理函数与set/getsockopt处理函数

核心处理流程在netfilter框架中(我们可以暂时不关注namespace相关的操作)。

以IPV4为例,我们看看LVS都在netfilter框架中做了什么 : 

1829 static struct nf_hook_ops ip_vs_ops[] __read_mostly = {
1830         /* After packet filtering, change source only for VS/NAT */
1831         {
1832                 .hook           = ip_vs_reply4,
1833                 .owner          = THIS_MODULE,
1834                 .pf             = NFPROTO_IPV4,
1835                 .hooknum        = NF_INET_LOCAL_IN,
1836                 .priority       = NF_IP_PRI_NAT_SRC - 2,
1837         },
1838         /* After packet filtering, forward packet through VS/DR, VS/TUN,
1839          * or VS/NAT(change destination), so that filtering rules can be
1840          * applied to IPVS. */
1841         {
1842                 .hook           = ip_vs_remote_request4,
1843                 .owner          = THIS_MODULE,
1844                 .pf             = NFPROTO_IPV4,
1845                 .hooknum        = NF_INET_LOCAL_IN,
1846                 .priority       = NF_IP_PRI_NAT_SRC - 1,
1847         },
1848         /* Before ip_vs_in, change source only for VS/NAT */
1849         {
1850                 .hook           = ip_vs_local_reply4,
1851                 .owner          = THIS_MODULE,
1852                 .pf             = NFPROTO_IPV4,
1853                 .hooknum        = NF_INET_LOCAL_OUT,
1854                 .priority       = NF_IP_PRI_NAT_DST + 1,
1855         },
1856         /* After mangle, schedule and forward local requests */
1857         {
1858                 .hook           = ip_vs_local_request4,
1859                 .owner          = THIS_MODULE,
1860                 .pf             = NFPROTO_IPV4,
1861                 .hooknum        = NF_INET_LOCAL_OUT,
1862                 .priority       = NF_IP_PRI_NAT_DST + 2,
1863         },
1864         /* After packet filtering (but before ip_vs_out_icmp), catch icmp
1865          * destined for 0.0.0.0/0, which is for incoming IPVS connections */
1866         {
1867                 .hook           = ip_vs_forward_icmp,
1868                 .owner          = THIS_MODULE,
1869                 .pf             = NFPROTO_IPV4,
1870                 .hooknum        = NF_INET_FORWARD,
1871                 .priority       = 99,
1872         },
1873         /* After packet filtering, change source only for VS/NAT */
1874         {
1875                 .hook           = ip_vs_reply4,
1876                 .owner          = THIS_MODULE,
1877                 .pf             = NFPROTO_IPV4,
1878                 .hooknum        = NF_INET_FORWARD,
1879                 .priority       = 100,
1880         },

我们可以看到,LVS使用了netfilter五个HOOK点中的三个,分别是 : LOCAL_IN,LOCAL_OUT,FORWARD,我们分别介绍 :

LOCAL_IN 节点


LOCAL_IN节点LVS一共注册了两个函数,分别是 ip_vs_reply4 和  ip_vs_remote_request4。

ip_vs_reply4 : 只用于 LVS NAT 模式,并且只能处理TCP,UDP,SCTP


    ---> ip_vs_out()
        ---> ip_vs_fill_iph_skb() : 得到IP头
        ---> 判断是否是ICMP,如果是则调用ip_vs_out_icmp()函数处理
        ---> ip_vs_proto_data_get() : 取四层处理结构
        ---> 检查处理分片
        ---> 调用proto->conn_out_get() 得到当前connection
            ---> 如果得到connection,则调用handle_response()处理response
            ---> handle_response()
                ---> 
        ---> 如果没有connecton,则检测是否有VS和这个包匹配,如果有则再次检测这个包是否是TCP或RST的包,如果不是则发送ICMP目的不可达消息
            
            

ip_vs_remote_request4 : For DRand TUN模式


    ---> ip_vs_in()
        ---> 首先是合法性检测,ignore不合法的包
        ---> ipvs_fill_iph_skb()    :    得到ip头信息
        ---> 过滤掉RAW SOCKET的包
        ---> 处理ICMP包
        ---> ip_vs_proto_data_get() 找到proto结构,这个结构保存在 net->ipvs->proto_data_table[hash] 表中
        ---> 调用proto结构的 conn_in_get() 取的connection, connection保存在全局的表 ip_vs_conn_tab[hash] 中
            ---> 查找失败则调用 proto->conn_schedule() 创建connection
                ---> ip_vs_scheduler() : 找到与此包匹配的调度策略,创建connection
                    ---> sched->schedule() : 调用调度策略函数,按照既定的调度测率找到real server
                    ---> ip_vs_conn_new()  :  创建新的connection
                        ---> kmem_cache_alloc()    :   为connection分配内存
                        ---> 初始化connection
                        ---> ip_vs_bind_xmit()     :   根据LVS类型绑定connection的发送函数
                        ---> 将此connection加入ip_vs_conn_tab[hash] 表
                    ---> ip_vs_conn_stats()    :    更新connection统计信息
            ---> ip_vs_in_stats()    :    更新统计信息
            ---> connection->packet_xmit()    :     发包
            ---> synchronization 工作
    

LOCAL OUT 节点


LOCAL OUT 节点注册了两个函数 : ip_vs_local_reply4 和 ip_vs_local_request4

ip_vs_local_reply4 : 

    ---> ip_vs_out() :同上 ip_vs_reply4()

ip_vs_local_request4 :     


    ---> ip_vs_in()  :  同上  ip_vs_remote_request4()


FORWARD节点


FORWARD节点注册了两个函数 : ip_vs_reply4 和 ip_vs_forward_icmp

ip_vs_reply4 : 


    ---> ip_vs_out()   :  同上

ip_vs_forward_icmp :


    ---> ip_vs_in_icmp()    :    处理 outside to inside 方向的ICMP报文

我们可以看到,其实LVS在内核中核心的函数其实就两个 : ip_vs_in() 与 ip_vs_out()。



数据流过程分析


我们通过一个数据包在LVS架构中的处理流程来分析LVS的工作过程。
假设我们添加了这样的规则 : 

ipvsadm -A -t 192.168.132.254:80 -s rr -p 120

ipvsadm -a -t 192.168.132.254:80 -r 192.168.132.64:80 -g 
ipvsadm -a -t 192.168.132.254:80 -r 192.168.132.68:80 -g

我们可以看到,此规则为一个VS : 192.168.132.254:80, 两个real server 分别是 192.168.132.64:80 与 192.168.132.68:80, LVS模式为DR,调度算法为Round Robin。

配置过程我们ignore掉。

加入一个client要访问此虚拟服务器,那么一个TCP发起包为 : 192.168.132.111:2345 -》 192.168.132.254:80, 我们看看这个包的处理流程。

    ---> LVS 收到这个包, 然后路由发现此包是到本地虚拟server地址的数据包,然后将其上送到LOCAL_IN HOOK 点。
        ---> ip_vs_reply4() 首先被调用,因为其优先级高
            ---> 尝试找到与此包关联的connection,因为是第一个包,所以找不到
            ---> return NF_ACCEPT, 进入下一个HOOK点处理
        ---> ip_vs_remote_request4() 被调用,
            ---> Call ip_vs_in() 函数
                ---> 首先是包的预处理工作,找到IP头,找到protocol处理结构
                ---> 然后尝试按照此包找到一个已经存在的connection,由于是第一个包,所以失败
                ---> 然后调用proto->conn_schedule() 创建一个新的connection
                    ---> TCP : tcp_conn_schedule()
                        ---> 首先取得TCP 头
                        ---> 调用ip_vs_service_find() 找到虚拟服务器的管理结构
                        ---> 调用 ip_vs_schedule() 
                            ---> 找到此VS所使用的调度器的管理结构,执行调度函数,找到目的real server地址
                            ---> 调用 ip_vs_conn_new() 创建新的connection,将次connection加入全局HASH表
                                ---> 调用 ip_vs_bind_xmit() 为此 connection 绑定发送函数
                ---> 然后调用 connection->packet_xmit()发包
                    ---> 对于DR模式来说,发送函数是 ip_vs_dr_xmit()
                        ---> 调用 __ip_vs_get_out_rt() 来确定新的路由并设置到此skb包中关联
                        --->  调用 ip_vs_send_or_cont() 来最终将此包发送到dst指定的real server
                            ---> 重要的是设置skb->ipvs_property = 1 
                                ---> 发送过程中要经过LOCAL_OUT hook 点
                                ---> 调用ip_vs_out() 函数,直接返回 NF_ACCEPT
                                ---> 再调用ip_vs_in() 函数,直接返回NF_ACCEPT
                                ---> dst_output() 最终发包
                --->  最后更新connection状态

我们的例子是DR模式,real server收到包后可以直接向client返回数据,不必经过LVS server。其他的模式NAT, TUNNEL和DR模式大同小异,在生产环境中DR模式用的多一点,毕竟DR模式在性能上还是有优势的。


总结    


LVS是非常好的,基于国内的,linux开源软件,我在上面大致分析了其数据面,即kernel中的数据处理流程,总的来说LVS的设计以及实现非常的简单但是高效,稳定,是一款优秀的linux open src项目。希望我的分析能够为大家起到抛砖引玉的作用 ;)

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值