/* * Find a server address that can be used by `caller' to contact * the local service specified by `serv_uaddr'. If `clnt_uaddr' is * non-NULL, it is used instead of `caller' as a hint suggesting * the best address (e.g. the `r_addr' field of an rpc, which * contains the rpcbind server address that the caller used). * * Returns the best server address as a malloc'd "universal address" * string which should be freed by the caller. On error, returns NULL. */ char * addrmerge(struct netbuf *caller, const char *serv_uaddr, const char *clnt_uaddr, const char *netid) { struct ifaddrs *ifap, *ifp = NULL, *bestif; struct netbuf *serv_nbp = NULL, *hint_nbp = NULL, tbuf; struct sockaddr *caller_sa, *hint_sa, *ifsa, *ifmasksa, *serv_sa; struct sockaddr_storage ss; struct netconfig *nconf; char *caller_uaddr = NULL; const char *hint_uaddr = NULL; char *ret = NULL; int bestif_goodness; #ifdef ND_DEBUG if (debugging) fprintf(stderr, "addrmerge(caller, %s, %s, %s\n", serv_uaddr, clnt_uaddr == NULL ? "NULL" : clnt_uaddr, netid); #endif caller_sa = caller->buf; if ((nconf = rpcbind_get_conf(netid)) == NULL) goto freeit; if ((caller_uaddr = taddr2uaddr(nconf, caller)) == NULL) goto freeit; /* * Use `clnt_uaddr' as the hint if non-NULL, but ignore it if its * address family is different from that of the caller. */ hint_sa = NULL; if (clnt_uaddr != NULL) { hint_uaddr = clnt_uaddr; if ((hint_nbp = uaddr2taddr(nconf, clnt_uaddr)) == NULL) goto freeit; hint_sa = hint_nbp->buf; } if (hint_sa == NULL || hint_sa->sa_family != caller_sa->sa_family) { hint_uaddr = caller_uaddr; hint_sa = caller->buf; } #ifdef ND_DEBUG if (debugging) fprintf(stderr, "addrmerge: hint %s\n", hint_uaddr); #endif /* Local caller, just return the server address. */ if (strncmp(caller_uaddr, "0.0.0.0.", 8) == 0 || strncmp(caller_uaddr, "::.", 3) == 0 || caller_uaddr[0] == '/') { ret = strdup(serv_uaddr); goto freeit; } if (getifaddrs(&ifp) < 0) goto freeit; /* * Loop through all interface addresses. We are listening to an address * if any of the following are true: * a) It's a loopback address * b) It was specified with the -h command line option * c) There were no -h command line options. * * Among addresses on which we are listening, choose in order of * preference an address that is: * * a) Equal to the hint * b) A link local address with the same scope ID as the client's * address, if the client's address is also link local * c) An address on the same subnet as the client's address * d) A non-localhost, non-p2p address * e) Any usable address */ bestif = NULL; bestif_goodness = 0; for (ifap = ifp; ifap != NULL; ifap = ifap->ifa_next) { ifsa = ifap->ifa_addr; ifmasksa = ifap->ifa_netmask; /* Skip addresses where we don't listen */ if (ifsa == NULL || ifsa->sa_family != hint_sa->sa_family || !(ifap->ifa_flags & IFF_UP)) continue; if (!(ifap->ifa_flags & IFF_LOOPBACK) && !listen_addr(ifsa)) continue; if ((hint_sa->sa_family == AF_INET) && ((((struct sockaddr_in*)hint_sa)->sin_addr.s_addr == ((struct sockaddr_in*)ifsa)->sin_addr.s_addr))) { const int goodness = 4; bestif_goodness = goodness; bestif = ifap; goto found; } #ifdef INET6 if ((hint_sa->sa_family == AF_INET6) && (0 == memcmp(&((struct sockaddr_in6*)hint_sa)->sin6_addr, &((struct sockaddr_in6*)ifsa)->sin6_addr, sizeof(struct in6_addr))) && (((struct sockaddr_in6*)hint_sa)->sin6_scope_id == (((struct sockaddr_in6*)ifsa)->sin6_scope_id))) { const int goodness = 4; bestif_goodness = goodness; bestif = ifap; goto found; } if (hint_sa->sa_family == AF_INET6) { /* * For v6 link local addresses, if the caller is on * a link-local address then use the scope id to see * which one. */ if (IN6_IS_ADDR_LINKLOCAL(&SA2SIN6ADDR(ifsa)) && IN6_IS_ADDR_LINKLOCAL(&SA2SIN6ADDR(caller_sa)) && IN6_IS_ADDR_LINKLOCAL(&SA2SIN6ADDR(hint_sa))) { if (SA2SIN6(ifsa)->sin6_scope_id == SA2SIN6(caller_sa)->sin6_scope_id) { const int goodness = 3; if (bestif_goodness < goodness) { bestif = ifap; bestif_goodness = goodness; } } } } #endif /* INET6 */ if (0 == bitmaskcmp(hint_sa, ifsa, ifmasksa)) { const int goodness = 2; if (bestif_goodness < goodness) { bestif = ifap; bestif_goodness = goodness; } } if (!(ifap->ifa_flags & (IFF_LOOPBACK | IFF_POINTOPOINT))) { const int goodness = 1; if (bestif_goodness < goodness) { bestif = ifap; bestif_goodness = goodness; } } if (bestif == NULL) bestif = ifap; } if (bestif == NULL) goto freeit; found: /* * Construct the new address using the address from * `bestif', and the port number from `serv_uaddr'. */ serv_nbp = uaddr2taddr(nconf, serv_uaddr); if (serv_nbp == NULL) goto freeit; serv_sa = serv_nbp->buf; memcpy(&ss, bestif->ifa_addr, bestif->ifa_addr->sa_len); switch (ss.ss_family) { case AF_INET: SA2SIN(&ss)->sin_port = SA2SIN(serv_sa)->sin_port; break; #ifdef INET6 case AF_INET6: SA2SIN6(&ss)->sin6_port = SA2SIN6(serv_sa)->sin6_port; break; #endif } tbuf.len = ss.ss_len; tbuf.maxlen = sizeof(ss); tbuf.buf = &ss; ret = taddr2uaddr(nconf, &tbuf); freeit: if (caller_uaddr != NULL) free(caller_uaddr); if (hint_nbp != NULL) { free(hint_nbp->buf); free(hint_nbp); } if (serv_nbp != NULL) { free(serv_nbp->buf); free(serv_nbp); } if (ifp != NULL) freeifaddrs(ifp); #ifdef ND_DEBUG if (debugging) fprintf(stderr, "addrmerge: returning %s\n", ret); #endif return ret; }
/* * Find a server address that can be used by `caller' to contact * the local service specified by `serv_uaddr'. If `clnt_uaddr' is * non-NULL, it is used instead of `caller' as a hint suggesting * the best address (e.g. the `r_addr' field of an rpc, which * contains the rpcbind server address that the caller used). * * Returns the best server address as a malloc'd "universal address" * string which should be freed by the caller. On error, returns NULL. */ char * addrmerge(arpc_addr_t *caller, const char *serv_uaddr, const char *clnt_uaddr, const char *netid) { struct ifaddrs *ifap, *ifp = NULL, *bestif; arpc_addr_t *serv_nbp = NULL, *hint_nbp = NULL, tbuf; struct sockaddr *caller_sa, *hint_sa, *ifsa, *ifmasksa, *serv_sa; struct sockaddr_storage ss; socklen_t len; const char *hint_uaddr = NULL; char *caller_uaddr = NULL; char *ret = NULL; int err; #ifdef ND_DEBUG if (debugging) fprintf(stderr, "addrmerge(caller, %s, %s, %s\n", serv_uaddr, clnt_uaddr == NULL ? "NULL" : clnt_uaddr, netid); #endif caller_sa = (struct sockaddr *)caller->buf; err = ar_taddr2uaddr(rpcbind_ioctx, netid, caller, &caller_uaddr); if (err != 0) { goto freeit; } /* * Use `clnt_uaddr' as the hint if non-NULL, but ignore it if its * address family is different from that of the caller. */ hint_sa = NULL; if (clnt_uaddr != NULL) { hint_uaddr = clnt_uaddr; err = ar_uaddr2taddr(rpcbind_ioctx, netid, clnt_uaddr, &hint_nbp); if (err != 0) { goto freeit; } hint_sa = (struct sockaddr *)hint_nbp->buf; } if (hint_sa == NULL || hint_sa->sa_family != caller_sa->sa_family) { hint_uaddr = caller_uaddr; hint_sa = (struct sockaddr *)caller->buf; } #ifdef ND_DEBUG if (debugging) { fprintf(stderr, "addrmerge: hint %s\n", hint_uaddr); } #endif /* Local caller, just return the server address. */ if (strncmp(caller_uaddr, "0.0.0.0.", 8) == 0 || strncmp(caller_uaddr, "::.", 3) == 0 || caller_uaddr[0] == '/') { ret = strdup(serv_uaddr); goto freeit; } if (getifaddrs(&ifp) < 0) { goto freeit; } /* * Loop through all interfaces. For each interface, see if the * network portion of its address is equal to that of the client. * If so, we have found the interface that we want to use. */ bestif = NULL; for (ifap = ifp; ifap != NULL; ifap = ifap->ifa_next) { ifsa = ifap->ifa_addr; ifmasksa = ifap->ifa_netmask; if (ifsa == NULL || ifsa->sa_family != hint_sa->sa_family || !(ifap->ifa_flags & IFF_UP)) { continue; } switch (hint_sa->sa_family) { case AF_INET: /* * If the hint address matches this interface * address/netmask, then we're done. */ if (!bitmaskcmp(&SA2SINADDR(ifsa), &SA2SINADDR(hint_sa), &SA2SINADDR(ifmasksa), sizeof(struct in_addr))) { bestif = ifap; goto found; } break; case AF_INET6: /* * For v6 link local addresses, if the caller is on * a link-local address then use the scope id to see * which one. */ in6_fillscopeid(SA2SIN6(ifsa)); if (IN6_IS_ADDR_LINKLOCAL(&SA2SIN6ADDR(ifsa)) && IN6_IS_ADDR_LINKLOCAL(&SA2SIN6ADDR(caller_sa)) && IN6_IS_ADDR_LINKLOCAL(&SA2SIN6ADDR(hint_sa))) { if (SA2SIN6(ifsa)->sin6_scope_id == SA2SIN6(caller_sa)->sin6_scope_id) { bestif = ifap; goto found; } } else if (!bitmaskcmp(&SA2SIN6ADDR(ifsa), &SA2SIN6ADDR(hint_sa), &SA2SIN6ADDR(ifmasksa), sizeof(struct in6_addr))) { bestif = ifap; goto found; } break; default: continue; } /* * Remember the first possibly useful interface, preferring * "normal" to point-to-point and loopback ones. */ if (bestif == NULL || (!(ifap->ifa_flags & (IFF_LOOPBACK | IFF_POINTOPOINT)) && (bestif->ifa_flags & (IFF_LOOPBACK | IFF_POINTOPOINT)))) { bestif = ifap; } } if (bestif == NULL) { goto freeit; } found: /* * Construct the new address using the the address from * `bestif', and the port number from `serv_uaddr'. */ err = ar_uaddr2taddr(rpcbind_ioctx, netid, serv_uaddr, &serv_nbp); if (err != 0) { goto freeit; } serv_sa = (struct sockaddr *)serv_nbp->buf; switch (ss.ss_family) { case AF_INET: len = sizeof(struct sockaddr_in); memcpy(&ss, bestif->ifa_addr, len); SA2SIN(&ss)->sin_port = SA2SIN(serv_sa)->sin_port; break; case AF_INET6: len = sizeof(struct sockaddr_in6); memcpy(&ss, bestif->ifa_addr, len); SA2SIN6(&ss)->sin6_port = SA2SIN6(serv_sa)->sin6_port; break; default: memset(&ss, 0, sizeof(ss)); len = 0; break; } tbuf.len = len; tbuf.maxlen = sizeof(ss); tbuf.buf = (char *)&ss; err = ar_taddr2uaddr(rpcbind_ioctx, netid, &tbuf, &ret); if (err != 0) { ret = NULL; } freeit: if (caller_uaddr != NULL) { free(caller_uaddr); } if (hint_nbp != NULL) { if (hint_nbp->buf) { free(hint_nbp->buf); } free(hint_nbp); } if (serv_nbp != NULL) { if (serv_nbp->buf) { free(serv_nbp->buf); } free(serv_nbp); } if (ifp != NULL) { freeifaddrs(ifp); } #ifdef ND_DEBUG if (debugging) { fprintf(stderr, "addrmerge: returning %s\n", ret); } #endif return ret; }
char * addrmerge(struct netbuf *caller, char *serv_uaddr, char *clnt_uaddr, char *netid) { struct ifaddrs *ifap, *ifp, *bestif; #ifdef INET6 struct sockaddr_in6 *servsin6, *sin6mask, *clntsin6, *ifsin6, *realsin6; struct sockaddr_in6 *newsin6; #endif struct sockaddr_in *servsin, *sinmask, *clntsin, *newsin, *ifsin; struct netbuf *serv_nbp, *clnt_nbp = NULL, tbuf; struct sockaddr *serv_sa; struct sockaddr *clnt_sa; struct sockaddr_storage ss; struct netconfig *nconf; struct sockaddr *clnt = caller->buf; char *ret = NULL; #ifdef INET6 servsin6 = ifsin6 = newsin6 = NULL; /* XXXGCC -Wuninitialized */ #endif servsin = newsin = NULL; /* XXXGCC -Wuninitialized */ #ifdef RPCBIND_DEBUG if (debugging) fprintf(stderr, "addrmerge(caller, %s, %s, %s\n", serv_uaddr, clnt_uaddr, netid); #endif nconf = getnetconfigent(netid); if (nconf == NULL) return NULL; /* * Local merge, just return a duplicate. */ if (clnt_uaddr != NULL && strncmp(clnt_uaddr, "0.0.0.0.", 8) == 0) return strdup(clnt_uaddr); serv_nbp = uaddr2taddr(nconf, serv_uaddr); if (serv_nbp == NULL) return NULL; serv_sa = (struct sockaddr *)serv_nbp->buf; if (clnt_uaddr != NULL) { clnt_nbp = uaddr2taddr(nconf, clnt_uaddr); if (clnt_nbp == NULL) { free(serv_nbp); return NULL; } clnt_sa = (struct sockaddr *)clnt_nbp->buf; if (clnt_sa->sa_family == AF_LOCAL) { free(serv_nbp); free(clnt_nbp); free(clnt_sa); return strdup(serv_uaddr); } } else { clnt_sa = (struct sockaddr *) malloc(sizeof (struct sockaddr_storage)); memcpy(clnt_sa, clnt, clnt->sa_len); } if (getifaddrs(&ifp) < 0) { free(serv_nbp); free(clnt_sa); if (clnt_nbp != NULL) free(clnt_nbp); return 0; } /* * Loop through all interfaces. For each interface, see if the * network portion of its address is equal to that of the client. * If so, we have found the interface that we want to use. */ for (ifap = ifp; ifap != NULL; ifap = ifap->ifa_next) { if (ifap->ifa_addr->sa_family != clnt->sa_family || !(ifap->ifa_flags & IFF_UP)) continue; switch (clnt->sa_family) { case AF_INET: /* * realsin: address that recvfrom gave us. * ifsin: address of interface being examined. * clntsin: address that client want us to contact * it on * servsin: local address of RPC service. * sinmask: netmask of this interface * newsin: initially a copy of clntsin, eventually * the merged address */ servsin = (struct sockaddr_in *)serv_sa; clntsin = (struct sockaddr_in *)clnt_sa; sinmask = (struct sockaddr_in *)ifap->ifa_netmask; newsin = (struct sockaddr_in *)&ss; ifsin = (struct sockaddr_in *)ifap->ifa_addr; if (!bitmaskcmp(&ifsin->sin_addr, &clntsin->sin_addr, &sinmask->sin_addr, sizeof (struct in_addr))) { goto found; } break; #ifdef INET6 case AF_INET6: /* * realsin6: address that recvfrom gave us. * ifsin6: address of interface being examined. * clntsin6: address that client want us to contact * it on * servsin6: local address of RPC service. * sin6mask: netmask of this interface * newsin6: initially a copy of clntsin, eventually * the merged address * * For v6 link local addresses, if the client contacted * us via a link-local address, and wants us to reply * to one, use the scope id to see which one. */ realsin6 = (struct sockaddr_in6 *)clnt; ifsin6 = (struct sockaddr_in6 *)ifap->ifa_addr; in6_fillscopeid(ifsin6); clntsin6 = (struct sockaddr_in6 *)clnt_sa; servsin6 = (struct sockaddr_in6 *)serv_sa; sin6mask = (struct sockaddr_in6 *)ifap->ifa_netmask; newsin6 = (struct sockaddr_in6 *)&ss; if (IN6_IS_ADDR_LINKLOCAL(&ifsin6->sin6_addr) && IN6_IS_ADDR_LINKLOCAL(&realsin6->sin6_addr) && IN6_IS_ADDR_LINKLOCAL(&clntsin6->sin6_addr)) { if (ifsin6->sin6_scope_id != realsin6->sin6_scope_id) continue; goto found; } if (!bitmaskcmp(&ifsin6->sin6_addr, &clntsin6->sin6_addr, &sin6mask->sin6_addr, sizeof (struct in6_addr))) goto found; break; #endif default: goto freeit; } } /* * Didn't find anything. Get the first possibly useful interface, * preferring "normal" interfaces to point-to-point and loopback * ones. */ bestif = NULL; for (ifap = ifp; ifap != NULL; ifap = ifap->ifa_next) { if (ifap->ifa_addr->sa_family != clnt->sa_family || !(ifap->ifa_flags & IFF_UP)) continue; if (!(ifap->ifa_flags & IFF_LOOPBACK) && !(ifap->ifa_flags & IFF_POINTOPOINT)) { bestif = ifap; break; } if (bestif == NULL) bestif = ifap; else if ((bestif->ifa_flags & IFF_LOOPBACK) && !(ifap->ifa_flags & IFF_LOOPBACK)) bestif = ifap; } ifap = bestif; found: switch (clnt->sa_family) { case AF_INET: memcpy(newsin, ifap->ifa_addr, clnt_sa->sa_len); newsin->sin_port = servsin->sin_port; tbuf.len = clnt_sa->sa_len; tbuf.maxlen = sizeof (struct sockaddr_storage); tbuf.buf = newsin; break; #ifdef INET6 case AF_INET6: assert(newsin6); memcpy(newsin6, ifsin6, clnt_sa->sa_len); newsin6->sin6_port = servsin6->sin6_port; tbuf.maxlen = sizeof (struct sockaddr_storage); tbuf.len = clnt_sa->sa_len; tbuf.buf = newsin6; break; #endif default: goto freeit; } if (ifap != NULL) ret = taddr2uaddr(nconf, &tbuf); freeit: freenetconfigent(nconf); free(serv_sa); free(serv_nbp); if (clnt_sa != NULL) free(clnt_sa); if (clnt_nbp != NULL) free(clnt_nbp); freeifaddrs(ifp); #ifdef RPCBIND_DEBUG if (debugging) fprintf(stderr, "addrmerge: returning %s\n", ret); #endif return ret; }