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 
  52 static ipadm_status_t   i_ipadm_create_linklocal(ipadm_handle_t,
  53                             ipadm_addrobj_t);
  54 static void             i_ipadm_make_linklocal(struct sockaddr_in6 *,
  55                             const struct in6_addr *);
  56 static 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  */
  63 ipadm_status_t
  64 i_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  */
  73 ipadm_status_t
  74 i_ipadm_enable_autoconf(const char *ifname)
  75 {
  76         return (i_ipadm_send_ndpd_cmd(ifname, NULL, IPADM_ENABLE_AUTOCONF));
  77 }
  78 
  79 ipadm_status_t
  80 i_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, NULL);
 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 
 120 ipadm_status_t
 121 i_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 
 139 static ipadm_status_t
 140 i_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          */
 153 retry:
 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 
 201 fail:
 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  */
 226 static void
 227 i_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  */
 245 static ipadm_status_t
 246 i_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));
 298 fail:
 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  */
 307 int
 308 ipadm_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  */
 349 int
 350 ipadm_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 }