interface.c revision a1196271e1d6e6ce9cc40c8b94f5b659935e82bc
/*
* 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
* 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 <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <search.h>
#include <libdevinfo.h>
#include <libdlpi.h>
#include <netinet/if_ether.h>
#include <dhcpmsg.h>
#include "agent.h"
#include "interface.h"
#include "util.h"
#include "packet.h"
#include "states.h"
/*
* Interface flags to watch: things that should be under our direct control.
*/
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
*/
{
"%s", pname);
return (NULL);
}
pname);
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.
*/
goto failure;
}
goto failure;
}
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.
*/
*error = DHCP_IPC_E_INT;
goto failure;
}
*error = DHCP_IPC_E_INT;
goto failure;
}
/*
* For IPMP underlying interfaces, stash the interface index
* 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) {
*error = DHCP_IPC_E_INT;
goto failure;
}
}
}
/*
* For IPv4, if the hardware type is still unknown, use DLPI to
* determine it, the hardware address, and hardware address length.
*/
int rc;
dlpi_strerror(rc));
goto failure;
}
dlpi_strerror(rc));
goto failure;
}
dlpi_strerror(rc));
goto failure;
}
"pif_hwaddr for %s", pname);
goto failure;
}
}
dlpi_close(dh);
}
return (pif);
dlpi_close(dh);
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
{
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
{
if (pif->pif_hold_count == 0) {
return;
}
if (--pif->pif_hold_count == 0) {
} else {
}
}
/*
* 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.
*/
{
else
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
*/
{
break;
}
return (pif);
}
/*
*
* input: dhcp_pif_t *: the physical interface to be updated
* boolean_t: B_TRUE if the interface is going up
* output: none
*/
void
{
if (isup)
else
}
}
}
/* Helper for insert_lif: extract addresses as defined */
} else { \
}
/*
* 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
*/
{
int fd;
"%s", lname);
return (NULL);
}
lname);
goto failure;
}
else
else
*error = DHCP_IPC_E_INT;
goto failure;
}
else
*error = DHCP_IPC_E_INT;
goto failure;
}
*error = DHCP_IPC_E_INT;
goto failure;
}
/*
* If we've just detected the interface going up or down, then signal
* an appropriate action. There may be other state machines here.
*/
}
*error = DHCP_IPC_E_INT;
lname);
goto failure;
}
*error = DHCP_IPC_E_INT;
lname);
goto failure;
}
lif->lif_broadcast =
}
cached_v6_max_mtu = 0;
else
cached_v4_max_mtu = 0;
return (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
{
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
{
if (lif->lif_hold_count == 0) {
return;
}
return;
}
if (--lif->lif_hold_count == 0) {
"release_lif: still holding lease at last hold!");
cached_v6_max_mtu = 0;
else
cached_v4_max_mtu = 0;
} else {
}
}
/*
* 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
{
if (lif->lif_plumbed) {
return;
}
if (lif->lif_removed) {
} else {
}
/* Remove from PIF list */
/* If we were part of a lease, then remove ourselves */
}
if (lif->lif_dad_wait) {
}
}
}
}
/*
* 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
*/
{
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
const char *aname)
{
int fd;
char abuf1[INET6_ADDRSTRLEN];
char abuf2[INET6_ADDRSTRLEN];
return (B_FALSE);
}
"checkaddr: ignoring ioctl error on %s %x: %s",
} else if (isv6) {
struct sockaddr_in6 *sin6 =
"checkaddr: expected %s %s on %s, have %s", aname,
return (B_FALSE);
}
} else {
struct sockaddr_in *sinp =
"checkaddr: expected %s %s on %s, have %s", aname,
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
*/
{
int fd;
"verify_lif: SIOCGLIFFLAGS failed on %s",
}
return (B_FALSE);
}
/*
* If important flags have changed, then abandon the interface.
*/
return (B_FALSE);
}
/*
* Check for delete and recreate.
*/
}
return (B_FALSE);
}
"verify_lif: ifindex on %s changed: %u to %u",
return (B_FALSE);
}
if (pif->pif_under_ipmp) {
}
return (B_FALSE);
}
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.)
*/
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.
*/
"peer address"));
} else {
"netmask"))
return (B_FALSE);
}
}
/*
* 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
{
int fd;
/*
* If there's nothing here, then don't touch the interface. This can
* happen when an already-canonized LIF is recanonized.
*/
return;
}
return;
}
"canonize_lif: cannot clear %s; flags are %llx",
return;
}
if (isv6) {
struct sockaddr_in6 *sin6 =
} else {
struct sockaddr_in *sinv =
}
"canonize_lif: can't clear local address on %s",
}
/* Clearing the address means that we're no longer waiting on DAD */
if (lif->lif_dad_wait) {
}
"canonize_lif: can't clear remote address on %s",
}
} else if (!isv6) {
"canonize_lif: can't clear broadcast address on %s",
}
}
/*
* 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 */
"canonize_lif: can't clear netmask on %s",
} else {
/*
* When the netmask is cleared, the kernel actually sets
* the netmask to 255.0.0.0. So, refetch that netmask.
*/
"canonize_lif: can't reload cleared "
} else {
/* Refetch succeeded, update LIF */
lif->lif_netmask =
}
}
}
}
/*
* 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
*/
{
char abuf[INET6_ADDRSTRLEN];
struct sockaddr_in6 *sin6;
int error;
"plumb_lif: entry for %s already exists!", abuf);
return (NULL);
}
}
/* First, create a new zero-address logical interface */
return (NULL);
}
/* Next, set the netmask to all ones */
goto failure;
}
/* Now set the interface address */
goto failure;
}
/* Mark the interface up */
goto failure;
}
/*
* See comment in set_lif_dhcp().
*/
goto failure;
}
/* Now we can create the internal LIF structure */
goto failure;
return (lif);
}
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
{
if (lif->lif_plumbed) {
}
}
/*
* 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.
*/
}
} else {
}
}
/*
* 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
*/
{
*cp = '\0';
return (NULL);
lname);
return (NULL);
}
/* If LIF creation fails, then insert_lif discards our PIF hold */
}
/*
* 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
{
int fd;
int err;
}
/*
* Check for conflicting sources of address control, and other
* unacceptable configurations.
*/
IFF_VIRTUAL)) {
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 (!is_adopting) {
}
} 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.
*/
return (DHCP_IPC_E_INT);
}
}
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
{
int fd;
return;
return;
}
/*
* 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
{
int fd;
return;
return;
return;
}
/*
* 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.
*/
{
int fd;
return (B_FALSE);
}
/*
* Check for conflicting sources of address control, and other
* unacceptable configurations.
*/
IFF_VIRTUAL)) {
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.
*/
return (B_TRUE);
return (B_TRUE);
return (B_FALSE);
} else {
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.
*/
{
const char *errmsg;
int on = 1;
return (B_FALSE);
}
errmsg = "cannot create v4 socket";
goto failure;
}
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) {
&on, sizeof (int)) == -1) {
errmsg = "cannot set IP_UNSPEC_SRC";
goto failure;
}
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.
*/
sizeof (uchar_t)) == -1) {
errmsg = "cannot set IP_BROADCAST_TTL";
goto failure;
}
sizeof (int)) == -1) {
errmsg = "cannot set IP_BOUND_IF";
goto failure;
}
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.
*/
errmsg = "cannot set IFF_NOFAILOVER";
goto failure;
}
}
/*
* If this is initial bringup, make sure the address we're acquiring a
* lease on is IFF_UP.
*/
/*
* Start from a clean slate.
*/
errmsg = "cannot bring up";
goto failure;
}
/*
* When bringing 0.0.0.0 IFF_UP, the kernel changes the
* netmask to 255.0.0.0, so re-fetch our expected netmask.
*/
errmsg = "cannot get netmask";
goto failure;
}
lif->lif_netmask =
}
/*
* 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.
*/
errmsg = "cannot get IPMP group interface flags";
goto failure;
}
errmsg = "cannot bring up IPMP group interface";
goto failure;
}
}
}
errmsg = "cannot register to receive DHCP packets";
goto failure;
}
return (B_TRUE);
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
{
}
}
}
/*
* 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
{
}
}
/*
* 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
*/
{
/*
* If there's a timer running, cancel it and release its lease
* reference.
*/
if (!cancel_timer(dt))
return (B_FALSE);
}
return (B_TRUE);
} else {
"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
{
return;
if (cancel_timer(dt)) {
"cancel_lif_timer: canceled expiry timer on %s",
} else {
"cancel_lif_timer: cannot cancel timer on %s",
}
}
/*
* cancel_lif_timers(): cancels the LIF-related timers
*
* input: dhcp_lif_t *: the logical interface to operate on
* output: none
*/
void
{
}
/*
* 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
*/
{
if (*mtup == 0) {
/* Set an arbitrary lower bound */
*mtup = 1024;
}
}
}
}
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
*/
{
if (lif->lif_expired)
numexp++;
}
}
if (numlifs == 0)
return (DHCP_EXP_NOLIFS);
else if (numexp == 0)
return (DHCP_EXP_NOEXP);
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.
*/
{
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)
{
/*
* 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.
*/
"remove_v6_strays: cannot read number of interfaces");
numifs = 10;
} else {
}
/*
* 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.
*/
for (;;) {
"remove_v6_strays: cannot allocate memory");
return;
}
errno = 0;
break;
numifs <<= 1;
} else {
return;
}
}
/*
* Get the interface flags; we're interested in the DHCP ones.
*/
continue;
if (!(flags & IFF_DHCPRUNNING))
continue;
/*
* If the interface has a link-local address, then we don't
* control it. Just remove the flag.
*/
continue;
continue;
}
/*
* All others are (or were) under our control. Clean up by
* removing them.
*/
"remove_v6_strays: SIOCLIFREMOVEIF %s",
}
}
}