ipvs代码分析
linux-2.6.26中ipvs代码包括头文件在内,总共有12996行.分析的顺序我按照自己看代码的过程,分为以下4个部分
- 启动中的ipvs- 根据ipvs模块加载时的执行顺序为线索来分析ipvs代码,直到ipvs的模块加载完成
- 运行中的ipvs- ipvs启动完成以后,根据数据报在ipvs模块中被处理的过程为线索来分析ipvs代码,直到最后选出rs -> 修改数据报头部 -> 发出数据
- ipvs的调度- 对第2部分中的ipvs选择rs的调度过程和调度方法进行更详细的分析
- ipvs的数据报转送- 对第2部分中的ipvs修改数据报,并发出数据的过程进行更详细的分析
ipvs中还用到了些其他的技术,比如hash算法,socket buff等.对整个代码理解不构成影响,则作为单独的部分提出来进行分析
启动中的ipvs
当计算机启动以后,内核的随着被加载,内核的模块也会被加载.加载的那一刻开始,很多东西就运动起来了.对于ipvs来说,最开始运行的就是初始化函数__init ip_vs_init(),与之对应的还有一个退出时运行的函数[__exit]就从这里开始分析吧
net/ipv4/ipvs/ip_vs_core.c-
- * Initialize IP Virtual Server
-
- static int __init ip_vs_init(void)
- {
- int ret;
-
- ret = ip_vs_control_init();
- if (ret < 0) {
- IP_VS_ERR("can't setup control.\n");
- goto cleanup_nothing;
- }
-
- ip_vs_protocol_init();
-
- ret = ip_vs_app_init();
- if (ret < 0) {
- IP_VS_ERR("can't setup application helper.\n");
- goto cleanup_protocol;
- }
-
- ret = ip_vs_conn_init();
- if (ret < 0) {
- IP_VS_ERR("can't setup connection table.\n");
- goto cleanup_app;
- }
-
- ret = nf_register_hooks(ip_vs_ops, ARRAY_SIZE(ip_vs_ops));
- if (ret < 0) {
- IP_VS_ERR("can't register hooks.\n");
- goto cleanup_conn;
- }
-
- IP_VS_INFO("ipvs loaded.\n");
- return ret;
-
- cleanup_conn:
- ip_vs_conn_cleanup();
- cleanup_app:
- ip_vs_app_cleanup();
- cleanup_protocol:
- ip_vs_protocol_cleanup();
- ip_vs_control_cleanup();
- cleanup_nothing:
- return ret;
- }
|
ip_vs_control_init()
ipvs一旦运起起来,管理员是通过ipvsadm来控制它的.在这个部分里,将会搞清楚用户空间工具ipvsadm是如何同内核空间ipvs通讯的,以及ipvsadm所做的配置是以什么形式保存的
net/ipv4/ipvs/ip_vs_ctl.c- int ip_vs_control_init(void)
- {
- int ret;
- int idx;
-
- EnterFunction(2);
-
- ret = nf_register_sockopt(&ip_vs_sockopts);
- if (ret) {
- IP_VS_ERR("cannot register sockopt.\n");
- return ret;
- }
-
- proc_net_fops_create(&init_net, "ip_vs", 0, &ip_vs_info_fops);
- proc_net_fops_create(&init_net, "ip_vs_stats",0, &ip_vs_stats_fops);
-
- sysctl_header = register_sysctl_paths(net_vs_ctl_path, vs_vars);
-
-
- for(idx = 0; idx < IP_VS_SVC_TAB_SIZE; idx++) {
- INIT_LIST_HEAD(&ip_vs_svc_table[idx]);
- INIT_LIST_HEAD(&ip_vs_svc_fwm_table[idx]);
- }
- for(idx = 0; idx < IP_VS_RTAB_SIZE; idx++) {
- INIT_LIST_HEAD(&ip_vs_rtable[idx]);
- }
-
- memset(&ip_vs_stats, 0, sizeof(ip_vs_stats));
- spin_lock_init(&ip_vs_stats.lock);
- ip_vs_new_estimator(&ip_vs_stats);
-
-
- schedule_delayed_work(&defense_work, DEFENSE_TIMER_PERIOD);
-
- LeaveFunction(2);
- return 0;
- }
|
根据我个人的经验,再linux内核中对sock进行操作的时候,大多都会有些结构名称带有opt/ops.
opt一般是泛指某种具体的操作(operate),可能是添加/删除/修改某个连接...比如nf_register_sockopt(),nf_setsockopt(),nf_sockopt_find().
ops可理解为opt的复数形式,表示一钟类型操作的集合,结构中一般具有对sock进行处理的函数指针,一般为双向链表结构,协议族类型,比如nf_hook_ops,nf_sockopt_ops
在这个函数里出现了一个ip_vs_sockopts变量
ip_vs_sockopts的原型为[::nf_sockopt_ops::]定义为[::ip_vs_sockopts::]
- list为一个双向链表结构,在ip_vs_sockopts中并没有定义,这个list会在nf_register_sockopt()中被定义,后面会讲到
- pf定义了协议族类型,这里定义的是AF_INET(互联网ip协议),在socket.h中定义了各种类型的协议族
- set_optmin:set命令字范围下限,[::所有IPVS socket options操作的宏定义::]
- set_optmax:set命令字范围上限
- set:函数指针,当用户空间的setsockopt()被调用,该函数负责将相应参数从用户空间传入内核空间,定义为do_ip_vs_set_ctl
- compat_set:兼容2.4以下版本的set函数指针,未定义
- get_optmin:get命令字范围下限
- get_optmax:get命令字范围上限
- get:函数指针,当用户空间的getsockopt()被调用,该函数负责将相应参数从用户空间传入内核空间,,定义为do_ip_vs_get_ctl
- compat_get:兼容2.4以下版本的get函数指针,未定义
- owner:只能在指定的模块中使用,定义为THIS_MODULE
这个变量的内容包含了用户空间ipvsadm所能做出的所有操作,每个具体的操作用一个数字(int)表示,这里的操作是对于netfilter而言的,所以ipvs的操作代号不能和netfilter或者netfilter的其他模块冲突,必须唯一,否则将无法用nf_register_sockopt()注册
操作分为2类(get/set),每类操作都有一个范围(min/max),用nf_register_sockopt()进行注册,成功以后,就会将ip_vs_sockopts加入到nf_sockopts双向链表中,ipvs的操作就像是一个嵌入到netfilter的模块,每当用户空间使用getsockopt() 以及 setsockopt()函数时,内核空间会去调用ip_setsockopt() 以及ip_getsockopt(),如果操作(opt)不认识,而且系统内有netfilter模块,则还会去尝试调用nf_setsockopt() 以及 nf_getsockopt()这个2个函数都会调用nf_sockopt()用nf_sockopt_find()寻找有没有相对应的opt.从代码中可以看出nf_sockopt_find()会遍历nf_sockopts这个双向链表,找出能覆盖opt范围(optmin < opt < optmax)的ops节点.如果找到了ops,就调用相对应的ops->get()或者ops->set()
nf_sockopts的结构如下
+-<-----------------------------------<-----------~~~---------------------<-+
| +-----------+ +--------------+ +-------------+ |
+---> |nf_sockopts| |ip_vs_sockopts| | ..._sockopts| |
| next --------------> | list.next ------~~~-> | list.next ---->--+
+-<------ prev | <------------ list.prev | <-~~~----- list.prev |
| +-----------+ | get | | get |
| | compat_get | | compat_get |
| | get_optmin | | get_optmin |
| | get_optmax | | get_optmax |
| | set | | set |
| | compat_set | | compat_set | <---+
| | set_optmin | | get_optmin | |
| | set_optmax | | get_optmax | |
| +--------------+ +-------------+ |
+->----------------------------------->-----------~~~--------------------->-+
在ip_vs_sockopts中,get和set这2个函数指针分别指向了do_ip_vs_get_ctl()和do_ip_vs_set_ctl()这2个函数
do_ip_vs_get_ctl()
net/ipv4/ipvs/ip_vs_ctl.c- static int
- do_ip_vs_get_ctl(struct sock *sk, int cmd, void __user *user, int *len)
- {
- unsigned char arg[128];
- int ret = 0;
-
- if (!capable(CAP_NET_ADMIN))
- return -EPERM;
-
- if (*len < get_arglen[GET_CMDID(cmd)]) {
- IP_VS_ERR("get_ctl: len %u < %u\n",
- *len, get_arglen[GET_CMDID(cmd)]);
- return -EINVAL;
- }
-
- if (copy_from_user(arg, user, get_arglen[GET_CMDID(cmd)]) != 0)
- return -EFAULT;
-
- if (mutex_lock_interruptible(&__ip_vs_mutex))
- return -ERESTARTSYS;
-
- switch (cmd) {
- case IP_VS_SO_GET_VERSION:
- {
- char buf[64];
-
- sprintf(buf, "IP Virtual Server version %d.%d.%d (size=%d)",
- NVERSION(IP_VS_VERSION_CODE), IP_VS_CONN_TAB_SIZE);
- if (copy_to_user(user, buf, strlen(buf)+1) != 0) {
- ret = -EFAULT;
- goto out;
- }
- *len = strlen(buf)+1;
- }
- break;
-
- case IP_VS_SO_GET_INFO:
- {
- struct ip_vs_getinfo info;
- info.version = IP_VS_VERSION_CODE;
- info.size = IP_VS_CONN_TAB_SIZE;
- info.num_services = ip_vs_num_services;
- if (copy_to_user(user, &info, sizeof(info)) != 0)
- ret = -EFAULT;
- }
- break;
-
- case IP_VS_SO_GET_SERVICES:
- {
- struct ip_vs_get_services *get;
- int size;
-
- get = (struct ip_vs_get_services *)arg;
- size = sizeof(*get) +
- sizeof(struct ip_vs_service_entry) * get->num_services;
- if (*len != size) {
- IP_VS_ERR("length: %u != %u\n", *len, size);
- ret = -EINVAL;
- goto out;
- }
- ret = __ip_vs_get_service_entries(get, user);
- }
- break;
-
- case IP_VS_SO_GET_SERVICE:
- {
- struct ip_vs_service_entry *entry;
- struct ip_vs_service *svc;
-
- entry = (struct ip_vs_service_entry *)arg;
- if (entry->fwmark)
- svc = __ip_vs_svc_fwm_get(entry->fwmark);
- else
- svc = __ip_vs_service_get(entry->protocol,
- entry->addr, entry->port);
- if (svc) {
- ip_vs_copy_service(entry, svc);
- if (copy_to_user(user, entry, sizeof(*entry)) != 0)
- ret = -EFAULT;
- ip_vs_service_put(svc);
- } else
- ret = -ESRCH;
- }
- break;
-
- case IP_VS_SO_GET_DESTS:
- {
- struct ip_vs_get_dests *get;
- int size;
-
- get = (struct ip_vs_get_dests *)arg;
- size = sizeof(*get) +
- sizeof(struct ip_vs_dest_entry) * get->num_dests;
- if (*len != size) {
- IP_VS_ERR("length: %u != %u\n", *len, size);
- ret = -EINVAL;
- goto out;
- }
- ret = __ip_vs_get_dest_entries(get, user);
- }
- break;
-
- case IP_VS_SO_GET_TIMEOUT:
- {
- struct ip_vs_timeout_user t;
-
- __ip_vs_get_timeouts(&t);
- if (copy_to_user(user, &t, sizeof(t)) != 0)
- ret = -EFAULT;
- }
- break;
-
- case IP_VS_SO_GET_DAEMON:
- {
- struct ip_vs_daemon_user d[2];
-
- memset(&d, 0, sizeof(d));
- if (ip_vs_sync_state & IP_VS_STATE_MASTER) {
- d[0].state = IP_VS_STATE_MASTER;
- strlcpy(d[0].mcast_ifn, ip_vs_master_mcast_ifn, sizeof(d[0].mcast_ifn));
- d[0].syncid = ip_vs_master_syncid;
- }
- if (ip_vs_sync_state & IP_VS_STATE_BACKUP) {
- d[1].state = IP_VS_STATE_BACKUP;
- strlcpy(d[1].mcast_ifn, ip_vs_backup_mcast_ifn, sizeof(d[1].mcast_ifn));
- d[1].syncid = ip_vs_backup_syncid;
- }
- if (copy_to_user(user, &d, sizeof(d)) != 0)
- ret = -EFAULT;
- }
- break;
-
- default:
- ret = -EINVAL;
- }
-
- out:
- mutex_unlock(&__ip_vs_mutex);
- return ret;
- }
|
do_ip_vs_set_ctl()
net/ipv4/ipvs/ip_vs_ctl.c- static int
- do_ip_vs_set_ctl(struct sock *sk, int cmd, void __user *user, unsigned int len)
- {
- int ret;
- unsigned char arg[MAX_ARG_LEN];
- struct ip_vs_service_user *usvc;
- struct ip_vs_service *svc;
- struct ip_vs_dest_user *udest;
-
- if (!capable(CAP_NET_ADMIN))
- return -EPERM;
-
- if (len != set_arglen[SET_CMDID(cmd)]) {
- IP_VS_ERR("set_ctl: len %u != %u\n",
- len, set_arglen[SET_CMDID(cmd)]);
- return -EINVAL;
- }
-
- if (copy_from_user(arg, user, len) != 0)
- return -EFAULT;
-
-
- ip_vs_use_count_inc();
-
- if (mutex_lock_interruptible(&__ip_vs_mutex)) {
- ret = -ERESTARTSYS;
- goto out_dec;
- }
-
- if (cmd == IP_VS_SO_SET_FLUSH) {
-
- ret = ip_vs_flush();
- goto out_unlock;
- } else if (cmd == IP_VS_SO_SET_TIMEOUT) {
-
- ret = ip_vs_set_timeout((struct ip_vs_timeout_user *)arg);
- goto out_unlock;
- } else if (cmd == IP_VS_SO_SET_STARTDAEMON) {
- struct ip_vs_daemon_user *dm = (struct ip_vs_daemon_user *)arg;
- ret = start_sync_thread(dm->state, dm->mcast_ifn, dm->syncid);
- goto out_unlock;
- } else if (cmd == IP_VS_SO_SET_STOPDAEMON) {
- struct ip_vs_daemon_user *dm = (struct ip_vs_daemon_user *)arg;
- ret = stop_sync_thread(dm->state);
- goto out_unlock;
- }
-
- usvc = (struct ip_vs_service_user *)arg;
- udest = (struct ip_vs_dest_user *)(usvc + 1);
-
- if (cmd == IP_VS_SO_SET_ZERO) {
-
- if (!usvc->fwmark && !usvc->addr && !usvc->port) {
- ret = ip_vs_zero_all();
- goto out_unlock;
- }
- }
-
-
- if (usvc->protocol!=IPPROTO_TCP && usvc->protocol!=IPPROTO_UDP) {
- IP_VS_ERR("set_ctl: invalid protocol: %d %d.%d.%d.%d:%d %s\n",
- usvc->protocol, NIPQUAD(usvc->addr),
- ntohs(usvc->port), usvc->sched_name);
- ret = -EFAULT;
- goto out_unlock;
- }
-
-
- if (usvc->fwmark == 0)
- svc = __ip_vs_service_get(usvc->protocol,
- usvc->addr, usvc->port);
- else
- svc = __ip_vs_svc_fwm_get(usvc->fwmark);
-
- if (cmd != IP_VS_SO_SET_ADD
- && (svc == NULL || svc->protocol != usvc->protocol)) {
- ret = -ESRCH;
- goto out_unlock;
- }
-
- switch (cmd) {
- case IP_VS_SO_SET_ADD:
- if (svc != NULL)
- ret = -EEXIST;
- else
- ret = ip_vs_add_service(usvc, &svc);
- break;
- case IP_VS_SO_SET_EDIT:
- ret = ip_vs_edit_service(svc, usvc);
- break;
- case IP_VS_SO_SET_DEL:
- ret = ip_vs_del_service(svc);
- if (!ret)
- goto out_unlock;
- break;
- case IP_VS_SO_SET_ZERO:
- ret = ip_vs_zero_service(svc);
- break;
- case IP_VS_SO_SET_ADDDEST:
- ret = ip_vs_add_dest(svc, udest);
- break;
- case IP_VS_SO_SET_EDITDEST:
- ret = ip_vs_edit_dest(svc, udest);
- break;
- case IP_VS_SO_SET_DELDEST:
- ret = ip_vs_del_dest(svc, udest);
- break;
- default:
- ret = -EINVAL;
- }
-
- if (svc)
- ip_vs_service_put(svc);
-
- out_unlock:
- mutex_unlock(&__ip_vs_mutex);
- out_dec:
-
- ip_vs_use_count_dec();
-
- return ret;
- }
|
可以看到,set/get的大部分操作就是对svc/rs的信息进行读取/添加/修改/删除,那么这么多信息,如何才能快速得访问呢?这里用到了3个hash的双向链表结构
我们可以用svc的协议/地址/端口通过hash换算得到一个0~255范围内的值x,然后对ip_vs_svc_table[x]进行操作
我们可以用svc的firewall mask通过hash换算得到一个0~255范围内的值x,然后对ip_vs_svc_fwm_table[x]进行操作
我们可以用rs的协议/地址/端口通过hash换算得到一个0~15范围内的值x,然后对ip_vs_rtable[x]进行操作
举一个例子,链表结构大致是这个样子的
ip_vs_svc_table[0] <-> svc.list <-> svc.list <-~~-> ip_vs_svc_table[0]
ip_vs_svc_table[1] <-> svc.list <-> svc.list <-~~-> ip_vs_svc_table[1]
ip_vs_svc_table[2] <-> svc.list <-> svc.list <-~~-> ip_vs_svc_table[2]
~
~
ip_vs_svc_table[255] <-> svc.list <-> svc.list <-> ... <-> ip_vs_svc_table[255]
ip_vs_protocol_init()
初始化ipvs能支持的协议
net/ipv4/ipvs/ip_vs_proto.c- int ip_vs_protocol_init(void)
- {
- char protocols[64];
- #define REGISTER_PROTOCOL(p) \
- do { \
- register_ip_vs_protocol(p); \
- strcat(protocols, ", "); \
- strcat(protocols, (p)->name); \
- } while (0)
-
- protocols[0] = '\0';
- protocols[2] = '\0';
- #ifdef CONFIG_IP_VS_PROTO_TCP
- REGISTER_PROTOCOL(&ip_vs_protocol_tcp);
- #endif
- #ifdef CONFIG_IP_VS_PROTO_UDP
- REGISTER_PROTOCOL(&ip_vs_protocol_udp);
- #endif
- #ifdef CONFIG_IP_VS_PROTO_AH
- REGISTER_PROTOCOL(&ip_vs_protocol_ah);
- #endif
- #ifdef CONFIG_IP_VS_PROTO_ESP
- REGISTER_PROTOCOL(&ip_vs_protocol_esp);
- #endif
- IP_VS_INFO("Registered protocols (%s)\n", &protocols[2]);
-
- return 0;
- }
|
认证头(AH)协议:协议的目的是用来增加IP数据包的安全性。AH协议提供无连接的完整性、数据源认证和抗重播保护服务。
封装安全载荷(ESP)协议:协议的目的和认证头(AH)一样,是用于提高IP的安全性。ESP提供数据保密、数据源认证、无连接完整性、抗重播服务和有限的数据流保护。
对于ipvs来说,其中ah,esp只与传输层有关,一般不用做什么处理,相应的代码可以在ip_vs_proto_esp/ah.c中找到
tcp协议和udp协议基本上大同小异,这里主要对tcp进行分析,用来注册的变量ip_vs_protocol_tcp如下
net/ipv4/ipvs/ip_vs_proto_tcp.c- struct ip_vs_protocol ip_vs_protocol_tcp = {
- .name = "TCP",
- .protocol = IPPROTO_TCP,
- .num_states = IP_VS_TCP_S_LAST,
- .dont_defrag = 0,
- .appcnt = ATOMIC_INIT(0),
- .init = ip_vs_tcp_init,
- .exit = ip_vs_tcp_exit,
- .register_app = tcp_register_app,
- .unregister_app = tcp_unregister_app,
- .conn_schedule = tcp_conn_schedule,
- .conn_in_get = tcp_conn_in_get, //
- .conn_out_get = tcp_conn_out_get, //
- .snat_handler = tcp_snat_handler,
- .dnat_handler = tcp_dnat_handler,
- .csum_check = tcp_csum_check, //
- .state_name = tcp_state_name, //
- .state_transition = tcp_state_transition, //
- .app_conn_bind = tcp_app_conn_bind, //
- .debug_packet = ip_vs_tcpudp_debug_packet, //
- .timeout_change = tcp_timeout_change, //
- .set_state_timeout = tcp_set_state_timeout, //
- };
|
这个变量是使用register_ip_vs_protocol()来注册的
net/ipv4/ipvs/ip_vs_proto.c-
- * register an ipvs protocol
-
- static int __used register_ip_vs_protocol(struct ip_vs_protocol *pp)
- {
- unsigned hash = IP_VS_PROTO_HASH(pp->protocol);
-
- pp->next = ip_vs_proto_table[hash];
- ip_vs_proto_table[hash] = pp;
-
- if (pp->init != NULL)
- pp->init(pp);
-
- return 0;
- }
|
看到这里,有些人可能会认为一共才4个协议,感觉hash表+单链结构是不是太过复杂了?看看下面的hashkey的取得就能明白了,其实就是用protocol当hashkey.单链结构也是非常简单的,总共只用到32个指针
net/ipv4/ipvs/ip_vs_proto.c-
- * IPVS protocols can only be registered/unregistered when the ipvs
- * module is loaded/unloaded, so no lock is needed in accessing the
- * ipvs protocol table.
-
-
- #define IP_VS_PROTO_TAB_SIZE 32
- #define IP_VS_PROTO_HASH(proto) ((proto) & (IP_VS_PROTO_TAB_SIZE-1))
-
- static struct ip_vs_protocol *ip_vs_proto_table[IP_VS_PROTO_TAB_SIZE];
|
接下来看看tcp协议是如何初始化的
net/ipv4/ipvs/ip_vs_proto_tcp.c- static void ip_vs_tcp_init(struct ip_vs_protocol *pp)
- {
- IP_VS_INIT_HASH_TABLE(tcp_apps);
- pp->timeout_table = tcp_timeouts;
- }
|
到这里,关于tcp协议的初始化就完成了
ip_vs_app_init()
net/ipv4/ipvs/ip_vs_app.c- int ip_vs_app_init(void)
- {
-
- proc_net_fops_create(&init_net, "ip_vs_app", 0, &ip_vs_app_fops);
- return 0;
- }
|
这个部分是由ip_masq_app.c文件改写而成,原先用作数据报文的伪装(在kernel2.2中).ipvs在具体运行中好像并没有用到这个部分(至少我测试的时候,这部分的数据一直为空)
ip_vs_conn_init()
net/ipv4/ipvs/ip_vs_conn.c- int ip_vs_conn_init(void)
- {
- int idx;
-
-
- * Allocate the connection hash table and initialize its list heads
-
- ip_vs_conn_tab = vmalloc(IP_VS_CONN_TAB_SIZE*sizeof(struct list_head));
- if (!ip_vs_conn_tab)
- return -ENOMEM;
-
-
- ip_vs_conn_cachep = kmem_cache_create("ip_vs_conn",
- sizeof(struct ip_vs_conn), 0,
- SLAB_HWCACHE_ALIGN, NULL);
- if (!ip_vs_conn_cachep) {
- vfree(ip_vs_conn_tab);
- return -ENOMEM;
- }
-
- IP_VS_INFO("Connection hash table configured "
- "(size=%d, memory=%ldKbytes)\n",
- IP_VS_CONN_TAB_SIZE,
- (long)(IP_VS_CONN_TAB_SIZE*sizeof(struct list_head))/1024);
- IP_VS_DBG(0, "Each connection entry needs %Zd bytes at least\n",
- sizeof(struct ip_vs_conn));
-
- for (idx = 0; idx < IP_VS_CONN_TAB_SIZE; idx++) {
- INIT_LIST_HEAD(&ip_vs_conn_tab[idx]);
- }
-
- for (idx = 0; idx < CT_LOCKARRAY_SIZE; idx++) {
- rwlock_init(&__ip_vs_conntbl_lock_array[idx].l);
- }
-
- proc_net_fops_create(&init_net, "ip_vs_conn", 0, &ip_vs_conn_fops);
- proc_net_fops_create(&init_net, "ip_vs_conn_sync", 0, &ip_vs_conn_sync_fops);
-
-
- get_random_bytes(&ip_vs_conn_rnd, sizeof(ip_vs_conn_rnd));
-
- return 0;
- }
|
值得留意的是用来存放连接状态的数据结构使用了kmem_cache_create()来注册到系统的缓存区.由于ipvs运行以后,连接会非常频繁,那么用来存储连接状态的conn_table会不断的添加,删除.对于内存来说,会有很大开销,而频繁释放和分配的内存他们的结构和大小是一致的.slab算法就是针对这样特点设计,会大幅度提高使用效率,用kmem_cache_zalloc申请的内存时带上ip_vs_conn_cachep这个参数,就会在ip_vs_conn_cachep的内存块里分配到一个单位的空间,对于kmem_cache_free和kmem_cache_destroy也一样.
这样,连接的初始化也就完成了
nf_register_hooks()
net/netfilter/core.c- int nf_register_hooks(struct nf_hook_ops *reg, unsigned int n)
- {
- unsigned int i;
- int err = 0;
-
- for (i = 0; i < n; i++) {
- err = nf_register_hook(®[i]);
- if (err)
- goto err;
- }
- return err;
-
- err:
- if (i > 0)
- nf_unregister_hooks(reg, i);
- return err;
- }
- EXPORT_SYMBOL(nf_register_hooks);
|
那再来看看nf_register_hook()是如何工作的
net/netfilter/core.c- int nf_register_hook(struct nf_hook_ops *reg)
- {
- struct nf_hook_ops *elem;
- int err;
-
- err = mutex_lock_interruptible(&nf_hook_mutex);
- if (err < 0)
- return err;
- list_for_each_entry(elem, &nf_hooks[reg->pf][reg->hooknum], list) {
- if (reg->priority < elem->priority)
- break;
- }
- list_add_rcu(®->list, elem->list.prev);
- mutex_unlock(&nf_hook_mutex);
- return 0;
- }
- EXPORT_SYMBOL(nf_register_hook);
|
运行到这里,所有的准备工作就完成了.管理员就可以在用户空间使用ipvsadm工具向ipvs发出指令.如果ipvs中有相应的配置,那么ipvs也就会工作起来
ipvs是如何工作的呢?是由什么事件来触发ipvs的进行相应的操作呢?那就继续往下,进入第二部分的分析《运行中的ipvs》
参考资料
- Kernel command using Linux system calls
- netfilter: Linux 防火墙在内核中的实现