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