diff --git a/Makefile.in b/Makefile.in index 07a29825b..8788412bc 100644 --- a/Makefile.in +++ b/Makefile.in @@ -482,9 +482,9 @@ doc: if test -n "$(doxygen)"; then \ $(doxygen) $(srcdir)/doc/unbound.doxygen; fi if test "$(WITH_PYUNBOUND)" = "yes" -o "$(WITH_PYTHONMODULE)" = "yes"; \ - then if test -x "`which sphinx-build 2>&1`"; then \ - sphinx-build -b html pythonmod/doc doc/html/pythonmod; \ - sphinx-build -b html libunbound/python/doc doc/html/pyunbound;\ + then if test -x "`which sphinx-build-$(PY_MAJOR_VERSION) 2>&1`"; then \ + sphinx-build-$(PY_MAJOR_VERSION) -b html pythonmod/doc doc/html/pythonmod; \ + sphinx-build-$(PY_MAJOR_VERSION) -b html libunbound/python/doc doc/html/pyunbound;\ fi ;\ fi diff --git a/doc/Changelog b/doc/Changelog index c339e15b6..4f33a9b11 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,9 @@ +22 November 2018: Wouter + - With ./configure --with-pyunbound --with-pythonmodule + PYTHON_VERSION=3.6 or with 2.7 unbound can compile and unit tests + succeed for the python module. + - pythonmod logs the python error and traceback on failure. + 21 November 2018: Wouter - Scrub NS records from NODATA responses as well. diff --git a/doc/unbound.doxygen b/doc/unbound.doxygen index fe3987681..bea788896 100644 --- a/doc/unbound.doxygen +++ b/doc/unbound.doxygen @@ -612,17 +612,21 @@ RECURSIVE = YES EXCLUDE = ./build \ ./compat \ + ./contrib \ util/configparser.c \ util/configparser.h \ util/configlexer.c \ util/locks.h \ + pythonmod/doc \ + pythonmod/examples \ pythonmod/unboundmodule.py \ pythonmod/interface.h \ - pythonmod/examples/resgen.py \ - pythonmod/examples/resmod.py \ - pythonmod/examples/resip.py \ + pythonmod/ubmodule-msg.py \ + pythonmod/ubmodule-tst.py \ libunbound/python/unbound.py \ libunbound/python/libunbound_wrap.c \ + libunbound/python/doc \ + libunbound/python/examples \ ./ldns-src \ doc/control_proto_spec.txt \ doc/requirements.txt diff --git a/pythonmod/interface.i b/pythonmod/interface.i index 68e0975aa..473135f42 100644 --- a/pythonmod/interface.i +++ b/pythonmod/interface.i @@ -45,15 +45,20 @@ i = 0; while (i < len) { - i += name[i] + 1; + i += ((unsigned int)name[i]) + 1; cnt++; } list = PyList_New(cnt); i = 0; cnt = 0; while (i < len) { - PyList_SetItem(list, cnt, PyBytes_FromStringAndSize(name + i + 1, name[i])); - i += name[i] + 1; + char buf[LDNS_MAX_LABELLEN+1]; + if(((unsigned int)name[i])+1 <= (int)sizeof(buf)) { + memmove(buf, name + i + 1, (unsigned int)name[i]); + buf[(unsigned int)name[i]] = 0; + PyList_SetItem(list, cnt, PyString_FromString(buf)); + } + i += ((unsigned int)name[i]) + 1; cnt++; } return list; @@ -161,11 +166,11 @@ struct query_info { %} %inline %{ - PyObject* dnameAsStr(const char* dname) { + PyObject* dnameAsStr(PyObject* dname) { char buf[LDNS_MAX_DOMAINLEN+1]; buf[0] = '\0'; - dname_str((uint8_t*)dname, buf); - return PyBytes_FromString(buf); + dname_str((uint8_t*)PyBytes_AsString(dname), buf); + return PyString_FromString(buf); } %} @@ -1211,7 +1216,7 @@ int checkList(PyObject *l) for (i=0; i < PyList_Size(l); i++) { item = PyList_GetItem(l, i); - if (!PyBytes_Check(item)) + if (!PyBytes_Check(item) && !PyUnicode_Check(item)) return 0; } return 1; @@ -1226,23 +1231,40 @@ int pushRRList(sldns_buffer* qb, PyObject *l, uint32_t default_ttl, int qsec, PyObject* item; int i; size_t len; + char* s; + PyObject* ascstr; for (i=0; i < PyList_Size(l); i++) { + ascstr = NULL; item = PyList_GetItem(l, i); + if(PyObject_TypeCheck(item, &PyBytes_Type)) { + s = PyBytes_AsString(item); + } else { + ascstr = PyUnicode_AsASCIIString(item); + s = PyBytes_AsString(ascstr); + } len = sldns_buffer_remaining(qb); if(qsec) { - if(sldns_str2wire_rr_question_buf(PyBytes_AsString(item), + if(sldns_str2wire_rr_question_buf(s, sldns_buffer_current(qb), &len, NULL, NULL, 0, NULL, 0) - != 0) + != 0) { + if(ascstr) + Py_DECREF(ascstr); return 0; + } } else { - if(sldns_str2wire_rr_buf(PyBytes_AsString(item), + if(sldns_str2wire_rr_buf(s, sldns_buffer_current(qb), &len, NULL, default_ttl, - NULL, 0, NULL, 0) != 0) + NULL, 0, NULL, 0) != 0) { + if(ascstr) + Py_DECREF(ascstr); return 0; + } } + if(ascstr) + Py_DECREF(ascstr); sldns_buffer_skip(qb, len); sldns_buffer_write_u16_at(qb, count_offset, diff --git a/pythonmod/pythonmod.c b/pythonmod/pythonmod.c index 35a20434b..1e3d30b91 100644 --- a/pythonmod/pythonmod.c +++ b/pythonmod/pythonmod.c @@ -110,6 +110,128 @@ struct pythonmod_qstate { #include "pythonmod/interface.h" #endif +/** log python error */ +static void +log_py_err(void) +{ + char *result = NULL; + PyObject *modStringIO = NULL; + PyObject *modTB = NULL; + PyObject *obFuncStringIO = NULL; + PyObject *obStringIO = NULL; + PyObject *obFuncTB = NULL; + PyObject *argsTB = NULL; + PyObject *obResult = NULL; + PyObject *ascstr = NULL; + PyObject *exc_typ, *exc_val, *exc_tb; + + /* Fetch the error state now before we cruch it */ + /* exc val contains the error message + * exc tb contains stack traceback and other info. */ + PyErr_Fetch(&exc_typ, &exc_val, &exc_tb); + PyErr_NormalizeException(&exc_typ, &exc_val, &exc_tb); + + /* Import the modules we need - cStringIO and traceback */ + modStringIO = PyImport_ImportModule("cStringIO"); + if (modStringIO==NULL) /* python 1.4 and before */ + modStringIO = PyImport_ImportModule("StringIO"); + if (modStringIO==NULL) /* python 3 */ + modStringIO = PyImport_ImportModule("io"); + if (modStringIO==NULL) { + log_err("pythonmod: cannot print exception, " + "cannot ImportModule cStringIO or StringIO"); + goto cleanup; + } + modTB = PyImport_ImportModule("traceback"); + if (modTB==NULL) { + log_err("pythonmod: cannot print exception, " + "cannot ImportModule traceback"); + goto cleanup; + } + + /* Construct a cStringIO object */ + obFuncStringIO = PyObject_GetAttrString(modStringIO, "StringIO"); + if (obFuncStringIO==NULL) { + log_err("pythonmod: cannot print exception, " + "cannot GetAttrString cStringIO.StringIO"); + goto cleanup; + } + obStringIO = PyObject_CallObject(obFuncStringIO, NULL); + if (obStringIO==NULL) { + log_err("pythonmod: cannot print exception, " + "cannot cStringIO.StringIO()"); + goto cleanup; + } + + /* Get the traceback.print_exception function, and call it. */ + obFuncTB = PyObject_GetAttrString(modTB, "print_exception"); + if (obFuncTB==NULL) { + log_err("pythonmod: cannot print exception, " + "cannot GetAttrString traceback.print_exception"); + goto cleanup; + } + argsTB = Py_BuildValue("OOOOO", (exc_typ ? exc_typ : Py_None), + (exc_val ? exc_val : Py_None), (exc_tb ? exc_tb : Py_None), + Py_None, obStringIO); + if (argsTB==NULL) { + log_err("pythonmod: cannot print exception, " + "cannot BuildValue for print_exception"); + goto cleanup; + } + + obResult = PyObject_CallObject(obFuncTB, argsTB); + if (obResult==NULL) { + PyErr_Print(); + log_err("pythonmod: cannot print exception, " + "call traceback.print_exception() failed"); + goto cleanup; + } + + /* Now call the getvalue() method in the StringIO instance */ + Py_DECREF(obFuncStringIO); + obFuncStringIO = PyObject_GetAttrString(obStringIO, "getvalue"); + if (obFuncStringIO==NULL) { + log_err("pythonmod: cannot print exception, " + "cannot GetAttrString cStringIO.getvalue"); + goto cleanup; + } + Py_DECREF(obResult); + obResult = PyObject_CallObject(obFuncStringIO, NULL); + if (obResult==NULL) { + log_err("pythonmod: cannot print exception, " + "call cStringIO.getvalue() failed"); + goto cleanup; + } + + /* And it should be a string all ready to go - duplicate it. */ + if (!PyString_Check(obResult) && !PyUnicode_Check(obResult)) { + log_err("pythonmod: cannot print exception, " + "cStringIO.getvalue() result did not String_Check"); + goto cleanup; + } + if(PyString_Check(obResult)) { + result = PyString_AsString(obResult); + } else { + ascstr = PyUnicode_AsASCIIString(obResult); + result = PyBytes_AsString(ascstr); + } + log_err("pythonmod: python error: %s", result); + +cleanup: + Py_XDECREF(modStringIO); + Py_XDECREF(modTB); + Py_XDECREF(obFuncStringIO); + Py_XDECREF(obStringIO); + Py_XDECREF(obFuncTB); + Py_XDECREF(argsTB); + Py_XDECREF(obResult); + Py_XDECREF(ascstr); + + /* clear the exception, by not restoring it */ + /* Restore the exception state */ + /* PyErr_Restore(exc_typ, exc_val, exc_tb); */ +} + int pythonmod_init(struct module_env* env, int id) { /* Initialize module */ @@ -193,13 +315,26 @@ int pythonmod_init(struct module_env* env, int id) /* TODO: deallocation of pe->... if an error occurs */ - if (PyRun_SimpleFile(script_py, pe->fname) < 0) - { + if (PyRun_SimpleFile(script_py, pe->fname) < 0) { log_err("pythonmod: can't parse Python script %s", pe->fname); + /* print the error to logs too, run it again */ + fseek(script_py, 0, SEEK_SET); + /* we don't run the file, like this, because then side-effects + * s = PyRun_File(script_py, pe->fname, Py_file_input, + * PyModule_GetDict(PyImport_AddModule("__main__")), pe->dict); + * could happen (again). Instead we parse the file again to get + * the error string in the logs, for when the daemon has stderr + * removed. SimpleFile run already printed to stderr, for then + * this is called from unbound-checkconf or unbound -dd the user + * has a nice formatted error. + */ + /* ignore the NULL return of _node, it is NULL due to the parse failure + * that we are expecting */ + (void)PyParser_SimpleParseFile(script_py, pe->fname, Py_file_input); + log_py_err(); PyGILState_Release(gil); return 0; } - fclose(script_py); if ((pe->func_init = PyDict_GetItemString(pe->dict, "init_standard")) == NULL) @@ -244,7 +379,7 @@ int pythonmod_init(struct module_env* env, int id) if (PyErr_Occurred()) { log_err("pythonmod: Exception occurred in function init"); - PyErr_Print(); + log_py_err(); Py_XDECREF(res); Py_XDECREF(py_init_arg); PyGILState_Release(gil); @@ -274,7 +409,7 @@ void pythonmod_deinit(struct module_env* env, int id) res = PyObject_CallFunction(pe->func_deinit, "i", id); if (PyErr_Occurred()) { log_err("pythonmod: Exception occurred in function deinit"); - PyErr_Print(); + log_py_err(); } /* Free result if any */ Py_XDECREF(res); @@ -312,7 +447,7 @@ void pythonmod_inform_super(struct module_qstate* qstate, int id, struct module_ if (PyErr_Occurred()) { log_err("pythonmod: Exception occurred in function inform_super"); - PyErr_Print(); + log_py_err(); qstate->ext_state[id] = module_error; } else if ((res == NULL) || (!PyObject_IsTrue(res))) @@ -353,7 +488,7 @@ void pythonmod_operate(struct module_qstate* qstate, enum module_ev event, if (PyErr_Occurred()) { log_err("pythonmod: Exception occurred in function operate, event: %s", strmodulevent(event)); - PyErr_Print(); + log_py_err(); qstate->ext_state[id] = module_error; } else if ((res == NULL) || (!PyObject_IsTrue(res))) diff --git a/testdata/pylib.tdir/pylib.test b/testdata/pylib.tdir/pylib.test index 9456691aa..893aaf64f 100644 --- a/testdata/pylib.tdir/pylib.test +++ b/testdata/pylib.tdir/pylib.test @@ -19,9 +19,13 @@ fi #echo export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:../../.libs:." #export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:../../.libs:." +if grep "PY_MAJOR_VERSION=3" $PRE/Makefile; then + PYTHON="python3"; else PYTHON="python2"; fi +if test ! -x `which $PYTHON` 2>&1; then PYTHON="python"; fi + # do the test echo "> pylib.lookup.py www.example.com." -./pylib.lookup.py www.example.com. | tee outfile +$PYTHON pylib.lookup.py www.example.com. | tee outfile echo "> cat logfiles" cat fwd.log diff --git a/testdata/pymod.tdir/pymod.py b/testdata/pymod.tdir/pymod.py index 3f6fed1c6..a8018e7f7 100644 --- a/testdata/pymod.tdir/pymod.py +++ b/testdata/pymod.tdir/pymod.py @@ -59,12 +59,15 @@ def setTTL(qstate, ttl): def dataHex(data, prefix=""): res = "" - for i in range(0, (len(data)+15)/16): + for i in range(0, int((len(data)+15)/16)): res += "%s0x%02X | " % (prefix, i*16) - d = map(lambda x:ord(x), data[i*16:i*16+17]) + if type(data[0]) == type(1): + d = map(lambda x:int(x), data[i*16:i*16+17]) + else: + d = map(lambda x:ord(x), data[i*16:i*16+17]) for ch in d: res += "%02X " % ch - for i in range(0,17-len(d)): + for i in range(0,17-len(data[i*16:i*16+17])): res += " " res += "| " for ch in d: @@ -76,35 +79,35 @@ def dataHex(data, prefix=""): return res def printReturnMsg(qstate): - print "Return MSG rep :: flags: %04X, QDcount: %d, Security:%d, TTL=%d" % (qstate.return_msg.rep.flags, qstate.return_msg.rep.qdcount,qstate.return_msg.rep.security, qstate.return_msg.rep.ttl) - print " qinfo :: qname:",qstate.return_msg.qinfo.qname_list, qstate.return_msg.qinfo.qname_str, "type:",qstate.return_msg.qinfo.qtype_str, "class:",qstate.return_msg.qinfo.qclass_str + print ("Return MSG rep :: flags: %04X, QDcount: %d, Security:%d, TTL=%d" % (qstate.return_msg.rep.flags, qstate.return_msg.rep.qdcount, qstate.return_msg.rep.security, qstate.return_msg.rep.ttl)) + print (" qinfo :: qname:",qstate.return_msg.qinfo.qname_list, qstate.return_msg.qinfo.qname_str, "type:",qstate.return_msg.qinfo.qtype_str, "class:",qstate.return_msg.qinfo.qclass_str) if (qstate.return_msg.rep): - print "RRSets:",qstate.return_msg.rep.rrset_count + print ("RRSets:",qstate.return_msg.rep.rrset_count) prevkey = None for i in range(0,qstate.return_msg.rep.rrset_count): r = qstate.return_msg.rep.rrsets[i] rk = r.rk - print i,":",rk.dname_list, rk.dname_str, "flags: %04X" % rk.flags, - print "type:",rk.type_str,"(%d)" % ntohs(rk.type), "class:",rk.rrset_class_str,"(%d)" % ntohs(rk.rrset_class) + print (i,":",rk.dname_list, rk.dname_str, "flags: %04X" % rk.flags) + print ("type:",rk.type_str,"(%d)" % ntohs(rk.type), "class:",rk.rrset_class_str,"(%d)" % ntohs(rk.rrset_class)) d = r.entry.data - print " RRDatas:",d.count+d.rrsig_count + print (" RRDatas:",d.count+d.rrsig_count) for j in range(0,d.count+d.rrsig_count): - print " ",j,":","TTL=",d.rr_ttl[j],"RR data:" - print dataHex(d.rr_data[j]," ") + print (" ",j,":","TTL=",d.rr_ttl[j],"RR data:") + print (dataHex(d.rr_data[j]," ")) def operate(id, event, qstate, qdata): log_info("pythonmod: operate called, id: %d, event:%s" % (id, strmodulevent(event))) - #print "pythonmod: per query data", qdata + #print ("pythonmod: per query data", qdata) - print "Query:", ''.join(map(lambda x:chr(max(32,ord(x))),qstate.qinfo.qname)), qstate.qinfo.qname_list, qstate.qinfo.qname_str, - print "Type:",qstate.qinfo.qtype_str,"(%d)" % qstate.qinfo.qtype, - print "Class:",qstate.qinfo.qclass_str,"(%d)" % qstate.qinfo.qclass - print + print ("Query:", qstate.qinfo.qname, qstate.qinfo.qname_list, qstate.qinfo.qname_str) + print ("Type:",qstate.qinfo.qtype_str,"(%d)" % qstate.qinfo.qtype) + print ("Class:",qstate.qinfo.qclass_str,"(%d)" % qstate.qinfo.qclass) + print () if (event == MODULE_EVENT_NEW or event == MODULE_EVENT_PASS) and (qstate.qinfo.qname_str.endswith("www2.example.com.")): - print qstate.qinfo.qname_str + print (qstate.qinfo.qname_str) qstate.ext_state[id] = MODULE_FINISHED @@ -121,6 +124,7 @@ def operate(id, event, qstate, qdata): if (qstate.qinfo.qtype == RR_TYPE_TXT) or (qstate.qinfo.qtype == RR_TYPE_ANY): msg.answer.append("%s 10 IN TXT path=/" % qstate.qinfo.qname_str) + print(msg.answer) if not msg.set_return_msg(qstate): qstate.ext_state[id] = MODULE_ERROR return True diff --git a/testdata/pymod_thread.tdir/pymod_thread.py b/testdata/pymod_thread.tdir/pymod_thread.py index 31e1d43f6..30c258875 100644 --- a/testdata/pymod_thread.tdir/pymod_thread.py +++ b/testdata/pymod_thread.tdir/pymod_thread.py @@ -59,12 +59,15 @@ def setTTL(qstate, ttl): def dataHex(data, prefix=""): res = "" - for i in range(0, (len(data)+15)/16): + for i in range(0, int((len(data)+15)/16)): res += "%s0x%02X | " % (prefix, i*16) - d = map(lambda x:ord(x), data[i*16:i*16+17]) + if type(data[0]) == type(1): + d = map(lambda x:int(x), data[i*16:i*16+17]) + else: + d = map(lambda x:ord(x), data[i*16:i*16+17]) for ch in d: - res += "%02X " % ch - for i in range(0,17-len(d)): + res += "%02X " % int(ch) + for i in range(0,17-len(data[i*16:i*16+17])): res += " " res += "| " for ch in d: @@ -76,43 +79,43 @@ def dataHex(data, prefix=""): return res def printReturnMsg(qstate): - print "Return MSG rep :: flags: %04X, QDcount: %d, Security:%d, TTL=%d" % (qstate.return_msg.rep.flags, qstate.return_msg.rep.qdcount,qstate.return_msg.rep.security, qstate.return_msg.rep.ttl) - print " qinfo :: qname:",qstate.return_msg.qinfo.qname_list, qstate.return_msg.qinfo.qname_str, "type:",qstate.return_msg.qinfo.qtype_str, "class:",qstate.return_msg.qinfo.qclass_str + print ("Return MSG rep :: flags: %04X, QDcount: %d, Security:%d, TTL=%d" % (qstate.return_msg.rep.flags, qstate.return_msg.rep.qdcount, qstate.return_msg.rep.security, qstate.return_msg.rep.ttl)) + print (" qinfo :: qname:",qstate.return_msg.qinfo.qname_list, qstate.return_msg.qinfo.qname_str, "type:",qstate.return_msg.qinfo.qtype_str, "class:",qstate.return_msg.qinfo.qclass_str) if (qstate.return_msg.rep): - print "RRSets:",qstate.return_msg.rep.rrset_count + print ("RRSets:",qstate.return_msg.rep.rrset_count) prevkey = None for i in range(0,qstate.return_msg.rep.rrset_count): r = qstate.return_msg.rep.rrsets[i] rk = r.rk - print i,":",rk.dname_list, rk.dname_str, "flags: %04X" % rk.flags, - print "type:",rk.type_str,"(%d)" % ntohs(rk.type), "class:",rk.rrset_class_str,"(%d)" % ntohs(rk.rrset_class) + print (i,":",rk.dname_list, rk.dname_str, "flags: %04X" % rk.flags) + print ("type:",rk.type_str,"(%d)" % ntohs(rk.type), "class:",rk.rrset_class_str,"(%d)" % ntohs(rk.rrset_class)) d = r.entry.data - print " RRDatas:",d.count+d.rrsig_count + print (" RRDatas:",d.count+d.rrsig_count) for j in range(0,d.count+d.rrsig_count): - print " ",j,":","TTL=",d.rr_ttl[j],"RR data:" - print dataHex(d.rr_data[j]," ") + print (" ",j,":","TTL=",d.rr_ttl[j],"RR data:") + print (dataHex(d.rr_data[j]," ")) def operate(id, event, qstate, qdata): log_info("pythonmod: operate called, id: %d, event:%s" % (id, strmodulevent(event))) - #print "pythonmod: per query data", qdata + #print ("pythonmod: per query data", qdata) - print "Query:", ''.join(map(lambda x:chr(max(32,ord(x))),qstate.qinfo.qname)), qstate.qinfo.qname_list, qstate.qinfo.qname_str, - print "Type:",qstate.qinfo.qtype_str,"(%d)" % qstate.qinfo.qtype, - print "Class:",qstate.qinfo.qclass_str,"(%d)" % qstate.qinfo.qclass - print + print ("Query:", qstate.qinfo.qname, qstate.qinfo.qname_list, qstate.qinfo.qname_str) + print ("Type:",qstate.qinfo.qtype_str,"(%d)" % qstate.qinfo.qtype) + print ("Class:",qstate.qinfo.qclass_str,"(%d)" % qstate.qinfo.qclass) + print () if (event == MODULE_EVENT_NEW or event == MODULE_EVENT_PASS) and (qstate.qinfo.qname_str.endswith("example.com.")): - print qstate.qinfo.qname_str + print (qstate.qinfo.qname_str) qstate.ext_state[id] = MODULE_FINISHED - # eat time - y = 20 - for z in range(2, 10000): - y = y*2 - z/2 - y = y/2 + z + # eat time + y = 20 + for z in range(2, 10000): + y = y*2 - z/2 + y = y/2 + z msg = DNSMessage(qstate.qinfo.qname_str, RR_TYPE_A, RR_CLASS_IN, PKT_QR | PKT_RA | PKT_AA) #, 300) #msg.authority.append("xxx.seznam.cz. 10 IN A 192.168.1.1") diff --git a/testdata/pymod_thread.tdir/pymod_thread.testns b/testdata/pymod_thread.tdir/pymod_thread.testns index 55926bb50..c92a07909 100644 --- a/testdata/pymod_thread.tdir/pymod_thread.testns +++ b/testdata/pymod_thread.tdir/pymod_thread.testns @@ -22,3 +22,83 @@ SECTION ANSWER www2 IN A 10.20.30.40 ENTRY_END +ENTRY_BEGIN +MATCH opcode qtype qname +REPLY QR AA NOERROR +ADJUST copy_id +SECTION QUESTION +www3 IN A +SECTION ANSWER +www3 IN A 10.20.30.40 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +REPLY QR AA NOERROR +ADJUST copy_id +SECTION QUESTION +www4 IN A +SECTION ANSWER +www4 IN A 10.20.30.40 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +REPLY QR AA NOERROR +ADJUST copy_id +SECTION QUESTION +www5 IN A +SECTION ANSWER +www5 IN A 10.20.30.40 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +REPLY QR AA NXDOMAIN +ADJUST copy_id +SECTION QUESTION +www6 IN A +SECTION AUTHORITY +example.com. 3600 IN SOA a. b. 2018100719 7200 3600 1209600 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +REPLY QR AA NXDOMAIN +ADJUST copy_id +SECTION QUESTION +www7 IN A +SECTION AUTHORITY +example.com. 3600 IN SOA a. b. 2018100719 7200 3600 1209600 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +REPLY QR AA NXDOMAIN +ADJUST copy_id +SECTION QUESTION +www8 IN A +SECTION AUTHORITY +example.com. 3600 IN SOA a. b. 2018100719 7200 3600 1209600 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +REPLY QR AA NXDOMAIN +ADJUST copy_id +SECTION QUESTION +www9 IN A +SECTION AUTHORITY +example.com. 3600 IN SOA a. b. 2018100719 7200 3600 1209600 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +REPLY QR AA NXDOMAIN +ADJUST copy_id +SECTION QUESTION +www10 IN A +SECTION AUTHORITY +example.com. 3600 IN SOA a. b. 2018100719 7200 3600 1209600 3600 +ENTRY_END +