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