1112758Ssam/*
2112758Ssam * $NetBSD: util.c,v 1.4 2000/08/03 00:04:30 fvdl Exp $
3112758Ssam * $FreeBSD$
4112758Ssam */
5112758Ssam
6112758Ssam/*-
7112758Ssam * Copyright (c) 2000 The NetBSD Foundation, Inc.
8112758Ssam * All rights reserved.
9112758Ssam *
10112758Ssam * This code is derived from software contributed to The NetBSD Foundation
11112758Ssam * by Frank van der Linden.
12112758Ssam *
13112758Ssam * Redistribution and use in source and binary forms, with or without
14112758Ssam * modification, are permitted provided that the following conditions
15112758Ssam * are met:
16112758Ssam * 1. Redistributions of source code must retain the above copyright
17112758Ssam *    notice, this list of conditions and the following disclaimer.
18112758Ssam * 2. Redistributions in binary form must reproduce the above copyright
19112758Ssam *    notice, this list of conditions and the following disclaimer in the
20112758Ssam *    documentation and/or other materials provided with the distribution.
21112758Ssam *
22112758Ssam * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
23112758Ssam * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
24112758Ssam * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
25112758Ssam * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
26112758Ssam * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27112758Ssam * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28105197Ssam * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29105197Ssam * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30105197Ssam * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31105197Ssam * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32105197Ssam * POSSIBILITY OF SUCH DAMAGE.
33105197Ssam */
34105197Ssam
35105197Ssam#include <sys/types.h>
36105197Ssam#include <sys/socket.h>
37295126Sglebius#include <sys/queue.h>
38105197Ssam#include <net/if.h>
39105197Ssam#include <netinet/in.h>
40105197Ssam#include <ifaddrs.h>
41195699Srwatson#include <sys/poll.h>
42105197Ssam#include <rpc/rpc.h>
43105197Ssam#include <errno.h>
44105197Ssam#include <stdlib.h>
45105197Ssam#include <string.h>
46108989Ssam#include <unistd.h>
47108989Ssam#include <netdb.h>
48105197Ssam#include <netconfig.h>
49105197Ssam#include <stdio.h>
50105197Ssam#include <arpa/inet.h>
51105197Ssam
52105197Ssam#include "rpcbind.h"
53105197Ssam
54105197Ssamstatic struct sockaddr_in *local_in4;
55105197Ssam#ifdef INET6
56105197Ssamstatic struct sockaddr_in6 *local_in6;
57105197Ssam#endif
58105197Ssam
59120585Ssamstatic int bitmaskcmp(struct sockaddr *, struct sockaddr *, struct sockaddr *);
60120585Ssam
61105197Ssam/*
62105197Ssam * For all bits set in "mask", compare the corresponding bits in
63105197Ssam * "dst" and "src", and see if they match. Returns 0 if the addresses
64105197Ssam * match.
65105197Ssam */
66105197Ssamstatic int
67105197Ssambitmaskcmp(struct sockaddr *dst, struct sockaddr *src, struct sockaddr *mask)
68105197Ssam{
69105197Ssam	int i;
70199894Sbz	u_int8_t *p1, *p2, *netmask;
71105197Ssam	int bytelen;
72105197Ssam
73105197Ssam	if (dst->sa_family != src->sa_family ||
74105197Ssam	    dst->sa_family != mask->sa_family)
75105197Ssam		return (1);
76187815Svanhu
77187815Svanhu	switch (dst->sa_family) {
78105197Ssam	case AF_INET:
79187815Svanhu		p1 = (uint8_t*) &SA2SINADDR(dst);
80187815Svanhu		p2 = (uint8_t*) &SA2SINADDR(src);
81187815Svanhu		netmask = (uint8_t*) &SA2SINADDR(mask);
82187815Svanhu		bytelen = sizeof(struct in_addr);
83187815Svanhu		break;
84187815Svanhu#ifdef INET6
85187815Svanhu	case AF_INET6:
86243882Sglebius		p1 = (uint8_t*) &SA2SIN6ADDR(dst);
87187815Svanhu		p2 = (uint8_t*) &SA2SIN6ADDR(src);
88187815Svanhu		netmask = (uint8_t*) &SA2SIN6ADDR(mask);
89187815Svanhu		bytelen = sizeof(struct in6_addr);
90243882Sglebius		break;
91187815Svanhu#endif
92187815Svanhu	default:
93187815Svanhu		return (1);
94187815Svanhu	}
95187815Svanhu
96187815Svanhu	for (i = 0; i < bytelen; i++)
97187815Svanhu		if ((p1[i] & netmask[i]) != (p2[i] & netmask[i]))
98187815Svanhu			return (1);
99187815Svanhu	return (0);
100187815Svanhu}
101187815Svanhu
102187815Svanhu/*
103187815Svanhu * Find a server address that can be used by `caller' to contact
104187815Svanhu * the local service specified by `serv_uaddr'. If `clnt_uaddr' is
105187815Svanhu * non-NULL, it is used instead of `caller' as a hint suggesting
106187815Svanhu * the best address (e.g. the `r_addr' field of an rpc, which
107105197Ssam * contains the rpcbind server address that the caller used).
108105197Ssam *
109105197Ssam * Returns the best server address as a malloc'd "universal address"
110187815Svanhu * string which should be freed by the caller. On error, returns NULL.
111187815Svanhu */
112187815Svanhuchar *
113187815Svanhuaddrmerge(struct netbuf *caller, const char *serv_uaddr, const char *clnt_uaddr,
114187815Svanhu	  const char *netid)
115187815Svanhu{
116243882Sglebius	struct ifaddrs *ifap, *ifp = NULL, *bestif;
117187815Svanhu	struct netbuf *serv_nbp = NULL, *hint_nbp = NULL, tbuf;
118187815Svanhu	struct sockaddr *caller_sa, *hint_sa, *ifsa, *ifmasksa, *serv_sa;
119187815Svanhu	struct sockaddr_storage ss;
120187815Svanhu	struct netconfig *nconf;
121187815Svanhu	char *caller_uaddr = NULL;
122105197Ssam#ifdef ND_DEBUG
123187815Svanhu	const char *hint_uaddr = NULL;
124187815Svanhu#endif
125187815Svanhu	char *ret = NULL;
126187815Svanhu	int bestif_goodness;
127187815Svanhu
128187815Svanhu#ifdef ND_DEBUG
129187815Svanhu	if (debugging)
130187815Svanhu		fprintf(stderr, "addrmerge(caller, %s, %s, %s\n", serv_uaddr,
131187815Svanhu		    clnt_uaddr == NULL ? "NULL" : clnt_uaddr, netid);
132187815Svanhu#endif
133105197Ssam	caller_sa = caller->buf;
134105197Ssam	if ((nconf = rpcbind_get_conf(netid)) == NULL)
135105197Ssam		goto freeit;
136252026Sae	if ((caller_uaddr = taddr2uaddr(nconf, caller)) == NULL)
137105197Ssam		goto freeit;
138105197Ssam
139105197Ssam	/*
140105197Ssam	 * Use `clnt_uaddr' as the hint if non-NULL, but ignore it if its
141105197Ssam	 * address family is different from that of the caller.
142113075Sdes	 */
143113075Sdes	hint_sa = NULL;
144105197Ssam	if (clnt_uaddr != NULL) {
145105197Ssam#ifdef ND_DEBUG
146105197Ssam		hint_uaddr = clnt_uaddr;
147105197Ssam#endif
148105197Ssam		if ((hint_nbp = uaddr2taddr(nconf, clnt_uaddr)) == NULL)
149105197Ssam			goto freeit;
150105197Ssam		hint_sa = hint_nbp->buf;
151105197Ssam	}
152105197Ssam	if (hint_sa == NULL || hint_sa->sa_family != caller_sa->sa_family) {
153105197Ssam#ifdef ND_DEBUG
154105197Ssam		hint_uaddr = caller_uaddr;
155105197Ssam#endif
156105197Ssam		hint_sa = caller->buf;
157105197Ssam	}
158105197Ssam
159105197Ssam#ifdef ND_DEBUG
160105197Ssam	if (debugging)
161105197Ssam		fprintf(stderr, "addrmerge: hint %s\n", hint_uaddr);
162105197Ssam#endif
163105197Ssam	/* Local caller, just return the server address. */
164120585Ssam	if (strncmp(caller_uaddr, "0.0.0.0.", 8) == 0 ||
165105197Ssam	    strncmp(caller_uaddr, "::.", 3) == 0 || caller_uaddr[0] == '/') {
166105197Ssam		ret = strdup(serv_uaddr);
167105197Ssam		goto freeit;
168105197Ssam	}
169105197Ssam
170105197Ssam	if (getifaddrs(&ifp) < 0)
171105197Ssam		goto freeit;
172105197Ssam
173105197Ssam	/*
174105197Ssam	 * Loop through all interface addresses.  We are listening to an address
175105197Ssam	 * if any of the following are true:
176105197Ssam	 * a) It's a loopback address
177105197Ssam	 * b) It was specified with the -h command line option
178105197Ssam	 * c) There were no -h command line options.
179120585Ssam	 *
180120585Ssam	 * Among addresses on which we are listening, choose in order of
181120585Ssam	 * preference an address that is:
182105197Ssam	 *
183105197Ssam	 * a) Equal to the hint
184105197Ssam	 * b) A link local address with the same scope ID as the client's
185105197Ssam	 *    address, if the client's address is also link local
186105197Ssam	 * c) An address on the same subnet as the client's address
187105197Ssam	 * d) A non-localhost, non-p2p address
188105197Ssam	 * e) Any usable address
189105197Ssam	 */
190120585Ssam	bestif = NULL;
191120585Ssam	bestif_goodness = 0;
192120585Ssam	for (ifap = ifp; ifap != NULL; ifap = ifap->ifa_next) {
193120585Ssam		ifsa = ifap->ifa_addr;
194105197Ssam		ifmasksa = ifap->ifa_netmask;
195105197Ssam
196105197Ssam		/* Skip addresses where we don't listen */
197105197Ssam		if (ifsa == NULL || ifsa->sa_family != hint_sa->sa_family ||
198105197Ssam		    !(ifap->ifa_flags & IFF_UP))
199105197Ssam			continue;
200105197Ssam
201105197Ssam		if (!(ifap->ifa_flags & IFF_LOOPBACK) && !listen_addr(ifsa))
202105197Ssam			continue;
203105197Ssam
204243882Sglebius		if ((hint_sa->sa_family == AF_INET) &&
205298075Spfg		    ((((struct sockaddr_in*)hint_sa)->sin_addr.s_addr ==
206105197Ssam		      ((struct sockaddr_in*)ifsa)->sin_addr.s_addr))) {
207120585Ssam			const int goodness = 4;
208105197Ssam
209105197Ssam			bestif_goodness = goodness;
210105197Ssam			bestif = ifap;
211105197Ssam			goto found;
212105197Ssam		}
213105197Ssam#ifdef INET6
214105197Ssam		if ((hint_sa->sa_family == AF_INET6) &&
215105197Ssam		    (0 == memcmp(&((struct sockaddr_in6*)hint_sa)->sin6_addr,
216105197Ssam				 &((struct sockaddr_in6*)ifsa)->sin6_addr,
217105197Ssam				 sizeof(struct in6_addr))) &&
218105197Ssam		    (((struct sockaddr_in6*)hint_sa)->sin6_scope_id ==
219105197Ssam		    (((struct sockaddr_in6*)ifsa)->sin6_scope_id))) {
220105197Ssam			const int goodness = 4;
221105197Ssam
222105197Ssam			bestif_goodness = goodness;
223105197Ssam			bestif = ifap;
224105197Ssam			goto found;
225105197Ssam		}
226105197Ssam		if (hint_sa->sa_family == AF_INET6) {
227105197Ssam			/*
228105197Ssam			 * For v6 link local addresses, if the caller is on
229105197Ssam			 * a link-local address then use the scope id to see
230105197Ssam			 * which one.
231105197Ssam			 */
232105197Ssam			if (IN6_IS_ADDR_LINKLOCAL(&SA2SIN6ADDR(ifsa)) &&
233105197Ssam			    IN6_IS_ADDR_LINKLOCAL(&SA2SIN6ADDR(caller_sa)) &&
234105197Ssam			    IN6_IS_ADDR_LINKLOCAL(&SA2SIN6ADDR(hint_sa))) {
235105197Ssam				if (SA2SIN6(ifsa)->sin6_scope_id ==
236105197Ssam				    SA2SIN6(caller_sa)->sin6_scope_id) {
237105197Ssam					const int goodness = 3;
238105197Ssam
239105197Ssam					if (bestif_goodness < goodness) {
240105197Ssam						bestif = ifap;
241105197Ssam						bestif_goodness = goodness;
242252026Sae					}
243105197Ssam				}
244105197Ssam			}
245105197Ssam		}
246105197Ssam#endif /* INET6 */
247105197Ssam		if (0 == bitmaskcmp(hint_sa, ifsa, ifmasksa)) {
248105197Ssam			const int goodness = 2;
249105197Ssam
250105197Ssam			if (bestif_goodness < goodness) {
251105197Ssam				bestif = ifap;
252105197Ssam				bestif_goodness = goodness;
253105197Ssam			}
254252026Sae		}
255105197Ssam		if (!(ifap->ifa_flags & (IFF_LOOPBACK | IFF_POINTOPOINT))) {
256105197Ssam			const int goodness = 1;
257105197Ssam
258105197Ssam			if (bestif_goodness < goodness) {
259105197Ssam				bestif = ifap;
260105197Ssam				bestif_goodness = goodness;
261105197Ssam			}
262105197Ssam		}
263105197Ssam		if (bestif == NULL)
264105197Ssam			bestif = ifap;
265105197Ssam	}
266105197Ssam	if (bestif == NULL)
267105197Ssam		goto freeit;
268105197Ssam
269105197Ssamfound:
270105197Ssam	/*
271105197Ssam	 * Construct the new address using the address from
272105197Ssam	 * `bestif', and the port number from `serv_uaddr'.
273105197Ssam	 */
274105197Ssam	serv_nbp = uaddr2taddr(nconf, serv_uaddr);
275105197Ssam	if (serv_nbp == NULL)
276105197Ssam		goto freeit;
277105197Ssam	serv_sa = serv_nbp->buf;
278105197Ssam
279252026Sae	memcpy(&ss, bestif->ifa_addr, bestif->ifa_addr->sa_len);
280105197Ssam	switch (ss.ss_family) {
281105197Ssam	case AF_INET:
282105197Ssam		SA2SIN(&ss)->sin_port = SA2SIN(serv_sa)->sin_port;
283105197Ssam		break;
284105197Ssam#ifdef INET6
285105197Ssam	case AF_INET6:
286105197Ssam		SA2SIN6(&ss)->sin6_port = SA2SIN6(serv_sa)->sin6_port;
287105197Ssam		break;
288105197Ssam#endif
289105197Ssam	}
290105197Ssam	tbuf.len = ss.ss_len;
291105197Ssam	tbuf.maxlen = sizeof(ss);
292105197Ssam	tbuf.buf = &ss;
293105197Ssam	ret = taddr2uaddr(nconf, &tbuf);
294105197Ssam
295105197Ssamfreeit:
296105197Ssam	free(caller_uaddr);
297105197Ssam	if (hint_nbp != NULL) {
298105197Ssam		free(hint_nbp->buf);
299105197Ssam		free(hint_nbp);
300105197Ssam	}
301105197Ssam	if (serv_nbp != NULL) {
302105197Ssam		free(serv_nbp->buf);
303105197Ssam		free(serv_nbp);
304105197Ssam	}
305105197Ssam	if (ifp != NULL)
306105197Ssam		freeifaddrs(ifp);
307105197Ssam
308105197Ssam#ifdef ND_DEBUG
309105197Ssam	if (debugging)
310105197Ssam		fprintf(stderr, "addrmerge: returning %s\n", ret);
311105197Ssam#endif
312105197Ssam	return ret;
313105197Ssam}
314105197Ssam
315105197Ssamvoid
316105197Ssamnetwork_init(void)
317105197Ssam{
318105197Ssam#ifdef INET6
319105197Ssam	struct ifaddrs *ifap, *ifp;
320105197Ssam	struct ipv6_mreq mreq6;
321105197Ssam	unsigned int ifindex;
322105197Ssam	int s;
323105197Ssam#endif
324105197Ssam	int ecode;
325105197Ssam	struct addrinfo hints, *res;
326
327	memset(&hints, 0, sizeof hints);
328	hints.ai_family = AF_INET;
329	if ((ecode = getaddrinfo(NULL, "sunrpc", &hints, &res))) {
330		if (debugging)
331			fprintf(stderr, "can't get local ip4 address: %s\n",
332			    gai_strerror(ecode));
333	} else {
334		local_in4 = (struct sockaddr_in *)malloc(sizeof *local_in4);
335		if (local_in4 == NULL) {
336			if (debugging)
337				fprintf(stderr, "can't alloc local ip4 addr\n");
338			exit(1);
339		}
340		memcpy(local_in4, res->ai_addr, sizeof *local_in4);
341		freeaddrinfo(res);
342	}
343
344#ifdef INET6
345	hints.ai_family = AF_INET6;
346	if ((ecode = getaddrinfo(NULL, "sunrpc", &hints, &res))) {
347		if (debugging)
348			fprintf(stderr, "can't get local ip6 address: %s\n",
349			    gai_strerror(ecode));
350	} else {
351		local_in6 = (struct sockaddr_in6 *)malloc(sizeof *local_in6);
352		if (local_in6 == NULL) {
353			if (debugging)
354				fprintf(stderr, "can't alloc local ip6 addr\n");
355			exit(1);
356		}
357		memcpy(local_in6, res->ai_addr, sizeof *local_in6);
358		freeaddrinfo(res);
359	}
360
361	/*
362	 * Now join the RPC ipv6 multicast group on all interfaces.
363	 */
364	if (getifaddrs(&ifp) < 0)
365		return;
366
367	mreq6.ipv6mr_interface = 0;
368	inet_pton(AF_INET6, RPCB_MULTICAST_ADDR, &mreq6.ipv6mr_multiaddr);
369
370	s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
371	if (s == -1) {
372		if (debugging)
373			fprintf(stderr, "couldn't create ip6 socket");
374		goto done_inet6;
375	}
376
377	/*
378	 * Loop through all interfaces. For each IPv6 multicast-capable
379	 * interface, join the RPC multicast group on that interface.
380	 */
381	for (ifap = ifp; ifap != NULL; ifap = ifap->ifa_next) {
382		if (ifap->ifa_addr->sa_family != AF_INET6 ||
383		    !(ifap->ifa_flags & IFF_MULTICAST))
384			continue;
385		ifindex = if_nametoindex(ifap->ifa_name);
386		if (ifindex == mreq6.ipv6mr_interface)
387			/*
388			 * Already did this one.
389			 */
390			continue;
391		mreq6.ipv6mr_interface = ifindex;
392		if (setsockopt(s, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq6,
393		    sizeof mreq6) < 0)
394			if (debugging)
395				perror("setsockopt v6 multicast");
396	}
397done_inet6:
398	freeifaddrs(ifp);
399#endif
400
401	/* close(s); */
402}
403
404struct sockaddr *
405local_sa(int af)
406{
407	switch (af) {
408	case AF_INET:
409		return (struct sockaddr *)local_in4;
410#ifdef INET6
411	case AF_INET6:
412		return (struct sockaddr *)local_in6;
413#endif
414	default:
415		return NULL;
416	}
417}
418