#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;
#define DHCP_IFF_WATCH (IFF_DHCPRUNNING | IFF_DEPRECATED | IFF_ADDRCONF | \
IFF_TEMPORARY)
static void clear_lif_dhcp(dhcp_lif_t *);
static void update_pif_mtu(dhcp_pif_t *);
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;
}
(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, 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_mtu_orig = pif->pif_mtu = lifr.lifr_mtu;
dhcpmsg(MSG_DEBUG, "insert_pif: original MTU of %s is %u", pname,
pif->pif_mtu_orig);
if (pif->pif_mtu < DHCP_DEF_MAX_SIZE) {
dhcpmsg(MSG_ERROR, "insert_pif: MTU of %s is too small to "
"support DHCP (%u < %u)", pname, pif->pif_mtu,
DHCP_DEF_MAX_SIZE);
*error = DHCP_IPC_E_INVIF;
goto failure;
}
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);
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;
}
}
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);
}
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);
}
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);
update_pif_mtu(pif);
if (pif->pif_mtu != pif->pif_mtu_orig) {
dhcpmsg(MSG_CRIT, "release_pif: PIF %s MTU is %u, "
"expected %u", pif->pif_name, pif->pif_mtu,
pif->pif_mtu_orig);
}
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);
}
}
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);
}
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);
}
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);
}
}
}
#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; \
}
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, 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 ((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);
}
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);
}
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);
}
}
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);
}
remque(lif);
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);
}
}
}
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);
}
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);
}
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 ((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);
}
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 (!checkaddr(lif, SIOCGLIFADDR, &lif->lif_v6addr, "local address"))
return (B_FALSE);
if (isv6) {
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"));
}
}
static void
canonize_lif(dhcp_lif_t *lif, boolean_t dhcponly)
{
boolean_t isv6;
int fd;
struct lifreq lifr;
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_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);
}
}
if (!isv6) {
if (ioctl(fd, SIOCSLIFNETMASK, &lifr) == -1) {
dhcpmsg(MSG_ERR,
"canonize_lif: can't clear netmask on %s",
lif->lif_name);
} else {
if (ioctl(fd, SIOCGLIFNETMASK, &lifr) == -1) {
dhcpmsg(MSG_ERR,
"canonize_lif: can't reload cleared "
"netmask on %s", lif->lif_name);
} else {
lif->lif_netmask =
((struct sockaddr_in *)&lifr.lifr_addr)->
sin_addr.s_addr;
}
}
}
}
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);
}
}
(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);
}
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;
}
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;
}
if (ioctl(v6_sock_fd, SIOCGLIFFLAGS, &lifr) == -1) {
dhcpmsg(MSG_ERR, "plumb_lif: SIOCGLIFFLAGS %s",
lifr.lifr_name);
goto failure;
}
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;
}
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);
}
void
unplumb_lif(dhcp_lif_t *lif)
{
dhcp_lease_t *dlp;
clear_lif_mtu(lif);
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;
}
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);
}
}
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);
}
return (insert_lif(pif, lname, error));
}
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;
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 (lifr.lifr_flags & IFF_DHCPRUNNING) {
dhcpmsg(MSG_VERBOSE, "set_lif_dhcp: IFF_DHCPRUNNING already set"
" on %s", lif->lif_name);
} else {
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);
}
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);
}
static void
update_pif_mtu(dhcp_pif_t *pif)
{
uint_t mtu = 0;
for (dhcp_lif_t *lif = pif->pif_lifs; lif != NULL;
lif = lif->lif_next) {
if (lif->lif_mtu == 0) {
continue;
}
if (mtu == 0 || mtu > lif->lif_mtu) {
mtu = lif->lif_mtu;
}
}
if (mtu == 0) {
dhcpmsg(MSG_DEBUG2, "update_pif_mtu: restoring %s MTU to "
"original %u", pif->pif_name, pif->pif_mtu_orig);
mtu = pif->pif_mtu_orig;
}
if (pif->pif_mtu == mtu) {
dhcpmsg(MSG_DEBUG2, "update_pif_mtu: %s MTU is already %u",
pif->pif_name, mtu);
return;
}
dhcpmsg(MSG_DEBUG, "update_pif_mtu: %s MTU change: %u -> %u",
pif->pif_name, pif->pif_mtu, mtu);
struct lifreq lifr;
(void) memset(&lifr, 0, sizeof (lifr));
(void) strlcpy(lifr.lifr_name, pif->pif_name, LIFNAMSIZ);
lifr.lifr_mtu = mtu;
int fd = pif->pif_isv6 ? v6_sock_fd : v4_sock_fd;
if (ioctl(fd, SIOCSLIFMTU, &lifr) == -1) {
dhcpmsg(MSG_ERR, "update_pif_mtu: SIOCSLIFMTU (%u) failed "
"for %s", mtu, pif->pif_name);
return;
}
pif->pif_mtu = mtu;
}
void
set_lif_mtu(dhcp_lif_t *lif, uint_t mtu)
{
dhcpmsg(MSG_DEBUG, "set_lif_mtu: %s requests MTU %u", lif->lif_name,
mtu);
lif->lif_mtu = mtu;
update_pif_mtu(lif->lif_pif);
}
void
clear_lif_mtu(dhcp_lif_t *lif)
{
if (lif->lif_mtu != 0) {
dhcpmsg(MSG_DEBUG, "clear_lif_mtu: %s clears MTU request",
lif->lif_name);
}
lif->lif_mtu = 0;
update_pif_mtu(lif->lif_pif);
}
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;
}
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);
}
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);
}
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);
}
}
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 (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;
}
}
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 (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 (bringup && !(lifr.lifr_flags & IFF_UP)) {
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;
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;
}
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);
}
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;
}
}
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++;
}
}
boolean_t
schedule_lif_timer(dhcp_lif_t *lif, dhcp_timer_t *dt, iu_tq_callback_t *expire)
{
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);
}
}
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);
}
}
void
cancel_lif_timers(dhcp_lif_t *lif)
{
cancel_lif_timer(lif, &lif->lif_preferred);
cancel_lif_timer(lif, &lif->lif_expire);
}
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;
*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);
}
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);
}
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);
}
void
remove_v6_strays(void)
{
struct lifnum lifn;
struct lifconf lifc;
struct lifreq *lifrp, *lifrmax;
uint_t numifs;
uint64_t flags;
(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;
}
(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++) {
if (ioctl(v6_sock_fd, SIOCGLIFFLAGS, lifrp) == -1)
continue;
flags = lifrp->lifr_flags;
if (!(flags & IFF_DHCPRUNNING))
continue;
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;
}
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);
}