ipadm_ndpd.c revision 12016:0248e987199b
1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26/*
27 * This file contains the functions that are required for communicating
28 * with in.ndpd while creating autoconfigured addresses.
29 */
30
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <strings.h>
35#include <errno.h>
36#include <fcntl.h>
37#include <unistd.h>
38#include <sys/sockio.h>
39#include <sys/types.h>
40#include <sys/stat.h>
41#include <sys/socket.h>
42#include <netinet/in.h>
43#include <inet/ip.h>
44#include <arpa/inet.h>
45#include <assert.h>
46#include <poll.h>
47#include <ipadm_ndpd.h>
48#include "libipadm_impl.h"
49
50#define	NDPDTIMEOUT		5000
51#define	PREFIXLEN_LINKLOCAL	10
52
53static ipadm_status_t	i_ipadm_create_linklocal(ipadm_handle_t,
54			    ipadm_addrobj_t);
55static void		i_ipadm_make_linklocal(struct sockaddr_in6 *,
56			    const struct in6_addr *);
57static ipadm_status_t	i_ipadm_send_ndpd_cmd(const char *,
58			    const struct ipadm_addrobj_s *, int);
59
60/*
61 * Sends message to in.ndpd asking not to do autoconf for the given interface,
62 * until IPADM_CREATE_ADDRS or IPADM_ENABLE_AUTOCONF is sent.
63 */
64ipadm_status_t
65i_ipadm_disable_autoconf(const char *ifname)
66{
67	return (i_ipadm_send_ndpd_cmd(ifname, NULL, IPADM_DISABLE_AUTOCONF));
68}
69
70/*
71 * Sends message to in.ndpd to enable autoconf for the given interface,
72 * until another IPADM_DISABLE_AUTOCONF is sent.
73 */
74ipadm_status_t
75i_ipadm_enable_autoconf(const char *ifname)
76{
77	return (i_ipadm_send_ndpd_cmd(ifname, NULL, IPADM_ENABLE_AUTOCONF));
78}
79
80ipadm_status_t
81i_ipadm_create_ipv6addrs(ipadm_handle_t iph, ipadm_addrobj_t addr,
82    uint32_t i_flags)
83{
84	ipadm_status_t status;
85
86	/*
87	 * Create the link local based on the given token. If the same intfid
88	 * was already used with a different address object, this step will
89	 * fail.
90	 */
91	status = i_ipadm_create_linklocal(iph, addr);
92	if (status != IPADM_SUCCESS)
93		return (status);
94
95	/*
96	 * Request in.ndpd to start the autoconfiguration.
97	 * If autoconfiguration was already started by another means (e.g.
98	 * "ifconfig" ), in.ndpd will return EEXIST.
99	 */
100	if (addr->ipadm_stateless || addr->ipadm_stateful) {
101		status = i_ipadm_send_ndpd_cmd(addr->ipadm_ifname, addr,
102		    IPADM_CREATE_ADDRS);
103		if (status != IPADM_SUCCESS &&
104		    status != IPADM_NDPD_NOT_RUNNING) {
105			(void) i_ipadm_delete_addr(iph, addr);
106			return (status);
107		}
108	}
109
110	/* Persist the intfid. */
111	status = i_ipadm_addr_persist(iph, addr, B_FALSE, i_flags);
112	if (status != IPADM_SUCCESS) {
113		(void) i_ipadm_delete_addr(iph, addr);
114		(void) i_ipadm_send_ndpd_cmd(addr->ipadm_ifname, addr,
115		    IPADM_DELETE_ADDRS);
116	}
117
118	return (status);
119}
120
121ipadm_status_t
122i_ipadm_delete_ipv6addrs(ipadm_handle_t iph, ipadm_addrobj_t ipaddr)
123{
124	ipadm_status_t status;
125
126	/*
127	 * Send a msg to in.ndpd to remove the autoconfigured addresses,
128	 * and delete the link local that was created.
129	 */
130	status = i_ipadm_send_ndpd_cmd(ipaddr->ipadm_ifname, ipaddr,
131	    IPADM_DELETE_ADDRS);
132	if (status == IPADM_NDPD_NOT_RUNNING)
133		status = IPADM_SUCCESS;
134	if (status == IPADM_SUCCESS)
135		status = i_ipadm_delete_addr(iph, ipaddr);
136
137	return (status);
138}
139
140static ipadm_status_t
141i_ipadm_create_linklocal(ipadm_handle_t iph, ipadm_addrobj_t addr)
142{
143	boolean_t addif = B_FALSE;
144	struct sockaddr_in6 *sin6;
145	struct lifreq lifr;
146	int err;
147	ipadm_status_t status;
148	in6_addr_t ll_template = {0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
149	    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };
150
151	bzero(&lifr, sizeof (lifr));
152	(void) strlcpy(lifr.lifr_name, addr->ipadm_ifname, LIFNAMSIZ);
153
154	if ((err = ioctl(iph->iph_sock6, SIOCGLIFADDR, (caddr_t)&lifr)) < 0)
155		return (ipadm_errno2status(errno));
156
157	/*
158	 * If no address exists on 0th logical interface,
159	 * create link-local address on it. Else, create a new
160	 * logical interface.
161	 */
162	sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr;
163	if (!IN6_IS_ADDR_UNSPECIFIED((&sin6->sin6_addr))) {
164		if (ioctl(iph->iph_sock6, SIOCLIFADDIF, (caddr_t)&lifr) < 0)
165			return (ipadm_errno2status(errno));
166		addr->ipadm_lifnum = i_ipadm_get_lnum(lifr.lifr_name);
167		addif = B_TRUE;
168	}
169	/* Create the link-local address */
170	bzero(&lifr.lifr_addr, sizeof (lifr.lifr_addr));
171	(void) plen2mask(PREFIXLEN_LINKLOCAL, AF_INET6, &lifr.lifr_addr);
172	if ((err = ioctl(iph->iph_sock6, SIOCSLIFNETMASK, (caddr_t)&lifr)) < 0)
173		goto fail;
174	if (addr->ipadm_intfidlen == 0) {
175		/*
176		 * If we have to use the default interface id,
177		 * we just need to set the prefix to the link-local prefix.
178		 * SIOCSLIFPREFIX sets the address with the given prefix
179		 * and the default interface id.
180		 */
181		sin6->sin6_addr = ll_template;
182		err = ioctl(iph->iph_sock6, SIOCSLIFPREFIX, (caddr_t)&lifr);
183		if (err < 0)
184			goto fail;
185	} else {
186		/* Make a linklocal address in sin6 and set it */
187		i_ipadm_make_linklocal(sin6, &addr->ipadm_intfid.sin6_addr);
188		err = ioctl(iph->iph_sock6, SIOCSLIFADDR, (caddr_t)&lifr);
189		if (err < 0)
190			goto fail;
191	}
192	if ((err = ioctl(iph->iph_sock6, SIOCGLIFFLAGS, (char *)&lifr)) < 0)
193		goto fail;
194	lifr.lifr_flags |= IFF_UP;
195	if ((err = ioctl(iph->iph_sock6, SIOCSLIFFLAGS, (char *)&lifr)) < 0)
196		goto fail;
197	return (IPADM_SUCCESS);
198
199fail:
200	if (errno == EEXIST)
201		status = IPADM_ADDRCONF_EXISTS;
202	else
203		status = ipadm_errno2status(errno);
204	/* Remove the linklocal that was created. */
205	if (addif) {
206		(void) ioctl(iph->iph_sock6, SIOCLIFREMOVEIF, (caddr_t)&lifr);
207	} else {
208		struct sockaddr_in6 *sin6;
209
210		sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr;
211		lifr.lifr_flags &= ~IFF_UP;
212		(void) ioctl(iph->iph_sock6, SIOCSLIFFLAGS, (caddr_t)&lifr);
213		sin6->sin6_family = AF_INET6;
214		sin6->sin6_addr = in6addr_any;
215		(void) ioctl(iph->iph_sock6, SIOCSLIFADDR, (caddr_t)&lifr);
216	}
217	return (status);
218}
219
220/*
221 * Make a linklocal address based on the given intfid and copy it into
222 * the output parameter `sin6'.
223 */
224static void
225i_ipadm_make_linklocal(struct sockaddr_in6 *sin6, const struct in6_addr *intfid)
226{
227	int i;
228	in6_addr_t ll_template = {0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
229	    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };
230
231	sin6->sin6_family = AF_INET6;
232	sin6->sin6_addr = *intfid;
233	for (i = 0; i < 4; i++) {
234		sin6->sin6_addr.s6_addr[i] =
235		    sin6->sin6_addr.s6_addr[i] | ll_template.s6_addr[i];
236	}
237}
238
239/*
240 * Function that forms an ndpd msg and sends it to the in.ndpd daemon's loopback
241 * listener socket.
242 */
243static ipadm_status_t
244i_ipadm_send_ndpd_cmd(const char *ifname, const struct ipadm_addrobj_s *addr,
245    int cmd)
246{
247	int fd;
248	struct sockaddr_un servaddr;
249	int flags;
250	ipadm_ndpd_msg_t msg;
251	int retval;
252
253	if (addr == NULL &&
254	    (cmd == IPADM_CREATE_ADDRS || cmd == IPADM_DELETE_ADDRS)) {
255		return (IPADM_INVALID_ARG);
256	}
257
258	fd = socket(AF_UNIX, SOCK_STREAM, 0);
259	if (fd == -1)
260		return (IPADM_FAILURE);
261
262	/* Put the socket in non-blocking mode */
263	flags = fcntl(fd, F_GETFL, 0);
264	if (flags != -1)
265		(void) fcntl(fd, F_SETFL, flags | O_NONBLOCK);
266
267	/* Connect to in.ndpd */
268	bzero(&servaddr, sizeof (servaddr));
269	servaddr.sun_family = AF_UNIX;
270	(void) strlcpy(servaddr.sun_path, IPADM_UDS_PATH,
271	    sizeof (servaddr.sun_path));
272	if (connect(fd, (struct sockaddr *)&servaddr, sizeof (servaddr)) == -1)
273		goto fail;
274
275	bzero(&msg, sizeof (msg));
276	msg.inm_cmd = cmd;
277	(void) strlcpy(msg.inm_ifname, ifname, sizeof (msg.inm_ifname));
278	if (addr != NULL) {
279		msg.inm_intfid = addr->ipadm_intfid;
280		msg.inm_intfidlen = addr->ipadm_intfidlen;
281		msg.inm_stateless = addr->ipadm_stateless;
282		msg.inm_stateful = addr->ipadm_stateful;
283		if (cmd == IPADM_CREATE_ADDRS) {
284			(void) strlcpy(msg.inm_aobjname, addr->ipadm_aobjname,
285			    sizeof (msg.inm_aobjname));
286		}
287	}
288	if (ipadm_ndpd_write(fd, &msg, sizeof (msg)) < 0)
289		goto fail;
290	if (ipadm_ndpd_read(fd, &retval, sizeof (retval)) < 0)
291		goto fail;
292	(void) close(fd);
293	if (cmd == IPADM_CREATE_ADDRS && retval == EEXIST)
294		return (IPADM_ADDRCONF_EXISTS);
295	return (ipadm_errno2status(retval));
296fail:
297	(void) close(fd);
298	return (IPADM_NDPD_NOT_RUNNING);
299}
300
301/*
302 * Attempt to read `buflen' worth of bytes from `fd' into the buffer pointed
303 * to by `buf'.
304 */
305int
306ipadm_ndpd_read(int fd, void *buffer, size_t buflen)
307{
308	int		retval;
309	ssize_t		nbytes = 0;	/* total bytes processed */
310	ssize_t		prbytes;	/* per-round bytes processed */
311	struct pollfd	pfd;
312
313	while (nbytes < buflen) {
314
315		pfd.fd = fd;
316		pfd.events = POLLIN;
317
318		/*
319		 * Wait for data to come in or for the timeout to fire.
320		 */
321		retval = poll(&pfd, 1, NDPDTIMEOUT);
322		if (retval <= 0) {
323			if (retval == 0)
324				errno = ETIME;
325			break;
326		}
327
328		/*
329		 * Descriptor is ready; have at it.
330		 */
331		prbytes = read(fd, (caddr_t)buffer + nbytes, buflen - nbytes);
332		if (prbytes <= 0) {
333			if (prbytes == -1 && errno == EINTR)
334				continue;
335			break;
336		}
337		nbytes += prbytes;
338	}
339
340	return (nbytes == buflen ? 0 : -1);
341}
342
343/*
344 * Write `buflen' bytes from `buffer' to open file `fd'.  Returns 0
345 * if all requested bytes were written, or an error code if not.
346 */
347int
348ipadm_ndpd_write(int fd, const void *buffer, size_t buflen)
349{
350	size_t		nwritten;
351	ssize_t		nbytes;
352	const char	*buf = buffer;
353
354	for (nwritten = 0; nwritten < buflen; nwritten += nbytes) {
355		nbytes = write(fd, &buf[nwritten], buflen - nwritten);
356		if (nbytes == -1)
357			return (-1);
358		if (nbytes == 0) {
359			errno = EIO;
360			return (-1);
361		}
362	}
363
364	assert(nwritten == buflen);
365	return (0);
366}
367