Ignore:
Timestamp:
Aug 12, 2009 11:03:23 AM (15 years ago)
Author:
bennylp
Message:

Ticket #866: Allow application to specify more than one STUN servers for more robustness, and continue application startup if STUN resolution fails

PJSUA-LIB:

  • New fields in pjsua_config to specify more than one STUN servers (the stun_srv_cnt and stun_srv array)
  • The existing stun_host and stun_domain fields are deprecated, but backward compatibility is maintained. If stun_srv_cnt is zero, the library will import the entries from stun_host and stun_domain
  • The library will now resolve the STUN server entries one by one and test it before using it
  • New auxiliary API pjsua_resolve_stun_servers() to perform resolution and test against array of STUN servers

pjsua application:

  • The "stun-srv" command line options can now be specified more than once
File:
1 edited

Legend:

Unmodified
Added
Removed
  • pjproject/trunk/pjsip/src/pjsua-lib/pjsua_core.c

    r2859 r2864  
    2525 
    2626 
     27/* Internal prototypes */ 
     28static void resolve_stun_entry(pjsua_stun_resolve *sess); 
     29 
     30 
    2731/* PJSUA application instance. */ 
    2832struct pjsua_data pjsua_var; 
     
    6064    pjsua_var.stun_status = PJ_EUNKNOWN; 
    6165    pjsua_var.nat_status = PJ_EPENDING; 
     66    pj_list_init(&pjsua_var.stun_res); 
    6267} 
    6368 
     
    9398    cfg->thread_cnt = 1; 
    9499    cfg->nat_type_in_sdp = 1; 
     100    cfg->stun_ignore_failure = PJ_TRUE; 
    95101    cfg->force_lr = PJ_TRUE; 
    96102#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) 
     
    123129    pj_strdup_with_null(pool, &dst->stun_domain, &src->stun_domain); 
    124130    pj_strdup_with_null(pool, &dst->stun_host, &src->stun_host); 
     131 
     132    for (i=0; i<src->stun_srv_cnt; ++i) { 
     133        pj_strdup_with_null(pool, &dst->stun_srv[i], &src->stun_srv[i]); 
     134    } 
    125135} 
    126136 
     
    762772        goto on_error; 
    763773 
     774    /* Convert deprecated STUN settings */ 
     775    if (pjsua_var.ua_cfg.stun_srv_cnt==0) { 
     776        if (pjsua_var.ua_cfg.stun_domain.slen) { 
     777            pjsua_var.ua_cfg.stun_srv[pjsua_var.ua_cfg.stun_srv_cnt++] =  
     778                pjsua_var.ua_cfg.stun_domain; 
     779        } 
     780        if (pjsua_var.ua_cfg.stun_host.slen) { 
     781            pjsua_var.ua_cfg.stun_srv[pjsua_var.ua_cfg.stun_srv_cnt++] =  
     782                pjsua_var.ua_cfg.stun_host; 
     783        } 
     784    } 
    764785 
    765786    /* Start resolving STUN server */ 
    766  
    767     status = pjsua_resolve_stun_server(PJ_FALSE); 
     787    status = resolve_stun_server(PJ_FALSE); 
    768788    if (status != PJ_SUCCESS && status != PJ_EPENDING) { 
    769789        pjsua_perror(THIS_FILE, "Error resolving STUN server", status); 
     
    860880} 
    861881 
    862  
    863 /* 
    864  * Callback function to receive notification from the resolver 
    865  * when the resolution process completes. 
    866  */ 
    867 static void stun_dns_srv_resolver_cb(void *user_data, 
    868                                      pj_status_t status, 
    869                                      const pj_dns_srv_record *rec) 
    870 { 
     882/* Internal function to destroy STUN resolution session 
     883 * (pj_stun_resolve). 
     884 */ 
     885static void destroy_stun_resolve(pjsua_stun_resolve *sess) 
     886{ 
     887    PJSUA_LOCK(); 
     888    pj_list_erase(sess); 
     889    PJSUA_UNLOCK(); 
     890 
     891    pj_assert(sess->stun_sock==NULL); 
     892    pj_pool_release(sess->pool); 
     893} 
     894 
     895/* This is the internal function to be called when STUN resolution 
     896 * session (pj_stun_resolve) has completed. 
     897 */ 
     898static void stun_resolve_complete(pjsua_stun_resolve *sess) 
     899{ 
     900    pj_stun_resolve_result result; 
     901 
     902    pj_bzero(&result, sizeof(result)); 
     903    result.token = sess->token; 
     904    result.status = sess->status; 
     905    result.name = sess->srv[sess->idx]; 
     906    pj_memcpy(&result.addr, &sess->addr, sizeof(result.addr)); 
     907 
     908    if (result.status == PJ_SUCCESS) { 
     909        char addr[PJ_INET6_ADDRSTRLEN+10]; 
     910        pj_sockaddr_print(&result.addr, addr, sizeof(addr), 3); 
     911        PJ_LOG(4,(THIS_FILE,  
     912                  "STUN resolution success, using %.*s, address is %s", 
     913                  (int)sess->srv[sess->idx].slen, 
     914                  sess->srv[sess->idx].ptr, 
     915                  addr)); 
     916    } else { 
     917        char errmsg[PJ_ERR_MSG_SIZE]; 
     918        pj_strerror(result.status, errmsg, sizeof(errmsg)); 
     919        PJ_LOG(1,(THIS_FILE, "STUN resolution failed: %s", errmsg)); 
     920    } 
     921 
     922    sess->cb(&result); 
     923 
     924    if (!sess->blocking) { 
     925        destroy_stun_resolve(sess); 
     926    } 
     927} 
     928 
     929/* This is the callback called by the STUN socket (pj_stun_sock) 
     930 * to report it's state. We use this as part of testing the 
     931 * STUN server. 
     932 */ 
     933static pj_bool_t test_stun_on_status(pj_stun_sock *stun_sock,  
     934                                     pj_stun_sock_op op, 
     935                                     pj_status_t status) 
     936{ 
     937    pjsua_stun_resolve *sess; 
     938 
     939    sess = pj_stun_sock_get_user_data(stun_sock); 
     940    pj_assert(stun_sock == sess->stun_sock); 
     941 
     942    if (status != PJ_SUCCESS) { 
     943        char errmsg[PJ_ERR_MSG_SIZE]; 
     944        pj_strerror(status, errmsg, sizeof(errmsg)); 
     945 
     946        PJ_LOG(4,(THIS_FILE, "STUN resolution for %.*s failed: %s", 
     947                  (int)sess->srv[sess->idx].slen, 
     948                  sess->srv[sess->idx].ptr, errmsg)); 
     949 
     950        sess->status = status; 
     951 
     952        pj_stun_sock_destroy(stun_sock); 
     953        sess->stun_sock = NULL; 
     954 
     955        ++sess->idx; 
     956        resolve_stun_entry(sess); 
     957 
     958        return PJ_FALSE; 
     959 
     960    } else if (op == PJ_STUN_SOCK_BINDING_OP) { 
     961        pj_stun_sock_info ssi; 
     962 
     963        pj_stun_sock_get_info(stun_sock, &ssi); 
     964        pj_memcpy(&sess->addr, &ssi.srv_addr, sizeof(sess->addr)); 
     965 
     966        sess->status = PJ_SUCCESS; 
     967        pj_stun_sock_destroy(stun_sock); 
     968        sess->stun_sock = NULL; 
     969 
     970        stun_resolve_complete(sess); 
     971 
     972        return PJ_FALSE; 
     973 
     974    } else 
     975        return PJ_TRUE; 
     976     
     977} 
     978 
     979/* This is an internal function to resolve and test current 
     980 * server entry in pj_stun_resolve session. It is called by 
     981 * pjsua_resolve_stun_servers() and test_stun_on_status() above 
     982 */ 
     983static void resolve_stun_entry(pjsua_stun_resolve *sess) 
     984{ 
     985    /* Loop while we have entry to try */ 
     986    for (; sess->idx < sess->count; ++sess->idx) { 
     987        const int af = pj_AF_INET(); 
     988        pj_str_t hostpart; 
     989        pj_uint16_t port; 
     990        pj_stun_sock_cb stun_sock_cb; 
     991         
     992        pj_assert(sess->idx < sess->count); 
     993 
     994        /* Parse the server entry into host:port */ 
     995        sess->status = pj_sockaddr_parse2(af, 0, &sess->srv[sess->idx], 
     996                                          &hostpart, &port, NULL); 
     997        if (sess->status != PJ_SUCCESS) { 
     998            PJ_LOG(2,(THIS_FILE, "Invalid STUN server entry %.*s",  
     999                      (int)sess->srv[sess->idx].slen,  
     1000                      sess->srv[sess->idx].ptr)); 
     1001            continue; 
     1002        } 
     1003         
     1004        /* Use default port if not specified */ 
     1005        if (port == 0) 
     1006            port = PJ_STUN_PORT; 
     1007 
     1008        pj_assert(sess->stun_sock == NULL); 
     1009 
     1010        PJ_LOG(4,(THIS_FILE, "Trying STUN server %.*s (%d of %d)..", 
     1011                  (int)sess->srv[sess->idx].slen, 
     1012                  sess->srv[sess->idx].ptr, 
     1013                  sess->idx+1, sess->count)); 
     1014 
     1015        /* Use STUN_sock to test this entry */ 
     1016        pj_bzero(&stun_sock_cb, sizeof(stun_sock_cb)); 
     1017        stun_sock_cb.on_status = &test_stun_on_status; 
     1018        sess->status = pj_stun_sock_create(&pjsua_var.stun_cfg, "stunresolve", 
     1019                                           pj_AF_INET(), &stun_sock_cb, 
     1020                                           NULL, sess, &sess->stun_sock); 
     1021        if (sess->status != PJ_SUCCESS) { 
     1022            char errmsg[PJ_ERR_MSG_SIZE]; 
     1023            pj_strerror(sess->status, errmsg, sizeof(errmsg)); 
     1024            PJ_LOG(4,(THIS_FILE,  
     1025                     "Error creating STUN socket for %.*s: %s", 
     1026                      (int)sess->srv[sess->idx].slen, 
     1027                      sess->srv[sess->idx].ptr, errmsg)); 
     1028 
     1029            continue; 
     1030        } 
     1031 
     1032        sess->status = pj_stun_sock_start(sess->stun_sock, &hostpart, 
     1033                                          port, pjsua_var.resolver); 
     1034        if (sess->status != PJ_SUCCESS) { 
     1035            char errmsg[PJ_ERR_MSG_SIZE]; 
     1036            pj_strerror(sess->status, errmsg, sizeof(errmsg)); 
     1037            PJ_LOG(4,(THIS_FILE,  
     1038                     "Error starting STUN socket for %.*s: %s", 
     1039                      (int)sess->srv[sess->idx].slen, 
     1040                      sess->srv[sess->idx].ptr, errmsg)); 
     1041 
     1042            pj_stun_sock_destroy(sess->stun_sock); 
     1043            sess->stun_sock = NULL; 
     1044            continue; 
     1045        } 
     1046 
     1047        /* Done for now, testing will resume/complete asynchronously in 
     1048         * stun_sock_cb() 
     1049         */ 
     1050        return; 
     1051    } 
     1052 
     1053    if (sess->idx >= sess->count) { 
     1054        /* No more entries to try */ 
     1055        PJ_ASSERT_ON_FAIL(sess->status != PJ_SUCCESS,  
     1056                          sess->status = PJ_EUNKNOWN); 
     1057        stun_resolve_complete(sess); 
     1058    } 
     1059} 
     1060 
     1061 
     1062/* 
     1063 * Resolve STUN server. 
     1064 */ 
     1065PJ_DEF(pj_status_t) pjsua_resolve_stun_servers( unsigned count, 
     1066                                                pj_str_t srv[], 
     1067                                                pj_bool_t wait, 
     1068                                                void *token, 
     1069                                                pj_stun_resolve_cb cb) 
     1070{ 
     1071    pj_pool_t *pool; 
     1072    pjsua_stun_resolve *sess; 
     1073    pj_status_t status; 
    8711074    unsigned i; 
    8721075 
    873     PJ_UNUSED_ARG(user_data); 
    874  
    875     pjsua_var.stun_status = status; 
    876      
    877     if (status != PJ_SUCCESS) { 
    878         /* DNS SRV resolution failed. If stun_host is specified, resolve 
    879          * it with gethostbyname() 
    880          */ 
    881         if (pjsua_var.ua_cfg.stun_host.slen) { 
    882             pjsua_var.stun_status =  
    883                 pj_sockaddr_parse(pj_AF_INET(), 0, 
    884                                   &pjsua_var.ua_cfg.stun_host, 
    885                                   &pjsua_var.stun_srv); 
    886             if (pjsua_var.stun_status != PJ_SUCCESS) { 
    887                 pjsua_perror(THIS_FILE, "Invalid STUN server",  
    888                              pjsua_var.stun_status); 
    889             } else { 
    890                 if (pj_sockaddr_get_port(&pjsua_var.stun_srv)==0) 
    891                     pj_sockaddr_set_port(&pjsua_var.stun_srv, 3478); 
    892  
    893                 PJ_LOG(3,(THIS_FILE,  
    894                           "STUN server %.*s resolved, address is %s:%d", 
    895                           (int)pjsua_var.ua_cfg.stun_host.slen, 
    896                           pjsua_var.ua_cfg.stun_host.ptr, 
    897                           pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr), 
    898                           (int)pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port))); 
     1076    PJ_ASSERT_RETURN(count && srv && cb, PJ_EINVAL); 
     1077 
     1078    pool = pjsua_pool_create("stunres", 256, 256); 
     1079    if (!pool) 
     1080        return PJ_ENOMEM; 
     1081 
     1082    sess = PJ_POOL_ZALLOC_T(pool, pjsua_stun_resolve); 
     1083    sess->pool = pool; 
     1084    sess->token = token; 
     1085    sess->cb = cb; 
     1086    sess->count = count; 
     1087    sess->blocking = wait; 
     1088    sess->status = PJ_EPENDING; 
     1089    sess->srv = (pj_str_t*) pj_pool_calloc(pool, count, sizeof(pj_str_t)); 
     1090    for (i=0; i<count; ++i) { 
     1091        pj_strdup(pool, &sess->srv[i], &srv[i]); 
     1092    } 
     1093 
     1094    PJSUA_LOCK(); 
     1095    pj_list_push_back(&pjsua_var.stun_res, sess); 
     1096    PJSUA_UNLOCK(); 
     1097 
     1098    resolve_stun_entry(sess); 
     1099 
     1100    if (!wait) 
     1101        return PJ_SUCCESS; 
     1102 
     1103    while (sess->status == PJ_EPENDING) { 
     1104        pjsua_handle_events(50); 
     1105    } 
     1106 
     1107    status = sess->status; 
     1108    destroy_stun_resolve(sess); 
     1109 
     1110    return status; 
     1111} 
     1112 
     1113/* 
     1114 * Cancel pending STUN resolution. 
     1115 */ 
     1116PJ_DEF(pj_status_t) pjsua_cancel_stun_resolution( void *token, 
     1117                                                  pj_bool_t notify_cb) 
     1118{ 
     1119    pjsua_stun_resolve *sess; 
     1120    unsigned cancelled_count = 0; 
     1121 
     1122    PJSUA_LOCK(); 
     1123    sess = pjsua_var.stun_res.next; 
     1124    while (sess != &pjsua_var.stun_res) { 
     1125        pjsua_stun_resolve *next = sess->next; 
     1126 
     1127        if (sess->token == token) { 
     1128            if (notify_cb) { 
     1129                pj_stun_resolve_result result; 
     1130 
     1131                pj_bzero(&result, sizeof(result)); 
     1132                result.token = token; 
     1133                result.status = PJ_ECANCELLED; 
     1134 
     1135                sess->cb(&result); 
    8991136            } 
    900         } else { 
    901             char errmsg[PJ_ERR_MSG_SIZE]; 
    902  
    903             pj_strerror(status, errmsg, sizeof(errmsg)); 
    904             PJ_LOG(1,(THIS_FILE,  
    905                      "DNS SRV resolution failed for STUN server %.*s: %s", 
    906                      (int)pjsua_var.ua_cfg.stun_domain.slen, 
    907                      pjsua_var.ua_cfg.stun_domain.ptr, 
    908                      errmsg)); 
    909         } 
    910         return; 
    911     } 
    912  
    913     pj_assert(rec->count != 0 && rec->entry[0].server.addr_count != 0); 
    914     pj_sockaddr_in_init(&pjsua_var.stun_srv.ipv4, NULL, 
    915                         rec->entry[0].port); 
    916     pjsua_var.stun_srv.ipv4.sin_addr.s_addr =  
    917         rec->entry[0].server.addr[0].s_addr; 
    918  
    919     PJ_LOG(3,(THIS_FILE, "_stun._udp.%.*s resolved, found %d entry(s):", 
    920               (int)pjsua_var.ua_cfg.stun_domain.slen, 
    921               pjsua_var.ua_cfg.stun_domain.ptr, 
    922               rec->count)); 
    923  
    924     for (i=0; i<rec->count; ++i) { 
    925         PJ_LOG(3,(THIS_FILE,  
    926                   " %d: prio=%d, weight=%d  %s:%d",  
    927                   i, rec->entry[i].priority, rec->entry[i].weight, 
    928                   pj_inet_ntoa(rec->entry[i].server.addr[0]), 
    929                   (int)rec->entry[i].port)); 
    930     } 
    931  
     1137 
     1138            destroy_stun_resolve(sess); 
     1139            ++cancelled_count; 
     1140        } 
     1141 
     1142        sess = next; 
     1143    } 
     1144    PJSUA_UNLOCK(); 
     1145 
     1146    return cancelled_count ? PJ_SUCCESS : PJ_ENOTFOUND; 
     1147} 
     1148 
     1149static void internal_stun_resolve_cb(const pj_stun_resolve_result *result) 
     1150{ 
     1151    pjsua_var.stun_status = result->status; 
     1152    if (result->status == PJ_SUCCESS) { 
     1153        pj_memcpy(&pjsua_var.stun_srv, &result->addr, sizeof(result->addr)); 
     1154    } 
    9321155} 
    9331156 
     
    9351158 * Resolve STUN server. 
    9361159 */ 
    937 pj_status_t pjsua_resolve_stun_server(pj_bool_t wait) 
     1160pj_status_t resolve_stun_server(pj_bool_t wait) 
    9381161{ 
    9391162    if (pjsua_var.stun_status == PJ_EUNKNOWN) { 
     1163        pj_status_t status; 
     1164 
    9401165        /* Initialize STUN configuration */ 
    9411166        pj_stun_config_init(&pjsua_var.stun_cfg, &pjsua_var.cp.factory, 0, 
     
    9441169 
    9451170        /* Start STUN server resolution */ 
    946          
    947         pjsua_var.stun_status = PJ_EPENDING; 
    948  
    949         /* If stun_domain is specified, resolve STUN servers with DNS 
    950          * SRV resolution. 
    951          */ 
    952         if (pjsua_var.ua_cfg.stun_domain.slen) { 
    953             pj_str_t res_type; 
    954             pj_status_t status; 
    955  
    956             /* Fail if resolver is not configured */ 
    957             if (pjsua_var.resolver == NULL) { 
    958                 PJ_LOG(1,(THIS_FILE, "Nameserver must be configured when " 
    959                                      "stun_domain is specified")); 
    960                 pjsua_var.stun_status = PJLIB_UTIL_EDNSNONS; 
    961                 return PJLIB_UTIL_EDNSNONS; 
     1171        if (pjsua_var.ua_cfg.stun_srv_cnt) { 
     1172            pjsua_var.stun_status = PJ_EPENDING; 
     1173            status = pjsua_resolve_stun_servers(pjsua_var.ua_cfg.stun_srv_cnt, 
     1174                                                pjsua_var.ua_cfg.stun_srv, 
     1175                                                wait, NULL, 
     1176                                                &internal_stun_resolve_cb); 
     1177            if (wait || status != PJ_SUCCESS) { 
     1178                pjsua_var.stun_status = status; 
    9621179            } 
    963             res_type = pj_str("_stun._udp"); 
    964             status =  
    965                 pj_dns_srv_resolve(&pjsua_var.ua_cfg.stun_domain, &res_type, 
    966                                    3478, pjsua_var.pool, pjsua_var.resolver, 
    967                                    0, NULL, &stun_dns_srv_resolver_cb, NULL); 
    968             if (status != PJ_SUCCESS) { 
    969                 pjsua_perror(THIS_FILE, "Error starting DNS SRV resolution",  
    970                              pjsua_var.stun_status); 
    971                 pjsua_var.stun_status = status; 
    972                 return pjsua_var.stun_status; 
    973             } else { 
    974                 pjsua_var.stun_status = PJ_EPENDING; 
    975             } 
    976         } 
    977         /* Otherwise if stun_host is specified, resolve STUN server with 
    978          * gethostbyname(). 
    979          */ 
    980         else if (pjsua_var.ua_cfg.stun_host.slen) { 
    981             pjsua_var.stun_status =  
    982                 pj_sockaddr_parse(pj_AF_INET(), 0, 
    983                                   &pjsua_var.ua_cfg.stun_host, 
    984                                   &pjsua_var.stun_srv); 
    985             if (pjsua_var.stun_status != PJ_SUCCESS) { 
    986                 pjsua_perror(THIS_FILE, "Invalid STUN server",  
    987                                         pjsua_var.stun_status); 
    988                 return pjsua_var.stun_status; 
    989             } 
    990  
    991             if (pj_sockaddr_get_port(&pjsua_var.stun_srv)==0) 
    992                 pj_sockaddr_set_port(&pjsua_var.stun_srv, 3478); 
    993  
    994             PJ_LOG(3,(THIS_FILE,  
    995                       "STUN server %.*s resolved, address is %s:%d", 
    996                       (int)pjsua_var.ua_cfg.stun_host.slen, 
    997                       pjsua_var.ua_cfg.stun_host.ptr, 
    998                       pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr), 
    999                       (int)pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port))); 
    1000  
    1001         } 
    1002         /* Otherwise disable STUN. */ 
    1003         else { 
     1180        } else { 
    10041181            pjsua_var.stun_status = PJ_SUCCESS; 
    10051182        } 
    1006  
    1007  
    1008         return pjsua_var.stun_status; 
    10091183 
    10101184    } else if (pjsua_var.stun_status == PJ_EPENDING) { 
     
    10161190                pjsua_handle_events(10); 
    10171191        } 
    1018  
    1019         return pjsua_var.stun_status; 
    1020  
    1021     } else { 
    1022         /* STUN server has been resolved, return the status */ 
    1023         return pjsua_var.stun_status; 
    1024     } 
     1192    } 
     1193 
     1194    if (pjsua_var.stun_status != PJ_EPENDING && 
     1195        pjsua_var.stun_status != PJ_SUCCESS && 
     1196        pjsua_var.ua_cfg.stun_ignore_failure) 
     1197    { 
     1198        PJ_LOG(2,(THIS_FILE,  
     1199                  "Ignoring STUN resolution failure (by setting)")); 
     1200        pjsua_var.stun_status = PJ_SUCCESS; 
     1201    } 
     1202 
     1203    return pjsua_var.stun_status; 
    10251204} 
    10261205 
     
    10751254    /* Destroy endpoint. */ 
    10761255    if (pjsua_var.endpt) { 
     1256 
     1257        /* Terminate any pending STUN resolution */ 
     1258        if (!pj_list_empty(&pjsua_var.stun_res)) { 
     1259            pjsua_stun_resolve *sess = pjsua_var.stun_res.next; 
     1260            while (sess != &pjsua_var.stun_res) { 
     1261                pjsua_stun_resolve *next = sess->next; 
     1262                destroy_stun_resolve(sess); 
     1263                sess = next; 
     1264            } 
     1265        } 
     1266 
    10771267        /* Wait for some time to allow unregistration and ICE/TURN 
    10781268         * transports shutdown to complete:  
     
    12711461 
    12721462    /* Make sure STUN server resolution has completed */ 
    1273     status = pjsua_resolve_stun_server(PJ_TRUE); 
     1463    status = resolve_stun_server(PJ_TRUE); 
    12741464    if (status != PJ_SUCCESS) { 
    12751465        pjsua_perror(THIS_FILE, "Error resolving STUN server", status); 
     
    19832173 
    19842174    /* Make sure STUN server resolution has completed */ 
    1985     status = pjsua_resolve_stun_server(PJ_TRUE); 
     2175    status = resolve_stun_server(PJ_TRUE); 
    19862176    if (status != PJ_SUCCESS) { 
    19872177        pjsua_var.nat_status = status; 
Note: See TracChangeset for help on using the changeset viewer.