+15 December 2009: Wouter
+ - Answer to qclass=ANY queries, with class IN contents.
+ Test that validation also works.
+
11 December 2009: Wouter
- on IPv4 UDP turn off DF flag.
return forwards_lookup(fwd, &root, qclass);
}
+int
+forwards_next_root(struct iter_forwards* fwd, uint16_t* dclass)
+{
+ struct iter_forward_zone key;
+ rbnode_t* n;
+ struct iter_forward_zone* p;
+ if(*dclass == 0) {
+ /* first root item is first item in tree */
+ n = rbtree_first(fwd->tree);
+ if(n == RBTREE_NULL)
+ return 0;
+ p = (struct iter_forward_zone*)n;
+ if(dname_is_root(p->name)) {
+ *dclass = p->dclass;
+ return 1;
+ }
+ /* root not first item? search for higher items */
+ *dclass = p->dclass + 1;
+ return forwards_next_root(fwd, dclass);
+ }
+ /* find class n in tree, we may get a direct hit, or if we don't
+ * this is the last item of the previous class so rbtree_next() takes
+ * us to the next root (if any) */
+ key.node.key = &key;
+ key.name = (uint8_t*)"\000";
+ key.namelen = 1;
+ key.namelabs = 0;
+ key.dclass = *dclass;
+ n = NULL;
+ if(rbtree_find_less_equal(fwd->tree, &key, &n)) {
+ /* exact */
+ return 1;
+ } else {
+ /* smaller element */
+ if(!n || n == RBTREE_NULL)
+ return 0; /* nothing found */
+ n = rbtree_next(n);
+ if(n == RBTREE_NULL)
+ return 0; /* no higher */
+ p = (struct iter_forward_zone*)n;
+ if(dname_is_root(p->name)) {
+ *dclass = p->dclass;
+ return 1;
+ }
+ /* not a root node, return next higher item */
+ *dclass = p->dclass+1;
+ return forwards_next_root(fwd, dclass);
+ }
+}
+
size_t
forwards_get_mem(struct iter_forwards* fwd)
{
struct regional* region;
/**
* Zones are stored in this tree. Sort order is specially chosen.
- * first sorted on qtype. Then on dname in nsec-like order, so that
+ * first sorted on qclass. Then on dname in nsec-like order, so that
* a lookup on class, name will return an exact match or the closest
* match which gives the ancestor needed.
* contents of type iter_forward_zone.
struct delegpt* forwards_lookup_root(struct iter_forwards* fwd,
uint16_t qclass);
+/**
+ * Find next root item in forwards lookup tree.
+ * @param fwd: the forward storage
+ * @param qclass: class to look at next, or higher.
+ * @return false if none found, or if true stored in qclass.
+ */
+int forwards_next_root(struct iter_forwards* fwd, uint16_t* qclass);
+
/**
* Get memory in use by forward storage
* @param fwd: forward storage.
return NULL;
}
+int hints_next_root(struct iter_hints* hints, uint16_t* qclass)
+{
+ return name_tree_next_root(&hints->tree, qclass);
+}
+
size_t
hints_get_mem(struct iter_hints* hints)
{
struct regional* region;
/**
* Hints are stored in this tree. Sort order is specially chosen.
- * first sorted on qtype. Then on dname in nsec-like order, so that
+ * first sorted on qclass. Then on dname in nsec-like order, so that
* a lookup on class, name will return an exact match or the closest
* match which gives the ancestor needed.
* contents of type iter_hints_stub. The class IN root is in here.
*/
struct delegpt* hints_lookup_root(struct iter_hints* hints, uint16_t qclass);
+/**
+ * Find next root hints (to cycle through all root hints).
+ * @param hints: hint storage
+ * @param qclass: class for which root hints are sought.
+ * 0 means give the first available root hints class.
+ * x means, give class x or a higher class if any.
+ * returns the found class in this variable.
+ * @return true if a root hint class is found.
+ * false if not root hint class is found (qclass may have been changed).
+ */
+int hints_next_root(struct iter_hints* hints, uint16_t* qclass);
+
/**
* Given a qname/qclass combination, and the delegation point from the cache
* for this qname/qclass, determine if this combination indicates that a
}
return 1;
}
+
+int
+iter_get_next_root(struct iter_hints* hints, struct iter_forwards* fwd,
+ uint16_t* c)
+{
+ uint16_t c1 = *c, c2 = *c;
+ int r1 = hints_next_root(hints, &c1);
+ int r2 = forwards_next_root(fwd, &c2);
+ if(!r1 && !r2) /* got none, end of list */
+ return 0;
+ else if(!r1) /* got one, return that */
+ *c = c2;
+ else if(!r2)
+ *c = c1;
+ else if(c1 < c2) /* got both take smallest */
+ *c = c1;
+ else *c = c2;
+ return 1;
+}
#define ITERATOR_ITER_UTILS_H
#include "iterator/iter_resptype.h"
struct iter_env;
+struct iter_hints;
+struct iter_forwards;
struct config_file;
struct module_env;
struct delegpt_addr;
int iter_lookup_inzone_glue(struct module_env* env, struct delegpt* dp,
struct regional* region, struct query_info* qinfo);
+/**
+ * Lookup next root-hint or root-forward entry.
+ * @param hints: the hints.
+ * @param fwd: the forwards.
+ * @param c: the class to start searching at. 0 means find first one.
+ * @return false if no classes found, true if found and returned in c.
+ */
+int iter_get_next_root(struct iter_hints* hints, struct iter_forwards* fwd,
+ uint16_t* c);
+
#endif /* ITERATOR_ITER_UTILS_H */
return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
}
+ /* If the request is qclass=ANY, setup to generate each class */
+ if(qstate->qinfo.qclass == LDNS_RR_CLASS_ANY) {
+ iq->qchase.qclass = 0;
+ return next_state(iq, COLLECT_CLASS_STATE);
+ }
+
/* Resolver Algorithm Step 1 -- Look for the answer in local data. */
/* This either results in a query restart (CNAME cache response), a
}
}
+/**
+ * Process response for qclass=ANY queries for a particular class.
+ * Append to result or error-exit.
+ *
+ * @param qstate: query state.
+ * @param id: module id.
+ * @param forq: super query state.
+ */
+static void
+processClassResponse(struct module_qstate* qstate, int id,
+ struct module_qstate* forq)
+{
+ struct iter_qstate* foriq = (struct iter_qstate*)forq->minfo[id];
+ struct dns_msg* from = qstate->return_msg;
+ log_query_info(VERB_ALGO, "processClassResponse", &qstate->qinfo);
+ log_query_info(VERB_ALGO, "processClassResponse super", &forq->qinfo);
+ if(qstate->return_rcode != LDNS_RCODE_NOERROR) {
+ /* cause servfail for qclass ANY query */
+ foriq->response = NULL;
+ foriq->state = FINISHED_STATE;
+ return;
+ }
+ /* append result */
+ if(!foriq->response) {
+ /* allocate the response: copy RCODE, sec_state */
+ foriq->response = dns_copy_msg(from, forq->region);
+ if(!foriq->response) {
+ log_err("malloc failed for qclass ANY response");
+ foriq->state = FINISHED_STATE;
+ return;
+ }
+ foriq->response->qinfo.qclass = forq->qinfo.qclass;
+ /* qclass ANY does not receive the AA flag on replies */
+ foriq->response->rep->authoritative = 0;
+ } else {
+ struct dns_msg* to = foriq->response;
+ /* add _from_ this response _to_ existing collection */
+ /* if there are records, copy RCODE */
+ /* lower sec_state if this message is lower */
+ if(from->rep->rrset_count != 0) {
+ size_t n = from->rep->rrset_count+to->rep->rrset_count;
+ struct ub_packed_rrset_key** dest;
+ /* copy appropriate rcode */
+ to->rep->flags = from->rep->flags;
+ /* copy rrsets */
+ dest = regional_alloc(forq->region, sizeof(dest[0])*n);
+ if(!dest) {
+ log_err("malloc failed in collect ANY");
+ foriq->state = FINISHED_STATE;
+ return;
+ }
+ /* copy AN */
+ memcpy(dest, to->rep->rrsets, to->rep->an_numrrsets
+ * sizeof(dest[0]));
+ dest += to->rep->an_numrrsets;
+ memcpy(dest, from->rep->rrsets, from->rep->an_numrrsets
+ * sizeof(dest[0]));
+ dest += from->rep->an_numrrsets;
+ /* copy NS */
+ memcpy(dest, to->rep->rrsets+to->rep->an_numrrsets,
+ to->rep->ns_numrrsets * sizeof(dest[0]));
+ dest += to->rep->ns_numrrsets;
+ memcpy(dest, from->rep->rrsets+from->rep->an_numrrsets,
+ from->rep->ns_numrrsets * sizeof(dest[0]));
+ dest += from->rep->ns_numrrsets;
+ /* copy AR */
+ memcpy(dest, to->rep->rrsets+to->rep->an_numrrsets+
+ to->rep->ns_numrrsets,
+ to->rep->ar_numrrsets * sizeof(dest[0]));
+ dest += to->rep->ar_numrrsets;
+ memcpy(dest, from->rep->rrsets+from->rep->an_numrrsets+
+ from->rep->ns_numrrsets,
+ from->rep->ar_numrrsets * sizeof(dest[0]));
+ /* update counts */
+ to->rep->an_numrrsets += from->rep->an_numrrsets;
+ to->rep->ns_numrrsets += from->rep->ns_numrrsets;
+ to->rep->ar_numrrsets += from->rep->ar_numrrsets;
+ to->rep->rrset_count = n;
+ }
+ if(from->rep->security < to->rep->security) /* lowest sec */
+ to->rep->security = from->rep->security;
+ if(from->rep->qdcount != 0) /* insert qd if appropriate */
+ to->rep->qdcount = from->rep->qdcount;
+ if(from->rep->ttl < to->rep->ttl) /* use smallest TTL */
+ to->rep->ttl = from->rep->ttl;
+ }
+ /* are we done? */
+ foriq->num_current_queries --;
+ if(foriq->num_current_queries == 0)
+ foriq->state = FINISHED_STATE;
+}
+
+/**
+ * Collect class ANY responses and make them into one response. This
+ * state is started and it creates queries for all classes (that have
+ * root hints). The answers are then collected.
+ *
+ * @param qstate: query state.
+ * @param id: module id.
+ * @return true if the event needs more immediate processing, false if not.
+ */
+static int
+processCollectClass(struct module_qstate* qstate, int id)
+{
+ struct iter_qstate* iq = (struct iter_qstate*)qstate->minfo[id];
+ struct iter_env* ie = (struct iter_env*)qstate->env->modinfo[id];
+ struct module_qstate* subq;
+ /* If qchase.qclass == 0 then send out queries for all classes.
+ * Otherwise, do nothing (wait for all answers to arrive and the
+ * processClassResponse to put them together, and that moves us
+ * towards the Finished state when done. */
+ if(iq->qchase.qclass == 0) {
+ uint16_t c = 0;
+ iq->qchase.qclass = LDNS_RR_CLASS_ANY;
+ while(iter_get_next_root(ie->hints, qstate->env->fwds, &c)) {
+ /* generate query for this class */
+ log_nametypeclass(VERB_ALGO, "spawn collect query",
+ qstate->qinfo.qname, qstate->qinfo.qtype, c);
+ if(!generate_sub_request(qstate->qinfo.qname,
+ qstate->qinfo.qname_len, qstate->qinfo.qtype,
+ c, qstate, id, iq, INIT_REQUEST_STATE,
+ FINISHED_STATE, &subq,
+ (int)!(qstate->query_flags&BIT_CD))) {
+ return error_response(qstate, id,
+ LDNS_RCODE_SERVFAIL);
+ }
+ /* ignore subq, no special init required */
+ iq->num_current_queries ++;
+ if(c == 0xffff)
+ break;
+ else c++;
+ }
+ /* if no roots are configured at all, return */
+ if(iq->num_current_queries == 0) {
+ verbose(VERB_ALGO, "No hints or fwds, giving up "
+ "on qclass ANY");
+ return error_response(qstate, id, LDNS_RCODE_REFUSED);
+ }
+ /* return false, wait for queries to return */
+ }
+ /* if woke up here because of an answer, wait for more answers */
+ return 0;
+}
+
/**
* This handles the final state for first-tier responses (i.e., responses to
* externally generated queries).
iter_inform_super(struct module_qstate* qstate, int id,
struct module_qstate* super)
{
- if(qstate->return_rcode != LDNS_RCODE_NOERROR)
+ if(super->qinfo.qclass == LDNS_RR_CLASS_ANY)
+ processClassResponse(qstate, id, super);
+ else if(qstate->return_rcode != LDNS_RCODE_NOERROR)
error_supers(qstate, id, super);
else if(qstate->is_priming)
prime_supers(qstate, id, super);
case PRIME_RESP_STATE:
cont = processPrimeResponse(qstate, id);
break;
+ case COLLECT_CLASS_STATE:
+ cont = processCollectClass(qstate, id);
+ break;
case FINISHED_STATE:
cont = processFinished(qstate, iq, id);
break;
return "QUERY TARGETS STATE";
case PRIME_RESP_STATE :
return "PRIME RESPONSE STATE";
+ case COLLECT_CLASS_STATE :
+ return "COLLECT CLASS STATE";
case QUERY_RESP_STATE :
return "QUERY RESPONSE STATE";
case FINISHED_STATE :
case INIT_REQUEST_2_STATE :
case INIT_REQUEST_3_STATE :
case QUERYTARGETS_STATE :
+ case COLLECT_CLASS_STATE :
return 0;
default:
break;
/** Responses to priming queries finish at this state. */
PRIME_RESP_STATE,
+ /** Collecting query class information, for qclass=ANY, when
+ * it spawns off queries for every class, it returns here. */
+ COLLECT_CLASS_STATE,
+
/** Responses that are to be returned upstream end at this state.
* As well as responses to target queries. */
FINISHED_STATE
--- /dev/null
+; config options
+; The island of trust is at example.com
+server:
+ trust-anchor: "example.com. 3600 IN DS 2854 3 1 46e4ffc6e9a4793b488954bd3f0cc6af0dfb201b"
+ val-override-date: "20070916134226"
+ target-fetch-policy: "0 0 0 0 0"
+
+stub-zone:
+ name: "."
+ stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test lookup of class any response
+
+; K.ROOT-SERVERS.NET.
+RANGE_BEGIN 0 100
+ ADDRESS 193.0.14.129
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. IN NS K.ROOT-SERVERS.NET.
+SECTION ADDITIONAL
+K.ROOT-SERVERS.NET. IN A 193.0.14.129
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION AUTHORITY
+com. IN NS a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net. IN A 192.5.6.30
+ENTRY_END
+RANGE_END
+
+; a.gtld-servers.net.
+RANGE_BEGIN 0 100
+ ADDRESS 192.5.6.30
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+com. IN NS
+SECTION ANSWER
+com. IN NS a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net. IN A 192.5.6.30
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION AUTHORITY
+example.com. IN NS ns.example.com.
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
+ENTRY_END
+RANGE_END
+
+; ns.example.com.
+RANGE_BEGIN 0 100
+ ADDRESS 1.2.3.4
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION ANSWER
+example.com. IN NS ns.example.com.
+example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
+ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854}
+ENTRY_END
+
+; response to DNSKEY priming query
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN DNSKEY
+SECTION ANSWER
+example.com. 3600 IN DNSKEY 256 3 3 ALXLUsWqUrY3JYER3T4TBJII s70j+sDS/UT2QRp61SE7S3E EXopNXoFE73JLRmvpi/UrOO/Vz4Se 6wXv/CYCKjGw06U4WRgR YXcpEhJROyNapmdIKSx hOzfLVE1gqA0PweZR8d tY3aNQSRn3sPpwJr6Mi /PqQKAMMrZ9ckJpf1+b QMOOvxgzz2U1GS18b3y ZKcgTMEaJzd/GZYzi/B N2DzQ0MsrSwYXfsNLFO Bbs8PJMW4LYIxeeOe6rUgkWOF 7CC9Dh/dduQ1QrsJhmZAEFfd6ByYV+ ;{id = 2854 (zsk), size = 1688b}
+example.com. 3600 IN RRSIG DNSKEY 3 2 3600 20070926134802 20070829134802 2854 example.com. MCwCFG1yhRNtTEa3Eno2zhVVuy2EJX3wAhQeLyUp6+UXcpC5qGNu9tkrTEgPUg== ;{id = 2854}
+SECTION AUTHORITY
+example.com. IN NS ns.example.com.
+example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
+ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854}
+ENTRY_END
+
+; response to query of interest
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+www.example.com. IN A
+SECTION ANSWER
+www.example.com. IN A 10.20.30.40
+ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
+SECTION AUTHORITY
+example.com. IN NS ns.example.com.
+example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
+www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
+ENTRY_END
+RANGE_END
+
+STEP 1 QUERY
+ENTRY_BEGIN
+REPLY RD DO
+SECTION QUESTION
+www.example.com. ANY A
+ENTRY_END
+
+; recursion happens here.
+STEP 10 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all
+REPLY QR RD RA AD NOERROR
+SECTION QUESTION
+www.example.com. ANY A
+SECTION ANSWER
+www.example.com. IN A 10.20.30.40
+www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
+SECTION AUTHORITY
+example.com. IN NS ns.example.com.
+example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+SECTION ADDITIONAL
+ns.example.com. IN A 1.2.3.4
+ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
+ENTRY_END
+
+SCENARIO_END
}
return result;
}
+
+int
+name_tree_next_root(rbtree_t* tree, uint16_t* dclass)
+{
+ struct name_tree_node key;
+ rbnode_t* n;
+ struct name_tree_node* p;
+ if(*dclass == 0) {
+ /* first root item is first item in tree */
+ n = rbtree_first(tree);
+ if(n == RBTREE_NULL)
+ return 0;
+ p = (struct name_tree_node*)n;
+ if(dname_is_root(p->name)) {
+ *dclass = p->dclass;
+ return 1;
+ }
+ /* root not first item? search for higher items */
+ *dclass = p->dclass + 1;
+ return name_tree_next_root(tree, dclass);
+ }
+ /* find class n in tree, we may get a direct hit, or if we don't
+ * this is the last item of the previous class so rbtree_next() takes
+ * us to the next root (if any) */
+ key.node.key = &key;
+ key.name = (uint8_t*)"\000";
+ key.len = 1;
+ key.labs = 0;
+ key.dclass = *dclass;
+ n = NULL;
+ if(rbtree_find_less_equal(tree, &key, &n)) {
+ /* exact */
+ return 1;
+ } else {
+ /* smaller element */
+ if(!n || n == RBTREE_NULL)
+ return 0; /* nothing found */
+ n = rbtree_next(n);
+ if(n == RBTREE_NULL)
+ return 0; /* no higher */
+ p = (struct name_tree_node*)n;
+ if(dname_is_root(p->name)) {
+ *dclass = p->dclass;
+ return 1;
+ }
+ /* not a root node, return next higher item */
+ *dclass = p->dclass+1;
+ return name_tree_next_root(tree, dclass);
+ }
+}
struct name_tree_node* name_tree_lookup(rbtree_t* tree, uint8_t* name,
size_t len, int labs, uint16_t dclass);
+/**
+ * Find next root item in name tree.
+ * @param tree: the nametree.
+ * @param dclass: the class to look for next (or higher).
+ * @return false if no classes found, true means class put into c.
+ */
+int name_tree_next_root(rbtree_t* tree, uint16_t* dclass);
+
/**
* Init addr tree to be empty.
* @param tree: to init.
verbose(VERB_ALGO, "cannot validate RRSIG, no sigs on sigs.");
return 0;
}
+
return 1;
}
qstate->ext_state[id] = module_finished;
return;
}
+ /* qclass ANY should have validation result from spawned
+ * queries. If we get here, it is bogus or an internal error */
+ if(qstate->qinfo.qclass == LDNS_RR_CLASS_ANY) {
+ verbose(VERB_ALGO, "cannot validate classANY: bogus");
+ if(qstate->return_msg)
+ qstate->return_msg->rep->security =
+ sec_status_bogus;
+ qstate->ext_state[id] = module_finished;
+ return;
+ }
/* create state to start validation */
qstate->ext_state[id] = module_error; /* override this */
if(!vq) {