/* * For all bits set in "mask", compare the corresponding bits in * "dst" and "src", and see if they match. Returns 0 if the addresses * match. */ static int bitmaskcmp(struct sockaddr *dst, struct sockaddr *src, struct sockaddr *mask) { int i; u_int8_t *p1, *p2, *netmask; int bytelen; if (dst->sa_family != src->sa_family || dst->sa_family != mask->sa_family) return (1); switch (dst->sa_family) { case AF_INET: p1 = (uint8_t*) &SA2SINADDR(dst); p2 = (uint8_t*) &SA2SINADDR(src); netmask = (uint8_t*) &SA2SINADDR(mask); bytelen = sizeof(struct in_addr); break; #ifdef INET6 case AF_INET6: p1 = (uint8_t*) &SA2SIN6ADDR(dst); p2 = (uint8_t*) &SA2SIN6ADDR(src); netmask = (uint8_t*) &SA2SIN6ADDR(mask); bytelen = sizeof(struct in6_addr); break; #endif default: return (1); } for (i = 0; i < bytelen; i++) if ((p1[i] & netmask[i]) != (p2[i] & netmask[i])) return (1); return (0); }
/* * Match the sa against the list of addresses we've bound to. If * we've not specifically bound to anything, we match everything. * Otherwise, if the IPv4 or IPv6 address matches one of the addresses * in bound_sa, we return true. If not, we return false. */ int listen_addr(const struct sockaddr *sa) { int i; /* * If nhosts == 0, then there were no -h options on the * command line, so all addresses are addresses we're * listening to. */ if (nhosts == 0) return 1; for (i = 0; i < nhosts; i++) { if (bound_sa[i] == NULL || sa->sa_family != bound_sa[i]->sa_family) continue; switch (sa->sa_family) { case AF_INET: if (memcmp(&SA2SINADDR(sa), &SA2SINADDR(bound_sa[i]), sizeof(struct in_addr)) == 0) return (1); break; #ifdef INET6 case AF_INET6: if (memcmp(&SA2SIN6ADDR(sa), &SA2SIN6ADDR(bound_sa[i]), sizeof(struct in6_addr)) == 0) return (1); break; #endif default: break; } } return (0); }
/* * 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; }
/* * 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; }