七、内核空间与用户空间的数据交互
内核空间与用户空间的数据交互通过getsockopt和setsockopt来完成,这个两个函数用来控制相关socket文件描述符的的选项值,先来看这两个函数的原型:
set/getsockopt(2)函数的基本使用格式为:
int setsockopt(int sockfd, int proto, int cmd, void *data, int datalen)
int getsockopt(int sockfd, int proto, int cmd, void *data, int datalen)
第一个参数为socket的文件描述符,第2个参数proto是sock协议,IP RAW的就用SOL_SOCKET/SOL_IP等,TCP/UDP socket的可用SOL_SOCKET/SOL_IP/SOL_TCP/SOL_UDP等,即高层的socket是都可以使用低层socket的命令字 的;第3个参数cmd是操作命令字,由自己定义;第4个参数是数据缓冲区起始位置指针,set操作时是将缓冲区数据写入内核,get的时候是将内核中的数 据读入该缓冲区;第5个参数数据长度。
我们可以通过扩充新的命令字段来实现特殊应用程序的内核与用户空间的数据交换,内核实现新的sockopt命令字有两类,一类是添加完整的新的协议后引入,一类是在原有协议命令集的基础上增加新的命令字,以netfilter为例,它就是在原有的基础上扩展命令字,实现了内核与用户空间的数据交换。Netfilter新定义的命令字如下:
setsockopt新增命令字:
#define IPT_SO_SET_REPLACE //设置规则
#define IPT_SO_SET_ADD_COUNTERS //加入计数器
getsockopt新增命令字;
#define IPT_SO_GET_INFO //获取ipt_info
#define IPT_SO_GET_ENTRIES //获取规则
#define IPT_SO_GET_REVISION_MATCH //获取match
#define IPT_SO_GET_REVISION_TARGET //获取target
为了支持新增加的关键字,需要在内核中作相应的改变,先来看一个常规的setsockopt的调用流程:
sys_socketcall->sys_setsockopt->sock_setsockopt->raw_setsockopt->ip_setsockopt
所有做的改变在于,在ip_setsockopt调用时,如果发现是一个没有定义的协议,并且判断现在这个opt是否为netfilter所设置,如果是则调用netfilter所设置的特殊处理函数,于是加入netfilter对sockopt特殊处理后,新的流程如下:
sys_socketcall->sys_setsockopt->sock_setsockopt->raw_setsockopt->ip_setsockopt->nf_setsockopt
netfilter首先定义一些特殊的sockopt的操作,并将其注册到一个全局变量,如果发现有netfilter对opt的操作,就调用这些特殊的操作,每个netfilter的sockopt操作的结构如下:
struct nf_sockopt_ops
{
struct list_head list;
int pf;//协议族
/* Non-inclusive ranges: use 0/0/NULL to never get called. */
int set_optmin;
int set_optmax;
int (*set)(struct sock *sk, int optval, void __user *user, unsigned int len);//set函数
int (*compat_set)(struct sock *sk, int optval,
void __user *user, unsigned int len);
int get_optmin;
int get_optmax;
int (*get)(struct sock *sk, int optval, void __user *user, int *len);//get函数
int (*compat_get)(struct sock *sk, int optval,
void __user *user, int *len);
/* Use the module struct to lock set/get code in place */
struct module *owner;
};
进入netfilter后的处理流程如下:
nf_setsockopt-> nf_sockopt-> 调用nf_sockopt_ops相应的get或set操作,
netfilter中将nf_sockopt_ops的set和get操作设置成如下两个函数,根据不同的命令再调用具体的处理函数:
static int
do_ipt_set_ctl(struct sock *sk, int cmd, void __user *user, unsigned int len)
{
int ret;
if (!capable(CAP_NET_ADMIN))
return -EPERM;
switch (cmd) {
case IPT_SO_SET_REPLACE://设置新规则
ret = do_replace(user, len);
break;
case IPT_SO_SET_ADD_COUNTERS://设置计数器
ret = do_add_counters(user, len, 0);
break;
default:
duprintf("do_ipt_set_ctl: unknown request %i\n", cmd);
ret = -EINVAL;
}
return ret;
}
static int
do_ipt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len)
{
int ret;
if (!capable(CAP_NET_ADMIN))
return -EPERM;
switch (cmd) {
case IPT_SO_GET_INFO://获取规则信息
ret = get_info(user, len, 0);
break;
case IPT_SO_GET_ENTRIES://获取规则
ret = get_entries(user, len);
break;
case IPT_SO_GET_REVISION_MATCH://获取target或match
case IPT_SO_GET_REVISION_TARGET: {
struct ipt_get_revision rev;
int target;
if (*len != sizeof(rev)) {
ret = -EINVAL;
break;
}
if (copy_from_user(&rev, user, sizeof(rev)) != 0) {
ret = -EFAULT;
break;
}
if (cmd == IPT_SO_GET_REVISION_TARGET)
target = 1;
else
target = 0;
try_then_request_module(xt_find_revision(AF_INET, rev.name,
rev.revision,
target, &ret),
"ipt_%s", rev.name);
break;
}
default:
duprintf("do_ipt_get_ctl: unknown request %i\n", cmd);
ret = -EINVAL;
}
return ret;
}
内核与用户空间交互时使用使用的数据结构为ipt_replace:
struct ipt_replace
{
/* Which table. */
char name[IPT_TABLE_MAXNAMELEN];//表的名字
/* Which hook entry points are valid: bitmask. You can't
change this. */
unsigned int valid_hooks;//有效的hook
/* Number of entries */
unsigned int num_entries;//有多少条规则
/* Total size of new entries */
unsigned int size;新规则地尺寸
/* Hook entry points. */
unsigned int hook_entry[NF_IP_NUMHOOKS];//不同hook偏移
/* Underflow points. */
unsigned int underflow[NF_IP_NUMHOOKS];
/* Information about old entries: */
/* Number of counters (must be equal to current number of entries). */
unsigned int num_counters;
/* The old entries' counters. */
struct xt_counters __user *counters;
/* The entries (hang off end: not really an array). */
struct ipt_entry entries[0];//所有规则
};
以用户空间设置新规则所调用的 IPT_SO_SET_REPLACE命令为例,整个过程的执行流程如下:
do_replace->translate_table
__do_replace-> xt_find_table_lock
xt_replace_table
/*
* 函数:translate_table()
* 参数:
* name:表名称;
* valid_hooks:当前表所影响的hook
* newinfo:包含当前表的所有信息的结构
* size:表的大小
* number:表中的规则数
* hook_entries:记录所影响的HOOK的规则入口相对于下面的entries变量的偏移量
* underflows:与hook_entry相对应的规则表上限偏移量
* 作用:
* translate_table函数将newinfo表示的table的各个规则进行边界检查,然后对于newinfo所指的
* ipt_talbe_info结构中的hook_entries和underflows赋予正确的值,最后将表项向其他cpu拷贝
* 返回值:
* int ret==0表示成功返回
*/
static int
translate_table(const char *name,
unsigned int valid_hooks,
struct ipt_table_info *newinfo,
unsigned int size,
unsigned int number,
const unsigned int *hook_entries,
const unsigned int *underflows)
{
unsigned int i;
int ret;
newinfo->size = size;
newinfo->number = number;
/* 初始化所有Hooks为不可能的值. */
for (i = 0; i < NF_IP_NUMHOOKS; i++) {
newinfo->hook_entry = 0xFFFFFFFF;
newinfo->underflow = 0xFFFFFFFF;
}
duprintf("translate_table: size %u\n", newinfo->size);
i = 0;
/* 遍历所有规则,检查所有偏量,检查的工作都是由IPT_ENTRY_ITERATE这个宏来完成,并且它
ret = IPT_ENTR
的最后一个参数i,返回表的所有规则数. */ Y_ITERATE(newinfo->entries, newinfo->size,
check_entry_size_and_hooks,
newinfo,
newinfo->entries,
newinfo->entries + size,
hook_entries, underflows, &i);
if (ret != 0)
return ret;
/*实际计算得到的规则数与指定的不符*/
if (i != number) {
duprintf("translate_table: %u not %u entries\n",
i, number);
return -EINVAL;
}
/* 因为函数一开始将HOOK的偏移地址全部初始成了不可能的值,而在上一个宏的遍历中设置了
hook_entries和underflows的值,这里对它们进行检查 */
for (i = 0; i < NF_IP_NUMHOOKS; i++) {
/* 只检查当前表所影响的hook */
if (!(valid_hooks & (1 << i)))
continue;
if (newinfo->hook_entry == 0xFFFFFFFF) {
duprintf("Invalid hook entry %u %u\n",
i, hook_entries);
return -EINVAL;
}
if (newinfo->underflow == 0xFFFFFFFF) {
duprintf("Invalid underflow %u %u\n",
i, underflows);
return -EINVAL;
}
}
/*确保新的table中不存在规则环*/
if (!mark_source_chains(newinfo, valid_hooks))
return -ELOOP;
/* 对tables中的规则项进行完整性检查,保证每一个规则项在形式上是合法的*/
i = 0;
ret = IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size,
check_entry, name, size, &i);
/*检查失败,释放空间,返回*/
if (ret != 0) {
IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size,
cleanup_entry, &i);
return ret;
}
/* 为每个CPU复制一个完整的table项*/
for (i = 1; i < smp_num_cpus; i++) {
memcpy(newinfo->entries + SMP_ALIGN(newinfo->size)*i,
newinfo->entries,
SMP_ALIGN(newinfo->size));
}
return ret;
}
函数的核心处理,是调用了IPT_ENTRY_ITERATE,这个宏用来遍历每一个规则,然后
调用其第三个参数(函数指针)进行处理,前两个参数分别表示规则的起始位置和规则总大小,后面的参数则视情况而定。
/* fn returns 0 to continue iteration */
#define IPT_ENTRY_ITERATE(entries, size, fn, args...) \
({ \
unsigned int __i; \
int __ret = 0; \
struct ipt_entry *__entry; \
\
for (__i = 0; __i < (size); __i += __entry->next_offset) { \
__entry = (void *)(entries) + __i; \
\
__ret = fn(__entry , ## args); \
if (__ret != 0) \
break; \
} \
__ret; \
})
/*
*af:协议族
*name:表的名字
*/
struct xt_table *xt_find_table_lock(int af, const char *name)
{
struct xt_table *t;
if (mutex_lock_interruptible(&xt[af].mutex) != 0)
return ERR_PTR(-EINTR);
//遍历全局变量xt,找到指定协议族的中于name相同的表
list_for_each_entry(t, &xt[af].tables, list)
if (strcmp(t->name, name) == 0 && try_module_get(t->me))
return t;
mutex_unlock(&xt[af].mutex);
return NULL;
}