Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python module: support multiple instances #6

Merged
merged 2 commits into from
Jun 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions doc/unbound.conf.5.in
Original file line number Diff line number Diff line change
Expand Up @@ -1752,7 +1752,8 @@ clause gives the settings for the \fIpython\fR(1) script module. This module
acts like the iterator and validator modules do, on queries and answers.
To enable the script module it has to be compiled into the daemon,
and the word "python" has to be put in the \fBmodule\-config:\fR option
(usually first, or between the validator and iterator).
(usually first, or between the validator and iterator). Multiple instances of
the python module are supported by adding the word "python" more than once.
.LP
If the \fBchroot:\fR option is enabled, you should make sure Python's
library directory structure is bind mounted in the new root environment, see
Expand All @@ -1761,7 +1762,8 @@ absolute path relative to the new root, or as a relative path to the working
directory.
.TP
.B python\-script: \fI<python file>\fR
The script file to load.
The script file to load. Repeat this option for every python module instance
added to the \fBmodule\-config:\fR option.
.SS "DNS64 Module Options"
.LP
The dns64 module must be configured in the \fBmodule\-config:\fR "dns64
Expand Down
2 changes: 1 addition & 1 deletion pythonmod/interface.i
Original file line number Diff line number Diff line change
Expand Up @@ -1026,7 +1026,7 @@ struct config_file {
char* control_key_file;
char* control_cert_file;
int do_daemonize;
char* python_script;
struct config_strlist* python_script;
};

/* ************************************************************************************ *
Expand Down
79 changes: 51 additions & 28 deletions pythonmod/pythonmod.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ typedef struct PyThreadState PyThreadState;
typedef void* PyGILState_STATE;
#endif

/**
* counter for python module instances
* incremented by pythonmod_init(...)
*/
int py_mod_count = 0;

/** Python main thread */
PyThreadState* mainthr;

