Preliminary "PRECACHE" / push support...
authorMark Spencer <markster@digium.com>
Wed, 27 Oct 2004 13:58:31 +0000 (13:58 +0000)
committerMark Spencer <markster@digium.com>
Wed, 27 Oct 2004 13:58:31 +0000 (13:58 +0000)
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@4110 65c4cc65-6c06-0410-ace0-fbb531ad65f3

configs/dundi.conf.sample
include/asterisk/dundi.h
pbx/dundi-parser.c
pbx/pbx_dundi.c

index 5564c1c..950d1d6 100755 (executable)
@@ -134,7 +134,8 @@ autokill=yes
 ;
 ; include - Includes this peer when searching a particular context
 ;           for lookup (set "all" to perform all lookups with that
-;           host.
+;           host.  This is also the context in which peers are permitted
+;           to precache.
 ;
 ; noinclude - Disincludes this peer when searching a particular context
 ;             for lookup (set "all" to perform no lookups with that
@@ -142,7 +143,8 @@ autokill=yes
 ;
 ; permit - Permits this peer to search a given DUNDi context on
 ;          the local system.  Set "all" to permit this host to
-;          lookup all contexts.
+;          lookup all contexts.  This is also a context for which
+;          we will create/forward PRECACHE commands.
 ;
 ; deny -   Denies this peer to search a given DUNDi context on
 ;          the local system.  Set "all" to deny this host to
@@ -151,8 +153,18 @@ autokill=yes
 ; model - inbound, outbound, or symmetric for whether we receive 
 ;         requests only, transmit requests only, or do both.
 ;
-; canprecache - Permits this peer to provide answers (which are cached)
-;               for queries we did not make (a.k.a. pre-caching)
+; precache - Utilize/Permit precaching with this peer (to pre
+;            cache means to provide an answer when no request
+;            was made and is used so that machines with few
+;            routes can push those routes up a to a higher level).
+;            outgoing means we send precache routes to this peer,
+;            incoming means we permit this peer to send us
+;            precache routes.  symmetric means we do both.
+;
+; Note: You cannot mix symmetric/outbound model with symmetric/inbound
+; precache, nor can you mix symmetric/inbound model with symmetric/outbound
+; precache.
+;
 ;
 ; The '*' peer is special and matches an unspecified entity
 ;
@@ -182,6 +194,32 @@ qualify = yes
 ;qualify = yes
 ;order = secondary
 
+;
+; Sample "push mode" downstream host
+;
+;[00:0C:76:96:75:28]
+;model = incoming
+;host = dynamic
+;precache = incoming
+;inkey = littleguy
+;outkey = ourkey
+;include = e164        ; In this case used only for precaching
+;permit = e164      
+;qualify = yes
+
+;
+; Sample "push mode" upstream host
+;
+;[00:07:E9:3B:76:60]
+;model = outbound
+;precache = outbound
+;host = 216.207.245.34
+;register = yes
+;inkey = dhcp34
+;permit = all ; In this case used only for precaching
+;include = all 
+;qualify = yes
+;outkey=foo
 
 ;[*]
 ;
index 8bd22dd..09a3fb1 100755 (executable)
@@ -101,7 +101,8 @@ struct dundi_cause {
 #define DUNDI_COMMAND_DPRESPONSE       (2 | 0x40)      /* Respond to a discovery request */
 #define DUNDI_COMMAND_EIDQUERY         3                       /* Request information for a peer */
 #define DUNDI_COMMAND_EIDRESPONSE      (4 | 0x40)      /* Response to a peer query */
-#define DUNDI_COMMAND_PRECACHE         5                       /* Unsolicited answer pre-cache */
+#define DUNDI_COMMAND_PRECACHERQ       5                       /* Pre-cache Request */
+#define DUNDI_COMMAND_PRECACHERP       (6 | 0x40)      /* Pre-cache Response */
 #define DUNDI_COMMAND_INVALID          (7 | 0x40)      /* Invalid dialog state (does not require ack) */
 #define DUNDI_COMMAND_UNKNOWN          (8 | 0x40)      /* Unknown command */
 #define DUNDI_COMMAND_NULL                     9                       /* No-op */
@@ -185,4 +186,7 @@ int dundi_lookup(struct dundi_result *result, int maxret, struct ast_channel *ch
 
 /* Retrieve information on a specific EID */
 int dundi_query_eid(struct dundi_entity_info *dei, const char *dcontext, dundi_eid eid);
+
+/* Pre-cache to push upstream peers */
+int dundi_precache(const char *dcontext, const char *number);
 #endif /* _ASTERISK_DUNDI_H */
index f9f96bc..8647183 100755 (executable)
@@ -439,8 +439,8 @@ void dundi_showframe(struct dundi_hdr *fhi, int rx, struct sockaddr_in *sin, int
                "DPRESPONSE  ",
                "EIDQUERY    ",
                "EIDRESPONSE ",
-               "UNKNOWN (5)?",
-               "UNKNOWN (6)?",
+               "PRECACHERQ  ",
+               "PRECACHERP  ",
                "INVALID     ",
                "UNKNOWN CMD ",
                "NULL        ",
@@ -448,8 +448,7 @@ void dundi_showframe(struct dundi_hdr *fhi, int rx, struct sockaddr_in *sin, int
                "REGRESPONSE ",
                "CANCEL      ",
                "ENCRYPT     ",
-               "ENCREJ      ",
-               "PRECACHE    " };
+               "ENCREJ      " };
        char class2[20];
        char *class;
        char subclass2[20];
index 625bccc..ed70de5 100755 (executable)
@@ -209,6 +209,8 @@ static struct dundi_peer {
        struct sockaddr_in addr;        /* Address of DUNDi peer */
        struct permission *permit;
        struct permission *include;
+       struct permission *precachesend;
+       struct permission *precachereceive;
        dundi_eid us_eid;
        char inkey[80];
        char outkey[80];
@@ -216,7 +218,6 @@ static struct dundi_peer {
        int registerid;
        int qualifyid;
        int sentfullkey;
-       int canprecache;
        int order;
        unsigned char txenckey[256]; /* Transmitted encrypted key + sig */
        unsigned char rxenckey[256]; /* Cache received encrypted key + sig */
@@ -234,7 +235,8 @@ static struct dundi_peer {
        struct dundi_transaction *regtrans;     /* Registration transaction */
        struct dundi_transaction *qualtrans;    /* Qualify transaction */
        struct dundi_transaction *keypending;
-       int model;
+       int model;                                      /* Pull model */
+       int pcmodel;                            /* Push/precache model */
        int dynamic;                            /* Are we dynamic? */
        int lastms;                                     /* Last measured latency */
        int maxms;                                      /* Max permissible latency */
@@ -296,7 +298,8 @@ static int str2tech(char *str)
                return -1;
 }
 
-static int dundi_lookup_internal(struct dundi_result *result, int maxret, struct ast_channel *chan, const char *dcontext, const char *number, int ttl, int blockempty, struct dundi_hint_metadata *md, int *expiration, int cybpass, dundi_eid *avoid[], int direct[]);
+static int dundi_lookup_internal(struct dundi_result *result, int maxret, struct ast_channel *chan, const char *dcontext, const char *number, int ttl, int blockempty, struct dundi_hint_metadata *md, int *expiration, int cybpass, int modeselect, dundi_eid *skip, dundi_eid *avoid[], int direct[]);
+static int dundi_precache_internal(const char *context, const char *number, int ttl, dundi_eid *avoids[]);
 static struct dundi_transaction *create_transaction(struct dundi_peer *p);
 static struct dundi_transaction *find_transaction(struct dundi_hdr *hdr, struct sockaddr_in *sin)
 {
@@ -317,6 +320,7 @@ static struct dundi_transaction *find_transaction(struct dundi_hdr *hdr, struct
                switch(hdr->cmdresp & 0x7f) {
                case DUNDI_COMMAND_DPDISCOVER:
                case DUNDI_COMMAND_EIDQUERY:
+               case DUNDI_COMMAND_PRECACHERQ:
                case DUNDI_COMMAND_REGREQ:
                case DUNDI_COMMAND_NULL:
                case DUNDI_COMMAND_ENCRYPT:
@@ -595,7 +599,7 @@ static void *dundi_lookup_thread(void *data)
                
        if (max) {
                /* If we do not have a canonical result, keep looking */
-               res = dundi_lookup_internal(dr + ouranswers, MAX_RESULTS - ouranswers, NULL, st->called_context, st->called_number, st->ttl, 1, &hmd, &expiration, st->nocache, st->eids, st->directs);
+               res = dundi_lookup_internal(dr + ouranswers, MAX_RESULTS - ouranswers, NULL, st->called_context, st->called_number, st->ttl, 1, &hmd, &expiration, st->nocache, 0, NULL, st->eids, st->directs);
                if (res > 0) {
                        /* Append answer in result */
                        ouranswers += res;
@@ -629,6 +633,38 @@ static void *dundi_lookup_thread(void *data)
        return NULL;    
 }
 
+static void *dundi_precache_thread(void *data)
+{
+       struct dundi_query_state *st = data;
+       struct dundi_ie_data ied;
+       struct dundi_hint_metadata hmd;
+       char eid_str[20];
+
+       ast_log(LOG_DEBUG, "Whee, precaching '%s@%s' for '%s'\n", st->called_number, st->called_context, 
+               st->eids[0] ? dundi_eid_to_str(eid_str, sizeof(eid_str), st->eids[0]) :  "ourselves");
+       memset(&ied, 0, sizeof(ied));
+
+       /* Now produce precache */
+       dundi_precache_internal(st->called_context, st->called_number, st->ttl, st->eids);
+
+       ast_mutex_lock(&peerlock);
+       ast_log(LOG_WARNING, "XXX We should schedule retransmission here XXX\n");
+       /* Truncate if "don't ask" isn't present */
+       if (!(hmd.flags & DUNDI_HINT_DONT_ASK))
+               hmd.exten[0] = '\0';
+       if (st->trans->flags & FLAG_DEAD) {
+               ast_log(LOG_DEBUG, "Our transaction went away!\n");
+               st->trans->thread = 0;
+               destroy_trans(st->trans, 0);
+       } else {
+               dundi_send(st->trans, DUNDI_COMMAND_PRECACHERP, 0, 1, &ied);
+               st->trans->thread = 0;
+       }
+       ast_mutex_unlock(&peerlock);
+       free(st);
+       return NULL;    
+}
+
 static inline int calc_ms(struct timeval *start)
 {
        struct timeval tv;
@@ -751,19 +787,158 @@ static int dundi_answer_entity(struct dundi_transaction *trans, struct dundi_ies
        return 0;
 }
 
-static int dundi_answer_query(struct dundi_transaction *trans, struct dundi_ies *ies, char *ccontext)
+static int cache_save_hint(dundi_eid *eidpeer, struct dundi_request *req, struct dundi_hint *hint, int expiration)
+{
+       int unaffected;
+       char key1[256];
+       char key2[256];
+       char eidpeer_str[20];
+       char eidroot_str[20];
+       char data[80]="";
+       time_t timeout;
+
+       if (expiration < 0)
+               expiration = DUNDI_DEFAULT_CACHE_TIME;
+
+       /* Only cache hint if "don't ask" is there... */
+       if (!(ntohs(hint->flags)& DUNDI_HINT_DONT_ASK))
+               return 0;
+
+       unaffected = ntohs(hint->flags) & DUNDI_HINT_UNAFFECTED;
+
+       dundi_eid_to_str_short(eidpeer_str, sizeof(eidpeer_str), eidpeer);
+       dundi_eid_to_str_short(eidroot_str, sizeof(eidroot_str), &req->root_eid);
+       snprintf(key1, sizeof(key1), "hint/%s/%s/%s/e%08lx", eidpeer_str, hint->data, req->dcontext, unaffected ? 0 : req->crc32);
+       snprintf(key2, sizeof(key2), "hint/%s/%s/%s/r%s", eidpeer_str, hint->data, req->dcontext, eidroot_str);
+
+       time(&timeout);
+       timeout += expiration;
+       snprintf(data, sizeof(data), "%ld|", (long)(timeout));
+       
+       ast_db_put("dundi/cache", key1, data);
+       ast_log(LOG_DEBUG, "Caching hint at '%s'\n", key1);
+       ast_db_put("dundi/cache", key2, data);
+       ast_log(LOG_DEBUG, "Caching hint at '%s'\n", key2);
+       return 0;
+}
+
+static int cache_save(dundi_eid *eidpeer, struct dundi_request *req, int start, int unaffected, int expiration)
+{
+       int x;
+       char key1[256];
+       char key2[256];
+       char data[1024]="";
+       char eidpeer_str[20];
+       char eidroot_str[20];
+       time_t timeout;
+
+       if (expiration < 1)     
+               expiration = DUNDI_DEFAULT_CACHE_TIME;
+       dundi_eid_to_str_short(eidpeer_str, sizeof(eidpeer_str), eidpeer);
+       dundi_eid_to_str_short(eidroot_str, sizeof(eidroot_str), &req->root_eid);
+       snprintf(key1, sizeof(key1), "%s/%s/%s/e%08lx", eidpeer_str, req->number, req->dcontext, unaffected ? 0 : req->crc32);
+       snprintf(key2, sizeof(key2), "%s/%s/%s/r%s", eidpeer_str, req->number, req->dcontext, eidroot_str);
+       /* Build request string */
+       time(&timeout);
+       timeout += expiration;
+       snprintf(data, sizeof(data), "%ld|", (long)(timeout));
+       for (x=start;x<req->respcount;x++) {
+               /* Skip anything with an illegal pipe in it */
+               if (strchr(req->dr[x].dest, '|'))
+                       continue;
+               snprintf(data + strlen(data), sizeof(data) - strlen(data), "%d/%d/%d/%s/%s|", 
+                       req->dr[x].flags, req->dr[x].weight, req->dr[x].techint, req->dr[x].dest, 
+                       dundi_eid_to_str_short(eidpeer_str, sizeof(eidpeer_str), &req->dr[x].eid));
+       }
+       ast_db_put("dundi/cache", key1, data);
+       ast_db_put("dundi/cache", key2, data);
+       return 0;
+}
+
+static void dundi_precache_full(void)
+{
+       struct dundi_mapping *cur;
+       cur = mappings;
+       while(cur) {
+               ast_log(LOG_NOTICE, "Should precache context '%s'\n", cur->dcontext);
+               cur = cur->next;
+       }
+}
+
+static int dundi_prop_precache(struct dundi_transaction *trans, struct dundi_ies *ies, char *ccontext)
 {
        struct dundi_query_state *st;
        int totallen;
-       int x;
+       int x,z;
        struct dundi_ie_data ied;
        char *s;
+       struct dundi_result dr2[MAX_RESULTS];
+       struct dundi_request dr;
+       struct dundi_hint_metadata hmd;
+
        struct dundi_mapping *cur;
        int mapcount;
        int skipfirst = 0;
        
        pthread_t lookupthread;
        pthread_attr_t attr;
+
+       memset(&dr2, 0, sizeof(dr2));
+       memset(&dr, 0, sizeof(dr));
+       memset(&hmd, 0, sizeof(hmd));
+       
+       /* Forge request structure to hold answers for cache */
+       hmd.flags = DUNDI_HINT_DONT_ASK | DUNDI_HINT_UNAFFECTED;
+       dr.dr = dr2;
+       dr.maxcount = MAX_RESULTS;
+       dr.expiration = DUNDI_DEFAULT_CACHE_TIME;
+       dr.hmd = &hmd;
+       dr.pfds[0] = dr.pfds[1] = -1;
+       trans->parent = &dr;
+       strncpy(dr.dcontext, ies->called_context ? ies->called_context : "e164", sizeof(dr.dcontext));
+       strncpy(dr.number, ies->called_number, sizeof(dr.number) - 1);
+       
+       for (x=0;x<ies->anscount;x++) {
+               if (trans->parent->respcount < trans->parent->maxcount) {
+                       /* Make sure it's not already there */
+                       for (z=0;z<trans->parent->respcount;z++) {
+                               if ((trans->parent->dr[z].techint == ies->answers[x]->protocol) &&
+                                   !strcmp(trans->parent->dr[z].dest, ies->answers[x]->data)) 
+                                               break;
+                       }
+                       if (z == trans->parent->respcount) {
+                               /* Copy into parent responses */
+                               trans->parent->dr[trans->parent->respcount].flags = ntohs(ies->answers[x]->flags);
+                               trans->parent->dr[trans->parent->respcount].techint = ies->answers[x]->protocol;
+                               trans->parent->dr[trans->parent->respcount].weight = ntohs(ies->answers[x]->weight);
+                               trans->parent->dr[trans->parent->respcount].eid = ies->answers[x]->eid;
+                               if (ies->expiration > 0)
+                                       trans->parent->dr[trans->parent->respcount].expiration = ies->expiration;
+                               else
+                                       trans->parent->dr[trans->parent->respcount].expiration = DUNDI_DEFAULT_CACHE_TIME;
+                               dundi_eid_to_str(trans->parent->dr[trans->parent->respcount].eid_str, 
+                                       sizeof(trans->parent->dr[trans->parent->respcount].eid_str),
+                                       &ies->answers[x]->eid);
+                               strncpy(trans->parent->dr[trans->parent->respcount].dest, ies->answers[x]->data,
+                                       sizeof(trans->parent->dr[trans->parent->respcount].dest));
+                                       strncpy(trans->parent->dr[trans->parent->respcount].tech, tech2str(ies->answers[x]->protocol),
+                                       sizeof(trans->parent->dr[trans->parent->respcount].tech));
+                               trans->parent->respcount++;
+                               trans->parent->hmd->flags &= ~DUNDI_HINT_DONT_ASK;
+                       } else if (trans->parent->dr[z].weight > ies->answers[x]->weight) {
+                               /* Update weight if appropriate */
+                               trans->parent->dr[z].weight = ies->answers[x]->weight;
+                       }
+               } else
+                       ast_log(LOG_NOTICE, "Dropping excessive answers in precache for %s@%s\n",
+                               trans->parent->number, trans->parent->dcontext);
+
+       }
+       /* Save all the results (if any) we had.  Even if no results, still cache lookup. */
+       cache_save(&trans->them_eid, trans->parent, 0, 0, ies->expiration);
+       if (ies->hint)
+               cache_save_hint(&trans->them_eid, trans->parent, ies->hint, ies->expiration);
+
        totallen = sizeof(struct dundi_query_state);
        /* Count matching map entries */
        mapcount = 0;
@@ -773,6 +948,7 @@ static int dundi_answer_query(struct dundi_transaction *trans, struct dundi_ies
                        mapcount++;
                cur = cur->next;
        }
+       
        /* If no maps, return -1 immediately */
        if (!mapcount)
                return -1;
@@ -786,6 +962,7 @@ static int dundi_answer_query(struct dundi_transaction *trans, struct dundi_ies
                        skipfirst = 1;
        }
 
+       /* Prepare to run a query and then propagate that as necessary */
        totallen += mapcount * sizeof(struct dundi_mapping);
        totallen += (ies->eidcount - skipfirst) * sizeof(dundi_eid);
        st = malloc(totallen);
@@ -820,94 +997,118 @@ static int dundi_answer_query(struct dundi_transaction *trans, struct dundi_ies
                        cur = cur->next;
                }
                st->nummaps = mapcount;
-               ast_log(LOG_DEBUG, "Answering query for '%s@%s'!\n", ies->called_number, ies->called_context);
+               ast_log(LOG_DEBUG, "Forwarding precache for '%s@%s'!\n", ies->called_number, ies->called_context);
                pthread_attr_init(&attr);
                pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
                trans->thread = 1;
-               if (ast_pthread_create(&lookupthread, &attr, dundi_lookup_thread, st)) {
+               if (ast_pthread_create(&lookupthread, &attr, dundi_precache_thread, st)) {
                        trans->thread = 0;
                        ast_log(LOG_WARNING, "Unable to create thread!\n");
                        free(st);
                        memset(&ied, 0, sizeof(ied));
                        dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Out of threads");
-                       dundi_send(trans, DUNDI_COMMAND_DPRESPONSE, 0, 1, &ied);
+                       dundi_send(trans, DUNDI_COMMAND_PRECACHERP, 0, 1, &ied);
                        return -1;
                }
        } else {
                ast_log(LOG_WARNING, "Out of memory!\n");
                memset(&ied, 0, sizeof(ied));
                dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Out of memory");
-               dundi_send(trans, DUNDI_COMMAND_DPRESPONSE, 0, 1, &ied);
+               dundi_send(trans, DUNDI_COMMAND_PRECACHERP, 0, 1, &ied);
                return -1;
        }
        return 0;
 }
 
-static int cache_save_hint(dundi_eid *eidpeer, struct dundi_request *req, struct dundi_hint *hint, int expiration)
+static int dundi_answer_query(struct dundi_transaction *trans, struct dundi_ies *ies, char *ccontext)
 {
-       int unaffected;
-       char key1[256];
-       char key2[256];
-       char eidpeer_str[20];
-       char eidroot_str[20];
-       char data[80]="";
-       time_t timeout;
-
-       if (expiration < 0)
-               expiration = DUNDI_DEFAULT_CACHE_TIME;
-
-       /* Only cache hint if "don't ask" is there... */
-       if (!(ntohs(hint->flags)& DUNDI_HINT_DONT_ASK))
-               return 0;
-
-       unaffected = ntohs(hint->flags) & DUNDI_HINT_UNAFFECTED;
-
-       dundi_eid_to_str_short(eidpeer_str, sizeof(eidpeer_str), eidpeer);
-       dundi_eid_to_str_short(eidroot_str, sizeof(eidroot_str), &req->root_eid);
-       snprintf(key1, sizeof(key1), "hint/%s/%s/%s/e%08lx", eidpeer_str, hint->data, req->dcontext, unaffected ? 0 : req->crc32);
-       snprintf(key2, sizeof(key2), "hint/%s/%s/%s/r%s", eidpeer_str, hint->data, req->dcontext, eidroot_str);
-
-       time(&timeout);
-       timeout += expiration;
-       snprintf(data, sizeof(data), "%ld|", (long)(timeout));
+       struct dundi_query_state *st;
+       int totallen;
+       int x;
+       struct dundi_ie_data ied;
+       char *s;
+       struct dundi_mapping *cur;
+       int mapcount;
+       int skipfirst = 0;
        
-       ast_db_put("dundi/cache", key1, data);
-       ast_log(LOG_DEBUG, "Caching hint at '%s'\n", key1);
-       ast_db_put("dundi/cache", key2, data);
-       ast_log(LOG_DEBUG, "Caching hint at '%s'\n", key2);
-       return 0;
-}
+       pthread_t lookupthread;
+       pthread_attr_t attr;
+       totallen = sizeof(struct dundi_query_state);
+       /* Count matching map entries */
+       mapcount = 0;
+       cur = mappings;
+       while(cur) {
+               if (!strcasecmp(cur->dcontext, ccontext))
+                       mapcount++;
+               cur = cur->next;
+       }
+       /* If no maps, return -1 immediately */
+       if (!mapcount)
+               return -1;
 
-static int cache_save(dundi_eid *eidpeer, struct dundi_request *req, int start, int unaffected, int expiration)
-{
-       int x;
-       char key1[256];
-       char key2[256];
-       char data[1024]="";
-       char eidpeer_str[20];
-       char eidroot_str[20];
-       time_t timeout;
+       if (ies->eidcount > 1) {
+               /* Since it is a requirement that the first EID is the authenticating host
+                  and the last EID is the root, it is permissible that the first and last EID
+                  could be the same.  In that case, we should go ahead copy only the "root" section
+                  since we will not need it for authentication. */
+               if (!dundi_eid_cmp(ies->eids[0], ies->eids[ies->eidcount - 1]))
+                       skipfirst = 1;
+       }
 
-       if (expiration < 1)     
-               expiration = DUNDI_DEFAULT_CACHE_TIME;
-       dundi_eid_to_str_short(eidpeer_str, sizeof(eidpeer_str), eidpeer);
-       dundi_eid_to_str_short(eidroot_str, sizeof(eidroot_str), &req->root_eid);
-       snprintf(key1, sizeof(key1), "%s/%s/%s/e%08lx", eidpeer_str, req->number, req->dcontext, unaffected ? 0 : req->crc32);
-       snprintf(key2, sizeof(key2), "%s/%s/%s/r%s", eidpeer_str, req->number, req->dcontext, eidroot_str);
-       /* Build request string */
-       time(&timeout);
-       timeout += expiration;
-       snprintf(data, sizeof(data), "%ld|", (long)(timeout));
-       for (x=start;x<req->respcount;x++) {
-               /* Skip anything with an illegal pipe in it */
-               if (strchr(req->dr[x].dest, '|'))
-                       continue;
-               snprintf(data + strlen(data), sizeof(data) - strlen(data), "%d/%d/%d/%s/%s|", 
-                       req->dr[x].flags, req->dr[x].weight, req->dr[x].techint, req->dr[x].dest, 
-                       dundi_eid_to_str_short(eidpeer_str, sizeof(eidpeer_str), &req->dr[x].eid));
+       totallen += mapcount * sizeof(struct dundi_mapping);
+       totallen += (ies->eidcount - skipfirst) * sizeof(dundi_eid);
+       st = malloc(totallen);
+       if (st) {
+               memset(st, 0, totallen);
+               strncpy(st->called_context, ies->called_context, sizeof(st->called_context) - 1);
+               strncpy(st->called_number, ies->called_number, sizeof(st->called_number) - 1);
+               st->trans = trans;
+               st->ttl = ies->ttl - 1;
+               st->nocache = ies->cbypass;
+               if (st->ttl < 0)
+                       st->ttl = 0;
+               s = st->fluffy;
+               for (x=skipfirst;ies->eids[x];x++) {
+                       st->eids[x-skipfirst] = (dundi_eid *)s;
+                       *st->eids[x-skipfirst] = *ies->eids[x];
+                       st->directs[x-skipfirst] = ies->eid_direct[x];
+                       s += sizeof(dundi_eid);
+               }
+               /* Append mappings */
+               x = 0;
+               st->maps = (struct dundi_mapping *)s;
+               cur = mappings;
+               while(cur) {
+                       if (!strcasecmp(cur->dcontext, ccontext)) {
+                               if (x < mapcount) {
+                                       st->maps[x] = *cur;
+                                       st->maps[x].next = NULL;
+                                       x++;
+                               }
+                       }
+                       cur = cur->next;
+               }
+               st->nummaps = mapcount;
+               ast_log(LOG_DEBUG, "Answering query for '%s@%s'!\n", ies->called_number, ies->called_context);
+               pthread_attr_init(&attr);
+               pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+               trans->thread = 1;
+               if (ast_pthread_create(&lookupthread, &attr, dundi_lookup_thread, st)) {
+                       trans->thread = 0;
+                       ast_log(LOG_WARNING, "Unable to create thread!\n");
+                       free(st);
+                       memset(&ied, 0, sizeof(ied));
+                       dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Out of threads");
+                       dundi_send(trans, DUNDI_COMMAND_DPRESPONSE, 0, 1, &ied);
+                       return -1;
+               }
+       } else {
+               ast_log(LOG_WARNING, "Out of memory!\n");
+               memset(&ied, 0, sizeof(ied));
+               dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Out of memory");
+               dundi_send(trans, DUNDI_COMMAND_DPRESPONSE, 0, 1, &ied);
+               return -1;
        }
-       ast_db_put("dundi/cache", key1, data);
-       ast_db_put("dundi/cache", key2, data);
        return 0;
 }
 
@@ -957,6 +1158,8 @@ static int cache_lookup_internal(time_t now, struct dundi_request *req, char *ke
                                                        req->dr[req->respcount].techint = tech;
                                                        req->dr[req->respcount].expiration = expiration;
                                                        dundi_str_short_to_eid(&req->dr[req->respcount].eid, src);
+                                                       dundi_eid_to_str(req->dr[req->respcount].eid_str, 
+                                                               sizeof(req->dr[req->respcount].eid_str), &req->dr[req->respcount].eid);
                                                        strncpy(req->dr[req->respcount].dest, ptr,
                                                                sizeof(req->dr[req->respcount].dest));
                                                        strncpy(req->dr[req->respcount].tech, tech2str(tech),
@@ -1317,8 +1520,11 @@ static int handle_command_response(struct dundi_transaction *trans, struct dundi
        switch(cmd) {
        case DUNDI_COMMAND_DPDISCOVER:
        case DUNDI_COMMAND_EIDQUERY:
+       case DUNDI_COMMAND_PRECACHERQ:
                if (cmd == DUNDI_COMMAND_EIDQUERY)
                        resp = DUNDI_COMMAND_EIDRESPONSE;
+               else if (cmd == DUNDI_COMMAND_PRECACHERQ)
+                       resp = DUNDI_COMMAND_PRECACHERP;
                else
                        resp = DUNDI_COMMAND_DPRESPONSE;
                /* A dialplan or entity discover -- qualify by highest level entity */
@@ -1344,14 +1550,24 @@ static int handle_command_response(struct dundi_transaction *trans, struct dundi
                                                /* They're not permitted to access that context */
                                                dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Invalid or missing number/entity");
                                                dundi_send(trans, resp, 0, 1, &ied);
-                                       } else if (has_permission(peer->permit, ies.called_context)) {
+                                       } else if ((cmd == DUNDI_COMMAND_DPDISCOVER) && 
+                                                  (peer->model & DUNDI_MODEL_INBOUND) && 
+                                                          has_permission(peer->permit, ies.called_context)) {
                                                res = dundi_answer_query(trans, &ies, ies.called_context);
                                                if (res < 0) {
                                                        /* There is no such dundi context */
                                                        dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, "Unsupported DUNDI Context");
                                                        dundi_send(trans, resp, 0, 1, &ied);
                                                }
-                               
+                                       } else if ((cmd = DUNDI_COMMAND_PRECACHERQ) && 
+                                                  (peer->pcmodel & DUNDI_MODEL_INBOUND) && 
+                                                          has_permission(peer->include, ies.called_context)) {
+                                               res = dundi_prop_precache(trans, &ies, ies.called_context);
+                                               if (res < 0) {
+                                                       /* There is no such dundi context */
+                                                       dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, "Unsupported DUNDI Context");
+                                                       dundi_send(trans, resp, 0, 1, &ied);
+                                               }
                                        } else {
                                                /* They're not permitted to access that context */
                                                dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, "Permission to context denied");
@@ -1403,79 +1619,6 @@ static int handle_command_response(struct dundi_transaction *trans, struct dundi
                        }
                }
                break;
-       case DUNDI_COMMAND_PRECACHE:
-               /* Success of some sort */
-               ast_log(LOG_DEBUG, "Looks like a precache with %d answers\n", ies.anscount);
-               /* A dialplan or entity discover -- qualify by highest level entity */
-               peer = find_peer(ies.eids[0]);
-               if (peer && ies.called_number) {
-                       struct dundi_request dr;
-                       struct dundi_result dr2[1];
-                       memset(&dr, 0, sizeof(dr));
-                       memset(&dr2, 0, sizeof(dr2));
-                       /* Build placeholder Dundi Request */
-                       trans->us_eid = peer->us_eid;
-                       if (!ies.called_context)
-                               ies.called_context = "e164";
-                       strncpy(dr.dcontext, ies.called_context, sizeof(dr.dcontext) - 1);
-                       strncpy(dr.number, ies.called_number, sizeof(dr.number) - 1);
-                       dr.dr = dr2;
-                       trans->parent = &dr;
-                       
-                       /* Make sure we have all the proper auths */
-                       if (strlen(peer->inkey)) {
-                               authpass = encrypted;
-                       } else 
-                               authpass = 1;
-                       authpass &= has_permission(peer->include, ies.called_context);
-                       authpass &= peer->canprecache;
-                       if (authpass) {
-                               /* Okay we're authentiated and all, now we check if they're authorized */
-                               for (x=0;x<ies.anscount;x++) {
-                                       /* Copy into parent responses */
-                                       trans->parent->dr[0].flags = ntohs(ies.answers[x]->flags);
-                                       trans->parent->dr[0].techint = ies.answers[x]->protocol;
-                                       trans->parent->dr[0].weight = ntohs(ies.answers[x]->weight);
-                                       trans->parent->dr[0].eid = ies.answers[x]->eid;
-                                       if (ies.expiration > 0)
-                                               trans->parent->dr[0].expiration = ies.expiration;
-                                       else
-                                               trans->parent->dr[0].expiration = DUNDI_DEFAULT_CACHE_TIME;
-                                       dundi_eid_to_str(trans->parent->dr[0].eid_str, 
-                                               sizeof(trans->parent->dr[0].eid_str),
-                                               &ies.answers[x]->eid);
-                                       strncpy(trans->parent->dr[0].dest, ies.answers[x]->data,
-                                               sizeof(trans->parent->dr[0].dest));
-                                       strncpy(trans->parent->dr[0].tech, tech2str(ies.answers[x]->protocol),
-                                               sizeof(trans->parent->dr[0].tech));
-                                       trans->parent->respcount=1;
-                                       /* Save all the results (if any) we had.  Even if no results, still cache lookup.  Let
-                                          the cache know if this request was unaffected by our entity list. */
-                                       cache_save(&trans->them_eid, trans->parent, 0, 
-                                               ies.hint ? ntohs(ies.hint->flags) & DUNDI_HINT_UNAFFECTED : 0, ies.expiration);
-                               }
-                               if (ies.hint) {
-                                       cache_save_hint(&trans->them_eid, trans->parent, ies.hint, ies.expiration);
-                                       if (ntohs(ies.hint->flags) & DUNDI_HINT_TTL_EXPIRED)
-                                               trans->parent->hmd->flags |= DUNDI_HINT_TTL_EXPIRED;
-                                       if (ntohs(ies.hint->flags) & DUNDI_HINT_DONT_ASK) { 
-                                               if (strlen(ies.hint->data) > strlen(trans->parent->hmd->exten)) {
-                                                       strncpy(trans->parent->hmd->exten, ies.hint->data, 
-                                                               sizeof(trans->parent->hmd->exten) - 1);
-                                               }
-                                       } else {
-                                               trans->parent->hmd->flags &= ~DUNDI_HINT_DONT_ASK;
-                                       }
-                               }
-                       }
-               }
-               if (!authpass && ies.eids[0])
-                       ast_log(LOG_NOTICE, "Peer '%s' does not have permission to pre-cache!\n", 
-                               dundi_eid_to_str(eid_str, sizeof(eid_str), ies.eids[0]));
-               /* Close connection if not final */
-               if (!final) 
-                       dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
-               break;
        case DUNDI_COMMAND_DPRESPONSE:
                /* A dialplan response, lets see what we got... */
                if (ies.cause < 1) {
@@ -1646,6 +1789,7 @@ static int handle_command_response(struct dundi_transaction *trans, struct dundi
                break;
        case DUNDI_COMMAND_INVALID:
        case DUNDI_COMMAND_NULL:
+       case DUNDI_COMMAND_PRECACHERP:
                /* Do nothing special */
                if (!final) 
                        dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
@@ -2118,8 +2262,34 @@ static int dundi_do_lookup(int fd, int argc, char *argv[])
        else
                sort_results(dr, res);
        for (x=0;x<res;x++) {
-               ast_cli(fd, "%d. %5d %s/%s (%s)\n", x + 1, dr[x].weight, dr[x].tech, dr[x].dest, dundi_flags2str(fs, sizeof(fs), dr[x].flags));
+               ast_cli(fd, "%3d. %5d %s/%s (%s)\n", x + 1, dr[x].weight, dr[x].tech, dr[x].dest, dundi_flags2str(fs, sizeof(fs), dr[x].flags));
+               ast_cli(fd, "     from %s, expires in %d s\n", dr[x].eid_str, dr[x].expiration);
+       }
+       ast_cli(fd, "DUNDi lookup completed in %d ms\n", calc_ms(&start));
+       return RESULT_SUCCESS;
+}
+
+static int dundi_do_precache(int fd, int argc, char *argv[])
+{
+       int res;
+       char tmp[256] = "";
+       char *context;
+       struct timeval start;
+       if ((argc < 3) || (argc > 3))
+               return RESULT_SHOWUSAGE;
+       strncpy(tmp, argv[2], sizeof(tmp) - 1);
+       context = strchr(tmp, '@');
+       if (context) {
+               *context = '\0';
+               context++;
        }
+       gettimeofday(&start, NULL);
+       res = dundi_precache(context, tmp);
+       
+       if (res < 0) 
+               ast_cli(fd, "DUNDi precache returned error.\n");
+       else if (!res) 
+               ast_cli(fd, "DUNDi precache returned no error.\n");
        ast_cli(fd, "DUNDi lookup completed in %d ms\n", calc_ms(&start));
        return RESULT_SUCCESS;
 }
@@ -2429,6 +2599,12 @@ static char lookup_usage[] =
 "(or e164 if none is specified).  Bypasses cache if 'bypass'\n"
 "keyword is specified.\n";
 
+static char precache_usage[] =
+"Usage: dundi precache <number>[@context]\n"
+"       Lookup the given number within the given DUNDi context\n"
+"(or e164 if none is specified) and precaches the results to any\n"
+"upstream DUNDi push servers.\n";
+
 static char query_usage[] =
 "Usage: dundi query <entity>[@context]\n"
 "       Attempts to retrieve contact information for a specific\n"
@@ -2477,6 +2653,9 @@ static struct ast_cli_entry  cli_show_peer =
 static struct ast_cli_entry  cli_lookup =
        { { "dundi", "lookup", NULL }, dundi_do_lookup, "Lookup a number in DUNDi", lookup_usage };
 
+static struct ast_cli_entry  cli_precache =
+       { { "dundi", "precache", NULL }, dundi_do_precache, "Precache a number in DUNDi", precache_usage };
+
 static struct ast_cli_entry  cli_queryeid =
        { { "dundi", "query", NULL }, dundi_do_query, "Query a DUNDi EID", query_usage };
 
@@ -2752,6 +2931,8 @@ static int dundi_send(struct dundi_transaction *trans, int cmdresp, int flags, i
                        case DUNDI_COMMAND_DPRESPONSE:
                        case DUNDI_COMMAND_EIDQUERY:
                        case DUNDI_COMMAND_EIDRESPONSE:
+                       case DUNDI_COMMAND_PRECACHERQ:
+                       case DUNDI_COMMAND_PRECACHERP:
                                if (dundidebug)
                                        dundi_showframe(pack->h, 2, &trans->addr, pack->datalen - sizeof(struct dundi_hdr));
                                res = dundi_encrypt(trans, pack);
@@ -2832,6 +3013,63 @@ static int dundi_discover(struct dundi_transaction *trans)
        return dundi_send(trans, DUNDI_COMMAND_DPDISCOVER, 0, 0, &ied);
 }
 
+static int precache_trans(struct dundi_transaction *trans, struct dundi_mapping *maps, int mapcount)
+{
+       struct dundi_ie_data ied;
+       int x, res;
+       int max = 999999;
+       int expiration = DUNDI_DEFAULT_CACHE_TIME;
+       int ouranswers=0;
+       dundi_eid *avoid[1] = { NULL, };
+       int direct[1] = { 0, };
+       struct dundi_result dr[MAX_RESULTS];
+       struct dundi_hint_metadata hmd;
+       if (!trans->parent) {
+               ast_log(LOG_WARNING, "Tried to discover a transaction with no parent?!?\n");
+               return -1;
+       }
+       memset(&hmd, 0, sizeof(hmd));
+       memset(&dr, 0, sizeof(dr));
+       /* Look up the answers we're going to include */
+       for (x=0;x<mapcount;x++)
+               ouranswers = dundi_lookup_local(dr, maps + x, trans->parent->number, &trans->us_eid, ouranswers, &hmd);
+       if (ouranswers < 0)
+               ouranswers = 0;
+       for (x=0;x<ouranswers;x++) {
+               if (dr[x].weight < max)
+                       max = dr[x].weight;
+       }
+       if (max) {
+               /* If we do not have a canonical result, keep looking */
+               res = dundi_lookup_internal(dr + ouranswers, MAX_RESULTS - ouranswers, NULL, trans->parent->dcontext, trans->parent->number, trans->ttl, 1, &hmd, &expiration, 0, 1, &trans->them_eid, avoid, direct);
+               if (res > 0) {
+                       /* Append answer in result */
+                       ouranswers += res;
+               }
+       }
+       
+       memset(&ied, 0, sizeof(ied));
+       dundi_ie_append_short(&ied, DUNDI_IE_VERSION, DUNDI_DEFAULT_VERSION);
+       if (!dundi_eid_zero(&trans->us_eid))
+               dundi_ie_append_eid(&ied, DUNDI_IE_EID, &trans->us_eid);
+       for (x=0;x<trans->eidcount;x++)
+               dundi_ie_append_eid(&ied, DUNDI_IE_EID, &trans->eids[x]);
+       dundi_ie_append_str(&ied, DUNDI_IE_CALLED_NUMBER, trans->parent->number);
+       dundi_ie_append_str(&ied, DUNDI_IE_CALLED_CONTEXT, trans->parent->dcontext);
+       dundi_ie_append_short(&ied, DUNDI_IE_TTL, trans->ttl);
+       for (x=0;x<ouranswers;x++) {
+               /* Add answers */
+               if (dr[x].expiration && (expiration > dr[x].expiration))
+                       expiration = dr[x].expiration;
+               dundi_ie_append_answer(&ied, DUNDI_IE_ANSWER, &dr[x].eid, dr[x].techint, dr[x].flags, dr[x].weight, dr[x].dest);
+       }
+       dundi_ie_append_hint(&ied, DUNDI_IE_HINT, hmd.flags, hmd.exten);
+       dundi_ie_append_short(&ied, DUNDI_IE_EXPIRATION, expiration);
+       if (trans->autokilltimeout)
+               trans->autokillid = ast_sched_add(sched, trans->autokilltimeout, do_autokill, trans);
+       return dundi_send(trans, DUNDI_COMMAND_PRECACHERQ, 0, 0, &ied);
+}
+
 static int dundi_query(struct dundi_transaction *trans)
 {
        struct dundi_ie_data ied;
@@ -2857,13 +3095,22 @@ static int dundi_query(struct dundi_transaction *trans)
 static int discover_transactions(struct dundi_request *dr)
 {
        struct dundi_transaction *trans;
-       ast_mutex_lock(&peerlock);
        trans = dr->trans;
        while(trans) {
                dundi_discover(trans);
                trans = trans->next;
        }
-       ast_mutex_unlock(&peerlock);
+       return 0;
+}
+
+static int precache_transactions(struct dundi_request *dr, struct dundi_mapping *maps, int mapcount)
+{
+       struct dundi_transaction *trans;
+       trans = dr->trans;
+       while(trans) {
+               precache_trans(trans, maps, mapcount);
+               trans = trans->next;
+       }
        return 0;
 }
 
@@ -2993,21 +3240,36 @@ static void abort_request(struct dundi_request *dr)
        ast_mutex_unlock(&peerlock);
 }
 
-static void build_transactions(struct dundi_request *dr, int ttl, int order, int *foundcache, int *skipped, int blockempty, int nocache, dundi_eid *avoid[], int directs[])
+static void build_transactions(struct dundi_request *dr, int ttl, int order, int *foundcache, int *skipped, int blockempty, int nocache, int modeselect, dundi_eid *skip, dundi_eid *avoid[], int directs[])
 {
        struct dundi_peer *p;
        int x;
        int res;
+       int pass;
+       int allowconnect;
        char eid_str[20];
        ast_mutex_lock(&peerlock);
        p = peers;
        while(p) {
-               if (has_permission(p->include, dr->dcontext)) {
+               if (modeselect == 1) {
+                       /* Send the precache to push upstreams only! */
+                       pass = has_permission(p->permit, dr->dcontext) && (p->pcmodel & DUNDI_MODEL_OUTBOUND);
+                       allowconnect = 1;
+               } else {
+                       /* Normal lookup / EID query */
+                       pass = has_permission(p->include, dr->dcontext);
+                       allowconnect = p->model & DUNDI_MODEL_OUTBOUND;
+               }
+               if (skip) {
+                       if (!dundi_eid_cmp(skip, &p->eid))
+                               pass = 0;
+               }
+               if (pass) {
                        if (p->order <= order) {
                                /* Check order first, then check cache, regardless of
                                   omissions, this gets us more likely to not have an
                                   affected answer. */
-                               if(nocache || !(res = cache_lookup(dr, &p->eid, dr->crc32, &dr->expiration))) {
+                               if((nocache || !(res = cache_lookup(dr, &p->eid, dr->crc32, &dr->expiration)))) {
                                        res = 0;
                                        /* Make sure we haven't already seen it and that it won't
                                           affect our answer */
@@ -3020,11 +3282,13 @@ static void build_transactions(struct dundi_request *dr, int ttl, int order, int
                                                }
                                        }
                                        /* Make sure we can ask */
-                                       if (!avoid[x] && (!blockempty || !dundi_eid_zero(&p->us_eid))) {
-                                               /* Check for a matching or 0 cache entry */
-                                               append_transaction(dr, p, ttl, avoid);
-                                       } else
-                                               ast_log(LOG_DEBUG, "Avoiding '%s' in transaction\n", dundi_eid_to_str(eid_str, sizeof(eid_str), avoid[x]));
+                                       if (allowconnect) {
+                                               if (!avoid[x] && (!blockempty || !dundi_eid_zero(&p->us_eid))) {
+                                                       /* Check for a matching or 0 cache entry */
+                                                       append_transaction(dr, p, ttl, avoid);
+                                               } else
+                                                       ast_log(LOG_DEBUG, "Avoiding '%s' in transaction\n", dundi_eid_to_str(eid_str, sizeof(eid_str), avoid[x]));
+                                       }
                                }
                                *foundcache |= res;
                        } else if (!*skipped || (p->order < *skipped))
@@ -3121,7 +3385,7 @@ static unsigned long avoid_crc32(dundi_eid *avoid[])
        return acrc32;
 }
 
-static int dundi_lookup_internal(struct dundi_result *result, int maxret, struct ast_channel *chan, const char *dcontext, const char *number, int ttl, int blockempty, struct dundi_hint_metadata *hmd, int *expiration, int cbypass, dundi_eid *avoid[], int direct[])
+static int dundi_lookup_internal(struct dundi_result *result, int maxret, struct ast_channel *chan, const char *dcontext, const char *number, int ttl, int blockempty, struct dundi_hint_metadata *hmd, int *expiration, int cbypass, int modeselect, dundi_eid *skip, dundi_eid *avoid[], int direct[])
 {
        int res;
        struct dundi_request dr, *pending;
@@ -3187,7 +3451,7 @@ static int dundi_lookup_internal(struct dundi_result *result, int maxret, struct
                order = skipped;
                skipped = 0;
                foundcache = 0;
-               build_transactions(&dr, ttl, order, &foundcache, &skipped, blockempty, cbypass, avoid, direct);
+               build_transactions(&dr, ttl, order, &foundcache, &skipped, blockempty, cbypass, modeselect, skip, avoid, direct);
        } while (skipped && !foundcache && !dr.trans);
        /* If no TTL, abort and return 0 now after setting TTL expired hint.  Couldn't
           do this earlier because we didn't know if we were going to have transactions
@@ -3230,7 +3494,80 @@ int dundi_lookup(struct dundi_result *result, int maxret, struct ast_channel *ch
        int expiration = DUNDI_DEFAULT_CACHE_TIME;
        memset(&hmd, 0, sizeof(hmd));
        hmd.flags = DUNDI_HINT_DONT_ASK | DUNDI_HINT_UNAFFECTED;
-       return dundi_lookup_internal(result, maxret, chan, dcontext, number, dundi_ttl, 0, &hmd, &expiration, cbypass, avoid, direct);
+       return dundi_lookup_internal(result, maxret, chan, dcontext, number, dundi_ttl, 0, &hmd, &expiration, cbypass, 0, NULL, avoid, direct);
+}
+
+static int dundi_precache_internal(const char *context, const char *number, int ttl, dundi_eid *avoids[])
+{
+       struct dundi_request dr;
+       struct dundi_hint_metadata hmd;
+       struct dundi_result dr2[MAX_RESULTS];
+       struct timeval start;
+       struct dundi_mapping *maps=NULL, *cur;
+       int nummaps;
+       int foundcache, skipped, ttlms, ms;
+       if (!context)
+               context = "e164";
+       ast_log(LOG_DEBUG, "Precache internal (%s@%s)!\n", number, context);
+
+       ast_mutex_lock(&peerlock);
+       nummaps = 0;
+       cur = mappings;
+       while(cur) {
+               if (!strcasecmp(cur->dcontext, context))
+                       nummaps++;
+               cur = cur->next;
+       }
+       if (nummaps) {
+               maps = alloca(nummaps * sizeof(struct dundi_mapping));
+               nummaps = 0;
+               if (maps) {
+                       cur = mappings;
+                       while(cur) {
+                               if (!strcasecmp(cur->dcontext, context))
+                                       maps[nummaps++] = *cur;
+                               cur = cur->next;
+                       }
+               }
+       }
+       ast_mutex_unlock(&peerlock);
+       if (!nummaps || !maps)
+               return -1;
+       ttlms = DUNDI_FLUFF_TIME + ttl * DUNDI_TTL_TIME;
+       memset(&dr2, 0, sizeof(dr2));
+       memset(&dr, 0, sizeof(dr));
+       memset(&hmd, 0, sizeof(hmd));
+       dr.dr = dr2;
+       strncpy(dr.number, number, sizeof(dr.number) - 1);
+       strncpy(dr.dcontext, context ? context : "e164", sizeof(dr.dcontext) - 1);
+       dr.maxcount = MAX_RESULTS;
+       dr.expiration = DUNDI_DEFAULT_CACHE_TIME;
+       dr.hmd = &hmd;
+       pipe(dr.pfds);
+       dr.pfds[0] = dr.pfds[1] = -1;
+       build_transactions(&dr, ttl, 0, &foundcache, &skipped, 0, 1, 1, NULL, avoids, NULL);
+       optimize_transactions(&dr, 0);
+       precache_transactions(&dr, maps, nummaps);
+       gettimeofday(&start, NULL);
+       while(dr.trans && (calc_ms(&start) < ttlms)) {
+               if (dr.pfds[0] > -1) {
+                       ms = 100;
+                       ast_waitfor_n_fd(dr.pfds, 1, &ms, NULL);
+               } else
+                       usleep(1);
+       }
+       cancel_request(&dr);
+       if (dr.pfds[0] > -1) {
+               close(dr.pfds[0]);
+               close(dr.pfds[1]);
+       }
+       return 0;
+}
+
+int dundi_precache(const char *context, const char *number)
+{
+       dundi_eid *avoid[1] = { NULL, };
+       return dundi_precache_internal(context, number, dundi_ttl, avoid);
 }
 
 static int dundi_query_eid_internal(struct dundi_entity_info *dei, const char *dcontext, dundi_eid *eid, struct dundi_hint_metadata *hmd, int ttl, int blockempty, dundi_eid *avoid[])
@@ -3257,7 +3594,7 @@ static int dundi_query_eid_internal(struct dundi_entity_info *dei, const char *d
        if (rooteid)
                dr.root_eid = *rooteid;
        /* Create transactions */
-       build_transactions(&dr, ttl, 9999, &foundcache, &skipped, blockempty, 0, avoid, NULL);
+       build_transactions(&dr, ttl, 9999, &foundcache, &skipped, blockempty, 0, 0, NULL, avoid, NULL);
 
        /* If no TTL, abort and return 0 now after setting TTL expired hint.  Couldn't
           do this earlier because we didn't know if we were going to have transactions
@@ -3653,8 +3990,6 @@ static void build_peer(dundi_eid *eid, struct ast_variable *v)
                                strncpy(peer->inkey, v->value, sizeof(peer->inkey) - 1);
                        } else if (!strcasecmp(v->name, "outkey")) {
                                strncpy(peer->outkey, v->value, sizeof(peer->outkey) - 1);
-                       } else if (!strcasecmp(v->name, "canprecache")) {
-                               peer->canprecache = ast_true(v->value);
                        } else if (!strcasecmp(v->name, "host")) {
                                if (!strcasecmp(v->value, "dynamic")) {
                                        peer->dynamic = 1;
@@ -3712,15 +4047,38 @@ static void build_peer(dundi_eid *eid, struct ast_variable *v)
                                        peer->model = DUNDI_MODEL_OUTBOUND;
                                else if (!strcasecmp(v->value, "symmetric"))
                                        peer->model = DUNDI_MODEL_SYMMETRIC;
+                               else if (!strcasecmp(v->value, "none"))
+                                       peer->model = 0;
                                else {
-                                       ast_log(LOG_WARNING, "Unknown model '%s', should be 'outbound', 'inbound', or 'symmetric' at line %d\n", 
+                                       ast_log(LOG_WARNING, "Unknown model '%s', should be 'none', 'outbound', 'inbound', or 'symmetric' at line %d\n", 
+                                               v->value, v->lineno);
+                               }
+                       } else if (!strcasecmp(v->name, "precache")) {
+                               if (!strcasecmp(v->value, "inbound"))
+                                       peer->pcmodel = DUNDI_MODEL_INBOUND;
+                               else if (!strcasecmp(v->value, "outbound")) 
+                                       peer->pcmodel = DUNDI_MODEL_OUTBOUND;
+                               else if (!strcasecmp(v->value, "symmetric"))
+                                       peer->pcmodel = DUNDI_MODEL_SYMMETRIC;
+                               else if (!strcasecmp(v->value, "none"))
+                                       peer->pcmodel = 0;
+                               else {
+                                       ast_log(LOG_WARNING, "Unknown pcmodel '%s', should be 'none', 'outbound', 'inbound', or 'symmetric' at line %d\n", 
                                                v->value, v->lineno);
                                }
                        }
                        v = v->next;
                }
-               if (!peer->model) {
-                       ast_log(LOG_WARNING, "Peer '%s' lacks a model, discarding!\n", 
+               if (!peer->model && !peer->pcmodel) {
+                       ast_log(LOG_WARNING, "Peer '%s' lacks a model or pcmodel, discarding!\n", 
+                               dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
+                       peer->dead = 1;
+               } else if ((peer->model & DUNDI_MODEL_INBOUND) && (peer->pcmodel & DUNDI_MODEL_OUTBOUND)) {
+                       ast_log(LOG_WARNING, "Peer '%s' may not be both inbound/symmetric model and outbound/symmetric precache model, discarding!\n", 
+                               dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
+                       peer->dead = 1;
+               } else if ((peer->model & DUNDI_MODEL_OUTBOUND) && (peer->pcmodel & DUNDI_MODEL_INBOUND)) {
+                       ast_log(LOG_WARNING, "Peer '%s' may not be both outbound/symmetric model and inbound/symmetric precache model, discarding!\n", 
                                dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
                        peer->dead = 1;
                } else if (peer->include && !(peer->model & DUNDI_MODEL_OUTBOUND)) {
@@ -4014,6 +4372,7 @@ int unload_module(void)
        ast_cli_unregister(&cli_show_mappings);
        ast_cli_unregister(&cli_show_peer);
        ast_cli_unregister(&cli_lookup);
+       ast_cli_unregister(&cli_precache);
        ast_cli_unregister(&cli_queryeid);
        ast_unregister_switch(&dundi_switch);
        res = ast_unregister_application(app);
@@ -4064,6 +4423,7 @@ int load_module(void)
        ast_cli_register(&cli_show_mappings);
        ast_cli_register(&cli_show_peer);
        ast_cli_register(&cli_lookup);
+       ast_cli_register(&cli_precache);
        ast_cli_register(&cli_queryeid);
        if (ast_register_switch(&dundi_switch))
                ast_log(LOG_ERROR, "Unable to register DUNDi switch\n");