OpenVPN 中虛擬ip地址的自定義分配--總結

火星人 @ 2014-03-04 , reply:0


OpenVPN 中虛擬ip地址的自定義分配--總結

OpenVPN中自定義IP地址分配有兩種實現方式,一種是寫一個plugin,plugin_call這個調用可以被添加到任何地方,OpenVPN中的plugin掛載點也可在任意位置定義,因此利用plugin來自定義IP地址分配策略就有兩種方式,第一種方式是可以新定義一個plugin掛載點,緊接著動態分配IP的代碼,然後在plugin中實現策略,當然具體到如何將已經分配的ip地址的指針傳入plugin中還需要前文中介紹的方式,將ip地址的內存地址作為環境變數加入到env_set,然後...,這種方式需要修改OpenVPN的代碼,要麼自己定義一個掛載點,要麼在已有的CLIENT_CONNECT中加入新的內存地址環境變數;第二種方式是一種標準的方式,也就是OpenVPN建議的方式,那就是在multi_connection_established中做文章,OpenVPN的代碼不需要做任何修改:
static void multi_connection_established (struct multi_context *m, struct multi_instance *mi)
{
...
    if (plugin_defined (mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT)) {
        struct argv argv = argv_new (); //下面將要生成一個臨時文件的名字,該文件名中包含隨機數。
        const char *dc_file = create_temp_filename (mi->context.options.tmp_dir, "cc", &gc); //由於這個將要生成的文件只在此掛載點被使用一次,用來讓OpenVPN得到配置信息,比如IP地址或者路由之類,因此用完就要被刪除,可以也可能刪除失敗,因此為了防止別的client連入時誤用此文件,因此需要隨機生成一個文件,從而使得文件名不可預測。
        argv_printf (&argv, "%s", dc_file); //將臨時文件名加入plugin的argv參數中
        delete_file (dc_file); //既然我們已經得到了需要建立的文件名,刪除新建的文件
        if (plugin_call (mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT,&argv...) != OPENVPN_PLUGIN_FUNC_SUCCESS) {
           ... //multi_client_connect_post中將實現抉擇,如果plugin定義了IP地址,那麼將在此函數中刪除動態分配的ip地址
            multi_client_connect_post (m, mi, dc_file, option_permissions_mask, &option_types_found);//此函數會讀取由plugin生成的配置文件進行參數配置
              ...
    }
...
}
然後在plugin中這麼實現即可:(首先要在open回調函數中註冊OPENVPN_PLUGIN_CLIENT_CONNECT掛載點)
OPENVPN_EXPORT int openvpn_plugin_func_v1 (openvpn_plugin_handle_t handle, const int type, const char *argv[], const char *envp[])
{
        int rv = -1;
        struct plugin_context *context = (struct plugin_context *) handle; //plugin_context自定義,可以包含你需要的任何變數
        const char *YYY = get_env ("XXX", envp); //get_env函數是一個遍歷匹配的過程,XXX是你希望得到的用於IP分配決策的client端變數,比如可以是證書的CN項,也可以是其真實的ip地址
        ... //還可以從envp中得到任何別的變數,不僅僅是client端的,只要是env_set中的均可以得到
        ... //實現虛擬IP分配邏輯,可以通過資料庫,可以通過unix-pam機制,...不過記住最終的虛擬ip需要寫入argv代表的文件中,也就是OpenVPN傳入的那個文件名帶有隨機數的臨時文件中,寫法很簡單:sprintf(cmd, "echo 'ifconfig-push %s'>>%s", 虛擬ip, dc_file);system(cmd);
}
dc_file中不僅僅可以包含client端的虛擬ip地址信息,任何OpenVPN接受的配置信息都可以,比如說路由信息也是可以的。如此就完成了虛擬IP地址的自定義分配過程,如果不太擅長寫C語言,那麼還有另一種對等的方式可以使用,這就是寫一個腳本:
#!/bin/bash
...
#同樣取出需要的環境變數(env_set中的變數),用$XXX取出即可
#可以取很多
...
...可以通過perl/python等鏈接資料庫或者不鏈接資料庫而使用別的策略...
echo ifconfig-push 分配結果 >> $1
echo ... >> $1
#end
是不是更簡單?以上腳本的方式在OpenVPN中的依據是:
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, cert);
    if (openvpn_execve_check (&argv, mi->context.c2.es, S_SCRIPT, "client-connect command failed")) {
        multi_client_connect_post (m, mi, dc_file...);
    ...
}
最後我們注意一點,那就是和OPENVPN_PLUGIN_CLIENT_CONNECT這個宏對應的還有OPENVPN_PLUGIN_CLIENT_CONNECT_V2,並且在if (plugin_defined (mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT))前面還有一個deprecated callback的註釋,這個註釋其實也是我們想辦法通過在env_set中添加一個IP地址內存地址的原因之一,正如註釋所說,plugin的OPENVPN_PLUGIN_CLIENT_CONNECT掛載點是通過一個臨時文件來傳輸信息的,而讀寫文件是一件很麻煩的事情,整個讀寫過程不被OpenVPN地址空間監視,除了基於文件系統本身的系統調用測試,你甚至不知道文件建立/刪除成功了沒有,進程之外的IO行為很容易被別的進程截獲,更改,另外,文件IO是一個很耗時的過程,在大負載下會降低性能,更何況,OpenVPN當前是一個串列的大進程,本來的設計缺陷加上每個client一兩次文件IO更是雪上加霜。鑒於上述問題,OpenVPN推出了V2系列plugin掛載點,僅針對client連接這個掛載點來說,V2系列的plugin回調函數多了一個傳出參數struct plugin_return,更改了V1系列掛載點plugin對env_set可讀不可寫的缺陷:
struct plugin_return {  //返回參數,也就是plugin的傳出參數
    int n;
    struct openvpn_plugin_string_list *list;
};
struct openvpn_plugin_string_list { //傳出參數容器中的元素,鏈接成一個鏈表
    struct openvpn_plugin_string_list *next;
    char *name;
    char *value;
};
if (plugin_defined (mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT_V2)) {
    struct plugin_return pr;  //定義一個傳出參數
    plugin_return_init (&pr);
    if (plugin_call (mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT_V2, NULL, &pr, mi->context.c2.es) != OPENVPN_PLUGIN_FUNC_SUCCESS) {
    ...//下面的調用中接著調用plugin_return_get_column來搜集被plugin配置或者更改的信息,然後用options_string_import將之配置入OpenVPN的變數中
        multi_client_connect_post_plugin (m, mi, &pr, option_permissions_mask, &option_types_found);
    ...
}
static void multi_client_connect_post_plugin (...) {
    struct plugin_return config;
    plugin_return_get_column (pr, &config, "config"); //搜集被觸及的變數
      /* Did script generate a dynamic config file? */ //一看就知道此等代碼的作者也少不了複製-粘貼
      if (plugin_return_defined (&config)) {
        for (i = 0; i < config.n; ++i) { //遍歷每一個被觸及的變數
            if (config.list && config.list->value)
                    options_string_import (...); //將plugin中觸及的變數轉化為內部數據結構的欄位
    }
      ...
}
plugin的方式有V2系列調用,而腳本的方式卻沒有,因為腳本本身就在獨立的進程地址空間中運行,除了使用共享內存之外不可能有腳本「傳出參數」一說的,綜合對比文件和共享內存傳輸數據的複雜性和性能,還是不要使用共享內存了,所以依然使用文件。
     實現自定義分配IP地址的方式除了plugin以及腳本方式之外還有很多,實際上動態分配IP只是分配虛擬IP地址的一種方式罷了,在很多應用中,管理client比較重要,因此很多時候都是自定義的虛擬IP地址分配而不是通常我們自己玩時的動態IP地址分配。
《解決方案》

非常不錯!學習了




[火星人 via ] OpenVPN 中虛擬ip地址的自定義分配--總結已經有293次圍觀

http://www.coctec.com/docs/service/show-post-15643.html