/**
* Global state for the module.
*/
Expand All @@ -72,8 +81,6 @@ struct pythonmod_env {
/** Python script filename. */
const char* fname;

/** Python main thread */
PyThreadState* mainthr;
/** Python module. */
PyObject* module;

Expand Down Expand Up @@ -242,11 +249,15 @@ log_py_err(void)

int pythonmod_init(struct module_env* env, int id)
{
int py_mod_idx = py_mod_count++;

/* Initialize module */
FILE* script_py = NULL;
PyObject* py_init_arg, *res;
PyGILState_STATE gil;
int init_standard = 1;
int init_standard = 1, i = 0;

struct config_strlist* cfg_item = env->cfg->python_script;

struct pythonmod_env* pe = (struct pythonmod_env*)calloc(1, sizeof(struct pythonmod_env));
if (!pe)
Expand All @@ -258,14 +269,21 @@ int pythonmod_init(struct module_env* env, int id)
env->modinfo[id] = (void*) pe;

/* Initialize module */
pe->fname = env->cfg->python_script;
pe->fname=NULL; i = 0;
while (cfg_item!=NULL) {
if (py_mod_idx==i++) {
pe->fname=cfg_item->str;
break;
}
cfg_item = cfg_item->next;
}
if(pe->fname==NULL || pe->fname[0]==0) {
log_err("pythonmod: no script given.");
log_err("pythonmod[%d]: no script given.", py_mod_idx);
return 0;
}

/* Initialize Python libraries */
if (!Py_IsInitialized())
if (py_mod_count==1 && !Py_IsInitialized())
{
#if PY_MAJOR_VERSION >= 3
wchar_t progname[8];
Expand All @@ -281,29 +299,31 @@ int pythonmod_init(struct module_env* env, int id)
Py_Initialize();
PyEval_InitThreads();
SWIG_init();
pe->mainthr = PyEval_SaveThread();
mainthr = PyEval_SaveThread();
}

gil = PyGILState_Ensure();

/* Initialize Python */
PyRun_SimpleString("import sys \n");
PyRun_SimpleString("sys.path.append('.') \n");
if(env->cfg->directory && env->cfg->directory[0]) {
char wdir[1524];
snprintf(wdir, sizeof(wdir), "sys.path.append('%s') \n",
env->cfg->directory);
PyRun_SimpleString(wdir);
}
PyRun_SimpleString("sys.path.append('"RUN_DIR"') \n");
PyRun_SimpleString("sys.path.append('"SHARE_DIR"') \n");
PyRun_SimpleString("import distutils.sysconfig \n");
PyRun_SimpleString("sys.path.append(distutils.sysconfig.get_python_lib(1,0)) \n");
if (PyRun_SimpleString("from unboundmodule import *\n") < 0)
{
log_err("pythonmod: cannot initialize core module: unboundmodule.py");
PyGILState_Release(gil);
return 0;
if (py_mod_count==1) {
/* Initialize Python */
PyRun_SimpleString("import sys \n");
PyRun_SimpleString("sys.path.append('.') \n");
if(env->cfg->directory && env->cfg->directory[0]) {
char wdir[1524];
snprintf(wdir, sizeof(wdir), "sys.path.append('%s') \n",
env->cfg->directory);
PyRun_SimpleString(wdir);
}
PyRun_SimpleString("sys.path.append('"RUN_DIR"') \n");
PyRun_SimpleString("sys.path.append('"SHARE_DIR"') \n");
PyRun_SimpleString("import distutils.sysconfig \n");
PyRun_SimpleString("sys.path.append(distutils.sysconfig.get_python_lib(1,0)) \n");
if (PyRun_SimpleString("from unboundmodule import *\n") < 0)
{
log_err("pythonmod: cannot initialize core module: unboundmodule.py");
PyGILState_Release(gil);
return 0;
}
}

/* Check Python file load */
Expand Down Expand Up @@ -341,6 +361,7 @@ int pythonmod_init(struct module_env* env, int id)
(void)PyParser_SimpleParseFile(script_py, pe->fname, Py_file_input);
log_py_err();
PyGILState_Release(gil);
fclose(script_py);
return 0;
}
fclose(script_py);
Expand Down Expand Up @@ -425,9 +446,11 @@ void pythonmod_deinit(struct module_env* env, int id)
Py_XDECREF(pe->data);
PyGILState_Release(gil);

PyEval_RestoreThread(pe->mainthr);
Py_Finalize();
pe->mainthr = NULL;
if(--py_mod_count==0) {
PyEval_RestoreThread(mainthr);
Py_Finalize();
mainthr = NULL;
}
}
pe->fname = NULL;
free(pe);
Expand Down
30 changes: 28 additions & 2 deletions util/config_file.c
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ int config_set_option(struct config_file* cfg, const char* opt,
else S_STR("control-key-file:", control_key_file)
else S_STR("control-cert-file:", control_cert_file)
else S_STR("module-config:", module_conf)
else S_STR("python-script:", python_script)
else S_STRLIST("python-script:", python_script)
else S_YNO("disable-dnssec-lame-check:", disable_dnssec_lame_check)
#ifdef CLIENT_SUBNET
/* Can't set max subnet prefix here, since that value is used when
Expand Down Expand Up @@ -1054,7 +1054,7 @@ config_get_option(struct config_file* cfg, const char* opt,
else O_YNO(opt, "unblock-lan-zones", unblock_lan_zones)
else O_YNO(opt, "insecure-lan-zones", insecure_lan_zones)
else O_DEC(opt, "max-udp-size", max_udp_size)
else O_STR(opt, "python-script", python_script)
else O_LST(opt, "python-script", python_script)
else O_YNO(opt, "disable-dnssec-lame-check", disable_dnssec_lame_check)
else O_DEC(opt, "ip-ratelimit", ip_ratelimit)
else O_DEC(opt, "ratelimit", ratelimit)
Expand Down Expand Up @@ -1420,6 +1420,7 @@ config_delete(struct config_file* cfg)
free(cfg->dnstap_version);
config_deldblstrlist(cfg->ratelimit_for_domain);
config_deldblstrlist(cfg->ratelimit_below_domain);
config_delstrlist(cfg->python_script);
#ifdef USE_IPSECMOD
free(cfg->ipsecmod_hook);
config_delstrlist(cfg->ipsecmod_whitelist);
Expand Down Expand Up @@ -1630,6 +1631,31 @@ cfg_strlist_insert(struct config_strlist** head, char* item)
return 1;
}

int
cfg_strlist_append_ex(struct config_strlist** head, char* item)
{
struct config_strlist *s;
if(!item || !head)
return 0;
s = (struct config_strlist*)calloc(1, sizeof(struct config_strlist));
if(!s)
return 0;
s->str = item;
s->next = NULL;

if (*head==NULL) {
*head = s;
} else {
struct config_strlist *last = *head;
while (last->next!=NULL) {
last = last->next;
}
last->next = s;
}

return 1;
}

int
cfg_str2list_insert(struct config_str2list** head, char* item, char* i2)
{
Expand Down
10 changes: 9 additions & 1 deletion util/config_file.h
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ struct config_file {
char* control_cert_file;

/** Python script file */
char* python_script;
struct config_strlist* python_script;

/** Use systemd socket activation. */
int use_systemd;
Expand Down Expand Up @@ -820,6 +820,14 @@ char* config_collate_cat(struct config_strlist* list);
*/
int cfg_strlist_append(struct config_strlist_head* list, char* item);

/**
* Searches the end of a string list and appends the given text.
* @param head: pointer to strlist head variable.
* @param item: new item. malloced by caller. if NULL the insertion fails.
* @return true on success.
*/
int cfg_strlist_append_ex(struct config_strlist** head, char* item);

/**
* Find string in strlist.
* @param head: pointer to strlist head variable.
Expand Down
4 changes: 2 additions & 2 deletions util/configparser.c
Original file line number Diff line number Diff line change
Expand Up @@ -5716,8 +5716,8 @@ yyparse (void)
#line 2721 "./util/configparser.y" /* yacc.c:1648 */
{
OUTYY(("P(python-script:%s)\n", (yyvsp[0].str)));
free(cfg_parser->cfg->python_script);
cfg_parser->cfg->python_script = (yyvsp[0].str);
if(!cfg_strlist_append_ex(&cfg_parser->cfg->python_script, (yyvsp[0].str)))
yyerror("out of memory");
}
#line 5723 "util/configparser.c" /* yacc.c:1648 */
break;
Expand Down
4 changes: 2 additions & 2 deletions util/configparser.y
Original file line number Diff line number Diff line change
Expand Up @@ -2720,8 +2720,8 @@ content_py: py_script
py_script: VAR_PYTHON_SCRIPT STRING_ARG
{
OUTYY(("P(python-script:%s)\n", $2));
free(cfg_parser->cfg->python_script);
cfg_parser->cfg->python_script = $2;
if(!cfg_strlist_append_ex(&cfg_parser->cfg->python_script, $2))
yyerror("out of memory");
}
server_disable_dnssec_lame_check: VAR_DISABLE_DNSSEC_LAME_CHECK STRING_ARG
{
Expand Down