<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          LVS原理與實(shí)現(xiàn) - 實(shí)現(xiàn)篇

          共 18106字,需瀏覽 37分鐘

           ·

          2021-01-27 07:15

          在上一篇文章中,我們主要介紹了?LVS?的原理,接下來(lái)我們將會(huì)介紹?LVS?的代碼實(shí)現(xiàn)。

          本文使用的內(nèi)核版本是:2.4.23,而 LVS 的代碼在路徑:?/src/net/ipv4/ipvs?中。

          Netfilter

          在介紹?LVS?的實(shí)現(xiàn)前,我們需要了解以下?Netfilter?這個(gè)功能,因?yàn)?LVS?的實(shí)現(xiàn)使用了?Netfilter?的功能。

          Netfilter:顧名思義就是網(wǎng)絡(luò)過(guò)濾器(Network Filter),是 Linux 系統(tǒng)特有的網(wǎng)絡(luò)子系統(tǒng),用于過(guò)濾或修改進(jìn)出內(nèi)核協(xié)議棧的網(wǎng)絡(luò)數(shù)據(jù)包。一般可以用來(lái)實(shí)現(xiàn)網(wǎng)絡(luò)防火墻功能,其中?iptables?就是基于?Netfilter?實(shí)現(xiàn)的。

          Linux 內(nèi)核處理進(jìn)出網(wǎng)絡(luò)協(xié)議棧的數(shù)據(jù)包分為5個(gè)不同的階段,Netfilter?通過(guò)這5個(gè)階段注入鉤子函數(shù)(Hooks Function)來(lái)實(shí)現(xiàn)對(duì)數(shù)據(jù)包的過(guò)濾和修改。如下圖的藍(lán)色方框所示:


          這5個(gè)階段分為:

          • PER_ROUTING路由前階段,發(fā)生在內(nèi)核對(duì)數(shù)據(jù)包進(jìn)行路由判決前。

          • LOCAL_IN本地上送階段,發(fā)生在內(nèi)核通過(guò)路由判決后。如果數(shù)據(jù)包是發(fā)送給本機(jī)的,那么就把數(shù)據(jù)包上送到上層協(xié)議棧。

          • FORWARD轉(zhuǎn)發(fā)階段,發(fā)生在內(nèi)核通過(guò)路由判決后。如果數(shù)據(jù)包不是發(fā)送給本機(jī)的,那么就把數(shù)據(jù)包轉(zhuǎn)發(fā)出去。

          • LOCAL_OUT本地發(fā)送階段,發(fā)生在對(duì)發(fā)送數(shù)據(jù)包進(jìn)行路由判決之前。

          • POST_ROUTING路由后階段,發(fā)生在對(duì)發(fā)送數(shù)據(jù)包進(jìn)行路由判決之后。

          當(dāng)向?Netfilter?的這5個(gè)階段注冊(cè)鉤子函數(shù)后,內(nèi)核會(huì)在處理數(shù)據(jù)包時(shí),根據(jù)所在的不同階段來(lái)調(diào)用這些鉤子函數(shù)對(duì)數(shù)據(jù)包進(jìn)行處理。向?Netfilter?注冊(cè)鉤子函數(shù)可以通過(guò)函數(shù)?nf_register_hook()?來(lái)進(jìn)行,nf_register_hook()?函數(shù)的原型如下:

          int nf_register_hook(struct nf_hook_ops *reg);

          其中參數(shù)?reg?是類型為?struct nf_hook_ops?結(jié)構(gòu)的指針,struct nf_hook_ops?結(jié)構(gòu)的定義如下:

          struct nf_hook_ops{    struct list_head list;    nf_hookfn *hook;    int pf;    int hooknum;    int priority;};

          struct nf_hook_ops?結(jié)構(gòu)各個(gè)字段的作用如下:

          • list:用于連接同一階段中所有相同的鉤子函數(shù)列表。

          • hook:鉤子函數(shù)指針。

          • pf:協(xié)議類型,因?yàn)?Netfilter?可以用于不同的協(xié)議,如 IPV4 和 IPV6 等。

          • hooknum:所處的階段,也就是上面所說(shuō)的5個(gè)不同的階段。

          • priority:優(yōu)先級(jí),值越大優(yōu)先級(jí)約小。

          所以要使用?Netfilter?對(duì)網(wǎng)絡(luò)數(shù)據(jù)包進(jìn)行處理,只需要編寫好處理數(shù)據(jù)包的鉤子函數(shù),然后通過(guò)調(diào)用?nf_register_hook()?函數(shù)向?Netfilter?注冊(cè)即可。

          另外,鉤子函數(shù)?nf_hookfn?的原型如下:

          typedef unsigned int nf_hookfn(unsigned int hooknum, struct sk_buff **skb,     const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *));

          其參數(shù)說(shuō)明如下:

          • hooknum:所處的階段,也就是上面所說(shuō)的5個(gè)不同的階段。

          • skb:要處理的數(shù)據(jù)包。

          • in:輸入設(shè)備。

          • out:輸出設(shè)備。

          • okfn:如果鉤子函數(shù)執(zhí)行成功,即調(diào)用這個(gè)函數(shù)完成對(duì)數(shù)據(jù)包的后續(xù)處理工作。

          Netfilter?相關(guān)的知識(shí)點(diǎn)就介紹到這里,以后有機(jī)會(huì)會(huì)詳解講解?Netfilter?的原理和現(xiàn)實(shí)。

          LVS 實(shí)現(xiàn)

          前面我們主要簡(jiǎn)單介紹了?Netfilter?的使用,接下來(lái)我們將要分析?LVS?的代碼實(shí)現(xiàn)。

          1. 鉤子函數(shù)注冊(cè)

          LVS?主要通過(guò)向?Netfilter?的3個(gè)階段注冊(cè)鉤子函數(shù)來(lái)對(duì)數(shù)據(jù)包進(jìn)行處理,如下圖:


          • 在?LOCAL_IN?階段注冊(cè)了?ip_vs_in()?鉤子函數(shù)。

          • 在?FORWARD?階段注冊(cè)了?ip_vs_out()?鉤子函數(shù)。

          • 在?POST_ROUTING?階段注冊(cè)了?ip_vs_post_routing()?鉤子函數(shù)。

          我們?cè)?LVS 的初始化函數(shù)?ip_vs_init()?可以找到這些鉤子函數(shù)的注冊(cè)代碼,如下:

          static struct nf_hook_ops ip_vs_in_ops = {    { NULL, NULL },    ip_vs_in, PF_INET, NF_IP_LOCAL_IN, 100};
          static struct nf_hook_ops ip_vs_out_ops = { { NULL, NULL }, ip_vs_out, PF_INET, NF_IP_FORWARD, 100};
          static struct nf_hook_ops ip_vs_post_routing_ops = { { NULL, NULL }, ip_vs_post_routing, PF_INET, NF_IP_POST_ROUTING, NF_IP_PRI_NAT_SRC-1};
          static int __init ip_vs_init(void){ int ret; ... ret = nf_register_hook(&ip_vs_in_ops); ... ret = nf_register_hook(&ip_vs_out_ops); ... ret = nf_register_hook(&ip_vs_post_routing_ops); ... return ret;}
          • LOCAL_IN?階段:在路由判決之后,如果發(fā)現(xiàn)數(shù)據(jù)包是發(fā)送給本機(jī)的,那么就調(diào)用?ip_vs_in()?函數(shù)對(duì)數(shù)據(jù)包進(jìn)行處理。

          • FORWARD?階段:在路由判決之后,如果發(fā)現(xiàn)數(shù)據(jù)包不是發(fā)送給本機(jī)的,調(diào)用?ip_vs_out()?函數(shù)對(duì)數(shù)據(jù)包進(jìn)行處理。

          • POST_ROUTING?階段:在發(fā)送數(shù)據(jù)前,需要調(diào)用?ip_vs_post_routing()?函數(shù)對(duì)數(shù)據(jù)包進(jìn)行處理。

          2. LVS 角色介紹

          在介紹這些鉤子函數(shù)之前,我們先來(lái)了解一下?LVS?中的四個(gè)角色。如下:

          • ip_vs_service:服務(wù)配置對(duì)象,主要用于保存 LVS 的配置信息,如 支持的?傳輸層協(xié)議虛擬IP?和?端口?等。

          • ip_vs_dest:真實(shí)服務(wù)器對(duì)象,主要用于保存真實(shí)服務(wù)器 (Real-Server) 的配置,如?真實(shí)IP端口?和?權(quán)重?等。

          • ip_vs_scheduler:調(diào)度器對(duì)象,主要通過(guò)使用不同的調(diào)度算法來(lái)選擇合適的真實(shí)服務(wù)器對(duì)象。

          • ip_vs_conn:連接對(duì)象,主要為了維護(hù)相同的客戶端與真實(shí)服務(wù)器之間的連接關(guān)系。這是由于 TCP 協(xié)議是面向連接的,所以同一個(gè)的客戶端每次選擇真實(shí)服務(wù)器的時(shí)候必須保存一致,否則會(huì)出現(xiàn)連接中斷的情況,而連接對(duì)象就是為了維護(hù)這種關(guān)系。

          各個(gè)角色之間的關(guān)系如下圖所示:


          從上圖可以看出,ip_vs_service?對(duì)象的?destinations?字段用于保存?ip_vs_dest?對(duì)象的列表,而?scheduler?字段指向了一個(gè)?ip_vs_scheduler?對(duì)象。

          ip_vs_scheduler?對(duì)象的?schedule?字段指向了一個(gè)調(diào)度算法函數(shù),通過(guò)這個(gè)調(diào)度函數(shù)可以從?ip_vs_service?對(duì)象的?ip_vs_dest?對(duì)象列表中選擇一個(gè)合適的真實(shí)服務(wù)器。

          那么,ip_vs_service?對(duì)象和?ip_vs_dest?對(duì)象的信息怎么來(lái)的呢?答案是通過(guò)用戶配置創(chuàng)建。例如可以通過(guò)下面的命令來(lái)創(chuàng)建?ip_vs_service?對(duì)象和?ip_vs_dest?對(duì)象:

          node1?>?$?ipvsadm?-A?-t?node1:80?-s?wrrnode1?>?$?ipvsadm?-a?-t?node1:80?-r?node2?-m?-w?3node1?>?$?ipvsadm?-a?-t?node1:80?-r?node3?-m?-w?5

          第一行用于創(chuàng)建一個(gè)?ip_vs_service?對(duì)象,而第二和第三行用于向?ip_vs_service?對(duì)象添加?ip_vs_dest?對(duì)象到?destinations?列表中。關(guān)于 LVS 的配置這里不作詳細(xì)介紹,讀者可以參考其他關(guān)于 LVS 配置的資料。

          ip_vs_service 對(duì)象創(chuàng)建

          我們來(lái)看看 LVS 源碼是怎么創(chuàng)建一個(gè)?ip_vs_service?對(duì)象的,創(chuàng)建?ip_vs_service?對(duì)象通過(guò)?ip_vs_add_service()?函數(shù)完成,如下:

          static intip_vs_add_service(struct ip_vs_rule_user *ur, struct ip_vs_service **svc_p){    int ret = 0;    struct ip_vs_scheduler *sched;    struct ip_vs_service *svc = NULL;
          sched = ip_vs_scheduler_get(ur->sched_name); // 根據(jù)調(diào)度器名稱獲取調(diào)度策略對(duì)象 ... // 申請(qǐng)一個(gè) ip_vs_service 對(duì)象 svc = (struct ip_vs_service *)kmalloc(sizeof(struct ip_vs_service), GFP_ATOMIC); ... memset(svc, 0, sizeof(struct ip_vs_service)); // 設(shè)置 ip_vs_service 對(duì)象的各個(gè)字段 svc->protocol = ur->protocol; // 協(xié)議 svc->addr = ur->vaddr; // 虛擬IP svc->port = ur->vport; // 虛擬端口 svc->fwmark = ur->vfwmark; // 防火墻標(biāo)記 svc->flags = ur->vs_flags; // 標(biāo)志位 svc->timeout = ur->timeout * HZ; // 超時(shí)時(shí)間 svc->netmask = ur->netmask; // 網(wǎng)絡(luò)掩碼
          INIT_LIST_HEAD(&svc->destinations); svc->sched_lock = RW_LOCK_UNLOCKED; svc->stats.lock = SPIN_LOCK_UNLOCKED;
          ret = ip_vs_bind_scheduler(svc, sched); // 綁定調(diào)度器 ... ip_vs_svc_hash(svc); // 添加到ip_vs_service對(duì)象的hash表中 ... *svc_p = svc; return 0;}

          先說(shuō)明一下,參數(shù)?ur?是用戶通過(guò)命令行配置的規(guī)則信息。上面的代碼主要完成以下幾個(gè)工作:

          • 通過(guò)調(diào)用?ip_vs_scheduler_get()?函數(shù)來(lái)獲取一個(gè)?ip_vs_scheduler?(調(diào)度器) 對(duì)象。

          • 然后申請(qǐng)一個(gè)?ip_vs_service?對(duì)象并且根據(jù)用戶的配置設(shè)置其各個(gè)參數(shù),并且把調(diào)度器對(duì)象綁定這個(gè)?ip_vs_service?對(duì)象。

          • 最后把?ip_vs_service?對(duì)象添加到?ip_vs_service?對(duì)象的全局哈希表中(這是由于可以創(chuàng)建多個(gè)?ip_vs_service?對(duì)象,這些對(duì)象通過(guò)一個(gè)全局哈希表來(lái)存儲(chǔ))。

          ip_vs_dest 對(duì)象創(chuàng)建

          創(chuàng)建?ip_vs_dest?對(duì)象通過(guò)?ip_vs_add_dest()?函數(shù)完成,代碼如下:

          static int ip_vs_add_dest(struct ip_vs_service *svc, struct ip_vs_rule_user *ur){    struct ip_vs_dest *dest;    __u32 daddr = ur->daddr; // 目的IP    __u16 dport = ur->dport; // 目的端口    int ret;    ...    // 調(diào)用 ip_vs_new_dest() 函數(shù)創(chuàng)建一個(gè) ip_vs_dest 對(duì)象    ret = ip_vs_new_dest(svc, ur, &dest);    ...    // 把 ip_vs_dest 對(duì)象添加到 ip_vs_service 對(duì)象的 destinations 列表中    list_add(&dest->n_list, &svc->destinations);     svc->num_dests++;
          /* 調(diào)用調(diào)度器的 update_service() 方法更新 ip_vs_service 對(duì)象 */ svc->scheduler->update_service(svc); ... return 0;}

          ip_vs_add_dest()?函數(shù)主要通過(guò)調(diào)用?ip_vs_new_dest()?創(chuàng)建一個(gè)?ip_vs_dest?對(duì)象,然后將其添加到?ip_vs_service?對(duì)象的?destinations?列表中。我們來(lái)看看?ip_vs_new_dest()?函數(shù)的實(shí)現(xiàn):

          static intip_vs_new_dest(struct ip_vs_service *svc,               struct ip_vs_rule_user *ur,               struct ip_vs_dest **destp){    struct ip_vs_dest *dest;    ...    *destp = dest = (struct ip_vs_dest*)kmalloc(sizeof(struct ip_vs_dest), GFP_ATOMIC);    ...    memset(dest, 0, sizeof(struct ip_vs_dest));    // 設(shè)置 ip_vs_dest 對(duì)象的各個(gè)字段    dest->protocol = svc->protocol; // 協(xié)議    dest->vaddr = svc->addr;        // 虛擬IP    dest->vport = svc->port;        // 虛擬端口    dest->vfwmark = svc->fwmark;    // 虛擬網(wǎng)絡(luò)掩碼    dest->addr = ur->daddr;         // 真實(shí)IP    dest->port = ur->dport;         // 真實(shí)端口
          atomic_set(&dest->activeconns, 0); atomic_set(&dest->inactconns, 0); atomic_set(&dest->refcnt, 0);
          INIT_LIST_HEAD(&dest->d_list); dest->dst_lock = SPIN_LOCK_UNLOCKED; dest->stats.lock = SPIN_LOCK_UNLOCKED; __ip_vs_update_dest(svc, dest, ur); ... return 0;}

          ip_vs_new_dest()?函數(shù)的實(shí)現(xiàn)也比較簡(jiǎn)單,首先通過(guò)調(diào)用?kmalloc()?函數(shù)申請(qǐng)一個(gè)?ip_vs_dest?對(duì)象,然后根據(jù)用戶配置的規(guī)則信息來(lái)初始化?ip_vs_dest?對(duì)象的各個(gè)字段。

          ip_vs_scheduler 對(duì)象

          ip_vs_scheduler?(調(diào)度器) 對(duì)象用于從?ip_vs_service?對(duì)象的?destinations?列表中選擇一個(gè)合適的?ip_vs_dest?對(duì)象,其定義如下:

          struct ip_vs_scheduler {    struct list_head    n_list;     // 連接所有調(diào)度策略    char                *name;      // 調(diào)度策略名稱    atomic_t            refcnt;     // 應(yīng)用計(jì)數(shù)器    struct module       *module;    // 模塊對(duì)象(如果是通過(guò)模塊引入的)
          int (*init_service)(struct ip_vs_service *svc); // 用于初始化服務(wù) int (*done_service)(struct ip_vs_service *svc); // 用于停止服務(wù) int (*update_service)(struct ip_vs_service *svc); // 用于更新服務(wù)
          // 用于獲取一個(gè)真實(shí)服務(wù)器對(duì)象 (Real-Server) struct ip_vs_dest *(*schedule)(struct ip_vs_service *svc, struct iphdr *iph);};

          ip_vs_scheduler?對(duì)象的各個(gè)字段都在注釋說(shuō)明了,其中?schedule?字段是一個(gè)函數(shù)的指針,其指向一個(gè)調(diào)度函數(shù),用于從?ip_vs_service?對(duì)象的?destinations?列表中選擇一個(gè)合適的?ip_vs_dest?對(duì)象。

          我們可以通過(guò)一個(gè)最簡(jiǎn)單的調(diào)度模塊(輪詢調(diào)度模塊)來(lái)分析?ip_vs_scheduler?對(duì)象的工作原理(文件路徑:/net/ipv4/ipvs/ip_vs_rr.c):

          static struct ip_vs_scheduler ip_vs_rr_scheduler = {    {0},                /* n_list */    "rr",               /* name */    ATOMIC_INIT(0),     /* refcnt */    THIS_MODULE,        /* this module */    ip_vs_rr_init_svc,  /* service initializer */    ip_vs_rr_done_svc,  /* service done */    ip_vs_rr_update_svc,/* service updater */    ip_vs_rr_schedule,  /* select a server from the destination list */};

          首先輪詢調(diào)度模塊定義了一個(gè)?ip_vs_scheduler?對(duì)象,其中?schedule?字段設(shè)置為?ip_vs_rr_schedule()?函數(shù)。我們來(lái)看看?ip_vs_rr_schedule()?函數(shù)的實(shí)現(xiàn):

          static struct ip_vs_dest *ip_vs_rr_schedule(struct ip_vs_service *svc, struct iphdr *iph){    register struct list_head *p, *q;    struct ip_vs_dest *dest;
          write_lock(&svc->sched_lock); p = (struct list_head *)svc->sched_data; // 最后一次被調(diào)度的位置 p = p->next; q = p; // 遍歷 destinations 列表 do { if (q == &svc->destinations) { q = q->next; continue; } dest = list_entry(q, struct ip_vs_dest, n_list); // 找到一個(gè)權(quán)限值大于 0 的 ip_vs_dest 對(duì)象 if (atomic_read(&dest->weight) > 0) goto out; q = q->next; } while (q != p); write_unlock(&svc->sched_lock);
          return NULL;
          out: svc->sched_data = q; // 設(shè)置最后一次被調(diào)度的位置 ... return dest;}

          ip_vs_rr_schedule()?函數(shù)是輪詢調(diào)度算法的實(shí)現(xiàn),其實(shí)現(xiàn)原理如下:

          • ip_vs_service?對(duì)象的?sched_data?字段保存了最后一次調(diào)度的位置,所以每次調(diào)度時(shí)都是從這個(gè)字段讀取到最后一次調(diào)度的位置。

          • 從最后一次調(diào)度的位置開始遍歷,找到一個(gè)權(quán)限值(weight)大于 0 的?ip_vs_dest?對(duì)象。

          • 如果找到就把?ip_vs_service?對(duì)象的?sched_data?字段設(shè)置為最后被選擇的?ip_vs_dest?對(duì)象的位置。

          其原理可以通過(guò)以下圖片說(shuō)明:


          上圖描述的原理還是比較簡(jiǎn)單,首先從?sched_data?處開始遍歷,查找一個(gè)合適的?ip_vs_dest?對(duì)象,然后更新?sched_data?的位置。

          另外,由于?LVS?可以存在多種不同的調(diào)度對(duì)象(提供不同的調(diào)度算法),所以?LVS?把這些調(diào)度對(duì)象通過(guò)一個(gè)鏈表(ip_vs_schedulers)存儲(chǔ)起來(lái),而這些調(diào)度對(duì)象可以通過(guò)調(diào)度對(duì)象的名字(name?字段)來(lái)查詢。

          可以通過(guò)調(diào)用?register_ip_vs_scheduler()?函數(shù)向?LVS?注冊(cè)調(diào)度對(duì)象,而通過(guò)調(diào)用?ip_vs_scheduler_get()?函數(shù)來(lái)獲取指定名字的調(diào)度對(duì)象,這兩個(gè)函數(shù)的實(shí)現(xiàn)比較簡(jiǎn)單,這里就不作詳細(xì)介紹了。

          ip_vs_conn 對(duì)象

          ip_vs_conn?對(duì)象用于維護(hù)?客戶端?與?真實(shí)服務(wù)器?之間的關(guān)系,為什么需要維護(hù)它們之間的關(guān)系?原因是?TCP協(xié)議?面向連接的協(xié)議,所以每次調(diào)度都必須選擇相同的真實(shí)服務(wù)器,否則連接就會(huì)失效。


          如上圖所示,剛開始時(shí)調(diào)度器選擇了?Real-Server(1)?服務(wù)器進(jìn)行處理客戶端請(qǐng)求,但第二次調(diào)度時(shí)卻選擇了?Real-Server(2)?來(lái)處理客戶端請(qǐng)求。

          由于?TCP協(xié)議?需要客戶端與服務(wù)器進(jìn)行連接,但第二次請(qǐng)求的服務(wù)器發(fā)生了變化,所以連接狀態(tài)就失效了,這就為什么?LVS?需要維持客戶端與真實(shí)服務(wù)器連接關(guān)系的原因。

          LVS?通過(guò)?ip_vs_conn?對(duì)象來(lái)維護(hù)客戶端與真實(shí)服務(wù)器之間的連接關(guān)系,其定義如下:

          struct ip_vs_conn {    struct list_head    c_list;     /* 用于連接到哈希表 */
          __u32 caddr; /* 客戶端IP地址 */ __u32 vaddr; /* 虛擬IP地址 */ __u32 daddr; /* 真實(shí)服務(wù)器IP地址 */ __u16 cport; /* 客戶端端口 */ __u16 vport; /* 虛擬端口 */ __u16 dport; /* 真實(shí)服務(wù)器端口 */ __u16 protocol; /* 協(xié)議類型(UPD/TCP) */ ... /* 用于發(fā)送數(shù)據(jù)包的接口 */ int (*packet_xmit)(struct sk_buff *skb, struct ip_vs_conn *cp); ...};

          ip_vs_conn?對(duì)象各個(gè)字段的作用都在注釋中進(jìn)行說(shuō)明了,客戶端與真實(shí)服務(wù)器的連接關(guān)系就是通過(guò)?協(xié)議類型客戶端IP客戶端端口虛擬IP?和?虛擬端口?來(lái)進(jìn)行關(guān)聯(lián)的,也就是說(shuō)根據(jù)這五元組能夠確定一個(gè)?ip_vs_conn?對(duì)象。

          另外,在《原理篇》我們說(shuō)過(guò),LVS 有3中運(yùn)行模式:NAT模式DR模式?和?TUN模式。而對(duì)于不同的運(yùn)行模式,發(fā)送數(shù)據(jù)包的接口是不一樣的,所以?ip_vs_conn?對(duì)象的?packet_xmit?字段會(huì)根據(jù)不同的運(yùn)行模式來(lái)選擇不同的發(fā)送數(shù)據(jù)包接口,綁定發(fā)送數(shù)據(jù)包接口是通過(guò)?ip_vs_bind_xmit()?函數(shù)完成,如下:

          static inline void ip_vs_bind_xmit(struct ip_vs_conn *cp){    switch (IP_VS_FWD_METHOD(cp)) {    case IP_VS_CONN_F_MASQ:                     // NAT模式        cp->packet_xmit = ip_vs_nat_xmit;        break;    case IP_VS_CONN_F_TUNNEL:                   // TUN模式        cp->packet_xmit = ip_vs_tunnel_xmit;        break;    case IP_VS_CONN_F_DROUTE:                   // DR模式        cp->packet_xmit = ip_vs_dr_xmit;        break;    ...    }}

          一個(gè)客戶端請(qǐng)求到達(dá)?LVS?后,Director服務(wù)器?首先會(huì)查找客戶端是否已經(jīng)與真實(shí)服務(wù)器建立了連接關(guān)系,如果已經(jīng)建立了連接,那么直接使用這個(gè)連接關(guān)系。否則,通過(guò)調(diào)度器對(duì)象選擇一臺(tái)合適的真實(shí)服務(wù)器,然后創(chuàng)建客戶端與真實(shí)服務(wù)器的連接關(guān)系,并且保存到全局哈希表?ip_vs_conn_tab?中。流程圖如下:


          上面對(duì)?LVS?各個(gè)角色都進(jìn)行了介紹,下面開始講解?LVS?對(duì)數(shù)據(jù)包的轉(zhuǎn)發(fā)過(guò)程。

          3. 數(shù)據(jù)轉(zhuǎn)發(fā)

          因?yàn)?LVS?是一個(gè)負(fù)載均衡工具,所以其最重要的功能就是對(duì)數(shù)據(jù)的調(diào)度與轉(zhuǎn)發(fā), 而對(duì)數(shù)據(jù)的轉(zhuǎn)發(fā)是在前面介紹的?Netfilter?鉤子函數(shù)進(jìn)行的。

          對(duì)數(shù)據(jù)的轉(zhuǎn)發(fā)主要是通過(guò)?ip_vs_in()?和?ip_vs_out()?這兩個(gè)鉤子函數(shù):

          • ip_vs_in()?運(yùn)行在?Netfilter?的?LOCAL_IN?階段。

          • ip_vs_out()?運(yùn)行在?Netfilter?的?FORWARD?階段。

          FORWARD?階段發(fā)送在數(shù)據(jù)包不是發(fā)送給本機(jī)的情況,但是一般來(lái)說(shuō)數(shù)據(jù)包都是發(fā)送給本機(jī)的,所以對(duì)于?ip_vs_out()?這個(gè)函數(shù)的實(shí)現(xiàn)就不作介紹,我們主要重點(diǎn)分析?ip_vs_in()?這個(gè)函數(shù)。

          ip_vs_in() 鉤子函數(shù)

          有了前面的知識(shí)點(diǎn),我們對(duì)?ip_vs_in()?函數(shù)的分析就不那么困難了。下面我們分段對(duì)?ip_vs_in()?函數(shù)進(jìn)行分析:

          static unsigned intip_vs_in(unsigned int hooknum,         struct sk_buff **skb_p,         const struct net_device *in,         const struct net_device *out,         int (*okfn)(struct sk_buff *)){    struct sk_buff *skb = *skb_p;    struct iphdr *iph = skb->nh.iph; // IP頭部    union ip_vs_tphdr h;    struct ip_vs_conn *cp;    struct ip_vs_service *svc;    int ihl;    int ret;    ...    // 因?yàn)長(zhǎng)VS只支持TCP和UDP    if (iph->protocol != IPPROTO_TCP && iph->protocol != IPPROTO_UDP)        return NF_ACCEPT;
          ihl = iph->ihl << 2; // IP頭部長(zhǎng)度
          // IP頭部是否正確 if (ip_vs_header_check(skb, iph->protocol, ihl) == -1) return NF_DROP;
          iph = skb->nh.iph; // IP頭部指針 h.raw = (char*)iph + ihl; // TCP/UDP頭部指針

          上面的代碼主要對(duì)數(shù)據(jù)包的?IP頭部?進(jìn)行正確性驗(yàn)證,并且將?iph?變量指向?IP頭部,而?h?變量指向?TCP/UDP?頭部。

              // 根據(jù) "協(xié)議類型", "客戶端IP", "客戶端端口", "虛擬IP", "虛擬端口" 五元組獲取連接對(duì)象    cp = ip_vs_conn_in_get(iph->protocol, iph->saddr,                           h.portp[0], iph->daddr, h.portp[1]);
          // 1. 如果連接還沒(méi)建立 // 2. 如果是TCP協(xié)議的話, 第一個(gè)包必須是syn包, 或者UDP協(xié)議。 // 3. 根據(jù)協(xié)議、虛擬IP和虛擬端口查找服務(wù)對(duì)象 if (!cp && (h.th->syn || (iph->protocol != IPPROTO_TCP)) && (svc = ip_vs_service_get(skb->nfmark, iph->protocol, iph->daddr, h.portp[1]))) { ... // 通過(guò)調(diào)度器選擇一個(gè)真實(shí)服務(wù)器 // 并且創(chuàng)建一個(gè)新的連接對(duì)象, 建立真實(shí)服務(wù)器與客戶端連接關(guān)系 cp = ip_vs_schedule(svc, iph); ... }

          上面的代碼主要完成以下幾個(gè)功能:

          • 根據(jù)?協(xié)議類型客戶端IP客戶端端口虛擬IP?和?虛擬端口?五元組,然后調(diào)用?ip_vs_conn_in_get()?函數(shù)獲取連接對(duì)象。

          • 如果連接還沒(méi)建立,那么就調(diào)用?ip_vs_schedule()?函數(shù)調(diào)度一臺(tái)合適的真實(shí)服務(wù)器,然后創(chuàng)建一個(gè)連接對(duì)象,并且建立真實(shí)服務(wù)器與客戶端之間的連接關(guān)系。

          我們來(lái)分析一下?ip_vs_schedule()?函數(shù)的實(shí)現(xiàn):

          static struct ip_vs_conn *ip_vs_schedule(struct ip_vs_service *svc, struct iphdr *iph){    struct ip_vs_conn *cp = NULL;    struct ip_vs_dest *dest;    const __u16 *portp;    ...    portp = (__u16 *)&(((char *)iph)[iph->ihl*4]); // 指向TCP或者UDP頭部    ...    dest = svc->scheduler->schedule(svc, iph); // 通過(guò)調(diào)度器選擇一臺(tái)合適的真實(shí)服務(wù)器    ...    cp = ip_vs_conn_new(iph->protocol,                      // 協(xié)議類型                        iph->saddr,                         // 客戶端IP                        portp[0],                           // 客戶端端口                        iph->daddr,                         // 虛擬IP                        portp[1],                           // 虛擬端口                        dest->addr,                         // 真實(shí)服務(wù)器的IP                        dest->port ? dest->port : portp[1], // 真實(shí)服務(wù)器的端口                        0,                                  // flags                        dest);    ...    return cp;}

          ip_vs_schedule()?函數(shù)的主要工作如下:

          • 首先通過(guò)調(diào)用調(diào)度器(ip_vs_scheduler?對(duì)象)的?schedule()?方法從?ip_vs_service?對(duì)象的?destinations?鏈表中選擇一臺(tái)真實(shí)服務(wù)器(ip_vs_dest?對(duì)象)

          • 然后調(diào)用?ip_vs_conn_new()?函數(shù)創(chuàng)建一個(gè)新的?ip_vs_conn?對(duì)象。

          ip_vs_conn_new()?主要用于創(chuàng)建?ip_vs_conn?對(duì)象,并且根據(jù)?LVS?的運(yùn)行模式為其選擇正確的數(shù)據(jù)發(fā)送接口,其實(shí)現(xiàn)如下:

          struct ip_vs_conn *ip_vs_conn_new(int proto,                   // 協(xié)議類型               __u32 caddr, __u16 cport,    // 客戶端IP和端口               __u32 vaddr, __u16 vport,    // 虛擬IP和端口               __u32 daddr, __u16 dport,    // 真實(shí)服務(wù)器IP和端口               unsigned flags, struct ip_vs_dest *dest){    struct ip_vs_conn *cp;
          // 創(chuàng)建一個(gè) ip_vs_conn 對(duì)象 cp = kmem_cache_alloc(ip_vs_conn_cachep, GFP_ATOMIC); ... // 設(shè)置 ip_vs_conn 對(duì)象的各個(gè)字段 cp->protocol = proto; cp->caddr = caddr; cp->cport = cport; cp->vaddr = vaddr; cp->vport = vport; cp->daddr = daddr; cp->dport = dport; cp->flags = flags; ... ip_vs_bind_dest(cp, dest); // 將 ip_vs_conn 與真實(shí)服務(wù)器對(duì)象進(jìn)行綁定 ... ip_vs_bind_xmit(cp); // 綁定一個(gè)發(fā)送數(shù)據(jù)的接口 ... ip_vs_conn_hash(cp); // 把 ip_vs_conn 對(duì)象添加到連接信息表中
          return cp;}

          ip_vs_conn_new()?函數(shù)的主要工作如下:

          • 創(chuàng)建一個(gè)新的?ip_vs_conn?對(duì)象,并且設(shè)置其各個(gè)字段的值。

          • 調(diào)用?ip_vs_bind_dest()?函數(shù)將?ip_vs_conn?對(duì)象與真實(shí)服務(wù)器對(duì)象(ip_vs_dest?對(duì)象)進(jìn)行綁定。

          • 根據(jù)?LVS?的運(yùn)行模式,調(diào)用?ip_vs_bind_xmit()?函數(shù)為連接對(duì)象選擇一個(gè)正確的數(shù)據(jù)發(fā)送接口,ip_vs_bind_xmit()?函數(shù)在前面已經(jīng)介紹過(guò)。

          • 調(diào)用?ip_vs_conn_hash()?函數(shù)把新創(chuàng)建的?ip_vs_conn?對(duì)象添加到全局連接信息哈希表中。

          我們接著分析?ip_vs_in()?函數(shù):

              if (cp->packet_xmit)        ret = cp->packet_xmit(skb, cp); // 把數(shù)據(jù)包轉(zhuǎn)發(fā)出去    else {        ret = NF_ACCEPT;    }    ...    return ret;}

          ip_vs_in()?函數(shù)的最后部分就是通過(guò)調(diào)用數(shù)據(jù)發(fā)送接口把數(shù)據(jù)包轉(zhuǎn)發(fā)出去,對(duì)于?NAT模式?來(lái)說(shuō),數(shù)據(jù)發(fā)送接口就是?ip_vs_nat_xmit()

          數(shù)據(jù)發(fā)送接口:ip_vs_nat_xmit()

          接下來(lái),我們對(duì)?NAT模式?的數(shù)據(jù)發(fā)送接口?ip_vs_nat_xmit()?進(jìn)行分析。由于?ip_vs_nat_xmit()?函數(shù)的實(shí)現(xiàn)比較復(fù)雜,所以我們通過(guò)分段來(lái)分析:

          static int ip_vs_nat_xmit(struct sk_buff *skb, struct ip_vs_conn *cp){    struct rtable *rt;      /* Route to the other host */    struct iphdr  *iph;    union ip_vs_tphdr h;    int ihl;    unsigned short size;    int mtu;    ...    iph = skb->nh.iph;                // IP頭部    ihl = iph->ihl << 2;              // IP頭部長(zhǎng)度    h.raw = (char*) iph + ihl;        // 傳輸層頭部(TCP/UDP)    size = ntohs(iph->tot_len) - ihl; // 數(shù)據(jù)長(zhǎng)度    ...    // 找到真實(shí)服務(wù)器IP的路由信息    if (!(rt = __ip_vs_get_out_rt(cp, RT_TOS(iph->tos))))         goto tx_error_icmp;    ...    // 替換新路由信息    dst_release(skb->dst);    skb->dst = &rt->u.dst;

          上面的代碼主要完成兩個(gè)工作:

          • 調(diào)用?__ip_vs_get_out_rt()?函數(shù)查找真實(shí)服務(wù)器 IP 對(duì)應(yīng)的路由信息對(duì)象。

          • 把數(shù)據(jù)包的舊路由信息替換成新的路由信息。

          我們接著分析:

              iph->daddr = cp->daddr; // 修改目標(biāo)IP地址為真實(shí)服務(wù)器IP地址    h.portp[1] = cp->dport; // 修改目標(biāo)端口為真實(shí)服務(wù)器端口    ...    // 更新UDP/TCP頭部的校驗(yàn)和    if (!cp->app && (iph->protocol != IPPROTO_UDP || h.uh->check != 0)) {        ip_vs_fast_check_update(&h, cp->vaddr, cp->daddr, cp->vport,                                cp->dport, iph->protocol);
          if (skb->ip_summed == CHECKSUM_HW) skb->ip_summed = CHECKSUM_NONE;
          } else { switch (iph->protocol) { case IPPROTO_TCP: h.th->check = 0; h.th->check = csum_tcpudp_magic(iph->saddr, iph->daddr, size, iph->protocol, csum_partial(h.raw, size, 0)); break;
          case IPPROTO_UDP: h.uh->check = 0; h.uh->check = csum_tcpudp_magic(iph->saddr, iph->daddr, size, iph->protocol, csum_partial(h.raw, size, 0)); if (h.uh->check == 0) h.uh->check = 0xFFFF; break; }
          skb->ip_summed = CHECKSUM_UNNECESSARY; }

          上面的代碼完成兩個(gè)工作:

          • 修改目標(biāo)IP地址和端口為真實(shí)服務(wù)器IP地址和端口。

          • 更新?UDP/TCP 頭部?的校驗(yàn)和(checksum)。

          我們接著分析:

              ip_send_check(iph); // 計(jì)算IP頭部的校驗(yàn)和    ...    skb->nfcache |= NFC_IPVS_PROPERTY;
          ip_send(skb); // 把包發(fā)送出去 ... return NF_STOLEN; // 讓其他 Netfilter 的鉤子函數(shù)放棄處理該包}

          上面的代碼完成兩個(gè)工作:

          • 調(diào)用?ip_send_check()?函數(shù)重新計(jì)算數(shù)據(jù)包的?IP頭部?校驗(yàn)和。

          • 調(diào)用?ip_send()?函數(shù)把數(shù)據(jù)包發(fā)送出去。

          這樣,數(shù)據(jù)包的目標(biāo)IP地址和端口被替換成真實(shí)服務(wù)器的IP地址和端口,然后被發(fā)送到真實(shí)服務(wù)器處。至此,NAT模式?的分析已經(jīng)完畢。下面我們來(lái)總結(jié)一下整個(gè)流程:

          • 當(dāng)數(shù)據(jù)包進(jìn)入到?Director服務(wù)器?后,會(huì)被?LOCAL_IN階段?的?ip_vs_in()?鉤子函數(shù)進(jìn)行處理。

          • ip_vs_in()?函數(shù)首先查找客戶端與真實(shí)服務(wù)器的連接是否存在,如果存在就使用這個(gè)真實(shí)服務(wù)器。否則通過(guò)調(diào)度算法對(duì)象選擇一臺(tái)最合適的真實(shí)服務(wù)器,然后建立客戶端與真實(shí)服務(wù)器的連接關(guān)系。

          • 根據(jù)運(yùn)行模式來(lái)選擇發(fā)送數(shù)據(jù)的接口(如?NAT模式?對(duì)應(yīng)的是?ip_vs_nat_xmit()?函數(shù)),然后把數(shù)據(jù)轉(zhuǎn)發(fā)出去。

          • 轉(zhuǎn)發(fā)數(shù)據(jù)時(shí),首先會(huì)根據(jù)真實(shí)服務(wù)器的IP地址更新數(shù)據(jù)包的路由信息,然后再更新各個(gè)協(xié)議頭部的信息(如IP地址、端口和校驗(yàn)和等),然后把數(shù)據(jù)發(fā)送出去。

          總結(jié)

          本文主要分析 LVS 的實(shí)現(xiàn)原理,但由于本人能力有限,并且很多細(xì)節(jié)沒(méi)有分析,所以有問(wèn)題可以通過(guò)評(píng)論指出。另外,本文只介紹了?NAT模式?的原理,還有?DR模式?和?TUN模式?的沒(méi)有分析,有興趣可以自行閱讀源碼


          瀏覽 128
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91AV成人在线 | 欧美日韩在线免费观看 | 中文操逼| 欧美一级操逼片 | 欧美AAAAAA |