interface.c revision 1cb875ae88fb9463b368e725c2444776595895cb
/*
* 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 "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;
/*
* Check if this is a VRRP interface. If yes, its IP addresses (the
* VRRP virtual addresses) cannot be configured using DHCP.
*/
if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) {
*error = (errno == ENXIO) ? DHCP_IPC_E_INVIF : DHCP_IPC_E_INT;
dhcpmsg(MSG_ERR, "insert_pif: SIOCGLIFFLAGS for %s", pname);
goto failure;
}
if (lifr.lifr_flags & IFF_VRRP) {
*error = DHCP_IPC_E_INVIF;
dhcpmsg(MSG_ERROR, "insert_pif: VRRP virtual addresses over %s "
"cannot be configured using DHCP", pname);
goto failure;
}
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;
}
if (lif->lif_dad_wait) {
lif->lif_dad_wait = _B_FALSE;
dlp->dl_smach->dsm_lif_wait--;
}
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);
}
/* Clearing the address means that we're no longer waiting on DAD */
if (lif->lif_dad_wait) {
lif->lif_dad_wait = _B_FALSE;
lif->lif_lease->dl_smach->dsm_lif_wait--;
}
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. The caller (configure_v6_leases) deals with the DAD
* wait counters.
*
* 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. The DAD wait flags
* are handled by canonize_lif or by remove_lif.
*/
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
* output: int: set to DHCP_IPC_E_* if operation fails
*/
int
set_lif_dhcp(dhcp_lif_t *lif)
{
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 IFF_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 were able to bind to our IPC port).
*/
if (lifr.lifr_flags & IFF_DHCPRUNNING) {
dhcpmsg(MSG_VERBOSE, "set_lif_dhcp: IFF_DHCPRUNNING 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);
}