| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| * or http://www.opensolaris.org/os/licensing. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * |
| * When distributing Covered Code, include this CDDL HEADER in each |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| * If applicable, add the following below this CDDL HEADER, with the |
| * fields enclosed by brackets "[]" replaced with your own identifying |
| * information: Portions Copyright [yyyy] [name of copyright owner] |
| * |
| * CDDL HEADER END |
| */ |
| /* |
| * Copyright 2009 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <net/if.h> |
| #include <stdlib.h> |
| #include <sys/sockio.h> |
| #include <netinet/in.h> |
| #include <netinet/dhcp.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <search.h> |
| #include <libdevinfo.h> |
| #include <libdlpi.h> |
| #include <netinet/if_ether.h> |
| #include <arpa/inet.h> |
| #include <dhcpmsg.h> |
| #include <dhcp_inittab.h> |
| |
| #include "agent.h" |
| #include "interface.h" |
| #include "util.h" |
| #include "packet.h" |
| #include "states.h" |
| |
| dhcp_pif_t *v4root; |
| dhcp_pif_t *v6root; |
| |
| static uint_t cached_v4_max_mtu, cached_v6_max_mtu; |
| |
| /* |
| * Interface flags to watch: things that should be under our direct control. |
| */ |
| #define DHCP_IFF_WATCH (IFF_DHCPRUNNING | IFF_DEPRECATED | IFF_ADDRCONF | \ |
| IFF_TEMPORARY) |
| |
| static void clear_lif_dhcp(dhcp_lif_t *); |
| |
| /* |
| * insert_pif(): creates a new physical interface structure and chains it on |
| * the list. Initializes state that remains consistent across |
| * all use of the physical interface entry. |
| * |
| * input: const char *: the name of the physical interface |
| * boolean_t: if B_TRUE, this is DHCPv6 |
| * int *: ignored on input; if insert_pif fails, set to a DHCP_IPC_E_* |
| * error code with the reason why |
| * output: dhcp_pif_t *: a pointer to the new entry, or NULL on failure |
| */ |
| |
| dhcp_pif_t * |
| insert_pif(const char *pname, boolean_t isv6, int *error) |
| { |
| dhcp_pif_t *pif; |
| struct lifreq lifr; |
| lifgroupinfo_t lifgr; |
| dlpi_handle_t dh = NULL; |
| int fd = isv6 ? v6_sock_fd : v4_sock_fd; |
| |
| if ((pif = calloc(1, sizeof (*pif))) == NULL) { |
| dhcpmsg(MSG_ERR, "insert_pif: cannot allocate pif entry for " |
| "%s", pname); |
| *error = DHCP_IPC_E_MEMORY; |
| return (NULL); |
| } |
| |
| pif->pif_isv6 = isv6; |
| pif->pif_hold_count = 1; |
| pif->pif_running = B_TRUE; |
| |
| if (strlcpy(pif->pif_name, pname, LIFNAMSIZ) >= LIFNAMSIZ) { |
| dhcpmsg(MSG_ERROR, "insert_pif: interface name %s is too long", |
| pname); |
| *error = DHCP_IPC_E_INVIF; |
| goto failure; |
| } |
| |
| /* |
| * This is a bit gross, but IP has a confused interface. We must |
| * assume that the zeroth LIF is plumbed, and must query there to get |
| * the interface index number. |
| */ |
| (void) strlcpy(lifr.lifr_name, pname, LIFNAMSIZ); |
| |
| if (ioctl(fd, SIOCGLIFINDEX, &lifr) == -1) { |
| *error = (errno == ENXIO) ? DHCP_IPC_E_INVIF : DHCP_IPC_E_INT; |
| dhcpmsg(MSG_ERR, "insert_pif: SIOCGLIFINDEX for %s", pname); |
| goto failure; |
| } |
| pif->pif_index = lifr.lifr_index; |
| |
| if (ioctl(fd, SIOCGLIFMTU, &lifr) == -1) { |
| *error = (errno == ENXIO) ? DHCP_IPC_E_INVIF : DHCP_IPC_E_INT; |
| dhcpmsg(MSG_ERR, "insert_pif: SIOCGLIFMTU for %s", pname); |
| goto failure; |
| } |
| pif->pif_max = lifr.lifr_mtu; |
| |
| if (pif->pif_max < DHCP_DEF_MAX_SIZE) { |
| dhcpmsg(MSG_ERROR, "insert_pif: MTU of %s is too small to " |
| "support DHCP (%u < %u)", pname, pif->pif_max, |
| DHCP_DEF_MAX_SIZE); |
| *error = DHCP_IPC_E_INVIF; |
| goto failure; |
| } |
| |
| /* |
| * Check if the pif is in an IPMP group. Interfaces using IPMP don't |
| * have dedicated hardware addresses, and get their hardware type from |
| * the SIOCGLIFGROUPINFO ioctl rather than DLPI. |
| */ |
| if (ioctl(fd, SIOCGLIFGROUPNAME, &lifr) == -1) { |
| *error = DHCP_IPC_E_INT; |
| dhcpmsg(MSG_ERR, "insert_pif: SIOCGLIFGROUPNAME for %s", pname); |
| goto failure; |
| } |
| |
| if (lifr.lifr_groupname[0] != '\0') { |
| (void) strlcpy(lifgr.gi_grname, lifr.lifr_groupname, |
| LIFGRNAMSIZ); |
| if (ioctl(fd, SIOCGLIFGROUPINFO, &lifgr) == -1) { |
| *error = DHCP_IPC_E_INT; |
| dhcpmsg(MSG_ERR, "insert_pif: SIOCGLIFGROUPINFO for %s", |
| lifgr.gi_grname); |
| goto failure; |
| } |
| |
| pif->pif_hwtype = dlpi_arptype(lifgr.gi_mactype); |
| pif->pif_under_ipmp = (strcmp(pname, lifgr.gi_grifname) != 0); |
| (void) strlcpy(pif->pif_grifname, lifgr.gi_grifname, LIFNAMSIZ); |
| |
| /* |
| * For IPMP underlying interfaces, stash the interface index |
| * of the IPMP meta-interface; we'll use it to send/receive |
| * traffic. This is both necessary (since IP_BOUND_IF for |
| * non-unicast traffic won't work on underlying interfaces) |
| * and preferred (since a test address lease will be able to |
| * be maintained as long as another interface in the group is |
| * still functioning). |
| */ |
| if (pif->pif_under_ipmp) { |
| (void) strlcpy(lifr.lifr_name, pif->pif_grifname, |
| LIFNAMSIZ); |
| |
| if (ioctl(fd, SIOCGLIFINDEX, &lifr) == -1) { |
| *error = DHCP_IPC_E_INT; |
| dhcpmsg(MSG_ERR, "insert_pif: SIOCGLIFINDEX " |
| "for %s", lifr.lifr_name); |
| goto failure; |
| } |
| pif->pif_grindex = lifr.lifr_index; |
| } |
| } |
| |
| /* |
| * For IPv4, if the hardware type is still unknown, use DLPI to |
| * determine it, the hardware address, and hardware address length. |
| */ |
| if (!isv6 && pif->pif_hwtype == 0) { |
| int rc; |
| dlpi_info_t dlinfo; |
| |
| if ((rc = dlpi_open(pname, &dh, 0)) != DLPI_SUCCESS) { |
| dhcpmsg(MSG_ERROR, "insert_pif: dlpi_open: %s", |
| dlpi_strerror(rc)); |
| *error = DHCP_IPC_E_INVIF; |
| goto failure; |
| } |
| |
| if ((rc = dlpi_bind(dh, ETHERTYPE_IP, NULL)) != DLPI_SUCCESS) { |
| dhcpmsg(MSG_ERROR, "insert_pif: dlpi_bind: %s", |
| dlpi_strerror(rc)); |
| *error = DHCP_IPC_E_INVIF; |
| goto failure; |
| } |
| |
| if ((rc = dlpi_info(dh, &dlinfo, 0)) != DLPI_SUCCESS) { |
| dhcpmsg(MSG_ERROR, "insert_pif: dlpi_info: %s", |
| dlpi_strerror(rc)); |
| *error = DHCP_IPC_E_INVIF; |
| goto failure; |
| } |
| |
| pif->pif_hwtype = dlpi_arptype(dlinfo.di_mactype); |
| pif->pif_hwlen = dlinfo.di_physaddrlen; |
| |
| dhcpmsg(MSG_DEBUG, "insert_pif: %s: hwtype %d, hwlen %d", |
| pname, pif->pif_hwtype, pif->pif_hwlen); |
| |
| if (pif->pif_hwlen > 0) { |
| pif->pif_hwaddr = malloc(pif->pif_hwlen); |
| if (pif->pif_hwaddr == NULL) { |
| dhcpmsg(MSG_ERR, "insert_pif: cannot allocate " |
| "pif_hwaddr for %s", pname); |
| *error = DHCP_IPC_E_MEMORY; |
| goto failure; |
| } |
| (void) memcpy(pif->pif_hwaddr, dlinfo.di_physaddr, |
| pif->pif_hwlen); |
| } |
| |
| dlpi_close(dh); |
| dh = NULL; |
| } |
| |
| insque(pif, isv6 ? &v6root : &v4root); |
| |
| return (pif); |
| failure: |
| if (dh != NULL) |
| dlpi_close(dh); |
| release_pif(pif); |
| return (NULL); |
| } |
| |
| /* |
| * hold_pif(): acquire a hold on a physical interface structure. |
| * |
| * input: dhcp_pif_t *: a pointer to the PIF structure |
| * output: none |
| */ |
| |
| void |
| hold_pif(dhcp_pif_t *pif) |
| { |
| pif->pif_hold_count++; |
| dhcpmsg(MSG_DEBUG2, "hold_pif: hold count on %s: %u", pif->pif_name, |
| pif->pif_hold_count); |
| } |
| |
| /* |
| * release_pif(): release a hold on a physical interface structure; will |
| * destroy the structure on the last hold removed. |
| * |
| * input: dhcp_pif_t *: a pointer to the PIF structure |
| * output: none |
| */ |
| |
| void |
| release_pif(dhcp_pif_t *pif) |
| { |
| if (pif->pif_hold_count == 0) { |
| dhcpmsg(MSG_CRIT, "release_pif: extraneous release"); |
| return; |
| } |
| |
| if (--pif->pif_hold_count == 0) { |
| dhcpmsg(MSG_DEBUG, "release_pif: freeing PIF %s", |
| pif->pif_name); |
| |
| remque(pif); |
| free(pif->pif_hwaddr); |
| free(pif); |
| } else { |
| dhcpmsg(MSG_DEBUG2, "release_pif: hold count on %s: %u", |
| pif->pif_name, pif->pif_hold_count); |
| } |
| } |
| |
| /* |
| * lookup_pif_by_uindex(): Looks up PIF entries given truncated index and |
| * previous PIF pointer (or NULL for list start). |
| * Caller is expected to iterate through all |
| * potential matches to find interface of interest. |
| * |
| * input: uint16_t: the interface index (truncated) |
| * dhcp_pif_t *: the previous PIF, or NULL for list start |
| * boolean_t: B_TRUE if using DHCPv6, B_FALSE otherwise |
| * output: dhcp_pif_t *: the next matching PIF, or NULL if not found |
| * note: This operates using the 'truncated' (16-bit) ifindex as seen by |
| * routing socket clients. The value stored in pif_index is the |
| * 32-bit ifindex from the ioctl interface. |
| */ |
| |
| dhcp_pif_t * |
| lookup_pif_by_uindex(uint16_t ifindex, dhcp_pif_t *pif, boolean_t isv6) |
| { |
| if (pif == NULL) |
| pif = isv6 ? v6root : v4root; |
| else |
| pif = pif->pif_next; |
| |
| for (; pif != NULL; pif = pif->pif_next) { |
| if ((pif->pif_index & 0xffff) == ifindex) |
| break; |
| } |
| |
| return (pif); |
| } |
| |
| /* |
| * lookup_pif_by_name(): Looks up a physical interface entry given a name. |
| * |
| * input: const char *: the physical interface name |
| * boolean_t: B_TRUE if using DHCPv6, B_FALSE otherwise |
| * output: dhcp_pif_t *: the matching PIF, or NULL if not found |
| */ |
| |
| dhcp_pif_t * |
| lookup_pif_by_name(const char *pname, boolean_t isv6) |
| { |
| dhcp_pif_t *pif; |
| |
| pif = isv6 ? v6root : v4root; |
| |
| for (; pif != NULL; pif = pif->pif_next) { |
| if (strcmp(pif->pif_name, pname) == 0) |
| break; |
| } |
| |
| return (pif); |
| } |
| |
| /* |
| * pif_status(): update the physical interface up/down status. |
| * |
| * input: dhcp_pif_t *: the physical interface to be updated |
| * boolean_t: B_TRUE if the interface is going up |
| * output: none |
| */ |
| |
| void |
| pif_status(dhcp_pif_t *pif, boolean_t isup) |
| { |
| dhcp_lif_t *lif; |
| dhcp_smach_t *dsmp; |
| |
| pif->pif_running = isup; |
| dhcpmsg(MSG_DEBUG, "interface %s has %s", pif->pif_name, |
| isup ? "come back up" : "gone down"); |
| for (lif = pif->pif_lifs; lif != NULL; lif = lif->lif_next) { |
| for (dsmp = lif->lif_smachs; dsmp != NULL; |
| dsmp = dsmp->dsm_next) { |
| if (isup) |
| refresh_smach(dsmp); |
| else |
| remove_default_routes(dsmp); |
| } |
| } |
| } |
| |
| /* Helper for insert_lif: extract addresses as defined */ |
| #define ASSIGN_ADDR(v4, v6, lf) \ |
| if (pif->pif_isv6) { \ |
| lif->v6 = ((struct sockaddr_in6 *)&lifr.lf)->sin6_addr; \ |
| } else { \ |
| lif->v4 = ((struct sockaddr_in *)&lifr.lf)->sin_addr.s_addr; \ |
| } |
| |
| /* |
| * insert_lif(): Creates a new logical interface structure and chains it on |
| * the list for a given physical interface. Initializes state |
| * that remains consistent across all use of the logical |
| * interface entry. Caller's PIF hold is transferred to the |
| * LIF on success, and is dropped on failure. |
| * |
| * input: dhcp_pif_t *: pointer to the physical interface for this LIF |
| * const char *: the name of the logical interface |
| * int *: ignored on input; if insert_pif fails, set to a DHCP_IPC_E_* |
| * error code with the reason why |
| * output: dhcp_lif_t *: a pointer to the new entry, or NULL on failure |
| */ |
| |
| dhcp_lif_t * |
| insert_lif(dhcp_pif_t *pif, const char *lname, int *error) |
| { |
| dhcp_lif_t *lif; |
| int fd; |
| struct lifreq lifr; |
| |
| if ((lif = calloc(1, sizeof (*lif))) == NULL) { |
| dhcpmsg(MSG_ERR, "insert_lif: cannot allocate lif entry for " |
| "%s", lname); |
| *error = DHCP_IPC_E_MEMORY; |
| return (NULL); |
| } |
| |
| lif->lif_sock_ip_fd = -1; |
| lif->lif_packet_id = -1; |
| lif->lif_iaid_id = -1; |
| lif->lif_hold_count = 1; |
| lif->lif_pif = pif; |
| lif->lif_removed = B_TRUE; |
| init_timer(&lif->lif_preferred, 0); |
| init_timer(&lif->lif_expire, 0); |
| |
| if (strlcpy(lif->lif_name, lname, LIFNAMSIZ) >= LIFNAMSIZ) { |
| dhcpmsg(MSG_ERROR, "insert_lif: interface name %s is too long", |
| lname); |
| *error = DHCP_IPC_E_INVIF; |
| goto failure; |
| } |
| |
| (void) strlcpy(lifr.lifr_name, lname, LIFNAMSIZ); |
| |
| fd = pif->pif_isv6 ? v6_sock_fd : v4_sock_fd; |
| |
| if (ioctl(fd, SIOCGLIFMTU, &lifr) == -1) |
| lif->lif_max = 1024; |
| else |
| lif->lif_max = lifr.lifr_mtu; |
| |
| if (ioctl(fd, SIOCGLIFADDR, &lifr) == -1) { |
| if (errno == ENXIO) |
| *error = DHCP_IPC_E_INVIF; |
| else |
| *error = DHCP_IPC_E_INT; |
| dhcpmsg(MSG_ERR, "insert_lif: SIOCGLIFADDR for %s", lname); |
| goto failure; |
| } |
| ASSIGN_ADDR(lif_addr, lif_v6addr, lifr_addr); |
| |
| if (ioctl(fd, SIOCGLIFNETMASK, &lifr) == -1) { |
| if (errno == ENXIO) |
| *error = DHCP_IPC_E_INVIF; |
| else |
| *error = DHCP_IPC_E_INT; |
| dhcpmsg(MSG_ERR, "insert_lif: SIOCGLIFNETMASK for %s", lname); |
| goto failure; |
| } |
| ASSIGN_ADDR(lif_netmask, lif_v6mask, lifr_addr); |
| |
| if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) { |
| *error = DHCP_IPC_E_INT; |
| dhcpmsg(MSG_ERR, "insert_lif: SIOCGLIFFLAGS for %s", lname); |
| goto failure; |
| } |
| lif->lif_flags = lifr.lifr_flags; |
| |
| /* |
| * If we've just detected the interface going up or down, then signal |
| * an appropriate action. There may be other state machines here. |
| */ |
| if ((lifr.lifr_flags & IFF_RUNNING) && !pif->pif_running) { |
| pif_status(pif, B_TRUE); |
| } else if (!(lifr.lifr_flags & IFF_RUNNING) && pif->pif_running) { |
| pif_status(pif, B_FALSE); |
| } |
| |
| if (lifr.lifr_flags & IFF_POINTOPOINT) { |
| if (ioctl(fd, SIOCGLIFDSTADDR, &lifr) == -1) { |
| *error = DHCP_IPC_E_INT; |
| dhcpmsg(MSG_ERR, "insert_lif: SIOCGLIFDSTADDR for %s", |
| lname); |
| goto failure; |
| } |
| ASSIGN_ADDR(lif_peer, lif_v6peer, lifr_dstaddr); |
| } else if (!pif->pif_isv6 && (lifr.lifr_flags & IFF_BROADCAST)) { |
| if (ioctl(fd, SIOCGLIFBRDADDR, &lifr) == -1) { |
| *error = DHCP_IPC_E_INT; |
| dhcpmsg(MSG_ERR, "insert_lif: SIOCGLIFBRDADDR for %s", |
| lname); |
| goto failure; |
| } |
| lif->lif_broadcast = |
| ((struct sockaddr_in *)&lifr.lifr_broadaddr)->sin_addr. |
| s_addr; |
| } |
| |
| if (pif->pif_isv6) |
| cached_v6_max_mtu = 0; |
| else |
| cached_v4_max_mtu = 0; |
| |
| lif->lif_removed = B_FALSE; |
| insque(lif, &pif->pif_lifs); |
| |
| return (lif); |
| |
| failure: |
| release_lif(lif); |
| return (NULL); |
| } |
| |
| /* |
| * hold_lif(): acquire a hold on a logical interface structure. |
| * |
| * input: dhcp_lif_t *: a pointer to the LIF structure |
| * output: none |
| */ |
| |
| void |
| hold_lif(dhcp_lif_t *lif) |
| { |
| lif->lif_hold_count++; |
| dhcpmsg(MSG_DEBUG2, "hold_lif: hold count on %s: %u", lif->lif_name, |
| lif->lif_hold_count); |
| } |
| |
| /* |
| * release_lif(): release a hold on a logical interface structure; will |
| * destroy the structure on the last hold removed. |
| * |
| * input: dhcp_lif_t *: a pointer to the LIF structure |
| * output: none |
| */ |
| |
| void |
| release_lif(dhcp_lif_t *lif) |
| { |
| if (lif->lif_hold_count == 0) { |
| dhcpmsg(MSG_CRIT, "release_lif: extraneous release on %s", |
| lif->lif_name); |
| return; |
| } |
| |
| if (lif->lif_hold_count == 1 && !lif->lif_removed) { |
| unplumb_lif(lif); |
| return; |
| } |
| |
| if (--lif->lif_hold_count == 0) { |
| dhcp_pif_t *pif; |
| |
| dhcpmsg(MSG_DEBUG, "release_lif: freeing LIF %s", |
| lif->lif_name); |
| |
| if (lif->lif_lease != NULL) |
| dhcpmsg(MSG_CRIT, |
| "release_lif: still holding lease at last hold!"); |
| close_ip_lif(lif); |
| pif = lif->lif_pif; |
| if (pif->pif_isv6) |
| cached_v6_max_mtu = 0; |
| else |
| cached_v4_max_mtu = 0; |
| release_pif(pif); |
| free(lif); |
| } else { |
| dhcpmsg(MSG_DEBUG2, "release_lif: hold count on %s: %u", |
| lif->lif_name, lif->lif_hold_count); |
| } |
| } |
| |
| /* |
| * remove_lif(): remove a logical interface from its PIF and lease (if any) and |
| * the lease's hold on the LIF. Assumes that we did not plumb |
| * the interface. |
| * |
| * input: dhcp_lif_t *: a pointer to the LIF structure |
| * output: none |
| */ |
| |
| void |
| remove_lif(dhcp_lif_t *lif) |
| { |
| if (lif->lif_plumbed) { |
| dhcpmsg(MSG_CRIT, "remove_lif: attempted invalid removal of %s", |
| lif->lif_name); |
| return; |
| } |
| if (lif->lif_removed) { |
| dhcpmsg(MSG_CRIT, "remove_lif: extraneous removal of %s", |
| lif->lif_name); |
| } else { |
| dhcp_lif_t *lifnext; |
| dhcp_lease_t *dlp; |
| |
| dhcpmsg(MSG_DEBUG2, "remove_lif: removing %s", lif->lif_name); |
| lif->lif_removed = B_TRUE; |
| lifnext = lif->lif_next; |
| clear_lif_dhcp(lif); |
| cancel_lif_timers(lif); |
| if (lif->lif_iaid_id != -1 && |
| iu_cancel_timer(tq, lif->lif_iaid_id, NULL) == 1) { |
| lif->lif_iaid_id = -1; |
| release_lif(lif); |
| } |
| |
| /* Remove from PIF list */ |
| remque(lif); |
| |
| /* If we were part of a lease, then remove ourselves */ |
| if ((dlp = lif->lif_lease) != NULL) { |
| if (--dlp->dl_nlifs == 0) |
| dlp->dl_lifs = NULL; |
| else if (dlp->dl_lifs == lif) |
| dlp->dl_lifs = lifnext; |
| if (lif->lif_declined != NULL) { |
| dlp->dl_smach->dsm_lif_down--; |
| lif->lif_declined = NULL; |
| } |
| lif->lif_lease = NULL; |
| release_lif(lif); |
| } |
| } |
| } |
| |
| /* |
| * lookup_lif_by_name(): Looks up a logical interface entry given a name and |
| * a physical interface. |
| * |
| * input: const char *: the logical interface name |
| * const dhcp_pif_t *: the physical interface |
| * output: dhcp_lif_t *: the matching LIF, or NULL if not found |
| */ |
| |
| dhcp_lif_t * |
| lookup_lif_by_name(const char *lname, const dhcp_pif_t *pif) |
| { |
| dhcp_lif_t *lif; |
| |
| for (lif = pif->pif_lifs; lif != NULL; lif = lif->lif_next) { |
| if (strcmp(lif->lif_name, lname) == 0) |
| break; |
| } |
| |
| return (lif); |
| } |
| |
| /* |
| * checkaddr(): checks if the given address is still set on the given LIF |
| * |
| * input: const dhcp_lif_t *: the LIF to check |
| * int: the address to look up on the interface (ioctl) |
| * const in6_addr_t *: the address to compare to |
| * const char *: name of the address for logging purposes |
| * output: boolean_t: B_TRUE if the address is still set; B_FALSE if not |
| */ |
| |
| static boolean_t |
| checkaddr(const dhcp_lif_t *lif, int ioccmd, const in6_addr_t *addr, |
| const char *aname) |
| { |
| boolean_t isv6; |
| int fd; |
| struct lifreq lifr; |
| char abuf1[INET6_ADDRSTRLEN]; |
| char abuf2[INET6_ADDRSTRLEN]; |
| |
| (void) memset(&lifr, 0, sizeof (struct lifreq)); |
| (void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ); |
| |
| isv6 = lif->lif_pif->pif_isv6; |
| fd = isv6 ? v6_sock_fd : v4_sock_fd; |
| |
| if (ioctl(fd, ioccmd, &lifr) == -1) { |
| if (errno == ENXIO) { |
| dhcpmsg(MSG_WARNING, "checkaddr: interface %s is gone", |
| lif->lif_name); |
| return (B_FALSE); |
| } |
| dhcpmsg(MSG_DEBUG, |
| "checkaddr: ignoring ioctl error on %s %x: %s", |
| lif->lif_name, ioccmd, strerror(errno)); |
| } else if (isv6) { |
| struct sockaddr_in6 *sin6 = |
| (struct sockaddr_in6 *)&lifr.lifr_addr; |
| |
| if (!IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, addr)) { |
| dhcpmsg(MSG_WARNING, |
| "checkaddr: expected %s %s on %s, have %s", aname, |
| inet_ntop(AF_INET6, addr, abuf1, sizeof (abuf1)), |
| lif->lif_name, inet_ntop(AF_INET6, &sin6->sin6_addr, |
| abuf2, sizeof (abuf2))); |
| return (B_FALSE); |
| } |
| } else { |
| struct sockaddr_in *sinp = |
| (struct sockaddr_in *)&lifr.lifr_addr; |
| ipaddr_t v4addr; |
| |
| IN6_V4MAPPED_TO_IPADDR(addr, v4addr); |
| if (sinp->sin_addr.s_addr != v4addr) { |
| dhcpmsg(MSG_WARNING, |
| "checkaddr: expected %s %s on %s, have %s", aname, |
| inet_ntop(AF_INET, &v4addr, abuf1, sizeof (abuf1)), |
| lif->lif_name, inet_ntop(AF_INET, &sinp->sin_addr, |
| abuf2, sizeof (abuf2))); |
| return (B_FALSE); |
| } |
| } |
| return (B_TRUE); |
| } |
| |
| /* |
| * verify_lif(): verifies than a LIF is still valid (i.e., has not been |
| * explicitly or implicitly dropped or released) |
| * |
| * input: const dhcp_lif_t *: the LIF to verify |
| * output: boolean_t: B_TRUE if the LIF is still valid, B_FALSE otherwise |
| */ |
| |
| boolean_t |
| verify_lif(const dhcp_lif_t *lif) |
| { |
| boolean_t isv6; |
| int fd; |
| struct lifreq lifr; |
| dhcp_pif_t *pif = lif->lif_pif; |
| |
| (void) memset(&lifr, 0, sizeof (struct lifreq)); |
| (void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ); |
| |
| isv6 = pif->pif_isv6; |
| fd = isv6 ? v6_sock_fd : v4_sock_fd; |
| |
| if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) { |
| if (errno != ENXIO) { |
| dhcpmsg(MSG_ERR, |
| "verify_lif: SIOCGLIFFLAGS failed on %s", |
| lif->lif_name); |
| } |
| return (B_FALSE); |
| } |
| |
| /* |
| * If important flags have changed, then abandon the interface. |
| */ |
| if ((lif->lif_flags ^ lifr.lifr_flags) & DHCP_IFF_WATCH) { |
| dhcpmsg(MSG_DEBUG, "verify_lif: unexpected flag change on %s: " |
| "%llx to %llx (%llx)", lif->lif_name, lif->lif_flags, |
| lifr.lifr_flags, (lif->lif_flags ^ lifr.lifr_flags) & |
| DHCP_IFF_WATCH); |
| return (B_FALSE); |
| } |
| |
| /* |
| * Check for delete and recreate. |
| */ |
| if (ioctl(fd, SIOCGLIFINDEX, &lifr) == -1) { |
| if (errno != ENXIO) { |
| dhcpmsg(MSG_ERR, "verify_lif: SIOCGLIFINDEX failed " |
| "on %s", lif->lif_name); |
| } |
| return (B_FALSE); |
| } |
| if (lifr.lifr_index != pif->pif_index) { |
| dhcpmsg(MSG_DEBUG, |
| "verify_lif: ifindex on %s changed: %u to %u", |
| lif->lif_name, pif->pif_index, lifr.lifr_index); |
| return (B_FALSE); |
| } |
| |
| if (pif->pif_under_ipmp) { |
| (void) strlcpy(lifr.lifr_name, pif->pif_grifname, LIFNAMSIZ); |
| |
| if (ioctl(fd, SIOCGLIFINDEX, &lifr) == -1) { |
| if (errno != ENXIO) { |
| dhcpmsg(MSG_ERR, "verify_lif: SIOCGLIFINDEX " |
| "failed on %s", lifr.lifr_name); |
| } |
| return (B_FALSE); |
| } |
| |
| if (lifr.lifr_index != pif->pif_grindex) { |
| dhcpmsg(MSG_DEBUG, "verify_lif: IPMP group ifindex " |
| "on %s changed: %u to %u", lifr.lifr_name, |
| pif->pif_grindex, lifr.lifr_index); |
| return (B_FALSE); |
| } |
| } |
| |
| /* |
| * If the IP address, netmask, or broadcast address have changed, or |
| * the interface has been unplumbed, then we act like there has been an |
| * implicit drop. (Note that the netmask is under DHCP control for |
| * IPv4, but not for DHCPv6, and that IPv6 doesn't have broadcast |
| * addresses.) |
| */ |
| |
| if (!checkaddr(lif, SIOCGLIFADDR, &lif->lif_v6addr, "local address")) |
| return (B_FALSE); |
| |
| if (isv6) { |
| /* |
| * If it's not point-to-point, we're done. If it is, then |
| * check the peer's address as well. |
| */ |
| return (!(lif->lif_flags & IFF_POINTOPOINT) || |
| checkaddr(lif, SIOCGLIFDSTADDR, &lif->lif_v6peer, |
| "peer address")); |
| } else { |
| if (!checkaddr(lif, SIOCGLIFNETMASK, &lif->lif_v6mask, |
| "netmask")) |
| return (B_FALSE); |
| |
| return (checkaddr(lif, |
| (lif->lif_flags & IFF_POINTOPOINT) ? SIOCGLIFDSTADDR : |
| SIOCGLIFBRDADDR, &lif->lif_v6peer, "peer address")); |
| } |
| } |
| |
| /* |
| * canonize_lif(): puts the interface in a canonical (zeroed) form. This is |
| * used only on the "main" LIF for IPv4. All other interfaces |
| * are under dhcpagent control and are removed using |
| * unplumb_lif(). |
| * |
| * input: dhcp_lif_t *: the interface to canonize |
| * boolean_t: only canonize lif if it's under DHCP control |
| * output: none |
| */ |
| |
| static void |
| canonize_lif(dhcp_lif_t *lif, boolean_t dhcponly) |
| { |
| boolean_t isv6; |
| int fd; |
| struct lifreq lifr; |
| |
| /* |
| * If there's nothing here, then don't touch the interface. This can |
| * happen when an already-canonized LIF is recanonized. |
| */ |
| if (IN6_IS_ADDR_UNSPECIFIED(&lif->lif_v6addr)) |
| return; |
| |
| isv6 = lif->lif_pif->pif_isv6; |
| dhcpmsg(MSG_VERBOSE, "canonizing IPv%d interface %s", |
| isv6 ? 6 : 4, lif->lif_name); |
| |
| lif->lif_v6addr = my_in6addr_any; |
| lif->lif_v6mask = my_in6addr_any; |
| lif->lif_v6peer = my_in6addr_any; |
| |
| (void) memset(&lifr, 0, sizeof (struct lifreq)); |
| (void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ); |
| |
| fd = isv6 ? v6_sock_fd : v4_sock_fd; |
| |
| if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) { |
| if (errno != ENXIO) { |
| dhcpmsg(MSG_ERR, "canonize_lif: can't get flags for %s", |
| lif->lif_name); |
| } |
| return; |
| } |
| lif->lif_flags = lifr.lifr_flags; |
| |
| if (dhcponly && !(lifr.lifr_flags & IFF_DHCPRUNNING)) { |
| dhcpmsg(MSG_INFO, |
| "canonize_lif: cannot clear %s; flags are %llx", |
| lif->lif_name, lifr.lifr_flags); |
| return; |
| } |
| |
| (void) memset(&lifr.lifr_addr, 0, sizeof (lifr.lifr_addr)); |
| if (isv6) { |
| struct sockaddr_in6 *sin6 = |
| (struct sockaddr_in6 *)&lifr.lifr_addr; |
| |
| sin6->sin6_family = AF_INET6; |
| sin6->sin6_addr = my_in6addr_any; |
| } else { |
| struct sockaddr_in *sinv = |
| (struct sockaddr_in *)&lifr.lifr_addr; |
| |
| sinv->sin_family = AF_INET; |
| sinv->sin_addr.s_addr = htonl(INADDR_ANY); |
| } |
| |
| if (ioctl(fd, SIOCSLIFADDR, &lifr) == -1) { |
| dhcpmsg(MSG_ERR, |
| "canonize_lif: can't clear local address on %s", |
| lif->lif_name); |
| } |
| |
| if (lif->lif_flags & IFF_POINTOPOINT) { |
| if (ioctl(fd, SIOCSLIFDSTADDR, &lifr) == -1) { |
| dhcpmsg(MSG_ERR, |
| "canonize_lif: can't clear remote address on %s", |
| lif->lif_name); |
| } |
| } else if (!isv6) { |
| if (ioctl(fd, SIOCSLIFBRDADDR, &lifr) == -1) { |
| dhcpmsg(MSG_ERR, |
| "canonize_lif: can't clear broadcast address on %s", |
| lif->lif_name); |
| } |
| } |
| |
| /* |
| * Clear the netmask last as it has to be refetched after clearing. |
| * Netmask is under in.ndpd control with IPv6. |
| */ |
| if (!isv6) { |
| /* Clear the netmask */ |
| if (ioctl(fd, SIOCSLIFNETMASK, &lifr) == -1) { |
| dhcpmsg(MSG_ERR, |
| "canonize_lif: can't clear netmask on %s", |
| lif->lif_name); |
| } else { |
| /* |
| * When the netmask is cleared, the kernel actually sets |
| * the netmask to 255.0.0.0. So, refetch that netmask. |
| */ |
| if (ioctl(fd, SIOCGLIFNETMASK, &lifr) == -1) { |
| dhcpmsg(MSG_ERR, |
| "canonize_lif: can't reload cleared " |
| "netmask on %s", lif->lif_name); |
| } else { |
| /* Refetch succeeded, update LIF */ |
| lif->lif_netmask = |
| ((struct sockaddr_in *)&lifr.lifr_addr)-> |
| sin_addr.s_addr; |
| } |
| } |
| } |
| } |
| |
| /* |
| * plumb_lif(): Adds the LIF to the system. This is used for all |
| * DHCPv6-derived interfaces. The returned LIF has a hold |
| * on it. |
| * |
| * input: dhcp_lif_t *: the interface to unplumb |
| * output: none |
| */ |
| |
| dhcp_lif_t * |
| plumb_lif(dhcp_pif_t *pif, const in6_addr_t *addr) |
| { |
| dhcp_lif_t *lif; |
| char abuf[INET6_ADDRSTRLEN]; |
| struct lifreq lifr; |
| struct sockaddr_in6 *sin6; |
| int error; |
| |
| (void) inet_ntop(AF_INET6, addr, abuf, sizeof (abuf)); |
| |
| for (lif = pif->pif_lifs; lif != NULL; lif = lif->lif_next) { |
| if (IN6_ARE_ADDR_EQUAL(&lif->lif_v6addr, addr)) { |
| dhcpmsg(MSG_ERR, |
| "plumb_lif: entry for %s already exists!", abuf); |
| return (NULL); |
| } |
| } |
| |
| /* First, create a new zero-address logical interface */ |
| (void) memset(&lifr, 0, sizeof (lifr)); |
| (void) strlcpy(lifr.lifr_name, pif->pif_name, sizeof (lifr.lifr_name)); |
| if (ioctl(v6_sock_fd, SIOCLIFADDIF, &lifr) == -1) { |
| dhcpmsg(MSG_ERR, "plumb_lif: SIOCLIFADDIF %s", pif->pif_name); |
| return (NULL); |
| } |
| |
| /* Next, set the netmask to all ones */ |
| sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; |
| sin6->sin6_family = AF_INET6; |
| (void) memset(&sin6->sin6_addr, 0xff, sizeof (sin6->sin6_addr)); |
| if (ioctl(v6_sock_fd, SIOCSLIFNETMASK, &lifr) == -1) { |
| dhcpmsg(MSG_ERR, "plumb_lif: SIOCSLIFNETMASK %s", |
| lifr.lifr_name); |
| goto failure; |
| } |
| |
| /* Now set the interface address */ |
| sin6->sin6_addr = *addr; |
| if (ioctl(v6_sock_fd, SIOCSLIFADDR, &lifr) == -1) { |
| dhcpmsg(MSG_ERR, "plumb_lif: SIOCSLIFADDR %s %s", |
| lifr.lifr_name, abuf); |
| goto failure; |
| } |
| |
| /* Mark the interface up */ |
| if (ioctl(v6_sock_fd, SIOCGLIFFLAGS, &lifr) == -1) { |
| dhcpmsg(MSG_ERR, "plumb_lif: SIOCGLIFFLAGS %s", |
| lifr.lifr_name); |
| goto failure; |
| } |
| |
| /* |
| * See comment in set_lif_dhcp(). |
| */ |
| if (pif->pif_under_ipmp && !(lifr.lifr_flags & IFF_NOFAILOVER)) |
| lifr.lifr_flags |= IFF_NOFAILOVER | IFF_DEPRECATED; |
| |
| lifr.lifr_flags |= IFF_UP | IFF_DHCPRUNNING; |
| if (ioctl(v6_sock_fd, SIOCSLIFFLAGS, &lifr) == -1) { |
| dhcpmsg(MSG_ERR, "plumb_lif: SIOCSLIFFLAGS %s", |
| lifr.lifr_name); |
| goto failure; |
| } |
| |
| /* Now we can create the internal LIF structure */ |
| hold_pif(pif); |
| if ((lif = insert_lif(pif, lifr.lifr_name, &error)) == NULL) |
| goto failure; |
| |
| dhcpmsg(MSG_DEBUG, "plumb_lif: plumbed up %s on %s", abuf, |
| lif->lif_name); |
| lif->lif_plumbed = B_TRUE; |
| |
| return (lif); |
| |
| failure: |
| if (ioctl(v6_sock_fd, SIOCLIFREMOVEIF, &lifr) == -1 && |
| errno != ENXIO) { |
| dhcpmsg(MSG_ERR, "plumb_lif: SIOCLIFREMOVEIF %s", |
| lifr.lifr_name); |
| } |
| return (NULL); |
| } |
| |
| /* |
| * unplumb_lif(): Removes the LIF from dhcpagent and the system. This is used |
| * for all interfaces configured by DHCP (those in leases). |
| * |
| * input: dhcp_lif_t *: the interface to unplumb |
| * output: none |
| */ |
| |
| void |
| unplumb_lif(dhcp_lif_t *lif) |
| { |
| dhcp_lease_t *dlp; |
| |
| if (lif->lif_plumbed) { |
| struct lifreq lifr; |
| |
| (void) memset(&lifr, 0, sizeof (lifr)); |
| (void) strlcpy(lifr.lifr_name, lif->lif_name, |
| sizeof (lifr.lifr_name)); |
| if (ioctl(v6_sock_fd, SIOCLIFREMOVEIF, &lifr) == -1 && |
| errno != ENXIO) { |
| dhcpmsg(MSG_ERR, "unplumb_lif: SIOCLIFREMOVEIF %s", |
| lif->lif_name); |
| } |
| lif->lif_plumbed = B_FALSE; |
| } |
| |
| /* |
| * Special case: if we're "unplumbing" the main LIF for DHCPv4, then |
| * just canonize it and remove it from the lease. |
| */ |
| if ((dlp = lif->lif_lease) != NULL && dlp->dl_smach->dsm_lif == lif) { |
| canonize_lif(lif, B_TRUE); |
| cancel_lif_timers(lif); |
| if (lif->lif_declined != NULL) { |
| dlp->dl_smach->dsm_lif_down--; |
| lif->lif_declined = NULL; |
| } |
| dlp->dl_nlifs = 0; |
| dlp->dl_lifs = NULL; |
| lif->lif_lease = NULL; |
| release_lif(lif); |
| } else { |
| remove_lif(lif); |
| } |
| } |
| |
| /* |
| * attach_lif(): create a new logical interface, creating the physical |
| * interface as necessary. |
| * |
| * input: const char *: the logical interface name |
| * boolean_t: B_TRUE for IPv6 |
| * int *: set to DHCP_IPC_E_* if creation fails |
| * output: dhcp_lif_t *: pointer to new entry, or NULL on failure |
| */ |
| |
| dhcp_lif_t * |
| attach_lif(const char *lname, boolean_t isv6, int *error) |
| { |
| dhcp_pif_t *pif; |
| char pname[LIFNAMSIZ], *cp; |
| |
| (void) strlcpy(pname, lname, sizeof (pname)); |
| if ((cp = strchr(pname, ':')) != NULL) |
| *cp = '\0'; |
| |
| if ((pif = lookup_pif_by_name(pname, isv6)) != NULL) |
| hold_pif(pif); |
| else if ((pif = insert_pif(pname, isv6, error)) == NULL) |
| return (NULL); |
| |
| if (lookup_lif_by_name(lname, pif) != NULL) { |
| dhcpmsg(MSG_ERROR, "attach_lif: entry for %s already exists!", |
| lname); |
| release_pif(pif); |
| *error = DHCP_IPC_E_INVIF; |
| return (NULL); |
| } |
| |
| /* If LIF creation fails, then insert_lif discards our PIF hold */ |
| return (insert_lif(pif, lname, error)); |
| } |
| |
| /* |
| * set_lif_dhcp(): Set logical interface flags to show that it's managed |
| * by DHCP. |
| * |
| * input: dhcp_lif_t *: the logical interface |
| * boolean_t: B_TRUE if adopting |
| * output: int: set to DHCP_IPC_E_* if operation fails |
| */ |
| |
| int |
| set_lif_dhcp(dhcp_lif_t *lif, boolean_t is_adopting) |
| { |
| int fd; |
| int err; |
| struct lifreq lifr; |
| dhcp_pif_t *pif = lif->lif_pif; |
| |
| fd = pif->pif_isv6 ? v6_sock_fd : v4_sock_fd; |
| |
| (void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ); |
| |
| if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) { |
| err = errno; |
| dhcpmsg(MSG_ERR, "set_lif_dhcp: SIOCGLIFFLAGS for %s", |
| lif->lif_name); |
| return (err == ENXIO ? DHCP_IPC_E_INVIF : DHCP_IPC_E_INT); |
| } |
| lif->lif_flags = lifr.lifr_flags; |
| |
| /* |
| * Check for conflicting sources of address control, and other |
| * unacceptable configurations. |
| */ |
| if (lifr.lifr_flags & (IFF_LOOPBACK|IFF_ADDRCONF|IFF_TEMPORARY| |
| IFF_VIRTUAL)) { |
| dhcpmsg(MSG_ERR, "set_lif_dhcp: cannot use %s: flags are %llx", |
| lif->lif_name, lifr.lifr_flags); |
| return (DHCP_IPC_E_INVIF); |
| } |
| |
| /* |
| * if DHCPRUNNING is already set on the interface and we're |
| * not adopting it, the agent probably crashed and burned. |
| * note it, but don't let it stop the proceedings. we're |
| * pretty sure we're not already running, since we wouldn't |
| * have been able to bind to our IPC port. |
| */ |
| |
| if (lifr.lifr_flags & IFF_DHCPRUNNING) { |
| if (!is_adopting) { |
| dhcpmsg(MSG_WARNING, "set_lif_dhcp: DHCP flag already " |
| "set on %s", lif->lif_name); |
| } |
| } else { |
| /* |
| * If the lif is on an interface under IPMP, IFF_NOFAILOVER |
| * must be set or the kernel will prevent us from setting |
| * IFF_DHCPRUNNING (since the subsequent IFF_UP would lead to |
| * migration). We set IFF_DEPRECATED too since the kernel |
| * will set it automatically when setting IFF_NOFAILOVER, |
| * causing our lif_flags value to grow stale. |
| */ |
| if (pif->pif_under_ipmp && !(lifr.lifr_flags & IFF_NOFAILOVER)) |
| lifr.lifr_flags |= IFF_NOFAILOVER | IFF_DEPRECATED; |
| |
| lifr.lifr_flags |= IFF_DHCPRUNNING; |
| if (ioctl(fd, SIOCSLIFFLAGS, &lifr) == -1) { |
| dhcpmsg(MSG_ERR, "set_lif_dhcp: SIOCSLIFFLAGS for %s", |
| lif->lif_name); |
| return (DHCP_IPC_E_INT); |
| } |
| lif->lif_flags = lifr.lifr_flags; |
| } |
| return (DHCP_IPC_SUCCESS); |
| } |
| |
| /* |
| * clear_lif_dhcp(): Clear logical interface flags to show that it's no longer |
| * managed by DHCP. |
| * |
| * input: dhcp_lif_t *: the logical interface |
| * output: none |
| */ |
| |
| static void |
| clear_lif_dhcp(dhcp_lif_t *lif) |
| { |
| int fd; |
| struct lifreq lifr; |
| |
| fd = lif->lif_pif->pif_isv6 ? v6_sock_fd : v4_sock_fd; |
| |
| (void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ); |
| |
| if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) |
| return; |
| |
| if (!(lifr.lifr_flags & IFF_DHCPRUNNING)) |
| return; |
| |
| lif->lif_flags = lifr.lifr_flags &= ~IFF_DHCPRUNNING; |
| (void) ioctl(fd, SIOCSLIFFLAGS, &lifr); |
| } |
| |
| /* |
| * set_lif_deprecated(): Set the "deprecated" flag to tell users that this |
| * address will be going away. As the interface is |
| * going away, we don't care if there are errors. |
| * |
| * input: dhcp_lif_t *: the logical interface |
| * output: none |
| */ |
| |
| void |
| set_lif_deprecated(dhcp_lif_t *lif) |
| { |
| int fd; |
| struct lifreq lifr; |
| |
| if (lif->lif_flags & IFF_DEPRECATED) |
| return; |
| |
| fd = lif->lif_pif->pif_isv6 ? v6_sock_fd : v4_sock_fd; |
| |
| (void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ); |
| |
| if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) |
| return; |
| |
| if (lifr.lifr_flags & IFF_DEPRECATED) |
| return; |
| |
| lifr.lifr_flags |= IFF_DEPRECATED; |
| (void) ioctl(fd, SIOCSLIFFLAGS, &lifr); |
| lif->lif_flags = lifr.lifr_flags; |
| } |
| |
| /* |
| * clear_lif_deprecated(): Clear the "deprecated" flag to tell users that this |
| * address will not be going away. This happens if we |
| * get a renewal after preferred lifetime but before |
| * the valid lifetime. |
| * |
| * input: dhcp_lif_t *: the logical interface |
| * output: boolean_t: B_TRUE on success. |
| */ |
| |
| boolean_t |
| clear_lif_deprecated(dhcp_lif_t *lif) |
| { |
| int fd; |
| struct lifreq lifr; |
| |
| fd = lif->lif_pif->pif_isv6 ? v6_sock_fd : v4_sock_fd; |
| |
| (void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ); |
| |
| if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) { |
| dhcpmsg(MSG_ERR, "clear_lif_deprecated: SIOCGLIFFLAGS for %s", |
| lif->lif_name); |
| return (B_FALSE); |
| } |
| |
| /* |
| * Check for conflicting sources of address control, and other |
| * unacceptable configurations. |
| */ |
| if (lifr.lifr_flags & (IFF_LOOPBACK|IFF_ADDRCONF|IFF_TEMPORARY| |
| IFF_VIRTUAL)) { |
| dhcpmsg(MSG_ERR, "clear_lif_deprecated: cannot use %s: flags " |
| "are %llx", lif->lif_name, lifr.lifr_flags); |
| return (B_FALSE); |
| } |
| |
| /* |
| * Don't try to clear IFF_DEPRECATED if this is a test address, |
| * since IPMP's use of IFF_DEPRECATED is not compatible with ours. |
| */ |
| if (lifr.lifr_flags & IFF_NOFAILOVER) |
| return (B_TRUE); |
| |
| if (!(lifr.lifr_flags & IFF_DEPRECATED)) |
| return (B_TRUE); |
| |
| lifr.lifr_flags &= ~IFF_DEPRECATED; |
| if (ioctl(fd, SIOCSLIFFLAGS, &lifr) == -1) { |
| dhcpmsg(MSG_ERR, "clear_lif_deprecated: SIOCSLIFFLAGS for %s", |
| lif->lif_name); |
| return (B_FALSE); |
| } else { |
| lif->lif_flags = lifr.lifr_flags; |
| return (B_TRUE); |
| } |
| } |
| |
| /* |
| * open_ip_lif(): open up an IP socket for I/O on a given LIF (v4 only). |
| * |
| * input: dhcp_lif_t *: the logical interface to operate on |
| * in_addr_t: the address the socket will be bound to (in hbo) |
| * boolean_t: B_TRUE if the address should be brought up (if needed) |
| * output: boolean_t: B_TRUE if the socket was opened successfully. |
| */ |
| |
| boolean_t |
| open_ip_lif(dhcp_lif_t *lif, in_addr_t addr_hbo, boolean_t bringup) |
| { |
| const char *errmsg; |
| struct lifreq lifr; |
| int on = 1; |
| uchar_t ttl = 255; |
| uint32_t ifindex; |
| dhcp_pif_t *pif = lif->lif_pif; |
| |
| if (lif->lif_sock_ip_fd != -1) { |
| dhcpmsg(MSG_WARNING, "open_ip_lif: socket already open on %s", |
| lif->lif_name); |
| return (B_FALSE); |
| } |
| |
| lif->lif_sock_ip_fd = socket(AF_INET, SOCK_DGRAM, 0); |
| if (lif->lif_sock_ip_fd == -1) { |
| errmsg = "cannot create v4 socket"; |
| goto failure; |
| } |
| |
| if (!bind_sock(lif->lif_sock_ip_fd, IPPORT_BOOTPC, addr_hbo)) { |
| errmsg = "cannot bind v4 socket"; |
| goto failure; |
| } |
| |
| /* |
| * If we bound to INADDR_ANY, we have no IFF_UP source address to use. |
| * Thus, enable IP_UNSPEC_SRC so that we can send packets with an |
| * unspecified (0.0.0.0) address. Also, enable IP_DHCPINIT_IF so that |
| * the IP module will accept unicast DHCP traffic regardless of the IP |
| * address it's sent to. (We'll then figure out which packets are |
| * ours based on the xid.) |
| */ |
| if (addr_hbo == INADDR_ANY) { |
| if (setsockopt(lif->lif_sock_ip_fd, IPPROTO_IP, IP_UNSPEC_SRC, |
| &on, sizeof (int)) == -1) { |
| errmsg = "cannot set IP_UNSPEC_SRC"; |
| goto failure; |
| } |
| |
| if (setsockopt(lif->lif_sock_ip_fd, IPPROTO_IP, IP_DHCPINIT_IF, |
| &pif->pif_index, sizeof (int)) == -1) { |
| errmsg = "cannot set IP_DHCPINIT_IF"; |
| goto failure; |
| } |
| } |
| |
| /* |
| * Unfortunately, some hardware (such as the Linksys WRT54GC) |
| * decrements the TTL *prior* to accepting DHCP traffic destined |
| * for it. To workaround this, tell IP to use a TTL of 255 for |
| * broadcast packets sent from this socket. |
| */ |
| if (setsockopt(lif->lif_sock_ip_fd, IPPROTO_IP, IP_BROADCAST_TTL, &ttl, |
| sizeof (uchar_t)) == -1) { |
| errmsg = "cannot set IP_BROADCAST_TTL"; |
| goto failure; |
| } |
| |
| ifindex = pif->pif_under_ipmp ? pif->pif_grindex : pif->pif_index; |
| if (setsockopt(lif->lif_sock_ip_fd, IPPROTO_IP, IP_BOUND_IF, &ifindex, |
| sizeof (int)) == -1) { |
| errmsg = "cannot set IP_BOUND_IF"; |
| goto failure; |
| } |
| |
| (void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ); |
| if (ioctl(v4_sock_fd, SIOCGLIFFLAGS, &lifr) == -1) { |
| errmsg = "cannot get interface flags"; |
| goto failure; |
| } |
| |
| /* |
| * If the lif is part of an interface under IPMP, IFF_NOFAILOVER must |
| * be set or the kernel will prevent us from setting IFF_DHCPRUNNING |
| * (since the subsequent IFF_UP would lead to migration). We set |
| * IFF_DEPRECATED too since the kernel will set it automatically when |
| * setting IFF_NOFAILOVER, causing our lif_flags value to grow stale. |
| */ |
| if (pif->pif_under_ipmp && !(lifr.lifr_flags & IFF_NOFAILOVER)) { |
| lifr.lifr_flags |= IFF_NOFAILOVER | IFF_DEPRECATED; |
| if (ioctl(v4_sock_fd, SIOCSLIFFLAGS, &lifr) == -1) { |
| errmsg = "cannot set IFF_NOFAILOVER"; |
| goto failure; |
| } |
| } |
| lif->lif_flags = lifr.lifr_flags; |
| |
| /* |
| * If this is initial bringup, make sure the address we're acquiring a |
| * lease on is IFF_UP. |
| */ |
| if (bringup && !(lifr.lifr_flags & IFF_UP)) { |
| /* |
| * Start from a clean slate. |
| */ |
| canonize_lif(lif, B_FALSE); |
| |
| lifr.lifr_flags |= IFF_UP; |
| if (ioctl(v4_sock_fd, SIOCSLIFFLAGS, &lifr) == -1) { |
| errmsg = "cannot bring up"; |
| goto failure; |
| } |
| lif->lif_flags = lifr.lifr_flags; |
| |
| /* |
| * When bringing 0.0.0.0 IFF_UP, the kernel changes the |
| * netmask to 255.0.0.0, so re-fetch our expected netmask. |
| */ |
| if (ioctl(v4_sock_fd, SIOCGLIFNETMASK, &lifr) == -1) { |
| errmsg = "cannot get netmask"; |
| goto failure; |
| } |
| |
| lif->lif_netmask = |
| ((struct sockaddr_in *)&lifr.lifr_addr)->sin_addr.s_addr; |
| } |
| |
| /* |
| * Usually, bringing up the address we're acquiring a lease on is |
| * sufficient to allow packets to be sent and received via the |
| * IP_BOUND_IF we did earlier. However, if we're acquiring a lease on |
| * an underlying IPMP interface, the group interface will be used for |
| * sending and receiving IP packets via IP_BOUND_IF. Thus, ensure at |
| * least one address on the group interface is IFF_UP. |
| */ |
| if (bringup && pif->pif_under_ipmp) { |
| (void) strlcpy(lifr.lifr_name, pif->pif_grifname, LIFNAMSIZ); |
| if (ioctl(v4_sock_fd, SIOCGLIFFLAGS, &lifr) == -1) { |
| errmsg = "cannot get IPMP group interface flags"; |
| goto failure; |
| } |
| |
| if (!(lifr.lifr_flags & IFF_UP)) { |
| lifr.lifr_flags |= IFF_UP; |
| if (ioctl(v4_sock_fd, SIOCSLIFFLAGS, &lifr) == -1) { |
| errmsg = "cannot bring up IPMP group interface"; |
| goto failure; |
| } |
| } |
| } |
| |
| lif->lif_packet_id = iu_register_event(eh, lif->lif_sock_ip_fd, POLLIN, |
| dhcp_packet_lif, lif); |
| if (lif->lif_packet_id == -1) { |
| errmsg = "cannot register to receive DHCP packets"; |
| goto failure; |
| } |
| |
| return (B_TRUE); |
| failure: |
| dhcpmsg(MSG_ERR, "open_ip_lif: %s: %s", lif->lif_name, errmsg); |
| close_ip_lif(lif); |
| return (B_FALSE); |
| } |
| |
| /* |
| * close_ip_lif(): close an IP socket for I/O on a given LIF. |
| * |
| * input: dhcp_lif_t *: the logical interface to operate on |
| * output: none |
| */ |
| |
| void |
| close_ip_lif(dhcp_lif_t *lif) |
| { |
| if (lif->lif_packet_id != -1) { |
| (void) iu_unregister_event(eh, lif->lif_packet_id, NULL); |
| lif->lif_packet_id = -1; |
| } |
| if (lif->lif_sock_ip_fd != -1) { |
| (void) close(lif->lif_sock_ip_fd); |
| lif->lif_sock_ip_fd = -1; |
| } |
| } |
| |
| /* |
| * lif_mark_decline(): mark a LIF as having been declined due to a duplicate |
| * address or some other conflict. This is used in |
| * send_declines() to report failure back to the server. |
| * |
| * input: dhcp_lif_t *: the logical interface to operate on |
| * const char *: text string explaining why the address is declined |
| * output: none |
| */ |
| |
| void |
| lif_mark_decline(dhcp_lif_t *lif, const char *reason) |
| { |
| if (lif->lif_declined == NULL) { |
| dhcp_lease_t *dlp; |
| |
| lif->lif_declined = reason; |
| if ((dlp = lif->lif_lease) != NULL) |
| dlp->dl_smach->dsm_lif_down++; |
| } |
| } |
| |
| /* |
| * schedule_lif_timer(): schedules the LIF-related timer |
| * |
| * input: dhcp_lif_t *: the logical interface to operate on |
| * dhcp_timer_t *: the timer to schedule |
| * iu_tq_callback_t *: the callback to call upon firing |
| * output: boolean_t: B_TRUE if the timer was scheduled successfully |
| */ |
| |
| boolean_t |
| schedule_lif_timer(dhcp_lif_t *lif, dhcp_timer_t *dt, iu_tq_callback_t *expire) |
| { |
| /* |
| * If there's a timer running, cancel it and release its lease |
| * reference. |
| */ |
| if (dt->dt_id != -1) { |
| if (!cancel_timer(dt)) |
| return (B_FALSE); |
| release_lif(lif); |
| } |
| |
| if (schedule_timer(dt, expire, lif)) { |
| hold_lif(lif); |
| return (B_TRUE); |
| } else { |
| dhcpmsg(MSG_WARNING, |
| "schedule_lif_timer: cannot schedule timer"); |
| return (B_FALSE); |
| } |
| } |
| |
| /* |
| * cancel_lif_timer(): cancels a LIF-related timer |
| * |
| * input: dhcp_lif_t *: the logical interface to operate on |
| * dhcp_timer_t *: the timer to cancel |
| * output: none |
| */ |
| |
| static void |
| cancel_lif_timer(dhcp_lif_t *lif, dhcp_timer_t *dt) |
| { |
| if (dt->dt_id == -1) |
| return; |
| if (cancel_timer(dt)) { |
| dhcpmsg(MSG_DEBUG2, |
| "cancel_lif_timer: canceled expiry timer on %s", |
| lif->lif_name); |
| release_lif(lif); |
| } else { |
| dhcpmsg(MSG_WARNING, |
| "cancel_lif_timer: cannot cancel timer on %s", |
| lif->lif_name); |
| } |
| } |
| |
| /* |
| * cancel_lif_timers(): cancels the LIF-related timers |
| * |
| * input: dhcp_lif_t *: the logical interface to operate on |
| * output: none |
| */ |
| |
| void |
| cancel_lif_timers(dhcp_lif_t *lif) |
| { |
| cancel_lif_timer(lif, &lif->lif_preferred); |
| cancel_lif_timer(lif, &lif->lif_expire); |
| } |
| |
| /* |
| * get_max_mtu(): find the maximum MTU of all interfaces for I/O on common |
| * file descriptors (v4_sock_fd and v6_sock_fd). |
| * |
| * input: boolean_t: B_TRUE for IPv6, B_FALSE for IPv4 |
| * output: none |
| */ |
| |
| uint_t |
| get_max_mtu(boolean_t isv6) |
| { |
| uint_t *mtup = isv6 ? &cached_v6_max_mtu : &cached_v4_max_mtu; |
| |
| if (*mtup == 0) { |
| dhcp_pif_t *pif; |
| dhcp_lif_t *lif; |
| struct lifreq lifr; |
| |
| /* Set an arbitrary lower bound */ |
| *mtup = 1024; |
| pif = isv6 ? v6root : v4root; |
| for (; pif != NULL; pif = pif->pif_next) { |
| for (lif = pif->pif_lifs; lif != NULL; |
| lif = lif->lif_next) { |
| (void) strlcpy(lifr.lifr_name, lif->lif_name, |
| LIFNAMSIZ); |
| if (ioctl(v4_sock_fd, SIOCGLIFMTU, &lifr) != |
| -1 && lifr.lifr_mtu > *mtup) { |
| *mtup = lifr.lifr_mtu; |
| } |
| } |
| } |
| } |
| return (*mtup); |
| } |
| |
| /* |
| * expired_lif_state(): summarize the state of expired LIFs on a given state |
| * machine. |
| * |
| * input: dhcp_smach_t *: the state machine to scan |
| * output: dhcp_expire_t: overall state |
| */ |
| |
| dhcp_expire_t |
| expired_lif_state(dhcp_smach_t *dsmp) |
| { |
| dhcp_lease_t *dlp; |
| dhcp_lif_t *lif; |
| uint_t nlifs; |
| uint_t numlifs; |
| uint_t numexp; |
| |
| numlifs = numexp = 0; |
| for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) { |
| lif = dlp->dl_lifs; |
| nlifs = dlp->dl_nlifs; |
| numlifs += nlifs; |
| for (; nlifs > 0; nlifs--, lif = lif->lif_next) { |
| if (lif->lif_expired) |
| numexp++; |
| } |
| } |
| if (numlifs == 0) |
| return (DHCP_EXP_NOLIFS); |
| else if (numexp == 0) |
| return (DHCP_EXP_NOEXP); |
| else if (numlifs == numexp) |
| return (DHCP_EXP_ALLEXP); |
| else |
| return (DHCP_EXP_SOMEEXP); |
| } |
| |
| /* |
| * find_expired_lif(): find the first expired LIF on a given state machine |
| * |
| * input: dhcp_smach_t *: the state machine to scan |
| * output: dhcp_lif_t *: the first expired LIF, or NULL if none. |
| */ |
| |
| dhcp_lif_t * |
| find_expired_lif(dhcp_smach_t *dsmp) |
| { |
| dhcp_lease_t *dlp; |
| dhcp_lif_t *lif; |
| uint_t nlifs; |
| |
| for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) { |
| lif = dlp->dl_lifs; |
| nlifs = dlp->dl_nlifs; |
| for (; nlifs > 0; nlifs--, lif = lif->lif_next) { |
| if (lif->lif_expired) |
| return (lif); |
| } |
| } |
| return (NULL); |
| } |
| |
| /* |
| * remove_v6_strays(): remove any stray interfaces marked as DHCPRUNNING. Used |
| * only for DHCPv6. |
| * |
| * input: none |
| * output: none |
| */ |
| |
| void |
| remove_v6_strays(void) |
| { |
| struct lifnum lifn; |
| struct lifconf lifc; |
| struct lifreq *lifrp, *lifrmax; |
| uint_t numifs; |
| uint64_t flags; |
| |
| /* |
| * Get the approximate number of interfaces in the system. It's only |
| * approximate because the system is dynamic -- interfaces may be |
| * plumbed or unplumbed at any time. This is also the reason for the |
| * "+ 10" fudge factor: we're trying to avoid unnecessary looping. |
| */ |
| (void) memset(&lifn, 0, sizeof (lifn)); |
| lifn.lifn_family = AF_INET6; |
| lifn.lifn_flags = LIFC_ALLZONES | LIFC_NOXMIT | LIFC_TEMPORARY; |
| if (ioctl(v6_sock_fd, SIOCGLIFNUM, &lifn) == -1) { |
| dhcpmsg(MSG_ERR, |
| "remove_v6_strays: cannot read number of interfaces"); |
| numifs = 10; |
| } else { |
| numifs = lifn.lifn_count + 10; |
| } |
| |
| /* |
| * Get the interface information. We do this in a loop so that we can |
| * recover from EINVAL from the kernel -- delivered when the buffer is |
| * too small. |
| */ |
| (void) memset(&lifc, 0, sizeof (lifc)); |
| lifc.lifc_family = AF_INET6; |
| lifc.lifc_flags = LIFC_ALLZONES | LIFC_NOXMIT | LIFC_TEMPORARY; |
| for (;;) { |
| lifc.lifc_len = numifs * sizeof (*lifrp); |
| lifrp = realloc(lifc.lifc_buf, lifc.lifc_len); |
| if (lifrp == NULL) { |
| dhcpmsg(MSG_ERR, |
| "remove_v6_strays: cannot allocate memory"); |
| free(lifc.lifc_buf); |
| return; |
| } |
| lifc.lifc_buf = (caddr_t)lifrp; |
| errno = 0; |
| if (ioctl(v6_sock_fd, SIOCGLIFCONF, &lifc) == 0 && |
| lifc.lifc_len < numifs * sizeof (*lifrp)) |
| break; |
| if (errno == 0 || errno == EINVAL) { |
| numifs <<= 1; |
| } else { |
| dhcpmsg(MSG_ERR, "remove_v6_strays: SIOCGLIFCONF"); |
| free(lifc.lifc_buf); |
| return; |
| } |
| } |
| |
| lifrmax = lifrp + lifc.lifc_len / sizeof (*lifrp); |
| for (; lifrp < lifrmax; lifrp++) { |
| /* |
| * Get the interface flags; we're interested in the DHCP ones. |
| */ |
| if (ioctl(v6_sock_fd, SIOCGLIFFLAGS, lifrp) == -1) |
| continue; |
| flags = lifrp->lifr_flags; |
| if (!(flags & IFF_DHCPRUNNING)) |
| continue; |
| /* |
| * If the interface has a link-local address, then we don't |
| * control it. Just remove the flag. |
| */ |
| if (ioctl(v6_sock_fd, SIOCGLIFADDR, lifrp) == -1) |
| continue; |
| if (IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *)&lifrp-> |
| lifr_addr)->sin6_addr)) { |
| lifrp->lifr_flags = flags & ~IFF_DHCPRUNNING; |
| (void) ioctl(v6_sock_fd, SIOCSLIFFLAGS, lifrp); |
| continue; |
| } |
| /* |
| * All others are (or were) under our control. Clean up by |
| * removing them. |
| */ |
| if (ioctl(v6_sock_fd, SIOCLIFREMOVEIF, lifrp) == 0) { |
| dhcpmsg(MSG_DEBUG, "remove_v6_strays: removed %s", |
| lifrp->lifr_name); |
| } else if (errno != ENXIO) { |
| dhcpmsg(MSG_ERR, |
| "remove_v6_strays: SIOCLIFREMOVEIF %s", |
| lifrp->lifr_name); |
| } |
| } |
| free(lifc.lifc_buf); |
| } |