Ignore:
Timestamp:
Apr 28, 2009 1:47:45 PM (16 years ago)
Author:
bennylp
Message:

Ticket #800: Change in IP address selection algorithm (the pj_gethostip() function):

  • putting in weighting mechanism to select the "best" IP to use
File:
1 edited

Legend:

Unmodified
Added
Removed
  • pjproject/trunk/pjlib/src/pj/sock_common.c

    r2394 r2656  
    2626#include <pj/string.h> 
    2727#include <pj/compat/socket.h> 
     28 
     29#if 0 
     30    /* Enable some tracing */ 
     31    #include <pj/log.h> 
     32    #define THIS_FILE   "sock_common.c" 
     33    #define TRACE_(arg) PJ_LOG(4,arg) 
     34#else 
     35    #define TRACE_(arg) 
     36#endif 
    2837 
    2938 
     
    446455} 
    447456 
     457static pj_bool_t is_usable_ip(const pj_sockaddr *addr) 
     458{ 
     459    if (addr->addr.sa_family==PJ_AF_INET) { 
     460        /* Only consider if the address is not 127.0.0.0/8 or 0.0.0.0/8. 
     461         * The 0.0.0.0/8 is a special IP class that doesn't seem to be 
     462         * practically useful for our purpose. 
     463         */ 
     464        if ((pj_ntohl(addr->ipv4.sin_addr.s_addr)>>24)==127) 
     465            return PJ_FALSE; 
     466        if ((pj_ntohl(addr->ipv4.sin_addr.s_addr)>>24)==0) 
     467            return PJ_FALSE; 
     468 
     469        return PJ_TRUE; 
     470 
     471    } else if (addr->addr.sa_family==PJ_AF_INET6) { 
     472        pj_sockaddr ipv6_loop; 
     473        const pj_str_t loop = { "::1", 3}; 
     474        pj_status_t status; 
     475 
     476        status = pj_sockaddr_set_str_addr(PJ_AF_INET6, &ipv6_loop, &loop); 
     477        if (status != PJ_SUCCESS) 
     478            return PJ_TRUE; 
     479 
     480        if (pj_memcmp(&addr->ipv6.sin6_addr, &ipv6_loop.ipv6.sin6_addr, 16)==0) 
     481            return PJ_FALSE; 
     482 
     483        return PJ_TRUE; 
     484    } else { 
     485        return PJ_TRUE; 
     486    } 
     487} 
     488 
    448489/* Resolve the IP address of local machine */ 
    449490PJ_DEF(pj_status_t) pj_gethostip(int af, pj_sockaddr *addr) 
    450491{ 
    451     unsigned count; 
     492    unsigned i, count, cand_cnt; 
     493    enum { 
     494        CAND_CNT = 8, 
     495        WEIGHT_HOSTNAME = 1,    /* hostname IP is not always valid! */ 
     496        WEIGHT_DEF_ROUTE = 2, 
     497        WEIGHT_INTERFACE = 1 
     498    }; 
     499    /* candidates: */ 
     500    pj_sockaddr cand_addr[CAND_CNT]; 
     501    unsigned    cand_weight[CAND_CNT]; 
     502    int         selected_cand; 
     503    char        strip[PJ_INET6_ADDRSTRLEN+10]; 
    452504    pj_addrinfo ai; 
    453505    pj_status_t status; 
    454506 
     507    /* May not be used if TRACE_ is disabled */ 
     508    PJ_UNUSED_ARG(strip); 
    455509 
    456510#ifdef _MSC_VER 
     
    459513#endif 
    460514 
     515    cand_cnt = 0; 
     516    pj_bzero(cand_addr, sizeof(cand_addr)); 
     517    pj_bzero(cand_weight, sizeof(cand_weight)); 
     518    for (i=0; i<PJ_ARRAY_SIZE(cand_addr); ++i) { 
     519        cand_addr[i].addr.sa_family = (pj_uint16_t)af; 
     520        PJ_SOCKADDR_RESET_LEN(&cand_addr[i]); 
     521    } 
     522 
    461523    addr->addr.sa_family = (pj_uint16_t)af; 
    462524    PJ_SOCKADDR_RESET_LEN(addr); 
    463525 
    464     /* Try with resolving local hostname first */ 
     526    /* Get hostname's IP address */ 
    465527    count = 1; 
    466528    status = pj_getaddrinfo(af, pj_gethostname(), &count, &ai); 
    467529    if (status == PJ_SUCCESS) { 
    468530        pj_assert(ai.ai_addr.addr.sa_family == (pj_uint16_t)af); 
    469         pj_sockaddr_copy_addr(addr, &ai.ai_addr); 
    470     } 
    471  
    472  
    473     /* If we end up with 127.0.0.0/8 or 0.0.0.0/8, resolve the IP 
    474      * by getting the default interface to connect to some public host. 
    475      * The 0.0.0.0/8 is a special IP class that doesn't seem to be 
    476      * practically useful for our purpose. 
    477      */ 
    478     if (status != PJ_SUCCESS || !pj_sockaddr_has_addr(addr) || 
    479         (af==PJ_AF_INET && (pj_ntohl(addr->ipv4.sin_addr.s_addr)>>24)==127) || 
    480         (af==PJ_AF_INET && (pj_ntohl(addr->ipv4.sin_addr.s_addr)>>24)==0)) 
    481     { 
    482                 status = pj_getdefaultipinterface(af, addr); 
    483     } 
    484  
    485     /* If failed, get the first available interface */ 
    486     if (status != PJ_SUCCESS) { 
    487                 pj_sockaddr itf[1]; 
    488                 unsigned count = PJ_ARRAY_SIZE(itf); 
    489          
    490                 status = pj_enum_ip_interface(af, &count, itf); 
    491                 if (status == PJ_SUCCESS) { 
    492                     pj_assert(itf[0].addr.sa_family == (pj_uint16_t)af); 
    493                     pj_sockaddr_copy_addr(addr, &itf[0]); 
     531        pj_sockaddr_copy_addr(&cand_addr[cand_cnt], &ai.ai_addr); 
     532        pj_sockaddr_set_port(&cand_addr[cand_cnt], 0); 
     533        cand_weight[cand_cnt] += WEIGHT_HOSTNAME; 
     534        ++cand_cnt; 
     535 
     536        TRACE_((THIS_FILE, "hostname IP is %s", 
     537                pj_sockaddr_print(&ai.ai_addr, strip, sizeof(strip), 0))); 
     538    } 
     539 
     540 
     541    /* Get default interface (interface for default route) */ 
     542    if (cand_cnt < PJ_ARRAY_SIZE(cand_addr)) { 
     543        status = pj_getdefaultipinterface(af, addr); 
     544        if (status == PJ_SUCCESS) { 
     545            TRACE_((THIS_FILE, "default IP is %s", 
     546                    pj_sockaddr_print(addr, strip, sizeof(strip), 0))); 
     547 
     548            pj_sockaddr_set_port(addr, 0); 
     549            for (i=0; i<cand_cnt; ++i) { 
     550                if (pj_sockaddr_cmp(&cand_addr[i], addr)==0) 
     551                    break; 
     552            } 
     553 
     554            cand_weight[i] += WEIGHT_DEF_ROUTE; 
     555            if (i >= cand_cnt) { 
     556                pj_sockaddr_copy_addr(&cand_addr[i], addr); 
     557                ++cand_cnt; 
     558            } 
     559        } 
     560    } 
     561 
     562 
     563    /* Enumerate IP interfaces */ 
     564    if (cand_cnt < PJ_ARRAY_SIZE(cand_addr)) { 
     565        unsigned start_if = cand_cnt; 
     566        unsigned count = PJ_ARRAY_SIZE(cand_addr) - start_if; 
     567 
     568        status = pj_enum_ip_interface(af, &count, &cand_addr[start_if]); 
     569        if (status == PJ_SUCCESS && count) { 
     570            /* Clear the port number */ 
     571            for (i=0; i<count; ++i) 
     572                pj_sockaddr_set_port(&cand_addr[start_if+i], 0); 
     573 
     574            /* For each candidate that we found so far (that is the hostname 
     575             * address and default interface address, check if they're found 
     576             * in the interface list. If found, add the weight, and if not, 
     577             * decrease the weight. 
     578             */ 
     579            for (i=0; i<cand_cnt; ++i) { 
     580                unsigned j; 
     581                for (j=0; j<count; ++j) { 
     582                    if (pj_sockaddr_cmp(&cand_addr[i],  
     583                                        &cand_addr[start_if+j])==0) 
     584                        break; 
    494585                } 
     586 
     587                if (j == count) { 
     588                    /* Not found */ 
     589                    cand_weight[i] -= WEIGHT_INTERFACE; 
     590                } else { 
     591                    cand_weight[i] += WEIGHT_INTERFACE; 
     592                } 
     593            } 
     594 
     595            /* Add remaining interface to candidate list. */ 
     596            for (i=0; i<count; ++i) { 
     597                unsigned j; 
     598                for (j=0; j<cand_cnt; ++j) { 
     599                    if (pj_sockaddr_cmp(&cand_addr[start_if+i],  
     600                                        &cand_addr[j])==0) 
     601                        break; 
     602                } 
     603 
     604                if (j == cand_cnt) { 
     605                    pj_sockaddr_copy_addr(&cand_addr[cand_cnt],  
     606                                          &cand_addr[start_if+i]); 
     607                    cand_weight[cand_cnt] += WEIGHT_INTERFACE; 
     608                    ++cand_cnt; 
     609                } 
     610            } 
     611        } 
     612    } 
     613 
     614    /* Enumerate candidates to get the best IP address to choose */ 
     615    selected_cand = -1; 
     616    for (i=0; i<cand_cnt; ++i) { 
     617        TRACE_((THIS_FILE, "Checking candidate IP %s, weight=%d", 
     618                pj_sockaddr_print(&cand_addr[i], strip, sizeof(strip), 0), 
     619                cand_weight[i])); 
     620 
     621        if (!is_usable_ip(&cand_addr[i])) { 
     622            continue; 
     623        } 
     624 
     625        if (selected_cand == -1) 
     626            selected_cand = i; 
     627        else if (cand_weight[i] > cand_weight[selected_cand]) 
     628            selected_cand = i; 
    495629    } 
    496630 
    497631    /* If else fails, returns loopback interface as the last resort */ 
    498     if (status != PJ_SUCCESS) { 
    499                 if (af==PJ_AF_INET) { 
    500                     addr->ipv4.sin_addr.s_addr = pj_htonl (0x7f000001); 
    501                 } else { 
    502                     pj_in6_addr *s6_addr; 
    503          
    504                     s6_addr = (pj_in6_addr*) pj_sockaddr_get_addr(addr); 
    505                     pj_bzero(s6_addr, sizeof(pj_in6_addr)); 
    506                     s6_addr->s6_addr[15] = 1; 
    507                 } 
    508                 status = PJ_SUCCESS; 
    509     } 
    510  
    511     return status; 
     632    if (selected_cand == -1) { 
     633        if (af==PJ_AF_INET) { 
     634            addr->ipv4.sin_addr.s_addr = pj_htonl (0x7f000001); 
     635        } else { 
     636            pj_in6_addr *s6_addr; 
     637 
     638            s6_addr = (pj_in6_addr*) pj_sockaddr_get_addr(addr); 
     639            pj_bzero(s6_addr, sizeof(pj_in6_addr)); 
     640            s6_addr->s6_addr[15] = 1; 
     641        } 
     642        TRACE_((THIS_FILE, "Loopback IP %s returned", 
     643                pj_sockaddr_print(addr, strip, sizeof(strip), 0))); 
     644    } else { 
     645        pj_sockaddr_copy_addr(addr, &cand_addr[selected_cand]); 
     646        TRACE_((THIS_FILE, "Candidate %s selected", 
     647                pj_sockaddr_print(addr, strip, sizeof(strip), 0))); 
     648    } 
     649 
     650    return PJ_SUCCESS; 
    512651} 
    513652 
Note: See TracChangeset for help on using the changeset viewer.