Clean up the addrmerge() function, which was over-complicated and

contained a number of memory leaks. The changes include:

 - Add a comment describing what addrmerge() does.
 - Deal with 0.0.0.0./::. or AF_LOCAL callers correctly.
 - Use rpcbind_get_conf() instead of getnetconfigent() so we don't
   have to remember to free the returned netconfig struct.
 - Make just one pass through the ifaddrs list; we can pick up a fallback
   interface address in the same pass as the netmask comparison.
 - Define and use SA2SIN* macros to avoid the need for loads of
   protocol-specific local variables.
 - Use mostly protocol-independent code for building the netbuf version
   of the address to be returned.
 - Use the common cleanup code for virtually all error and non-error
   cases, fixing a number of memory leaks.
This commit is contained in:
Ian Dowse 2001-06-24 15:03:06 +00:00
parent 610be6ce41
commit 99d4ecdfc0
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=78705

View File

@ -58,6 +58,13 @@
#include "rpcbind.h"
#define SA2SIN(sa) ((struct sockaddr_in *)(sa))
#define SA2SINADDR(sa) (SA2SIN(sa)->sin_addr)
#ifdef INET6
#define SA2SIN6(sa) ((struct sockaddr_in6 *)(sa))
#define SA2SIN6ADDR(sa) (SA2SIN6(sa)->sin6_addr)
#endif
static struct sockaddr_in *local_in4;
#ifdef INET6
static struct sockaddr_in6 *local_in6;
@ -107,198 +114,175 @@ in6_fillscopeid(struct sockaddr_in6 *sin6)
}
#endif
/*
* 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, 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 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;
struct sockaddr *clnt = caller->buf;
char *caller_uaddr = NULL, *hint_uaddr = NULL;
char *ret = NULL;
#ifdef ND_DEBUG
if (debugging)
fprintf(stderr, "addrmerge(caller, %s, %s, %s\n", serv_uaddr,
clnt_uaddr, netid);
clnt_uaddr == NULL ? "NULL" : clnt_uaddr, netid);
#endif
nconf = getnetconfigent(netid);
if (nconf == NULL)
return NULL;
caller_sa = caller->buf;
if ((nconf = rpcbind_get_conf(netid)) == NULL)
goto freeit;
if ((caller_uaddr = taddr2uaddr(nconf, caller)) == NULL)
goto freeit;
/*
* Local merge, just return a duplicate.
* Use `clnt_uaddr' as the hint if non-NULL, but ignore it if its
* address family is different from that of the caller.
*/
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;
hint_sa = NULL;
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(clnt_uaddr);
}
} else {
clnt_sa = (struct sockaddr *)
malloc(sizeof (struct sockaddr_storage));
memcpy(clnt_sa, clnt, clnt->sa_len);
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;
}
if (getifaddrs(&ifp) < 0) {
free(serv_nbp);
free(clnt_sa);
if (clnt_nbp != NULL)
free(clnt_nbp);
return 0;
#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) {
if (ifap->ifa_addr->sa_family != clnt->sa_family ||
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 (clnt->sa_family) {
switch (hint_sa->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
* If the hint address matches this interface
* address/netmask, then we're done.
*/
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))) {
if (!bitmaskcmp(&SA2SINADDR(ifsa),
&SA2SINADDR(hint_sa), &SA2SINADDR(ifmasksa),
sizeof(struct in_addr))) {
bestif = ifap;
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.
* For v6 link local addresses, if the caller is on
* a link-local address then 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;
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;
}
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))
/*
* 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;
}
ifap = bestif;
if (bestif == NULL)
goto freeit;
found:
switch (clnt->sa_family) {
/*
* Construct the new address using the 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:
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;
SA2SIN(&ss)->sin_port = SA2SIN(serv_sa)->sin_port;
break;
#ifdef INET6
case AF_INET6:
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;
SA2SIN6(&ss)->sin6_port = SA2SIN6(serv_sa)->sin6_port;
break;
#endif
default:
goto freeit;
}
if (ifap != NULL)
ret = taddr2uaddr(nconf, &tbuf);
tbuf.len = ss.ss_len;
tbuf.maxlen = sizeof(ss);
tbuf.buf = &ss;
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);
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)