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 2024 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 status != IPADM_ADDRCONF_EXISTS) {
107 (void) i_ipadm_delete_addr(iph, addr);
108 return (status);
109 }
110 }
111
112 /* Persist the intfid. */
113 status = i_ipadm_addr_persist(iph, addr, B_FALSE, i_flags, NULL);
114 if (status != IPADM_SUCCESS) {
115 (void) i_ipadm_delete_addr(iph, addr);
116 (void) i_ipadm_send_ndpd_cmd(addr->ipadm_ifname, addr,
117 IPADM_DELETE_ADDRS);
118 }
119
120 return (status);
121 }
122
123 ipadm_status_t
i_ipadm_delete_ipv6addrs(ipadm_handle_t iph,ipadm_addrobj_t ipaddr)124 i_ipadm_delete_ipv6addrs(ipadm_handle_t iph, ipadm_addrobj_t ipaddr)
125 {
126 ipadm_status_t status;
127
128 /*
129 * Send a msg to in.ndpd to remove the autoconfigured addresses,
130 * and delete the link local that was created.
131 */
132 status = i_ipadm_send_ndpd_cmd(ipaddr->ipadm_ifname, ipaddr,
133 IPADM_DELETE_ADDRS);
134
135 /* if the entry is not found, or ndpd is not running, just carry on */
136 if (status == IPADM_NDPD_NOT_RUNNING || status == IPADM_ENXIO ||
137 status == IPADM_NOTFOUND)
138 status = IPADM_SUCCESS;
139
140 if (status == IPADM_SUCCESS)
141 status = i_ipadm_delete_addr(iph, ipaddr);
142
143 return (status);
144 }
145
146 static ipadm_status_t
i_ipadm_create_linklocal(ipadm_handle_t iph,ipadm_addrobj_t addr)147 i_ipadm_create_linklocal(ipadm_handle_t iph, ipadm_addrobj_t addr)
148 {
149 boolean_t addif = B_FALSE;
150 struct sockaddr_in6 *sin6;
151 struct lifreq lifr;
152 int err;
153 ipadm_status_t status;
154 in6_addr_t ll_template = {0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
155 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };
156
157 /*
158 * Create a logical interface if needed.
159 */
160 retry:
161 status = i_ipadm_do_addif(iph, addr, &addif);
162 if (status != IPADM_SUCCESS)
163 return (status);
164 if (!(iph->iph_flags & IPH_INIT)) {
165 status = i_ipadm_setlifnum_addrobj(iph, addr);
166 if (status == IPADM_ADDROBJ_EXISTS)
167 goto retry;
168 if (status != IPADM_SUCCESS)
169 return (status);
170 }
171
172 bzero(&lifr, sizeof (lifr));
173 (void) strlcpy(lifr.lifr_name, addr->ipadm_ifname, LIFNAMSIZ);
174 i_ipadm_addrobj2lifname(addr, lifr.lifr_name, sizeof (lifr.lifr_name));
175 sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr;
176
177 /* Create the link-local address */
178 bzero(&lifr.lifr_addr, sizeof (lifr.lifr_addr));
179 (void) plen2mask(PREFIXLEN_LINKLOCAL, AF_INET6,
180 (struct sockaddr *)&lifr.lifr_addr);
181 if ((err = ioctl(iph->iph_sock6, SIOCSLIFNETMASK, (caddr_t)&lifr)) < 0)
182 goto fail;
183 if (addr->ipadm_intfidlen == 0) {
184 /*
185 * If we have to use the default interface id,
186 * we just need to set the prefix to the link-local prefix.
187 * SIOCSLIFPREFIX sets the address with the given prefix
188 * and the default interface id.
189 */
190 sin6->sin6_addr = ll_template;
191 err = ioctl(iph->iph_sock6, SIOCSLIFPREFIX, (caddr_t)&lifr);
192 if (err < 0)
193 goto fail;
194 } else {
195 /* Make a linklocal address in sin6 and set it */
196 i_ipadm_make_linklocal(sin6, &addr->ipadm_intfid.sin6_addr);
197 err = ioctl(iph->iph_sock6, SIOCSLIFADDR, (caddr_t)&lifr);
198 if (err < 0)
199 goto fail;
200 }
201 if ((err = ioctl(iph->iph_sock6, SIOCGLIFFLAGS, (char *)&lifr)) < 0)
202 goto fail;
203 lifr.lifr_flags |= IFF_UP;
204 if ((err = ioctl(iph->iph_sock6, SIOCSLIFFLAGS, (char *)&lifr)) < 0)
205 goto fail;
206 return (IPADM_SUCCESS);
207
208 fail:
209 if (errno == EEXIST)
210 status = IPADM_ADDRCONF_EXISTS;
211 else
212 status = ipadm_errno2status(errno);
213 /* Remove the linklocal that was created. */
214 if (addif) {
215 (void) ioctl(iph->iph_sock6, SIOCLIFREMOVEIF, (caddr_t)&lifr);
216 } else {
217 struct sockaddr_in6 *sin6;
218
219 sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr;
220 lifr.lifr_flags &= ~IFF_UP;
221 (void) ioctl(iph->iph_sock6, SIOCSLIFFLAGS, (caddr_t)&lifr);
222 sin6->sin6_family = AF_INET6;
223 sin6->sin6_addr = in6addr_any;
224 (void) ioctl(iph->iph_sock6, SIOCSLIFADDR, (caddr_t)&lifr);
225 }
226 return (status);
227 }
228
229 /*
230 * Make a linklocal address based on the given intfid and copy it into
231 * the output parameter `sin6'.
232 */
233 static void
i_ipadm_make_linklocal(struct sockaddr_in6 * sin6,const struct in6_addr * intfid)234 i_ipadm_make_linklocal(struct sockaddr_in6 *sin6, const struct in6_addr *intfid)
235 {
236 int i;
237 in6_addr_t ll_template = {0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
238 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };
239
240 sin6->sin6_family = AF_INET6;
241 sin6->sin6_addr = *intfid;
242 for (i = 0; i < 4; i++) {
243 sin6->sin6_addr.s6_addr[i] =
244 sin6->sin6_addr.s6_addr[i] | ll_template.s6_addr[i];
245 }
246 }
247
248 /*
249 * Function that forms an ndpd msg and sends it to the in.ndpd daemon's loopback
250 * listener socket.
251 */
252 static ipadm_status_t
i_ipadm_send_ndpd_cmd(const char * ifname,const struct ipadm_addrobj_s * addr,int cmd)253 i_ipadm_send_ndpd_cmd(const char *ifname, const struct ipadm_addrobj_s *addr,
254 int cmd)
255 {
256 int fd;
257 struct sockaddr_un servaddr;
258 int flags;
259 ipadm_ndpd_msg_t msg;
260 int retval;
261
262 if (addr == NULL &&
263 (cmd == IPADM_CREATE_ADDRS || cmd == IPADM_DELETE_ADDRS)) {
264 return (IPADM_INVALID_ARG);
265 }
266
267 fd = socket(AF_UNIX, SOCK_STREAM, 0);
268 if (fd == -1)
269 return (IPADM_FAILURE);
270
271 /* Put the socket in non-blocking mode */
272 flags = fcntl(fd, F_GETFL, 0);
273 if (flags != -1)
274 (void) fcntl(fd, F_SETFL, flags | O_NONBLOCK);
275
276 /* Connect to in.ndpd */
277 bzero(&servaddr, sizeof (servaddr));
278 servaddr.sun_family = AF_UNIX;
279 (void) strlcpy(servaddr.sun_path, IPADM_UDS_PATH,
280 sizeof (servaddr.sun_path));
281 if (connect(fd, (struct sockaddr *)&servaddr, sizeof (servaddr)) == -1)
282 goto fail;
283
284 bzero(&msg, sizeof (msg));
285 msg.inm_cmd = cmd;
286 (void) strlcpy(msg.inm_ifname, ifname, sizeof (msg.inm_ifname));
287 if (addr != NULL) {
288 msg.inm_intfid = addr->ipadm_intfid;
289 msg.inm_intfidlen = addr->ipadm_intfidlen;
290 msg.inm_stateless = addr->ipadm_stateless;
291 msg.inm_stateful = addr->ipadm_stateful;
292 if (cmd == IPADM_CREATE_ADDRS) {
293 (void) strlcpy(msg.inm_aobjname, addr->ipadm_aobjname,
294 sizeof (msg.inm_aobjname));
295 }
296 }
297 if (ipadm_ndpd_write(fd, &msg, sizeof (msg)) < 0)
298 goto fail;
299 if (ipadm_ndpd_read(fd, &retval, sizeof (retval)) < 0)
300 goto fail;
301 (void) close(fd);
302 if (cmd == IPADM_CREATE_ADDRS && retval == EEXIST)
303 return (IPADM_ADDRCONF_EXISTS);
304 return (ipadm_errno2status(retval));
305 fail:
306 (void) close(fd);
307 return (IPADM_NDPD_NOT_RUNNING);
308 }
309
310 /*
311 * Attempt to read `buflen' worth of bytes from `fd' into the buffer pointed
312 * to by `buf'.
313 */
314 int
ipadm_ndpd_read(int fd,void * buffer,size_t buflen)315 ipadm_ndpd_read(int fd, void *buffer, size_t buflen)
316 {
317 int retval;
318 ssize_t nbytes = 0; /* total bytes processed */
319 ssize_t prbytes; /* per-round bytes processed */
320 struct pollfd pfd;
321
322 while (nbytes < buflen) {
323
324 pfd.fd = fd;
325 pfd.events = POLLIN;
326
327 /*
328 * Wait for data to come in or for the timeout to fire.
329 */
330 retval = poll(&pfd, 1, NDPDTIMEOUT);
331 if (retval <= 0) {
332 if (retval == 0)
333 errno = ETIME;
334 break;
335 }
336
337 /*
338 * Descriptor is ready; have at it.
339 */
340 prbytes = read(fd, (caddr_t)buffer + nbytes, buflen - nbytes);
341 if (prbytes <= 0) {
342 if (prbytes == -1 && errno == EINTR)
343 continue;
344 break;
345 }
346 nbytes += prbytes;
347 }
348
349 return (nbytes == buflen ? 0 : -1);
350 }
351
352 /*
353 * Write `buflen' bytes from `buffer' to open file `fd'. Returns 0
354 * if all requested bytes were written, or an error code if not.
355 */
356 int
ipadm_ndpd_write(int fd,const void * buffer,size_t buflen)357 ipadm_ndpd_write(int fd, const void *buffer, size_t buflen)
358 {
359 size_t nwritten;
360 ssize_t nbytes;
361 const char *buf = buffer;
362
363 for (nwritten = 0; nwritten < buflen; nwritten += nbytes) {
364 nbytes = write(fd, &buf[nwritten], buflen - nwritten);
365 if (nbytes == -1)
366 return (-1);
367 if (nbytes == 0) {
368 errno = EIO;
369 return (-1);
370 }
371 }
372
373 assert(nwritten == buflen);
374 return (0);
375 }
376