OpenVPN中虚拟ip地址的自定义分配

OpenVPN实现了自己的一套ip地址分配的方略,正如《OpenVPN中虚拟ip地址的分配》中所述,但是如果我觉得这套方式不好的话,我又怎么修改之呢,或者说如何实现自定义的ip地址分配策略呢?OpenVPN是开源的,因此修改代码是一个选择,然而任何优秀的开源项目都不会傻到非要让用户修改代码从而获得自定义特性,比如linux内核提供了lkm机制,apache提供了module机制,Eclipse的Plugin机制,OpenSSL提供了engine机制等等,OpenVPN也不例外,它提供了两种外挂扩展机制,一种是script机制,另一种是plugin机制,script机制就是在特定的事件点上运行一个脚本,在OpenVPN地址空间之外运行,而plugin机制则是在特定事件点执行一系列的动态库中的函数,在OpenVPN的地址空间内部执行,两类方式都能实现ip地址的自定义分配,本文依次叙之:
1.script方式
multi_connection_established中有以下调用:
if (mi->context.options.client_connect_script && cc_succeeded) {
struct argv argv = argv_new ();
const char *dc_file = NULL;
setenv_str (mi->context.c2.es, "script_type", "client-connect");
dc_file = create_temp_filename (mi->context.options.tmp_dir, "cc", &gc);
delete_file (dc_file);
argv_printf (&argv, "%sc %s",
mi->context.options.client_connect_script, dc_file); //将脚本名称和dc_file的名称传入了argv,作为要执行的脚本和其参数,环境变量就是该连接client的相关信息,脚本将来要根据这些信息来生成其ip地址
if (openvpn_execve_check (&argv, mi->context.c2.es, S_SCRIPT, "client-connect command failed")) { //执行脚本,在脚本中可以执行任意的事情,如果说要想自定义分配ip的话,那么就形成一个文件,名称是dc_file,里面写入ip地址的分配规则,具体规则就不详述了,肯定是有一定格式的。然后...
multi_client_connect_post (m, mi, dc_file, option_permissions_mask, &option_types_found);//然后在该函数里面会读取脚本生成的dc_file,读入该文件中分配的ip。
++cc_succeeded_count;
}
...
}
static void multi_client_connect_post (struct multi_context *m,
struct multi_instance *mi,
const char *dc_file,
unsigned int option_permissions_mask,
unsigned int *option_types_found)
{
if (test_file (dc_file)) { //如果script真的创建了一个保存有client端ip地址的文件,那么就做下面的
options_server_import (&mi->context.options, //读取文件,取得需要的信息
dc_file,
D_IMPORT_ERRORS|M_OPTERR,
option_permissions_mask,
option_types_found,
mi->context.c2.es);
if (!delete_file (dc_file)) //既然ip已经读取了,那么就删除文件吧
...//注释:之所以调用下面的函数是因为在multi_select_virtual_addr中,如果ip地址已经根据文件分配过了,那么就不再动态分配了,并且释放掉动态分配的entry
multi_select_virtual_addr (m, mi);
multi_set_virtual_addr_env (m, mi); //设置该连接的“环境变量”
}
}
就这样,以一个dc_file文件为中介,成功实现了ip地址的自定义分配,这只是一种方案,另一种方案就是所谓的plugin机制。
2.plugin机制
OpenVPN实现了可插拔的插件--plugin,类似于linux内核中的netfilter,就是在特定的几个挂载点或者叫监控点挂载一些钩子,然后当执行路径到达这些点的时候便会执行这些钩子,和netfilter非常类似,OpenVPN的plugin也是遍历调用的,说是在特定的点上挂载钩子,其实可以在任意的位置挂载,只要对你的逻辑有用,和netfilter一样,可以轻而易举地修改源代码使得任意点都可以成为挂载点。
所谓的plugin实际上就是一个动态库,和别的钩子机制一样,必须实现几个特定的函数,并且每个plugin必须申明自己在哪些点上挂载,只是OpenVPN的plugin中实现的函数并不是一般意义上的回调函数,而是名称特定的函数,这是为了让plugin最大限度解除对OpenVPN的依赖,对函数的调用是通过dlsym进行的,在windows下当然就是GetProcAddress了,
struct plugin {
bool initialized;
const char *so_pathname;
unsigned int plugin_type_mask;
int requested_initialization_point;
openvpn_plugin_open_v1 open1; //该函数定义自己需要挂载哪些点
...
openvpn_plugin_func_v1 func1;//该函数的实现就是一个大的switch-case或者if-elif-else
...//其他的回调函输
openvpn_plugin_handle_t plugin_handle;
};
int plugin_call (const struct plugin_list *pl,
const int type,
const struct argv *av,
struct plugin_return *pr,
struct env_set *es) //env_set就是一个name-value对的集合,但是OpenVPN却没有这么设计,而是...
{
...
struct gc_arena gc = gc_new ();
int i;
const char **envp;
const int n = plugin_n (pl);
bool success = false;
bool error = false;
bool deferred = false;
...
setenv_del (es, "script_type");
envp = make_env_array(es, false, &gc); //根据环境变量集合es生成一个char*数组,类似于main函数的argv,用于调用plugin的参数
for (i = 0; i < n; ++i)    {
const int status = plugin_call_item (&pl->common->plugins[i],
pl->per_client.per_client_context[i],
type,
av,
pr ? &pr->list[i] : NULL,
envp);
switch (status) {
case OPENVPN_PLUGIN_FUNC_SUCCESS:  //只要一个成功就设置success标志
success = true;
break;
...
default: //只要一个成功就设置error标志
error = true;
break;
}
}
...
if (type == OPENVPN_PLUGIN_ENABLE_PF && success) //在PF情形下,只要有成功的就成功
return OPENVPN_PLUGIN_FUNC_SUCCESS;
else if (error)  //只要有一个plugin执行失败则失败
return OPENVPN_PLUGIN_FUNC_ERROR;
...//由此可见OpenVPN的plugin(s)链的调用和netfilter还有所不同,netfilter是首次匹配原则,而这里却使用了不同的策略
return OPENVPN_PLUGIN_FUNC_SUCCESS;
}
在plugin中,env_set的作用十分大,这个结构体也很重要,它几乎包含了一个client对于一个server所有需要的信息,以name-value对的形式保存,也很容易理解,在OpenVPN的一个连接的生命周期中多次需要一些信息,而这些信息几乎都可以从env_set中得到,另外该结构体作为script和plugin函数参数的来源扮演了一个很重要的角色,但是也有美中不足,make_env_array函数将env_set组织成了一个char*数组,该函数很简单:
const char ** make_env_array (const struct env_set *es,
const bool check_allowed,
struct gc_arena *gc)
{
char **ret = NULL;
struct env_item *e = NULL;
int i = 0, n = 0;
if (es) {  //首先得到list的长度
for (e = es->list; e != NULL; e = e->next)
++n;
}//其实这么实现很丑陋,如果能在env_set中保存一个当前的数量,就不用这么实现了
ALLOC_ARRAY_CLEAR_GC (ret, char *, n+1, gc);
if (es) {  //然后将每一个es元素加入到char*数组
i = 0;
for (e = es->list; e != NULL; e = e->next) {
if (!check_allowed || env_allowed (e->string)) {
ret[i++] = e->string;  //而是将name和value合成一个字符串了
}
}
}
ret[i] = NULL;
return (const char **)ret;
}
void setenv_str_ex (struct env_set *es,
const char *name,
const char *value,
...        )
{
...
name_tmp = string_mod_const (name, name_include, name_exclude, name_replace, &gc);
val_tmp = string_mod_const (value, value_include, value_exclude, value_replace, &gc);
const char *str = construct_name_value (name_tmp, val_tmp, &gc); //用"="将name和value连接成了"name=value"
env_set_add (es, str);  //将最新构造的env_item加入进全局的set中
...
}
最后看一下env_set:
struct env_item {
char *string; //形如"name=value"的字符串
struct env_item *next; //下一个env_item
};
struct env_set {
struct gc_arena *gc;
struct env_item *list; //真正的list
};
在OpenVPN的根目录下有一个misc.c的文件,里面实现了env_set的各种操作,有add,delete,但是缺少search和modify,这就给在plugin中修改环境变量带来了巨大的不便(不要指望在script中修改,因为它和OpenVPN根本就不在一个地址空间),如果有形如env_set_modify的话,我们就可以随意在plugin中修改环境变量了,比如修改分配给client的ip地址。只可惜在调用plugin的func之前,env_set就被转化成了一个char*数组,这么就有去无回了,也就是说plugin只能读取OpenVPN的一个连接的环境变量而无法修改,那是否真的无法修改了呢?由于plugin和OpenVPN处于一个地址空间,那么我们可以将要修改的变量的地址作为新的环境变量item加入到env_set中去,当然这需要修改OpenVPN的代码了,必须在调用plugin之前加入新的item,然后在plugin的func中取出该地址再做修改,这就是在plugin中修改ip地址的办法中的办法。
比如我们想修改OpenVPN内核为client动态分配的ip地址,那么我们只需要在调用OPENVPN_PLUGIN_CLIENT_CONNECT挂载点的plugin之前做点手脚即可,我们需要这么做:
char address_buf[10] = {0}; //作为一个地址,10个字符够了,因为32系统中最大的地址就是0XFFFFFFFF(姑且不说顶端不能被userspace访问的OS内核),化为10进制后就是10个字符
unsigned int address = (unsigned int)&mi->context.c2.push_ifconfig_local;
sprintf(address_buf, "%d", address);
setenv_str (mi->context.c2.es, "ifconfig_pool_myaddress_address", address);
然后在plugin中仿照OpenVPN自带的plugin实例plugin/examples/simple.c中的get_env取出这个地址,然后修改之即可,具体怎么修改可以随意,也可以根据其他的环境变量采用更复杂的策略生成,指针都到手了,还有什么做不到的呢?

标签: 无
返回文章列表 文章二维码
本页链接的二维码
打赏二维码