# # 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 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # Copyright (c) 2016, Chris Fraire . # Copyright 2020 Joyent, Inc. # Copyright 2019 Joshua M. Clulow # PROG = dhcpagent ROOTFS_PROG = $(PROG) DEFAULTFILES = dhcpagent.dfl OBJS = adopt.o agent.o async.o bound.o class_id.o defaults.o inform.o \ init_reboot.o interface.o ipc_action.o packet.o release.o renew.o \ request.o script_handler.o select.o states.o util.o include ../../../Makefile.cmd include ../../../Makefile.ctf POFILES = $(OBJS:%.o=%.po) XGETFLAGS += -a -x dhcpagent.xcl CERRWARN += -Wno-switch CERRWARN += -Wno-parentheses # Hammerhead: Suppress pointer/int cast warnings in legacy dhcpagent code CERRWARN += -Wno-pointer-to-int-cast CERRWARN += -Wno-int-to-pointer-cast CERRWARN += -Wno-incompatible-pointer-types CSTD = $(CSTD_GNU99) # not linted SMATCH=off CPPFLAGS += -D_XOPEN_SOURCE=500 -D__EXTENSIONS__ LDLIBS += -lxnet -lnvpair -ldhcpagent -ldhcputil -linetutil -ldevinfo \ -ldlpi -lresolv -lipadm .KEEP_STATE: all: $(ROOTFS_PROG) install: all $(ROOTSBINPROG) $(ROOTETCDEFAULTFILES) $(PROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) $(POFILE): $(POFILES) $(RM) $@; $(CAT) $(POFILES) > $@; $(RM) $(POFILES) clean: $(RM) $(OBJS) include ../../../Makefile.targ 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 2007 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms. Architectural Overview for the DHCP agent Peter Memishian INTRODUCTION ============ The Solaris DHCP agent (dhcpagent) is a DHCP client implementation compliant with RFCs 2131, 3315, and others. The major forces shaping its design were: * Must be capable of managing multiple network interfaces. * Must consume little CPU, since it will always be running. * Must have a small memory footprint, since it will always be running. * Must not rely on any shared libraries outside of /lib, since it must run before all filesystems have been mounted. When a DHCP agent implementation is only required to control a single interface on a machine, the problem is expressed well as a simple state-machine, as shown in RFC2131. However, when a DHCP agent is responsible for managing more than one interface at a time, the problem becomes much more complicated. This can be resolved using threads or with an event-driven model. Given that DHCP's behavior can be expressed concisely as a state machine, the event-driven model is the closest match. While tried-and-true, that model is subtle and easy to get wrong. Indeed, much of the agent's code is there to manage the complexity of programming in an asynchronous event-driven paradigm. THE BASICS ========== The DHCP agent consists of roughly 30 source files, most with a companion header file. While the largest source file is around 1700 lines, most are much shorter. The source files can largely be broken up into three groups: * Source files that, along with their companion header files, define an abstract "object" that is used by other parts of the system. Examples include "packet.c", which along with "packet.h" provide a Packet object for use by the rest of the agent; and "async.c", which along with "async.h" defines an interface for managing asynchronous transactions within the agent. * Source files that implement a given state of the agent; for instance, there is a "request.c" which comprises all of the procedural "work" which must be done while in the REQUESTING state of the agent. By encapsulating states in files, it becomes easier to debug errors in the client/server protocol and adapt the agent to new constraints, since all the relevant code is in one place. * Source files, which along with their companion header files, encapsulate a given task or related set of tasks. The difference between this and the first group is that the interfaces exported from these files do not operate on an "object", but rather perform a specific task. Examples include "defaults.c", which provides a useful interface to /etc/default/dhcpagent file operations. OVERVIEW ======== Here we discuss the essential objects and subtle aspects of the DHCP agent implementation. Note that there is of course much more that is not discussed here, but after this overview you should be able to fend for yourself in the source code. For details on the DHCPv6 aspects of the design, and how this relates to the implementation present in previous releases of Solaris, see the README.v6 file. Event Handlers and Timer Queues ------------------------------- The most important object in the agent is the event handler, whose interface is in libinetutil.h and whose implementation is in libinetutil. The event handler is essentially an object-oriented wrapper around poll(2): other components of the agent can register to be called back when specific events on file descriptors happen -- for instance, to wait for requests to arrive on its IPC socket, the agent registers a callback function (accept_event()) that will be called back whenever a new connection arrives on the file descriptor associated with the IPC socket. When the agent initially begins in main(), it registers a number of events with the event handler, and then calls iu_handle_events(), which proceeds to wait for events to happen -- this function does not return until the agent is shutdown via signal. When the registered events occur, the callback functions are called back, which in turn might lead to additional callbacks being registered -- this is the classic event-driven model. (As an aside, note that programming in an event-driven model means that callbacks cannot block, or else the agent will become unresponsive.) A special kind of "event" is a timeout. Since there are many timers which must be maintained for each DHCP-controlled interface (such as a lease expiration timer, time-to-first-renewal (t1) timer, and so forth), an object-oriented abstraction to timers called a "timer queue" is provided, whose interface is in libinetutil.h with a corresponding implementation in libinetutil. The timer queue allows callback functions to be "scheduled" for callback after a certain amount of time has passed. The event handler and timer queue objects work hand-in-hand: the event handler is passed a pointer to a timer queue in iu_handle_events() -- from there, it can use the iu_earliest_timer() routine to find the timer which will next fire, and use this to set its timeout value in its call to poll(2). If poll(2) returns due to a timeout, the event handler calls iu_expire_timers() to expire all timers that expired (note that more than one may have expired if, for example, multiple timers were set to expire at the same time). Although it is possible to instantiate more than one timer queue or event handler object, it doesn't make a lot of sense -- these objects are really "singletons". Accordingly, the agent has two global variables, `eh' and `tq', which store pointers to the global event handler and timer queue. Network Interfaces ------------------ For each network interface managed by the agent, there is a set of associated state that describes both its general properties (such as the maximum MTU) and its connections to DHCP-related state (the protocol state machines). This state is stored in a pair of structures called `dhcp_pif_t' (the IP physical interface layer or PIF) and `dhcp_lif_t' (the IP logical interface layer or LIF). Each dhcp_pif_t represents a single physical interface, such as "hme0," for a given IP protocol version (4 or 6), and has a list of dhcp_lif_t structures representing the logical interfaces (such as "hme0:1") in use by the agent. This split is important because of differences between IPv4 and IPv6. For IPv4, each DHCP state machine manages a single IP address and associated configuration data. This corresponds to a single logical interface, which must be specified by the user. For IPv6, however, each DHCP state machine manages a group of addresses, and is associated with DUID value rather than with just an interface. Thus, DHCPv6 behaves more like in.ndpd in its creation of "ADDRCONF" interfaces. The agent automatically plumbs logical interfaces when needed and removes them when the addresses expire. The state for a given session is stored separately in `dhcp_smach_t'. This state machine then points to the main LIF used for I/O, and to a list of `dhcp_lease_t' structures representing individual leases, and each of those points to a list of LIFs corresponding to the individual addresses being managed. One point that was brushed over in the preceding discussion of event handlers and timer queues was context. Recall that the event-driven nature of the agent requires that functions cannot block, lest they starve out others and impact the observed responsiveness of the agent. As an example, consider the process of extending a lease: the agent must send a REQUEST packet and wait for an ACK or NAK packet in response. This is done by sending a REQUEST and then returning to the event handler that waits for an ACK or NAK packet to arrive on the file descriptor associated with the interface. Note however, that when the ACK or NAK does arrive, and the callback function called back, it must know which state machine this packet is for (it must get back its context). This could be handled through an ad-hoc mapping of file descriptors to state machines, but a cleaner approach is to have the event handler's register function (iu_register_event()) take in an opaque context pointer, which will then be passed back to the callback. In the agent, the context pointer used depends on the nature of the event: events on LIFs use the dhcp_lif_t pointer, events on the state machine use dhcp_smach_t, and so on. Note that there is nothing that guarantees the pointer passed into iu_register_event() or iu_schedule_timer() will still be valid when the callback is called back (for instance, the memory may have been freed in the meantime). To solve this problem, all of the data structures used in this way are reference counted. For more details on how the reference count scheme is implemented, see the closing comments in interface.h regarding memory management. Transactions ------------ Many operations performed via DHCP must be performed in groups -- for instance, acquiring a lease requires several steps: sending a DISCOVER, collecting OFFERs, selecting an OFFER, sending a REQUEST, and receiving an ACK, assuming everything goes well. Note however that due to the event-driven model the agent operates in, these operations are not inherently "grouped" -- instead, the agent sends a DISCOVER, goes back into the main event loop, waits for events (perhaps even requests on the IPC channel to begin acquiring a lease on another state machine), eventually checks to see if an acceptable OFFER has come in, and so forth. To some degree, the notion of the state machine's current state (SELECTING, REQUESTING, etc) helps control the potential chaos of the event-driven model (for instance, if while the agent is waiting for an OFFER on a given state machine, an IPC event comes in requesting that the leases be RELEASED, the agent knows to send back an error since the state machine must be in at least the BOUND state before a RELEASE can be performed.) However, states are not enough -- for instance, suppose that the agent begins trying to renew a lease. This is done by sending a REQUEST packet and waiting for an ACK or NAK, which might never come. If, while waiting for the ACK or NAK, the user sends a request to renew the lease as well, then if the agent were to send another REQUEST, things could get quite complicated (and this is only the beginning of this rathole). To protect against this, two objects exist: `async_action' and `ipc_action'. These objects are related, but independent of one another; the more essential object is the `async_action', which we will discuss first. In short, an `async_action' represents a pending transaction (aka asynchronous action), of which each state machine can have at most one. The `async_action' structure is embedded in the `dhcp_smach_t' structure, which is fine since there can be at most one pending transaction per state machine. Typical "asynchronous transactions" are START, EXTEND, and INFORM, since each consists of a sequence of packets that must be done without interruption. Note that not all DHCP operations are "asynchronous" -- for instance, a DHCPv4 RELEASE operation is synchronous (not asynchronous) since after the RELEASE is sent no reply is expected from the DHCP server, but DHCPv6 Release is asynchronous, as all DHCPv6 messages are transactional. Some operations, such as status query, are synchronous and do not affect the system state, and thus do not require sequencing. When the agent realizes it must perform an asynchronous transaction, it calls async_async() to open the transaction. If one is already pending, then the new transaction must fail (the details of failure depend on how the transaction was initiated, which is described in more detail later when the `ipc_action' object is discussed). If there is no pending asynchronous transaction, the operation succeeds. When the transaction is complete, either async_finish() or async_cancel() must be called to complete or cancel the asynchronous action on that state machine. If the transaction is unable to complete within a certain amount of time (more on this later), a timer should be used to cancel the operation. The notion of asynchronous transactions is complicated by the fact that they may originate from both inside and outside of the agent. For instance, a user initiates an asynchronous START transaction when he performs an `ifconfig hme0 dhcp start', but the agent will internally need to perform asynchronous EXTEND transactions to extend the lease before it expires. Note that user-initiated actions always have priority over internal actions: the former will cancel the latter, if necessary. This leads us into the `ipc_action' object. An `ipc_action' represents the IPC-related pieces of an asynchronous transaction that was started as a result of a user request, as well as the `BUSY' state of the administrative interface. Only IPC-generated asynchronous transactions have a valid `ipc_action' object. Note that since there can be at most one asynchronous action per state machine, there can also be at most one `ipc_action' per state machine (this means it can also conveniently be embedded inside the `dhcp_smach_t' structure). One of the main purposes of the `ipc_action' object is to timeout user events. When the user specifies a timeout value as an argument to ifconfig, he is specifying an `ipc_action' timeout; in other words, how long he is willing to wait for the command to complete. When this time expires, the ipc_action is terminated, as well as the asynchronous operation. The API provided for the `ipc_action' object is quite similar to the one for the `async_action' object: when an IPC request comes in for an operation requiring asynchronous operation, ipc_action_start() is called. When the request completes, ipc_action_finish() is called. If the user times out before the request completes, then ipc_action_timeout() is called. Packet Management ----------------- Another complicated area is packet management: building, manipulating, sending and receiving packets. These operations are all encapsulated behind a dozen or so interfaces (see packet.h) that abstract the unimportant details away from the rest of the agent code. In order to send a DHCP packet, code first calls init_pkt(), which returns a dhcp_pkt_t initialized suitably for transmission. Note that currently init_pkt() returns a dhcp_pkt_t that is actually allocated as part of the `dhcp_smach_t', but this may change in the future.. After calling init_pkt(), the add_pkt_opt*() functions are used to add options to the DHCP packet. Finally, send_pkt() and send_pkt_v6() can be used to transmit the packet to a given IP address. The send_pkt() function handles the details of packet timeout and retransmission. The last argument to send_pkt() is a pointer to a "stop function." If this argument is passed as NULL, then the packet will only be sent once (it won't be retransmitted). Otherwise, before each retransmission, the stop function will be called back prior to retransmission. The callback may alter dsm_send_timeout if necessary to place a cap on the next timeout; this is done for DHCPv6 in stop_init_reboot() in order to implement the CNF_MAX_RD constraint. The return value from this function indicates whether to continue retransmission or not, which allows the send_pkt() caller to control the retransmission policy without making it have to deal with the retransmission mechanism. See request.c for an example of this in action. The recv_pkt() function is simpler but still complicated by the fact that one may want to receive several different types of packets at once. The caller registers an event handler on the file descriptor, and then calls recv_pkt() to read in the packet along with meta information about the message (the sender and interface identifier). For IPv6, packet reception is done with a single socket, using IPV6_PKTINFO to determine the actual destination address and receiving interface. Packets are then matched against the state machines on the given interface through the transaction ID. For IPv4, due to oddities in the DHCP specification (discussed in PSARC/2007/571), a special IP_DHCPINIT_IF socket option must be used to allow unicast DHCP traffic to be received on an interface during lease acquisition. Since the IP_DHCPINIT_IF socket option can only enable one interface at a time, one socket must be used per interface. Time ---- The notion of time is an exceptionally subtle area. You will notice five ways that time is represented in the source: as lease_t's, uint32_t's, time_t's, hrtime_t's, and monosec_t's. Each of these types serves a slightly different function. The `lease_t' type is the simplest to understand; it is the unit of time in the CD_{LEASE,T1,T2}_TIME options in a DHCP packet, as defined by RFC2131. This is defined as a positive number of seconds (relative to some fixed point in time) or the value `-1' (DHCP_PERM) which represents infinity (i.e., a permanent lease). The lease_t should be used either when dealing with actual DHCP packets that are sent on the wire or for variables which follow the exact definition given in the RFC. The `uint32_t' type is also used to represent a relative time in seconds. However, here the value `-1' is not special and of course this type is not tied to any definition given in RFC2131. Use this for representing "offsets" from another point in time that are not DHCP lease times. The `time_t' type is the natural Unix type for representing time since the epoch. Unfortunately, it is affected by stime(2) or adjtime(2) and since the DHCP client is used during system installation (and thus when time is typically being configured), the time_t cannot be used in general to represent an absolute time since the epoch. For instance, if a time_t were used to keep track of when a lease began, and then a minute later stime(2) was called to adjust the system clock forward a year, then the lease would appeared to have expired a year ago even though it has only been a minute. For this reason, time_t's should only be used either when wall time must be displayed (such as in DHCP_STATUS ipc transaction) or when a time meaningful across reboots must be obtained (such as when caching an ACK packet at system shutdown). The `hrtime_t' type returned from gethrtime() works around the limitations of the time_t in that it is not affected by stime(2) or adjtime(2), with the disadvantage that it represents time from some arbitrary time in the past and in nanoseconds. The timer queue code deals with hrtime_t's directly since that particular piece of code is meant to be fairly independent of the rest of the DHCP client. However, dealing with nanoseconds is error-prone when all the other time types are in seconds. As a result, yet another time type, the `monosec_t' was created to represent a monotonically increasing time in seconds, and is really no more than (hrtime_t / NANOSEC). Note that this unit is typically used where time_t's would've traditionally been used. The function monosec() in util.c returns the current monosec, and monosec_to_time() can convert a given monosec to wall time, using the system's current notion of time. One additional limitation of the `hrtime_t' and `monosec_t' types is that they are unaware of the passage of time across checkpoint/resume events (e.g., those generated by sys-suspend(8)). For example, if gethrtime() returns time T, and then the machine is suspended for 2 hours, and then gethrtime() is called again, the time returned is not T + (2 * 60 * 60 * NANOSEC), but rather approximately still T. To work around this (and other checkpoint/resume related problems), when a system is resumed, the DHCP client makes the pessimistic assumption that all finite leases have expired while the machine was suspended and must be obtained again. This is known as "refreshing" the leases, and is handled by refresh_smachs(). Note that it appears like a more intelligent approach would be to record the time(2) when the system is suspended, compare that against the time(2) when the system is resumed, and use the delta between them to decide which leases have expired. Sadly, this cannot be done since through at least Solaris 10, it is not possible for userland programs to be notified of system suspend events. Configuration ------------- For the most part, the DHCP client only *retrieves* configuration data from the DHCP server, leaving the configuration to scripts (such as boot scripts), which themselves use dhcpinfo(1) to retrieve the data from the DHCP client. This is desirable because it keeps the mechanism of retrieving the configuration data decoupled from the policy of using the data. However, unless used in "inform" mode, the DHCP client *does* configure each IP interface enough to allow it to communicate with other hosts. Specifically, the DHCP client configures the interface's IP address, netmask, and broadcast address using the information provided by the server. Further, for IPv4 logical interface 0 ("hme0"), any provided default routes are also configured. For IPv6, only the IP addresses are set. The netmask (prefix) is then set automatically by in.ndpd, and routes are discovered in the usual way by router discovery or routing protocols. DHCPv6 doesn't set routes. Since logical interfaces cannot be specified as output interfaces in the kernel forwarding table, and in most cases, logical interfaces share a default route with their associated physical interface, the DHCP client does not automatically add or remove default routes when IPv4 leases are acquired or expired on logical interfaces. Event Scripting --------------- The DHCP client supports user program invocations on DHCP events. The supported events are BOUND, EXTEND, EXPIRE, DROP, RELEASE, and INFORM for DHCPv4, and BUILD6, EXTEND6, EXPIRE6, DROP6, LOSS6, RELEASE6, and INFORM6 for DHCPv6. The user program runs asynchronous to the DHCP client so that the main event loop stays active to process other events, including events triggered by the user program (for example, when it invokes dhcpinfo). The user program execution is part of the transaction of a DHCP command. For example, if the user program is not enabled, the transaction of the DHCP command START is considered over when an ACK is received and the interface is configured successfully. If the user program is enabled, it is invoked after the interface is configured successfully, and the transaction is considered over only when the user program exits. The event scripting implementation makes use of the asynchronous operations discussed in the "Transactions" section. An upper bound of 58 seconds is imposed on how long the user program can run. If the user program does not exit after 55 seconds, the signal SIGTERM is sent to it. If it still does not exit after additional 3 seconds, the signal SIGKILL is sent to it. Since the event handler is a wrapper around poll(), the DHCP client cannot directly observe the completion of the user program. Instead, the DHCP client creates a child "helper" process to synchronously monitor the user program (this process is also used to send the aformentioned signals to the process, if necessary). The DHCP client and the helper process share a pipe which is included in the set of poll descriptors monitored by the DHCP client's event handler. When the user program exits, the helper process passes the user program exit status to the DHCP client through the pipe, informing the DHCP client that the user program has finished. When the DHCP client is asked to shut down, it will wait for any running instances of the user program to complete. 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 2007 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms. ** PLEASE NOTE: ** ** This document discusses aspects of the DHCPv4 client design that have ** since changed (e.g., DLPI is no longer used). However, since those ** aspects affected the DHCPv6 design, the discussion has been left for ** historical record. DHCPv6 Client Low-Level Design Introduction This project adds DHCPv6 client-side (not server) support to Solaris. Future projects may add server-side support as well as enhance the basic capabilities added here. These future projects are not discussed in detail in this document. This document assumes that the reader is familiar with the following other documents: - RFC 3315: the primary description of DHCPv6 - RFCs 2131 and 2132: IPv4 DHCP - RFCs 2461 and 2462: IPv6 NDP and stateless autoconfiguration - RFC 3484: IPv6 default address selection - ifconfig(8): Solaris IP interface configuration - in.ndpd(8): Solaris IPv6 Neighbor and Router Discovery daemon - dhcpagent(8): Solaris DHCP client - dhcpinfo(1): Solaris DHCP parameter utility - ndpd.conf(5): in.ndpd configuration file - netstat(8): Solaris network status utility - snoop(8): Solaris network packet capture and inspection - "DHCPv6 Client High-Level Design" Several terms from those documents (such as the DHCPv6 IA_NA and IAADDR options) are used without further explanation in this document; see the reference documents above for details. The overall plan is to enhance the existing Solaris dhcpagent so that it is able to process DHCPv6. It would also have been possible to create a new, separate daemon process for this, or to integrate the feature into in.ndpd. These alternatives, and the reason for the chosen design, are discussed in Appendix A. This document discusses the internal design issues involved in the protocol implementation, and with the associated components (such as in.ndpd, snoop, and the kernel's source address selection algorithm). It does not discuss the details of the protocol itself, which are more than adequately described in the RFC, nor the individual lines of code, which will be in the code review. As a cross-reference, Appendix B has a summary of the components involved and the changes to each. Background In order to discuss the design changes for DHCPv6, it's necessary first to talk about the current IPv4-only design, and the assumptions built into that design. The main data structure used in dhcpagent is the 'struct ifslist'. Each instance of this structure represents a Solaris logical IP interface under DHCP's control. It also represents the shared state with the DHCP server that granted the address, the address itself, and copies of the negotiated options. There is one list in dhcpagent containing all of the IP interfaces that are under DHCP control. IP interfaces not under DHCP control (for example, those that are statically addressed) are not included in this list, even when plumbed on the system. These ifslist entries are chained like this: ifsheadp -> ifslist -> ifslist -> ifslist -> NULL net0 net0:1 net1 Each ifslist entry contains the address, mask, lease information, interface name, hardware information, packets, protocol state, and timers. The name of the logical IP interface under DHCP's control is also the name used in the administrative interfaces (dhcpinfo, ifconfig) and when logging events. Each entry holds open a DLPI stream and two sockets. The DLPI stream is nulled-out with a filter when not in use, but still consumes system resources. (Most significantly, it causes data copies in the driver layer that end up sapping performance.) The entry storage is managed by a insert/hold/release/remove model and reference counts. In this model, insert_ifs() allocates a new ifslist entry and inserts it into the global list, with the global list holding a reference. remove_ifs() removes it from the global list and drops that reference. hold_ifs() and release_ifs() are used by data structures that refer to ifslist entries, such as timer entries, to make sure that the ifslist entry isn't freed until the timer has been dispatched or deleted. The design is single-threaded, so code that walks the global list needn't bother taking holds on the ifslist structure. Only references that may be used at a different time (i.e., pointers stored in other data structures) need to be recorded. Packets are handled using PKT (struct dhcp; ), PKT_LIST (struct dhcp_list; ), and dhcp_pkt_t (struct dhcp_pkt; "packet.h"). PKT is just the RFC 2131 DHCP packet structure, and has no additional information, such as packet length. PKT_LIST contains a PKT pointer, length, decoded option arrays, and linkage for putting the packet in a list. Finally, dhcp_pkt_t has a PKT pointer and length values suitable for modifying the packet. Essentially, PKT_LIST is a wrapper for received packets, and dhcp_pkt_t is a wrapper for packets to be sent. The basic PKT structure is used in dhcpagent, inetboot, in.dhcpd, libdhcpagent, libdhcputil, and others. PKT_LIST is used in a similar set of places, including the kernel NFS modules. dhcp_pkt_t is (as the header file implies) limited to dhcpagent. In addition to these structures, dhcpagent maintains a set of internal supporting abstractions. Two key ones involved in this project are the "async operation" and the "IPC action." An async operation encapsulates the actions needed for a given operation, so that if cancellation is needed, there's a single point where the associated resources can be freed. An IPC action represents the user state related to the private interface used by ifconfig. DHCPv6 Inherent Differences DHCPv6 naturally has some commonality with IPv4 DHCP, but also has some significant differences. Unlike IPv4 DHCP, DHCPv6 relies on link-local IP addresses to do its work. This means that, on Solaris, the client doesn't need DLPI to perform any of the I/O; regular IP sockets will do the job. It also means that, unlike IPv4 DHCP, DHCPv6 does not need to obtain a lease for the address used in its messages to the server. The system provides the address automatically. IPv4 DHCP expects some messages from the server to be broadcast. DHCPv6 has no such mechanism; all messages from the server to the client are unicast. In the case where the client and server aren't on the same subnet, a relay agent is used to get the unicast replies back to the client's link-local address. With IPv4 DHCP, a single address plus configuration options is leased with a given client ID and a single state machine instance, and the implementation binds that to a single IP logical interface specified by the user. The lease has a "Lease Time," a required option, as well as two timers, called T1 (renew) and T2 (rebind), which are controlled by regular options. DHCPv6 uses a single client/server session to control the acquisition of configuration options and "identity associations" (IAs). The identity associations, in turn, contain lists of addresses for the client to use and the T1/T2 timer values. Each individual address has its own preferred and valid lifetime, with the address being marked "deprecated" at the end of the preferred interval, and removed at the end of the valid interval. IPv4 DHCP leaves many of the retransmit decisions up to the client, and some things (such as RELEASE and DECLINE) are sent just once. Others (such as the REQUEST message used for renew and rebind) are dealt with by heuristics. DHCPv6 treats each message to the server as a separate transaction, and resends each message using a common retransmission mechanism. DHCPv6 also has separate messages for Renew, Rebind, and Confirm rather than reusing the Request mechanism. The set of options (which are used to convey configuration information) for each protocol are distinct. Notably, two of the mistakes from IPv4 DHCP have been fixed: DHCPv6 doesn't carry a client name, and doesn't attempt to impersonate a routing protocol by setting a "default route." Another welcome change is the lack of a netmask/prefix length with DHCPv6. Instead, the client uses the Router Advertisement prefixes to set the correct interface netmask. This reduces the number of databases that need to be kept in sync. (The equivalent mechanism in IPv4 would have been the use of ICMP Address Mask Request / Reply, but the BOOTP designers chose to embed it in the address assignment protocol itself.) Otherwise, DHCPv6 is similar to IPv4 DHCP. The same overall renew/rebind and lease expiry strategy is used, although the state machine events must now take into account multiple IAs and the fact that each can cause RENEWING or REBINDING state independently. DHCPv6 And Solaris The protocol distinctions above have several important implications. For the logical interfaces: - Because Solaris uses IP logical interfaces to configure addresses, we must have multiple IP logical interfaces per IA with IPv6. - Because we need to support multiple addresses (and thus multiple IP logical interfaces) per IA and multiple IAs per client/server session, the IP logical interface name isn't a unique name for the lease. As a result, IP logical interfaces will come and go with DHCPv6, just as happens with the existing stateless address autoconfiguration support in in.ndpd. The logical interface names (visible in ifconfig) have no administrative significance. Fortunately, DHCPv6 does end up with one fixed name that can be used to identify a session. Because DHCPv6 uses link local addresses for communication with the server, the name of the IP logical interface that has this link local address (normally the same as the IP physical interface) can be used as an identifier for dhcpinfo and logging purposes. Dhcpagent Redesign Overview The redesign starts by refactoring the IP interface representation. Because we need to have multiple IP logical interfaces (LIFs) for a single identity association (IA), we should not store all of the DHCP state information along with the LIF information. For DHCPv6, we will need to keep LIFs on a single IP physical interface (PIF) together, so this is probably also a good time to reconsider the way dhcpagent represents physical interfaces. The current design simply replicates the state (notably the DLPI stream, but also the hardware address and other bits) among all of the ifslist entries on the same physical interface. The new design creates two lists of dhcp_pif_t entries, one list for IPv4 and the other for IPv6. Each dhcp_pif_t represents a PIF, with a list of dhcp_lif_t entries attached, each of which represents a LIF used by dhcpagent. This structure mirrors the kernel's ill_t and ipif_t interface representations. Next, the lease-tracking needs to be refactored. DHCPv6 is the functional superset in this case, as it has two lifetimes per address (LIF) and IA groupings with shared T1/T2 timers. To represent these groupings, we will use a new dhcp_lease_t structure. IPv4 DHCP will have one such structure per state machine, while DHCPv6 will have a list. (Note: the initial implementation will have only one lease per DHCPv6 state machine, because each state machine uses a single link-local address, a single DUID+IAID pair, and supports only Non-temporary Addresses [IA_NA option]. Future enhancements may use multiple leases per DHCPv6 state machine or support other IA types.) For all of these new structures, we will use the same insert/hold/ release/remove model as with the original ifslist. Finally, the remaining items (and the bulk of the original ifslist members) are kept on a per-state-machine basis. As this is no longer just an "interface," a new dhcp_smach_t structure will hold these, and the ifslist structure is gone. Lease Representation For DHCPv6, we need to track multiple LIFs per lease (IA), but we also need multiple LIFs per PIF. Rather than having two sets of list linkage for each LIF, we can observe that a LIF is on exactly one PIF and is a member of at most one lease, and then simplify: the lease structure will use a base pointer for the first LIF in the lease, and a count for the number of consecutive LIFs in the PIF's list of LIFs that belong to the lease. When removing a LIF from the system, we need to decrement the count of LIFs in the lease, and advance the base pointer if the LIF being removed is the first one. Inserting a LIF means just moving it into this list and bumping the counter. When removing a lease from a state machine, we need to dispose of the LIFs referenced. If the LIF being disposed is the main LIF for a state machine, then all that we can do is canonize the LIF (returning it to a default state); this represents the normal IPv4 DHCP operation on lease expiry. Otherwise, the lease is the owner of that LIF (it was created because of a DHCPv6 IA), and disposal means unplumbing the LIF from the actual system and removing the LIF entry from the PIF. Main Structure Linkage For IPv4 DHCP, the new linkage is straightforward. Using the same system configuration example as in the initial design discussion: +- lease +- lease +- lease | ^ | ^ | ^ | | | | | | \ smach \ smach \ smach \ ^| \ ^| \ ^| v|v v|v v|v lif ----> lif -> NULL lif -> NULL net0 net0:1 net1 ^ ^ | | v4root -> pif --------------------> pif -> NULL net0 net1 This diagram shows three separate state machines running (with backpointers omitted for clarity). Each state machine has a single "main" LIF with which it's associated (and named). Each also has a single lease structure that points back to the same LIF (count of 1), because IPv4 DHCP controls a single address allocation per state machine. DHCPv6 is a bit more complex. This shows DHCPv6 running on two interfaces (more or fewer interfaces are of course possible) and with multiple leases on the first interface, and each lease with multiple addresses (one with two addresses, the second with one). lease ----------------> lease -> NULL lease -> NULL ^ \(2) |(1) ^ \ (1) | \ | | \ smach \ | smach \ ^ | \ | ^ | \ | v v v | v v lif --> lif --> lif --> lif --> NULL lif --> lif -> NULL net0 net0:1 net0:4 net0:2 net1 net1:5 ^ ^ | | v6root -> pif ----------------------------------> pif -> NULL net0 net1 Note that there's intentionally no ordering based on name in the list of LIFs. Instead, the contiguous LIF structures in that list represent the addresses in each lease. The logical interfaces themselves are allocated and numbered by the system kernel, so they may not be sequential, and there may be gaps in the list if other entities (such as in.ndpd) are also configuring interfaces. Note also that with IPv4 DHCP, the lease points to the LIF that's also the main LIF for the state machine, because that's the IP interface that dhcpagent controls. With DHCPv6, the lease (one per IA structure) points to a separate set of LIFs that are created just for the leased addresses (one per IA address in an IAADDR option). The state machine alone points to the main LIF. Packet Structure Extensions Obviously, we need some DHCPv6 packet data structures and definitions. A new file will be introduced with the necessary #defines and structures. The key structure there will be: struct dhcpv6_message { uint8_t d6m_msg_type; uint8_t d6m_transid_ho; uint16_t d6m_transid_lo; }; typedef struct dhcpv6_message dhcpv6_message_t; This defines the usual (non-relay) DHCPv6 packet header, and is roughly equivalent to PKT for IPv4. Extending dhcp_pkt_t for DHCPv6 is straightforward, as it's used only within dhcpagent. This structure will be amended to use a union for v4/v6 and include a boolean to flag which version is in use. For the PKT_LIST structure, things are more complex. This defines both a queuing mechanism for received packets (typically OFFERs) and a set of packet decoding structures. The decoding structures are highly specific to IPv4 DHCP -- they have no means to handle nested or repeated options (as used heavily in DHCPv6) and make use of the DHCP_OPT structure which is specific to IPv4 DHCP -- and are somewhat expensive in storage, due to the use of arrays indexed by option code number. Worse, this structure is used throughout the system, so changes to it need to be made carefully. (For example, the existing 'pkt' member can't just be turned into a union.) For an initial prototype, since discarded, I created a new dhcp_plist_t structure to represent packet lists as used inside dhcpagent and made dhcp_pkt_t valid for use on input and output. The result is unsatisfying, though, as it results in code that manipulates far too many data structures in common cases; it's a sea of pointers to pointers. The better answer is to use PKT_LIST for both IPv4 and IPv6, adding the few new bits of metadata required to the end (receiving ifIndex, packet source/destination addresses), and staying within the overall existing design. For option parsing, dhcpv6_find_option() and dhcpv6_pkt_option() functions will be added to libdhcputil. The former function will walk a DHCPv6 option list, and provide safe (bounds-checked) access to the options inside. The function can be called recursively, so that option nesting can be handled fairly simply by nested loops, and can be called repeatedly to return each instance of a given option code number. The latter function is just a convenience wrapper on dhcpv6_find_option() that starts with a PKT_LIST pointer and iterates over the top-level options with a given code number. There are two special considerations for the use of these library interfaces: there's no "pad" option for DHCPv6 or alignment requirements on option headers or contents, and nested options always follow a structure that has type-dependent length. This means that code that handles options must all be written to deal with unaligned data, and suboption code must index the pointer past the type-dependent part. Packet Construction Unlike DHCPv4, DHCPv6 places the transaction timer value in an option. The existing code sets the current time value in send_pkt_internal(), which allows it to be updated in a straightforward way when doing retransmits. To make this work in a simple manner for DHCPv6, I added a remove_pkt_opt() function. The update logic just does a remove and re-adds the option. We could also just assume the presence of the option, find it, and modify in place, but the remove feature seems more general. DHCPv6 uses nesting options. To make this work, two new utility functions are needed. First, an add_pkt_subopt() function will take a pointer to an existing option and add an embedded option within it. The packet length and existing option length are updated. If that existing option isn't a top-level option, though, this means that the caller must update the lengths of all of the enclosing options up to the top level. To do this, update_v6opt_len() will be added. This is used in the special case of adding a Status Code option to an IAADDR option within an IA_NA top-level option. Sockets and I/O Handling DHCPv6 doesn't need or use either a DLPI or a broadcast IP socket. Instead, a single unicast-bound IP socket on a link-local address would be the most that is needed. This is roughly equivalent to if_sock_ip_fd in the existing design, but that existing socket is bound only after DHCP reaches BOUND state -- that is, when it switches away from DLPI. We need something different. This, along with the excess of open file descriptors in an otherwise idle daemon and the potentially serious performance problems in leaving DLPI open at all times, argues for a larger redesign of the I/O logic in dhcpagent. The first thing that we can do is eliminate the need for the per-ifslist if_sock_fd. This is used primarily for issuing ioctls to configure interfaces -- a task that would work as well with any open socket -- and is also registered to receive any ACK/NAK packets that may arrive via broadcast. Both of these can be eliminated by creating a pair of global sockets (IPv4 and IPv6), bound and configured for ACK/NAK reception. The only functional difference is that the list of running state machines must be scanned on reception to find the correct transaction ID, but the existing design effectively already goes to this effort because the kernel replicates received datagrams among all matching sockets, and each ifslist entry has a socket open. (The existing code for if_sock_fd makes oblique reference to unknown problems in the system that may prevent binding from working in some cases. The reference dates back some seven years to the original DHCP implementation. I've observed no such problems in extensive testing and if any do show up, they will be dealt with by fixing the underlying bugs.) This leads to an important simplification: it's no longer necessary to register, unregister, and re-register for packet reception while changing state -- register_acknak() and unregister_acknak() are gone. Instead, we always receive, and we dispatch the packets as they arrive. As a result, when receiving a DHCPv4 ACK or DHCPv6 Reply when in BOUND state, we know it's a duplicate, and we can discard. The next part is in minimizing DLPI usage. A DLPI stream is needed at most for each IPv4 PIF, and it's not needed when all of the DHCP instances on that PIF are bound. In fact, the current implementation deals with this in configure_bound() by setting a "blackhole" packet filter. The stream is left open. To simplify this, we will open at most one DLPI stream on a PIF, and use reference counts from the state machines to determine when the stream must be open and when it can be closed. This mechanism will be centralized in a set_smach_state() function that changes the state and opens/closes the DLPI stream when needed. This leads to another simplification. The I/O logic in the existing dhcpagent makes use of the protocol state to select between DLPI and sockets. Now that we keep track of this in a simpler manner, we no longer need to switch out on state in when sending a packet; just test the dsm_using_dlpi flag instead. Still another simplification is in the handling of DHCPv4 INFORM. The current code has separate logic in it for getting the interface state and address information. This is no longer necessary, as the LIF mechanism keeps track of the interface state. And since we have separate lease structures, and INFORM doesn't acquire a lease, we no longer have to be careful about canonizing the interface on shutdown. Although the default is to send all client messages to a well-known multicast address for servers and relays, DHCPv6 also has a mechanism that allows the client to send unicast messages to the server. The operation of this mechanism is slightly complex. First, the server sends the client a unicast address via an option. We may use this address as the destination (rather than the well-known multicast address for local DHCPv6 servers and relays) only if we have a viable local source address. This means using SIOCGDSTINFO each time we try to send unicast. Next, the server may send back a special status code: UseMulticast. If this is received, and if we were actually using unicast in our messages to the server, then we need to forget the unicast address, switch back to multicast, and resend our last message. Note that it's important to avoid the temptation to resend the last message every time UseMulticast is seen, and do it only once on switching back to multicast: otherwise, a potential feedback loop is created. Because IP_PKTINFO (PSARC 2006/466) has integrated, we could go a step further by removing the need for any per-LIF sockets and just use the global sockets for all but DLPI. However, in order to facilitate a Solaris 10 backport, this will be done separately as CR 6509317. In the case of DHCPv6, we already have IPV6_PKTINFO, so we will pave the way for IPv4 by beginning to using this now, and thus have just a single socket (bound to "::") for all of DHCPv6. Doing this requires switching from the old BSD4.2 -lsocket -lnsl to the standards-compliant -lxnet in order to use ancillary data. It may also be possible to remove the need for DLPI for IPv4, and incidentally simplify the code a fair amount, by adding a kernel option to allow transmission and reception of UDP packets over interfaces that are plumbed but not marked IFF_UP. This is left for future work. The State Machine Several parts of the existing state machine need additions to handle DHCPv6, which is a superset of DHCPv4. First, there are the RENEWING and REBINDING states. For IPv4 DHCP, these states map one-to-one with a single address and single lease that's undergoing renewal. It's a simple progression (on timeout) from BOUND, to RENEWING, to REBINDING and finally back to SELECTING to start over. Each retransmit is done by simply rescheduling the T1 or T2 timer. For DHCPv6, things are somewhat more complex. At any one time, there may be multiple IAs (leases) that are effectively in renewing or rebinding state, based on the T1/T2 timers for each IA, and many addresses that have expired. However, because all of the leases are related to a single server, and that server either responds to our requests or doesn't, we can simplify the states to be nearly identical to IPv4 DHCP. The revised definition for use with DHCPv6 is: - Transition from BOUND to RENEWING state when the first T1 timer (of any lease on the state machine) expires. At this point, as an optimization, we should begin attempting to renew any IAs that are within REN_TIMEOUT (10 seconds) of reaching T1 as well. We may as well avoid sending an excess of packets. - When a T1 lease timer expires and we're in RENEWING or REBINDING state, just ignore it, because the transaction is already in progress. - At each retransmit timeout, we should check to see if there are more IAs that need to join in because they've passed point T1 as well, and, if so, add them. This check isn't necessary at this time, because only a single IA_NA is possible with the initial design. - When we reach T2 on any IA and we're in BOUND or RENEWING state, enter REBINDING state. At this point, we have a choice. For those other IAs that are past T1 but not yet at T2, we could ignore them (sending only those that have passed point T2), continue to send separate Renew messages for them, or just include them in the Rebind message. This isn't an issue that must be dealt with for this project, but the plan is to include them in the Rebind message. - When a T2 lease timer expires and we're in REBINDING state, just ignore it, as with the corresponding T1 timer. - As addresses reach the end of their preferred lifetimes, set the IFF_DEPRECATED flag. As they reach the end of the valid lifetime, remove them from the system. When an IA (lease) becomes empty, just remove it. When there are no more leases left, return to SELECTING state to start over. Note that the RFC treats the IAs as separate entities when discussing the renew/rebind T1/T2 timers, but treats them as a unit when doing the initial negotiation. This is, to say the least, confusing, especially so given that there's no reason to expect that after having failed to elicit any responses at all from the server on one IA, the server will suddenly start responding when we attempt to renew some other IA. We rationalize this behavior by using a single renew/rebind state for the entire state machine (and thus client/server pair). There's a subtle timing difference here between DHCPv4 and DHCPv6. For DHCPv4, the client just sends packets more and more frequently (shorter timeouts) as the next state gets nearer. DHCPv6 treats each as a transaction, using the same retransmit logic as for other messages. The DHCPv6 method is a cleaner design, so we will change the DHCPv4 implementation to do the same, and compute the new timer values as part of stop_extending(). Note that it would be possible to start the SELECTING state earlier than waiting for the last lease to expire, and thus avoid a loss of connectivity. However, it this point, there are other servers on the network that have seen us attempting to Rebind for quite some time, and they have not responded. The likelihood that there's a server that will ignore Rebind but then suddenly spring into action on a Solicit message seems low enough that the optimization won't be done now. (Starting SELECTING state earlier may be done in the future, if it's found to be useful.) Persistent State IPv4 DHCP has only minimal need for persistent state, beyond the configuration parameters. The state is stored when "ifconfig dhcp drop" is run or the daemon receives SIGTERM, which is typically done only well after the system is booted and running. The daemon stores this state in /etc/dhcp, because it needs to be available when only the root file system has been mounted. Moreover, dhcpagent starts very early in the boot process. It runs as part of svc:/network/physical:default, which runs well before root is mounted read/write: svc:/system/filesystem/root:default -> svc:/system/metainit:default -> svc:/system/identity:node -> svc:/network/physical:default svc:/network/iscsi_initiator:default -> svc:/network/physical:default and, of course, well before either /var or /usr is mounted. This means that any persistent state must be kept in the root file system, and that if we write before shutdown, we have to cope gracefully with the root file system returning EROFS on write attempts. For DHCPv6, we need to try to keep our stable DUID and IAID values stable across reboots to fulfill the demands of RFC 3315. The DUID is either configured or automatically generated. When configured, it comes from the /etc/default/dhcpagent file, and thus does not need to be saved by the daemon. If automatically generated, there's exactly one of these created, and it will eventually be needed before /usr is mounted, if /usr is mounted over IPv6. This means a new file in the root file system, /etc/dhcp/duid, will be used to hold the automatically generated DUID. The determination of whether to use a configured DUID or one saved in a file is made in get_smach_cid(). This function will encapsulate all of the DUID parsing and generation machinery for the rest of dhcpagent. If root is not writable at the point when dhcpagent starts, and our attempt fails with EROFS, we will set a timer for 60 second intervals to retry the operation periodically. In the unlikely case that it just never succeeds or that we're rebooted before root becomes writable, then the impact will be that the daemon will wake up once a minute and, ultimately, we'll choose a different DUID on next start-up, and we'll thus lose our leases across a reboot. The IAID similarly must be kept stable if at all possible, but cannot be configured by the user. To do make these values stable, we will use two strategies. First the IAID value for a given interface (if not known) will just default to the IP ifIndex value, provided that there's no known saved IAID using that value. Second, we will save off the IAID we choose in a single /etc/dhcp/iaid file, containing an array of entries indexed by logical interface name. Keeping it in a single file allows us to scan for used and unused IAID values when necessary. This mechanism depends on the interface name, and thus will need to be revisited when Clearview vanity naming and NWAM are available. Currently, the boot system (GRUB, OBP, the miniroot) does not support installing over IPv6. This could change in the future, so one of the goals of the above stability plan is to support that event. When running in the miniroot on an x86 system, /etc/dhcp (and the rest of the root) is mounted on a read-only ramdisk. In this case, writing to /etc/dhcp will just never work. A possible solution would be to add a new privileged command in ifconfig that forces dhcpagent to write to an alternate location. The initial install process could then do "ifconfig dhcp write /a" to get the needed state written out to the newly-constructed system root. This part (the new write option) won't be implemented as part of this project, because it's not needed yet. Router Advertisements IPv6 Router Advertisements perform two functions related to DHCPv6: - they specify whether and how to run DHCPv6 on a given interface. - they provide a list of the valid prefixes on an interface. For the first function, in.ndpd needs to use the same DHCP control interfaces that ifconfig uses, so that it can launch dhcpagent and trigger DHCPv6 when necessary. Note that it never needs to shut down DHCPv6, as router advertisements can't do that. However, launching dhcpagent presents new problems. As a part of the "Quagga SMF Modifications" project (PSARC 2006/552), in.ndpd in Nevada is now privilege-aware and runs with limited privileges, courtesy of SMF. Dhcpagent, on the other hand, must run with all privileges. A simple work-around for this issue is to rip out the "privileges=" clause from the method_credential for in.ndpd. I've taken this direction initially, but the right longer-term answer seems to be converting dhcpagent into an SMF service. This is quite a bit more complex, as it means turning the /sbin/dhcpagent command line interface into a utility that manipulates the service and passes the command line options via IPC extensions. Such a design also begs the question of whether dhcpagent itself ought to run with reduced privileges. It could, but it still needs the ability to grant "all" (traditional UNIX root) privileges to the eventhook script, if present. There seem to be few ways to do this, though it's a good area for research. The second function, prefix handling, is also subtle. Unlike IPv4 DHCP, DHCPv6 does not give the netmask or prefix length along with the leased address. The client is on its own to determine the right netmask to use. This is where the advertised prefixes come in: these must be used to finish the interface configuration. We will have the DHCPv6 client configure each interface with an all-ones (/128) netmask by default. In.ndpd will be modified so that when it detects a new IFF_DHCPRUNNING IP logical interface, it checks for a known matching prefix, and sets the netmask as necessary. If no matching prefix is known, it will send a new Router Solicitation message to try to find one. When in.ndpd learns of a new prefix from a Router Advertisement, it will scan all of the IFF_DHCPRUNNING IP logical interfaces on the same physical interface and set the netmasks when necessary. Dhcpagent, for its part, will ignore the netmask on IPv6 interfaces when checking for changes that would require it to "abandon" the interface. Given the way that DHCPv6 and in.ndpd control both the horizontal and the vertical in plumbing and removing logical interfaces, and users do not, it might be worthwhile to consider roping off any direct user changes to IPv6 logical interfaces under control of in.ndpd or dhcpagent, and instead force users through a higher-level interface. This won't be done as part of this project, however. ARP Hardware Types There are multiple places within the DHCPv6 client where the mapping of DLPI MAC type to ARP Hardware Type is required: - When we are constructing an automatic, stable DUID for our own identity, we prefer to use a DUID-LLT if possible. This is done by finding a link-layer interface, opening it, reading the MAC address and type, and translating in the make_stable_duid() function in libdhcpagent. - When we translate a user-configured DUID from /etc/default/dhcpagent into a binary representation, we may have to deal with a physical interface name. In this case, we must open that interface and read the MAC address and type. - As part of the PIF data structure initialization, we need to read out the MAC type so that it can be used in the BOOTP/DHCPv4 'htype' field. Ideally, these would all be provided by a single libdlpi implementation. However, that project is on-going at this time and has not yet integrated. For the time being, a dlpi_to_arp() translation function (taking dl_mac_type and returning an ARP Hardware Type number) will be placed in libdhcputil. This temporary function should be removed and this section of the code updated when the new libdlpi from Clearview integrates. Field Mappings Old (all in ifslist) New next dhcp_smach_t.dsm_next prev dhcp_smach_t.dsm_prev if_hold_count dhcp_smach_t.dsm_hold_count if_ia dhcp_smach_t.dsm_ia if_async dhcp_smach_t.dsm_async if_state dhcp_smach_t.dsm_state if_dflags dhcp_smach_t.dsm_dflags if_name dhcp_smach_t.dsm_name (see text) if_index dhcp_pif_t.pif_index if_max dhcp_pif_t.pif_mtu if_min (was unused; removed) if_opt (was unused; removed) if_hwaddr dhcp_pif_t.pif_hwaddr if_hwlen dhcp_pif_t.pif_hwlen if_hwtype dhcp_pif_t.pif_hwtype if_cid dhcp_smach_t.dsm_cid if_cidlen dhcp_smach_t.dsm_cidlen if_prl dhcp_smach_t.dsm_prl if_prllen dhcp_smach_t.dsm_prllen if_daddr dhcp_pif_t.pif_daddr if_dlen dhcp_pif_t.pif_dlen if_saplen dhcp_pif_t.pif_saplen if_sap_before dhcp_pif_t.pif_sap_before if_dlpi_fd dhcp_pif_t.pif_dlpi_fd if_sock_fd v4_sock_fd and v6_sock_fd (globals) if_sock_ip_fd dhcp_lif_t.lif_sock_ip_fd if_timer (see text) if_t1 dhcp_lease_t.dl_t1 if_t2 dhcp_lease_t.dl_t2 if_lease dhcp_lif_t.lif_expire if_nrouters dhcp_smach_t.dsm_nrouters if_routers dhcp_smach_t.dsm_routers if_server dhcp_smach_t.dsm_server if_addr dhcp_lif_t.lif_v6addr if_netmask dhcp_lif_t.lif_v6mask if_broadcast dhcp_lif_t.lif_v6peer if_ack dhcp_smach_t.dsm_ack if_orig_ack dhcp_smach_t.dsm_orig_ack if_offer_wait dhcp_smach_t.dsm_offer_wait if_offer_timer dhcp_smach_t.dsm_offer_timer if_offer_id dhcp_pif_t.pif_dlpi_id if_acknak_id dhcp_lif_t.lif_acknak_id if_acknak_bcast_id v4_acknak_bcast_id (global) if_neg_monosec dhcp_smach_t.dsm_neg_monosec if_newstart_monosec dhcp_smach_t.dsm_newstart_monosec if_curstart_monosec dhcp_smach_t.dsm_curstart_monosec if_disc_secs dhcp_smach_t.dsm_disc_secs if_reqhost dhcp_smach_t.dsm_reqhost if_recv_pkt_list dhcp_smach_t.dsm_recv_pkt_list if_sent dhcp_smach_t.dsm_sent if_received dhcp_smach_t.dsm_received if_bad_offers dhcp_smach_t.dsm_bad_offers if_send_pkt dhcp_smach_t.dsm_send_pkt if_send_timeout dhcp_smach_t.dsm_send_timeout if_send_dest dhcp_smach_t.dsm_send_dest if_send_stop_func dhcp_smach_t.dsm_send_stop_func if_packet_sent dhcp_smach_t.dsm_packet_sent if_retrans_timer dhcp_smach_t.dsm_retrans_timer if_script_fd dhcp_smach_t.dsm_script_fd if_script_pid dhcp_smach_t.dsm_script_pid if_script_helper_pid dhcp_smach_t.dsm_script_helper_pid if_script_event dhcp_smach_t.dsm_script_event if_script_event_id dhcp_smach_t.dsm_script_event_id if_callback_msg dhcp_smach_t.dsm_callback_msg if_script_callback dhcp_smach_t.dsm_script_callback Notes: - The dsm_name field currently just points to the lif_name on the controlling LIF. This may need to be named differently in the future; perhaps when Zones are supported. - The timer mechanism will be refactored. Rather than using the separate if_timer[] array to hold the timer IDs and if_{t1,t2,lease} to hold the relative timer values, we will gather this information into a dhcp_timer_t structure: dt_id timer ID value dt_start relative start time New fields not accounted for above: dhcp_pif_t.pif_next linkage in global list of PIFs dhcp_pif_t.pif_prev linkage in global list of PIFs dhcp_pif_t.pif_lifs pointer to list of LIFs on this PIF dhcp_pif_t.pif_isv6 IPv6 flag dhcp_pif_t.pif_dlpi_count number of state machines using DLPI dhcp_pif_t.pif_hold_count reference count dhcp_pif_t.pif_name name of physical interface dhcp_lif_t.lif_next linkage in per-PIF list of LIFs dhcp_lif_t.lif_prev linkage in per-PIF list of LIFs dhcp_lif_t.lif_pif backpointer to parent PIF dhcp_lif_t.lif_smachs pointer to list of state machines dhcp_lif_t.lif_lease backpointer to lease holding LIF dhcp_lif_t.lif_flags interface flags (IFF_*) dhcp_lif_t.lif_hold_count reference count dhcp_lif_t.lif_dad_wait waiting for DAD resolution flag dhcp_lif_t.lif_removed removed from list flag dhcp_lif_t.lif_plumbed plumbed by dhcpagent flag dhcp_lif_t.lif_expired lease has expired flag dhcp_lif_t.lif_declined reason to refuse this address (string) dhcp_lif_t.lif_iaid unique and stable 32-bit identifier dhcp_lif_t.lif_iaid_id timer for delayed /etc writes dhcp_lif_t.lif_preferred preferred timer for v6; deprecate after dhcp_lif_t.lif_name name of logical interface dhcp_smach_t.dsm_lif controlling (main) LIF dhcp_smach_t.dsm_leases pointer to list of leases dhcp_smach_t.dsm_lif_wait number of LIFs waiting on DAD dhcp_smach_t.dsm_lif_down number of LIFs that have failed dhcp_smach_t.dsm_using_dlpi currently using DLPI flag dhcp_smach_t.dsm_send_tcenter v4 central timer value; v6 MRT dhcp_lease_t.dl_next linkage in per-state-machine list of leases dhcp_lease_t.dl_prev linkage in per-state-machine list of leases dhcp_lease_t.dl_smach back pointer to state machine dhcp_lease_t.dl_lifs pointer to first LIF configured by lease dhcp_lease_t.dl_nlifs number of configured consecutive LIFs dhcp_lease_t.dl_hold_count reference counter dhcp_lease_t.dl_removed removed from list flag dhcp_lease_t.dl_stale lease was not updated by Renew/Rebind Snoop The snoop changes are fairly straightforward. As snoop just decodes the messages, and the message format is quite different between DHCPv4 and DHCPv6, a new module will be created to handle DHCPv6 decoding, and will export a interpret_dhcpv6() function. The one bit of commonality between the two protocols is the use of ARP Hardware Type numbers, which are found in the underlying BOOTP message format for DHCPv4 and in the DUID-LL and DUID-LLT construction for DHCPv6. To simplify this, the existing static show_htype() function in snoop_dhcp.c will be renamed to arp_htype() (to better reflect its functionality), updated with more modern hardware types, moved to snoop_arp.c (where it belongs), and made a public symbol within snoop. While I'm there, I'll update snoop_arp.c so that when it prints an ARP message in verbose mode, it uses arp_htype() to translate the ar_hrd value. The snoop updates also involve the addition of a new "dhcp6" keyword for filtering. As a part of this, CR 6487534 will be fixed. IPv6 Source Address Selection One of the customer requests for DHCPv6 is to be able to predict the address selection behavior in the presence of both stateful and stateless addresses on the same network. Solaris implements RFC 3484 address selection behavior. In this scheme, the first seven rules implement some basic preferences for addresses, with Rule 8 being a deterministic tie breaker. Rule 8 relies on a special function, CommonPrefixLen, defined in the RFC, that compares leading bits of the address without regard to configured prefix length. As Rule 1 eliminates equal addresses, this always picks a single address. This rule, though, allows for additional checks: Rule 8 may be superseded if the implementation has other means of choosing among source addresses. For example, if the implementation somehow knows which source address will result in the "best" communications performance. We will thus split Rule 8 into three separate rules: - First, compare on configured prefix. The interface with the longest configured prefix length that also matches the candidate address will be preferred. - Next, check the type of address. Prefer statically configured addresses above all others. Next, those from DHCPv6. Next, stateless autoconfigured addresses. Finally, temporary addresses. (Note that Rule 7 will take care of temporary address preferences, so that this rule doesn't actually need to look at them.) - Finally, run the check-all-bits (CommonPrefixLen) tie breaker. The result of this is that if there's a local address in the same configured prefix, then we'll prefer that over other addresses. If there are multiple to choose from, then will pick static first, then DHCPv6, then dynamic. Finally, if there are still multiples, we'll use the "closest" address, bitwise. Also, this basic implementation scheme also addresses CR 6485164, so a fix for that will be included with this project. Minor Improvements Various small problems with the system encountered during development will be fixed along with this project. Some of these are: - List of ARPHRD_* types is a bit short; add some new ones. - List of IPPORT_* values is similarly sparse; add others in use by snoop. - dhcpmsg.h lacks PRINTFLIKE for dhcpmsg(); add it. - CR 6482163 causes excessive lint errors with libxnet; will fix. - libdhcpagent uses gettimeofday() for I/O timing, and this can drift on systems with NTP. It should use a stable time source (gethrtime()) instead, and should return better error values. - Controlling debug mode in the daemon shouldn't require changing the command line arguments or jumping through special hoops. I've added undocumented ".DEBUG_LEVEL=[0-3]" and ".VERBOSE=[01]" features to /etc/default/dhcpagent. - The various attributes of the IPC commands (requires privileges, creates a new session, valid with BOOTP, immediate reply) should be gathered together into one look-up table rather than scattered as hard-coded tests. - Remove the event unregistration from the command dispatch loop and get rid of the ipc_action_pending() botch. We'll get a zero-length read any time the client goes away, and that will be enough to trigger termination. This fix removes async_pending() and async_timeout() as well, and fixes CR 6487958 as a side-effect. - Throughout the dhcpagent code, there are private implementations of doubly-linked and singly-linked lists for each data type. These will all be removed and replaced with insque(3C) and remque(3C). Testing The implementation was tested using the TAHI test suite for DHCPv6 (www.tahi.org). There are some peculiar aspects to this test suite, and these issues directed some of the design. In particular: - If Renew/Rebind doesn't mention one of our leases, then we need to allow the message to be retransmitted. Real servers are unlikely to do this. - We must look for a status code within IAADDR and within IA_NA, and handle the paradoxical case of "NoAddrAvail." That doesn't make sense, as a server with no addresses wouldn't use those options. That option makes more sense at the top level of the message. - If we get "UseMulticast" when we were already using multicast, then ignore the error code. Sending another request would cause a loop. - TAHI uses "NoBinding" at the top level of the message. This status code only makes sense within an IA, as it refers to the GUID:IAID binding, which doesn't exist outside an IA. We must ignore such errors -- treat them as success. Interactions With Other Projects Clearview UV (vanity naming) will cause link names, and thus IP interface names, to become changeable over time. This will break the IAID stability mechanism if UV is used for arbitrary renaming, rather than as just a DR enhancement. When this portion of Clearview integrates, this part of the DHCPv6 design may need to be revisited. (The solution will likely be handled at some higher layer, such as within Network Automagic.) Clearview is also contributing a new libdlpi that will work for dhcpagent, and is thus removing the private dlpi_io.[ch] functions from this daemon. When that Clearview project integrates, the DHCPv6 project will need to adjust to the new interfaces, and remove or relocate the dlpi_to_arp() function. Futures Zones currently cannot address any IP interfaces by way of DHCP. This project will not fix that problem, but the DUID/IAID could be used to help fix it in the future. In particular, the DUID allows the client to obtain separate sets of addresses and configuration parameters on a single interface, just like an IPv4 Client ID, but it includes a clean mechanism for vendor extensions. If we associate the DUID with the zone identifier or name through an extension, then we have a really simple way of allocating per-zone addresses. Moreover, RFC 4361 describes a handy way of using DHCPv6 DUID/IAID values with IPv4 DHCP, which would quickly solve the problem of using DHCP for IPv4 address assignment in non-global zones as well. (One potential risk with this plan is that there may be server implementations that either do not implement the RFC correctly or otherwise mishandle the DUID. This has apparently bitten some early adopters.) Implementing the FQDN option for DHCPv6 would, given the current libdhcputil design, require a new 'type' of entry for the inittab6 file. This is because the design does not allow for any simple means to ``compose'' a sequence of basic types together. Thus, every type of option must either be a basic type, or an array of multiple instances of the same basic type. If we implement FQDN in the future, it may be useful to explore some means of allowing a given option instance to be a sequence of basic types. This project does not make the DNS resolver or any other subsystem use the data gathered by DHCPv6. It just makes the data available through dhcpinfo(1). Future projects should modify those services to use configuration data learned via DHCPv6. (One of the reasons this is not being done now is that Network Automagic [NWAM] will likely be changing this area substantially in the very near future, and thus the effort would be largely wasted.) Appendix A - Choice of Venue There are three logical places to implement DHCPv6: - in dhcpagent - in in.ndpd - in a new daemon (say, 'dhcp6agent') We need to access parameters via dhcpinfo, and should provide the same set of status and control features via ifconfig as are present for IPv4. (For the latter, if we fail to do that, it will likely confuse users. The expense for doing it is comparatively small, and it will be useful for testing, even though it should not be needed in normal operation.) If we implement somewhere other than dhcpagent, then we need to give that new daemon (in.ndpd or dhcp6agent) the same basic IPC features as dhcpagent already has. This means either extracting those bits (async.c and ipc_action.c) into a shared library or just copying them. Obviously, the former would be preferred, but as those bits depend on the rest of the dhcpagent infrastructure for timers and state handling, this means that the new process would have to look a lot like dhcpagent. Implementing DHCPv6 as part of in.ndpd is attractive, as it eliminates the confusion that the router discovery process for determining interface netmasks can cause, along with the need to do any signaling at all to bring DHCPv6 up. However, the need to make in.ndpd more like dhcpagent is unattractive. Having a new dhcp6agent daemon seems to have little to recommend it, other than leaving the existing dhcpagent code untouched. If we do that, then we end up with two implementations that do many similar things, and must be maintained in parallel. Thus, although it leads to some complexity in reworking the data structures to fit both protocols, on balance the simplest solution is to extend dhcpagent. Appendix B - Cross-Reference in.ndpd - Start dhcpagent and issue "dhcp start" command via libdhcpagent - Parse StatefulAddrConf interface option from ndpd.conf - Watch for M and O bits to trigger DHCPv6 - Handle "no routers found" case and start DHCPv6 - Track prefixes and set prefix length on IFF_DHCPRUNNING aliases - Send new Router Solicitation when prefix unknown - Change privileges so that dhcpagent can be launched successfully libdhcputil - Parse new /etc/dhcp/inittab6 file - Handle new UNUMBER24, SNUMBER64, IPV6, DUID and DOMAIN types - Add DHCPv6 option iterators (dhcpv6_find_option and dhcpv6_pkt_option) - Add dlpi_to_arp function (temporary) libdhcpagent - Add stable DUID and IAID creation and storage support functions and add new dhcp_stable.h include file - Support new DECLINING and RELEASING states introduced by DHCPv6. - Update implementation so that it doesn't rely on gettimeofday() for I/O timeouts - Extend the hostconf functions to support DHCPv6, using a new ".dh6" file snoop - Add support for DHCPv6 packet decoding (all types) - Add "dhcp6" filter keyword - Fix known bugs in DHCP filtering ifconfig - Remove inet-only restriction on "dhcp" keyword netstat - Remove strange "-I list" feature. - Add support for DHCPv6 and iterating over IPv6 interfaces. ip - Add extensions to IPv6 source address selection to prefer DHCPv6 addresses when all else is equal - Fix known bugs in source address selection (remaining from TX integration) other - Add ifindex and source/destination address into PKT_LIST. - Add more ARPHDR_* and IPPORT_* values. /* * 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. * * ADOPTING state of the client state machine. This is used only during * diskless boot with IPv4. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "agent.h" #include "async.h" #include "util.h" #include "packet.h" #include "interface.h" #include "states.h" typedef struct { char dk_if_name[IFNAMSIZ]; char dk_ack[1]; } dhcp_kcache_t; static int get_dhcp_kcache(dhcp_kcache_t **, size_t *); static boolean_t get_prom_prop(const char *, const char *, uchar_t **, uint_t *); /* * dhcp_adopt(): adopts the interface managed by the kernel for diskless boot * * input: void * output: boolean_t: B_TRUE success, B_FALSE on failure */ boolean_t dhcp_adopt(void) { int retval; dhcp_kcache_t *kcache = NULL; size_t kcache_size; PKT_LIST *plp = NULL; dhcp_lif_t *lif; dhcp_smach_t *dsmp = NULL; uint_t client_id_len; retval = get_dhcp_kcache(&kcache, &kcache_size); if (retval == 0 || kcache_size < sizeof (dhcp_kcache_t)) { dhcpmsg(MSG_CRIT, "dhcp_adopt: cannot fetch kernel cache"); goto failure; } dhcpmsg(MSG_DEBUG, "dhcp_adopt: fetched %s kcache", kcache->dk_if_name); /* * convert the kernel's ACK into binary */ plp = alloc_pkt_entry(strlen(kcache->dk_ack) / 2, B_FALSE); if (plp == NULL) goto failure; dhcpmsg(MSG_DEBUG, "dhcp_adopt: allocated ACK of %d bytes", plp->len); if (hexascii_to_octet(kcache->dk_ack, plp->len * 2, plp->pkt, &plp->len) != 0) { dhcpmsg(MSG_CRIT, "dhcp_adopt: cannot convert kernel ACK"); goto failure; } if (dhcp_options_scan(plp, B_TRUE) != 0) { dhcpmsg(MSG_CRIT, "dhcp_adopt: cannot parse kernel ACK"); goto failure; } /* * make an interface to represent the "cached interface" in * the kernel, hook up the ACK packet we made, and send out * the extend request (to attempt to renew the lease). * * we do a send_extend() instead of doing a dhcp_init_reboot() * because although dhcp_init_reboot() is more correct from a * protocol perspective, it introduces a window where a * diskless client has no IP address but may need to page in * more of this program. we could mlockall(), but that's * going to be a mess, especially with handling malloc() and * stack growth, so it's easier to just renew(). the only * catch here is that if we are not granted a renewal, we're * totally hosed and can only bail out. */ if ((lif = attach_lif(kcache->dk_if_name, B_FALSE, &retval)) == NULL) { dhcpmsg(MSG_ERROR, "dhcp_adopt: unable to attach %s: %d", kcache->dk_if_name, retval); goto failure; } if ((dsmp = insert_smach(lif, &retval)) == NULL) { dhcpmsg(MSG_ERROR, "dhcp_adopt: unable to create state " "machine for %s: %d", kcache->dk_if_name, retval); goto failure; } /* * If the agent is adopting a lease, then OBP is initially * searched for a client-id. */ dhcpmsg(MSG_DEBUG, "dhcp_adopt: getting /chosen:clientid property"); client_id_len = 0; if (!get_prom_prop("chosen", "client-id", &dsmp->dsm_cid, &client_id_len)) { /* * a failure occurred trying to acquire the client-id */ dhcpmsg(MSG_DEBUG, "dhcp_adopt: cannot allocate client id for %s", dsmp->dsm_name); goto failure; } else if (dsmp->dsm_hwtype == ARPHRD_IB && dsmp->dsm_cid == NULL) { /* * when the interface is infiniband and the agent * is adopting the lease there must be an OBP * client-id. */ dhcpmsg(MSG_DEBUG, "dhcp_adopt: no /chosen:clientid id for %s", dsmp->dsm_name); goto failure; } dsmp->dsm_cidlen = client_id_len; if (set_lif_dhcp(lif) != DHCP_IPC_SUCCESS) goto failure; if (!set_smach_state(dsmp, ADOPTING)) goto failure; dsmp->dsm_dflags = DHCP_IF_PRIMARY; /* * move to BOUND and use the information in our ACK packet. * adoption will continue after DAD via dhcp_adopt_complete. */ if (!dhcp_bound(dsmp, plp)) { dhcpmsg(MSG_CRIT, "dhcp_adopt: cannot use cached packet"); goto failure; } free(kcache); return (B_TRUE); failure: /* Note: no need to free lif; dsmp holds reference */ if (dsmp != NULL) remove_smach(dsmp); free(kcache); free_pkt_entry(plp); return (B_FALSE); } /* * dhcp_adopt_complete(): completes interface adoption process after kernel * duplicate address detection (DAD) is done. * * input: dhcp_smach_t *: the state machine on which a lease is being adopted * output: none */ void dhcp_adopt_complete(dhcp_smach_t *dsmp) { dhcpmsg(MSG_DEBUG, "dhcp_adopt_complete: completing adoption"); if (async_start(dsmp, DHCP_EXTEND, B_FALSE) == 0) { dhcpmsg(MSG_CRIT, "dhcp_adopt_complete: async_start failed"); return; } if (dhcp_extending(dsmp) == 0) { dhcpmsg(MSG_CRIT, "dhcp_adopt_complete: cannot send renew request"); return; } if (grandparent != (pid_t)0) { dhcpmsg(MSG_DEBUG, "adoption complete, signalling parent (%ld)" " to exit.", grandparent); (void) kill(grandparent, SIGALRM); } } /* * get_dhcp_kcache(): fetches the DHCP ACK and interface name from the kernel * * input: dhcp_kcache_t **: a dynamically-allocated cache packet * size_t *: the length of that packet (on return) * output: int: nonzero on success, zero on failure */ static int get_dhcp_kcache(dhcp_kcache_t **kernel_cachep, size_t *kcache_size) { char dummy; long size; size = sysinfo(SI_DHCP_CACHE, &dummy, sizeof (dummy)); if (size == -1) return (0); *kcache_size = size; *kernel_cachep = malloc(*kcache_size); if (*kernel_cachep == NULL) return (0); (void) sysinfo(SI_DHCP_CACHE, (caddr_t)*kernel_cachep, size); return (1); } /* * get_prom_prop(): get the value of the named property on the named node in * devinfo root. * * input: const char *: The name of the node containing the property. * const char *: The name of the property. * uchar_t **: The property value, modified iff B_TRUE is returned. * If no value is found the value is set to NULL. * uint_t *: The length of the property value * output: boolean_t: Returns B_TRUE if successful (no problems), * otherwise B_FALSE. * note: The memory allocated by this function must be freed by * the caller. */ static boolean_t get_prom_prop(const char *nodename, const char *propname, uchar_t **propvaluep, uint_t *lenp) { di_node_t root_node; di_node_t node; di_prom_handle_t phdl = DI_PROM_HANDLE_NIL; di_prom_prop_t pp; uchar_t *value = NULL; unsigned int len = 0; boolean_t success = B_TRUE; /* * locate root node */ if ((root_node = di_init("/", DINFOCPYALL)) == DI_NODE_NIL || (phdl = di_prom_init()) == DI_PROM_HANDLE_NIL) { dhcpmsg(MSG_DEBUG, "get_prom_prop: property root node " "not found"); goto get_prom_prop_cleanup; } /* * locate nodename within '/' */ for (node = di_child_node(root_node); node != DI_NODE_NIL; node = di_sibling_node(node)) { if (strcmp(di_node_name(node), nodename) == 0) { break; } } if (node == DI_NODE_NIL) { dhcpmsg(MSG_DEBUG, "get_prom_prop: node not found"); goto get_prom_prop_cleanup; } /* * scan all properties of /nodename for the 'propname' property */ for (pp = di_prom_prop_next(phdl, node, DI_PROM_PROP_NIL); pp != DI_PROM_PROP_NIL; pp = di_prom_prop_next(phdl, node, pp)) { dhcpmsg(MSG_DEBUG, "get_prom_prop: property = %s", di_prom_prop_name(pp)); if (strcmp(propname, di_prom_prop_name(pp)) == 0) { break; } } if (pp == DI_PROM_PROP_NIL) { dhcpmsg(MSG_DEBUG, "get_prom_prop: property not found"); goto get_prom_prop_cleanup; } /* * get the property; allocate some memory copy it out */ len = di_prom_prop_data(pp, (uchar_t **)&value); if (value == NULL) { /* * property data read problems */ success = B_FALSE; dhcpmsg(MSG_ERR, "get_prom_prop: cannot read property data"); goto get_prom_prop_cleanup; } if (propvaluep != NULL) { /* * allocate somewhere to copy the property value to */ *propvaluep = calloc(len, sizeof (uchar_t)); if (*propvaluep == NULL) { /* * allocation problems */ success = B_FALSE; dhcpmsg(MSG_ERR, "get_prom_prop: cannot allocate " "memory for property value"); goto get_prom_prop_cleanup; } /* * copy data out */ (void) memcpy(*propvaluep, value, len); /* * copy out the length if a suitable pointer has * been supplied */ if (lenp != NULL) { *lenp = len; } dhcpmsg(MSG_DEBUG, "get_prom_prop: property value " "length = %d", len); } get_prom_prop_cleanup: if (phdl != DI_PROM_HANDLE_NIL) { di_prom_fini(phdl); } if (root_node != DI_NODE_NIL) { di_fini(root_node); } return (success); } /* * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016-2017, Chris Fraire . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "async.h" #include "agent.h" #include "script_handler.h" #include "util.h" #include "class_id.h" #include "states.h" #include "packet.h" #include "interface.h" #include "defaults.h" #ifndef TEXT_DOMAIN #define TEXT_DOMAIN "SYS_TEST" #endif iu_timer_id_t inactivity_id; int class_id_len = 0; char *class_id; iu_eh_t *eh; iu_tq_t *tq; pid_t grandparent; int rtsock_fd; static boolean_t shutdown_started = B_FALSE; static boolean_t do_adopt = B_FALSE; static unsigned int debug_level = 0; static iu_eh_callback_t accept_event, ipc_event, rtsock_event; static void dhcp_smach_set_msg_reqhost(dhcp_smach_t *dsmp, ipc_action_t *iap); static DHCP_OPT * dhcp_get_ack_or_state(const dhcp_smach_t *dsmp, const PKT_LIST *plp, uint_t codenum, boolean_t *did_alloc); /* * The ipc_cmd_allowed[] table indicates which IPC commands are allowed in * which states; a non-zero value indicates the command is permitted. * * START is permitted if the state machine is fresh, or if we are in the * process of trying to obtain a lease (as a convenience to save the * administrator from having to do an explicit DROP). EXTEND, RELEASE, and * GET_TAG require a lease to be obtained in order to make sense. INFORM is * permitted if the interface is fresh or has an INFORM in progress or * previously done on it -- otherwise a DROP or RELEASE is first required. * PING and STATUS always make sense and thus are always permitted, as is DROP * in order to permit the administrator to always bail out. */ static int ipc_cmd_allowed[DHCP_NSTATES][DHCP_NIPC] = { /* D E P R S S I G */ /* R X I E T T N E */ /* O T N L A A F T */ /* P E G E R T O _ */ /* . N . A T U R T */ /* . D . S . S M A */ /* . . . E . . . G */ /* INIT */ { 1, 0, 1, 0, 1, 1, 1, 0 }, /* SELECTING */ { 1, 0, 1, 0, 1, 1, 0, 0 }, /* REQUESTING */ { 1, 0, 1, 0, 1, 1, 0, 0 }, /* PRE_BOUND */ { 1, 1, 1, 1, 0, 1, 0, 1 }, /* BOUND */ { 1, 1, 1, 1, 0, 1, 0, 1 }, /* RENEWING */ { 1, 1, 1, 1, 0, 1, 0, 1 }, /* REBINDING */ { 1, 1, 1, 1, 0, 1, 0, 1 }, /* INFORMATION */ { 1, 0, 1, 0, 1, 1, 1, 1 }, /* INIT_REBOOT */ { 1, 0, 1, 1, 1, 1, 0, 0 }, /* ADOPTING */ { 1, 0, 1, 1, 0, 1, 0, 0 }, /* INFORM_SENT */ { 1, 0, 1, 0, 1, 1, 1, 0 }, /* DECLINING */ { 1, 1, 1, 1, 0, 1, 0, 1 }, /* RELEASING */ { 1, 0, 1, 0, 0, 1, 0, 1 }, }; #define CMD_ISPRIV 0x1 /* Command requires privileges */ #define CMD_CREATE 0x2 /* Command creates an interface */ #define CMD_BOOTP 0x4 /* Command is valid with BOOTP */ #define CMD_IMMED 0x8 /* Reply is immediate (no BUSY state) */ static uint_t ipc_cmd_flags[DHCP_NIPC] = { /* DHCP_DROP */ CMD_ISPRIV|CMD_BOOTP, /* DHCP_EXTEND */ CMD_ISPRIV, /* DHCP_PING */ CMD_BOOTP|CMD_IMMED, /* DHCP_RELEASE */ CMD_ISPRIV, /* DHCP_START */ CMD_CREATE|CMD_ISPRIV|CMD_BOOTP, /* DHCP_STATUS */ CMD_BOOTP|CMD_IMMED, /* DHCP_INFORM */ CMD_CREATE|CMD_ISPRIV, /* DHCP_GET_TAG */ CMD_BOOTP|CMD_IMMED }; static boolean_t is_iscsi_active(void); int main(int argc, char **argv) { boolean_t is_daemon = B_TRUE; boolean_t is_verbose; int ipc_fd; int c; int aware = RTAW_UNDER_IPMP; struct rlimit rl; debug_level = df_get_int("", B_FALSE, DF_DEBUG_LEVEL); is_verbose = df_get_bool("", B_FALSE, DF_VERBOSE); /* * -l is ignored for compatibility with old agent. */ while ((c = getopt(argc, argv, "vd:l:fa")) != EOF) { switch (c) { case 'a': do_adopt = B_TRUE; grandparent = getpid(); break; case 'd': debug_level = strtoul(optarg, NULL, 0); break; case 'f': is_daemon = B_FALSE; break; case 'v': is_verbose = B_TRUE; break; case '?': (void) fprintf(stderr, "usage: %s [-a] [-d n] [-f] [-v]" "\n", argv[0]); return (EXIT_FAILURE); default: break; } } (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); if (geteuid() != 0) { dhcpmsg_init(argv[0], B_FALSE, is_verbose, debug_level); dhcpmsg(MSG_ERROR, "must be super-user"); dhcpmsg_fini(); return (EXIT_FAILURE); } if (is_daemon && daemonize() == 0) { dhcpmsg_init(argv[0], B_FALSE, is_verbose, debug_level); dhcpmsg(MSG_ERR, "cannot become daemon, exiting"); dhcpmsg_fini(); return (EXIT_FAILURE); } /* * Seed the random number generator, since we're going to need it * to set transaction id's and for exponential backoff. */ srand48(gethrtime() ^ gethostid() ^ getpid()); dhcpmsg_init(argv[0], is_daemon, is_verbose, debug_level); (void) atexit(dhcpmsg_fini); tq = iu_tq_create(); eh = iu_eh_create(); if (eh == NULL || tq == NULL) { errno = ENOMEM; dhcpmsg(MSG_ERR, "cannot create timer queue or event handler"); return (EXIT_FAILURE); } /* * ignore most signals that could be reasonably generated. */ (void) signal(SIGTERM, graceful_shutdown); (void) signal(SIGQUIT, graceful_shutdown); (void) signal(SIGPIPE, SIG_IGN); (void) signal(SIGUSR1, SIG_IGN); (void) signal(SIGUSR2, SIG_IGN); (void) signal(SIGINT, SIG_IGN); (void) signal(SIGHUP, SIG_IGN); (void) signal(SIGCHLD, SIG_IGN); /* * upon SIGTHAW we need to refresh any non-infinite leases. */ (void) iu_eh_register_signal(eh, SIGTHAW, refresh_smachs, NULL); class_id = get_class_id(); if (class_id != NULL) class_id_len = strlen(class_id); else dhcpmsg(MSG_WARNING, "get_class_id failed, continuing " "with no vendor class id"); /* * the inactivity timer is enabled any time there are no * interfaces under DHCP control. if DHCP_INACTIVITY_WAIT * seconds transpire without an interface under DHCP control, * the agent shuts down. */ inactivity_id = iu_schedule_timer(tq, DHCP_INACTIVITY_WAIT, inactivity_shutdown, NULL); /* * max out the number available descriptors, just in case.. */ rl.rlim_cur = RLIM_INFINITY; rl.rlim_max = RLIM_INFINITY; if (setrlimit(RLIMIT_NOFILE, &rl) == -1) dhcpmsg(MSG_ERR, "setrlimit failed"); (void) enable_extended_FILE_stdio(-1, -1); /* * Create and bind default IP sockets used to control interfaces and to * catch stray packets. */ if (!dhcp_ip_default()) return (EXIT_FAILURE); /* * create the ipc channel that the agent will listen for * requests on, and register it with the event handler so that * `accept_event' will be called back. */ switch (dhcp_ipc_init(&ipc_fd)) { case 0: break; case DHCP_IPC_E_BIND: dhcpmsg(MSG_ERROR, "dhcp_ipc_init: cannot bind to port " "%i (agent already running?)", IPPORT_DHCPAGENT); return (EXIT_FAILURE); default: dhcpmsg(MSG_ERROR, "dhcp_ipc_init failed"); return (EXIT_FAILURE); } if (iu_register_event(eh, ipc_fd, POLLIN, accept_event, 0) == -1) { dhcpmsg(MSG_ERR, "cannot register ipc fd for messages"); return (EXIT_FAILURE); } /* * Create the global routing socket. This is used for monitoring * interface transitions, so that we learn about the kernel's Duplicate * Address Detection status, and for inserting and removing default * routes as learned from DHCP servers. Both v4 and v6 are handed * with this one socket. */ rtsock_fd = socket(PF_ROUTE, SOCK_RAW, 0); if (rtsock_fd == -1) { dhcpmsg(MSG_ERR, "cannot open routing socket"); return (EXIT_FAILURE); } /* * We're IPMP-aware and can manage IPMP test addresses, so issue * RT_AWARE to get routing socket messages for interfaces under IPMP. */ if (setsockopt(rtsock_fd, SOL_ROUTE, RT_AWARE, &aware, sizeof (aware)) == -1) { dhcpmsg(MSG_ERR, "cannot set RT_AWARE on routing socket"); return (EXIT_FAILURE); } if (iu_register_event(eh, rtsock_fd, POLLIN, rtsock_event, 0) == -1) { dhcpmsg(MSG_ERR, "cannot register routing socket for messages"); return (EXIT_FAILURE); } /* * if the -a (adopt) option was specified, try to adopt the * kernel-managed interface before we start. */ if (do_adopt && !dhcp_adopt()) return (EXIT_FAILURE); /* * For DHCPv6, we own all of the interfaces marked DHCPRUNNING. As * we're starting operation here, if there are any of those interfaces * lingering around, they're strays, and need to be removed. * * It might be nice to save these addresses off somewhere -- for both * v4 and v6 -- and use them as hints for later negotiation. */ remove_v6_strays(); /* * enter the main event loop; this is where all the real work * takes place (through registering events and scheduling timers). * this function only returns when the agent is shutting down. */ switch (iu_handle_events(eh, tq)) { case -1: dhcpmsg(MSG_WARNING, "iu_handle_events exited abnormally"); break; case DHCP_REASON_INACTIVITY: dhcpmsg(MSG_INFO, "no interfaces to manage, shutting down..."); break; case DHCP_REASON_TERMINATE: dhcpmsg(MSG_INFO, "received SIGTERM, shutting down..."); break; case DHCP_REASON_SIGNAL: dhcpmsg(MSG_WARNING, "received unexpected signal, shutting " "down..."); break; } (void) iu_eh_unregister_signal(eh, SIGTHAW, NULL); iu_eh_destroy(eh); iu_tq_destroy(tq); return (EXIT_SUCCESS); } /* * drain_script(): event loop callback during shutdown * * input: eh_t *: unused * void *: unused * output: boolean_t: B_TRUE if event loop should exit; B_FALSE otherwise */ /* ARGSUSED */ boolean_t drain_script(iu_eh_t *ehp, void *arg) { if (shutdown_started == B_FALSE) { shutdown_started = B_TRUE; /* * Check if the system is diskless client and/or * there are active iSCSI sessions * * Do not drop the lease, or the system will be * unable to sync(dump) through nfs/iSCSI driver */ if (!do_adopt && !is_iscsi_active()) { nuke_smach_list(); } } return (script_count == 0); } /* * accept_event(): accepts a new connection on the ipc socket and registers * to receive its messages with the event handler * * input: iu_eh_t *: unused * int: the file descriptor in the iu_eh_t * the connection came in on * (other arguments unused) * output: void */ /* ARGSUSED */ static void accept_event(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) { int client_fd; int is_priv; if (dhcp_ipc_accept(fd, &client_fd, &is_priv) != 0) { dhcpmsg(MSG_ERR, "accept_event: accept on ipc socket"); return; } if (iu_register_event(eh, client_fd, POLLIN, ipc_event, (void *)is_priv) == -1) { dhcpmsg(MSG_ERROR, "accept_event: cannot register ipc socket " "for callback"); } } /* * ipc_event(): processes incoming ipc requests * * input: iu_eh_t *: unused * int: the file descriptor in the iu_eh_t * the request came in on * short: unused * iu_event_id_t: event ID * void *: indicates whether the request is from a privileged client * output: void */ /* ARGSUSED */ static void ipc_event(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) { ipc_action_t ia, *iap; dhcp_smach_t *dsmp; int error, is_priv = (int)arg; const char *ifname; boolean_t isv6; boolean_t dsm_created = B_FALSE; ipc_action_init(&ia); error = dhcp_ipc_recv_request(fd, &ia.ia_request, DHCP_IPC_REQUEST_WAIT); if (error != DHCP_IPC_SUCCESS) { if (error != DHCP_IPC_E_EOF) { dhcpmsg(MSG_ERROR, "ipc_event: dhcp_ipc_recv_request failed: %s", dhcp_ipc_strerror(error)); } else { dhcpmsg(MSG_DEBUG, "ipc_event: connection closed"); } if ((dsmp = lookup_smach_by_event(id)) != NULL) { ipc_action_finish(dsmp, error); } else { (void) iu_unregister_event(eh, id, NULL); (void) dhcp_ipc_close(fd); } return; } /* Fill in temporary ipc_action structure for utility functions */ ia.ia_cmd = DHCP_IPC_CMD(ia.ia_request->message_type); ia.ia_fd = fd; ia.ia_eid = id; if (ia.ia_cmd >= DHCP_NIPC) { dhcpmsg(MSG_ERROR, "ipc_event: invalid command (%s) attempted on %s", dhcp_ipc_type_to_string(ia.ia_cmd), ia.ia_request->ifname); send_error_reply(&ia, DHCP_IPC_E_CMD_UNKNOWN); return; } /* return EPERM for any of the privileged actions */ if (!is_priv && (ipc_cmd_flags[ia.ia_cmd] & CMD_ISPRIV)) { dhcpmsg(MSG_WARNING, "ipc_event: privileged ipc command (%s) attempted on %s", dhcp_ipc_type_to_string(ia.ia_cmd), ia.ia_request->ifname); send_error_reply(&ia, DHCP_IPC_E_PERM); return; } /* * Try to locate the state machine associated with this command. If * the command is DHCP_START or DHCP_INFORM and there isn't a state * machine already, make one (there may already be one from a previous * failed attempt to START or INFORM). Otherwise, verify the reference * is still valid. * * The interface name may be blank. In that case, we look up the * primary interface, and the requested type (v4 or v6) doesn't matter. */ isv6 = (ia.ia_request->message_type & DHCP_V6) != 0; ifname = ia.ia_request->ifname; if (*ifname == '\0') dsmp = primary_smach(isv6); else dsmp = lookup_smach(ifname, isv6); if (dsmp != NULL) { /* Note that verify_smach drops a reference */ hold_smach(dsmp); if (!verify_smach(dsmp)) dsmp = NULL; } if (dsmp == NULL) { /* * If the user asked for the primary DHCP interface by giving * an empty string and there is no primary, then check if we're * handling dhcpinfo. If so, then simulate primary selection. * Otherwise, report failure. */ if (ifname[0] == '\0') { if (ia.ia_cmd == DHCP_GET_TAG) dsmp = info_primary_smach(isv6); if (dsmp == NULL) error = DHCP_IPC_E_NOPRIMARY; /* * If there's no interface, and we're starting up, then create * it now, along with a state machine for it. Note that if * insert_smach fails, it discards the LIF reference. */ } else if (ipc_cmd_flags[ia.ia_cmd] & CMD_CREATE) { dhcp_lif_t *lif; lif = attach_lif(ifname, isv6, &error); if (lif != NULL && (dsmp = insert_smach(lif, &error)) != NULL) { /* * Get client ID for logical interface. (V4 * only, because V6 plumbs its own interfaces.) */ error = get_smach_cid(dsmp); if (error != DHCP_IPC_SUCCESS) { remove_smach(dsmp); dsmp = NULL; } dsm_created = (dsmp != NULL); } /* * Otherwise, this is an operation on an unknown interface. */ } else { error = DHCP_IPC_E_UNKIF; } if (dsmp == NULL) { send_error_reply(&ia, error); return; } } /* * If this is a request for DHCP to manage a lease on an address, * ensure that IFF_DHCPRUNNING is set (we don't set this when the lif * is created because the lif may have been created for INFORM). */ if (ia.ia_cmd == DHCP_START && (error = set_lif_dhcp(dsmp->dsm_lif)) != DHCP_IPC_SUCCESS) { if (dsm_created) remove_smach(dsmp); send_error_reply(&ia, error); return; } if ((dsmp->dsm_dflags & DHCP_IF_BOOTP) && !(ipc_cmd_flags[ia.ia_cmd] & CMD_BOOTP)) { dhcpmsg(MSG_ERROR, "command %s not valid for BOOTP on %s", dhcp_ipc_type_to_string(ia.ia_cmd), dsmp->dsm_name); send_error_reply(&ia, DHCP_IPC_E_BOOTP); return; } /* * verify that the state machine is in a state which will allow the * command. we do this up front so that we can return an error * *before* needlessly cancelling an in-progress transaction. */ if (!check_cmd_allowed(dsmp->dsm_state, ia.ia_cmd)) { dhcpmsg(MSG_DEBUG, "in state %s; not allowing %s command on %s", dhcp_state_to_string(dsmp->dsm_state), dhcp_ipc_type_to_string(ia.ia_cmd), dsmp->dsm_name); send_error_reply(&ia, ia.ia_cmd == DHCP_START && dsmp->dsm_state != INIT ? DHCP_IPC_E_RUNNING : DHCP_IPC_E_OUTSTATE); return; } dhcpmsg(MSG_DEBUG, "in state %s; allowing %s command on %s", dhcp_state_to_string(dsmp->dsm_state), dhcp_ipc_type_to_string(ia.ia_cmd), dsmp->dsm_name); if ((ia.ia_request->message_type & DHCP_PRIMARY) && is_priv) make_primary(dsmp); /* * The current design dictates that there can be only one outstanding * transaction per state machine -- this simplifies the code * considerably and also fits well with RFCs 2131 and 3315. It is * worth classifying the different DHCP commands into synchronous * (those which we will handle now and reply to immediately) and * asynchronous (those which require transactions and will be completed * at an indeterminate time in the future): * * DROP: removes the agent's management of a state machine. * asynchronous as the script program may be invoked. * * PING: checks to see if the agent has a named state machine. * synchronous, since no packets need to be sent * to the DHCP server. * * STATUS: returns information about a state machine. * synchronous, since no packets need to be sent * to the DHCP server. * * RELEASE: releases the agent's management of a state machine * and brings the associated interfaces down. asynchronous * as the script program may be invoked. * * EXTEND: renews a lease. asynchronous, since the agent * needs to wait for an ACK, etc. * * START: starts DHCP on a named state machine. asynchronous since * the agent needs to wait for OFFERs, ACKs, etc. * * INFORM: obtains configuration parameters for the system using * externally configured interface. asynchronous, since the * agent needs to wait for an ACK. * * Notice that EXTEND, INFORM, START, DROP and RELEASE are * asynchronous. Notice also that asynchronous commands may occur from * within the agent -- for instance, the agent will need to do implicit * EXTENDs to extend the lease. In order to make the code simpler, the * following rules apply for asynchronous commands: * * There can only be one asynchronous command at a time per state * machine. The current asynchronous command is managed by the async_* * api: async_start(), async_finish(), and async_cancel(). * async_start() starts management of a new asynchronous command on an * state machine, which should only be done after async_cancel() to * terminate a previous command. When the command is completed, * async_finish() should be called. * * Asynchronous commands started by a user command have an associated * ipc_action which provides the agent with information for how to get * in touch with the user command when the action completes. These * ipc_action records also have an associated timeout which may be * infinite. ipc_action_start() should be called when starting an * asynchronous command requested by a user, which sets up the timer * and keeps track of the ipc information (file descriptor, request * type). When the asynchronous command completes, ipc_action_finish() * should be called to return a command status code to the user and * close the ipc connection). If the command does not complete before * the timer fires, ipc_action_timeout() is called which closes the ipc * connection and returns DHCP_IPC_E_TIMEOUT to the user. Note that * independent of ipc_action_timeout(), ipc_action_finish() should be * called. * * on a case-by-case basis, here is what happens (per state machine): * * o When an asynchronous command is requested, then * async_cancel() is called to terminate any non-user * action in progress. If there's a user action running, * the user command is sent DHCP_IPC_E_PEND. * * o otherwise, the transaction is started with * async_start(). if the transaction is on behalf * of a user, ipc_action_start() is called to keep * track of the ipc information and set up the * ipc_action timer. * * o if the command completes normally and before a * timeout fires, then async_finish() is called. * if there was an associated ipc_action, * ipc_action_finish() is called to complete it. * * o if the command fails before a timeout fires, then * async_finish() is called, and the state machine is * is returned to a known state based on the command. * if there was an associated ipc_action, * ipc_action_finish() is called to complete it. * * o if the ipc_action timer fires before command * completion, then DHCP_IPC_E_TIMEOUT is returned to * the user. however, the transaction continues to * be carried out asynchronously. */ if (ipc_cmd_flags[ia.ia_cmd] & CMD_IMMED) { /* * Only immediate commands (ping, status, get_tag) need to * worry about freeing ia through one of the reply functions * before returning. */ iap = &ia; } else { /* * if shutdown request has been received, send back an error. */ if (shutdown_started) { send_error_reply(&ia, DHCP_IPC_E_OUTSTATE); return; } if (dsmp->dsm_dflags & DHCP_IF_BUSY) { send_error_reply(&ia, DHCP_IPC_E_PEND); return; } if (!ipc_action_start(dsmp, &ia)) { dhcpmsg(MSG_WARNING, "ipc_event: ipc_action_start " "failed for %s", dsmp->dsm_name); send_error_reply(&ia, DHCP_IPC_E_MEMORY); return; } /* Action structure consumed by above function */ iap = &dsmp->dsm_ia; } switch (iap->ia_cmd) { case DHCP_DROP: if (dsmp->dsm_droprelease) break; dsmp->dsm_droprelease = B_TRUE; /* * Ensure that a timer associated with the existing state * doesn't pop while we're waiting for the script to complete. * (If so, chaos can result -- e.g., a timer causes us to end * up in dhcp_selecting() would start acquiring a new lease on * dsmp while our DHCP_DROP dismantling is ongoing.) */ cancel_smach_timers(dsmp); (void) script_start(dsmp, isv6 ? EVENT_DROP6 : EVENT_DROP, dhcp_drop, NULL, NULL); break; /* not an immediate function */ case DHCP_EXTEND: dhcp_smach_set_msg_reqhost(dsmp, iap); (void) dhcp_extending(dsmp); break; case DHCP_GET_TAG: { dhcp_optnum_t optnum; void *opt = NULL; uint_t optlen; boolean_t did_alloc = B_FALSE; PKT_LIST *ack = dsmp->dsm_ack; int i; /* * verify the request makes sense. */ if (iap->ia_request->data_type != DHCP_TYPE_OPTNUM || iap->ia_request->data_length != sizeof (dhcp_optnum_t)) { send_error_reply(iap, DHCP_IPC_E_PROTO); break; } (void) memcpy(&optnum, iap->ia_request->buffer, sizeof (dhcp_optnum_t)); load_option: switch (optnum.category) { case DSYM_SITE: /* FALLTHRU */ case DSYM_STANDARD: for (i = 0; i < dsmp->dsm_pillen; i++) { if (dsmp->dsm_pil[i] == optnum.code) break; } if (i < dsmp->dsm_pillen) break; if (isv6) { opt = dhcpv6_pkt_option(ack, NULL, optnum.code, NULL); } else { opt = dhcp_get_ack_or_state(dsmp, ack, optnum.code, &did_alloc); } break; case DSYM_VENDOR: if (isv6) { dhcpv6_option_t *d6o; uint32_t ent; /* * Look through vendor options to find our * enterprise number. */ d6o = NULL; for (;;) { d6o = dhcpv6_pkt_option(ack, d6o, DHCPV6_OPT_VENDOR_OPT, &optlen); if (d6o == NULL) break; optlen -= sizeof (*d6o); if (optlen < sizeof (ent)) continue; (void) memcpy(&ent, d6o + 1, sizeof (ent)); if (ntohl(ent) != DHCPV6_SUN_ENT) continue; break; } if (d6o != NULL) { /* * Now find the requested vendor option * within the vendor options block. */ opt = dhcpv6_find_option( (char *)(d6o + 1) + sizeof (ent), optlen - sizeof (ent), NULL, optnum.code, NULL); } } else { /* * the test against VS_OPTION_START is broken * up into two tests to avoid compiler warnings * under intel. */ if ((optnum.code > VS_OPTION_START || optnum.code == VS_OPTION_START) && optnum.code <= VS_OPTION_END) opt = ack->vs[optnum.code]; } break; case DSYM_FIELD: if (isv6) { dhcpv6_message_t *d6m = (dhcpv6_message_t *)ack->pkt; dhcpv6_option_t *d6o; /* Validate the packet field the user wants */ optlen = optnum.code + optnum.size; if (d6m->d6m_msg_type == DHCPV6_MSG_RELAY_FORW || d6m->d6m_msg_type == DHCPV6_MSG_RELAY_REPL) { if (optlen > sizeof (dhcpv6_relay_t)) break; } else { if (optlen > sizeof (*d6m)) break; } opt = malloc(sizeof (*d6o) + optnum.size); if (opt != NULL) { d6o = opt; d6o->d6o_code = htons(optnum.code); d6o->d6o_len = htons(optnum.size); (void) memcpy(d6o + 1, (caddr_t)d6m + optnum.code, optnum.size); } } else { if (optnum.code + optnum.size > sizeof (PKT)) break; opt = malloc(optnum.size + DHCP_OPT_META_LEN); if (opt != NULL) { DHCP_OPT *v4opt = opt; v4opt->len = optnum.size; v4opt->code = optnum.code; (void) memcpy(v4opt->value, (caddr_t)ack->pkt + optnum.code, optnum.size); } } if (opt == NULL) { send_error_reply(iap, DHCP_IPC_E_MEMORY); return; } did_alloc = B_TRUE; break; default: send_error_reply(iap, DHCP_IPC_E_PROTO); return; } /* * return the option payload, if there was one. */ if (opt != NULL) { if (isv6) { dhcpv6_option_t d6ov; (void) memcpy(&d6ov, opt, sizeof (d6ov)); optlen = ntohs(d6ov.d6o_len) + sizeof (d6ov); } else { optlen = ((DHCP_OPT *)opt)->len + DHCP_OPT_META_LEN; } send_data_reply(iap, 0, DHCP_TYPE_OPTION, opt, optlen); if (did_alloc) free(opt); break; } else if (ack != dsmp->dsm_orig_ack) { /* * There wasn't any definition for the option in the * current ack, so now retry with the original ack if * the original ack is not the current ack. */ ack = dsmp->dsm_orig_ack; goto load_option; } /* * note that an "okay" response is returned either in * the case of an unknown option or a known option * with no payload. this is okay (for now) since * dhcpinfo checks whether an option is valid before * ever performing ipc with the agent. */ send_ok_reply(iap); break; } case DHCP_INFORM: dhcp_inform(dsmp); /* next destination: dhcp_acknak() */ break; /* not an immediate function */ case DHCP_PING: if (dsmp->dsm_dflags & DHCP_IF_FAILED) send_error_reply(iap, DHCP_IPC_E_FAILEDIF); else send_ok_reply(iap); break; case DHCP_RELEASE: if (dsmp->dsm_droprelease) break; dsmp->dsm_droprelease = B_TRUE; cancel_smach_timers(dsmp); /* see comment in DHCP_DROP above */ (void) script_start(dsmp, isv6 ? EVENT_RELEASE6 : EVENT_RELEASE, dhcp_release, "Finished with lease.", NULL); break; /* not an immediate function */ case DHCP_START: { PKT_LIST *ack, *oack; PKT_LIST *plp[2]; deprecate_leases(dsmp); dhcp_smach_set_msg_reqhost(dsmp, iap); /* * if we have a valid hostconf lying around, then jump * into INIT_REBOOT. if it fails, we'll end up going * through the whole selecting() procedure again. */ error = read_hostconf(dsmp->dsm_name, plp, 2, dsmp->dsm_isv6); ack = error > 0 ? plp[0] : NULL; oack = error > 1 ? plp[1] : NULL; /* * If the allocation of the old ack fails, that's fine; * continue without it. */ if (oack == NULL) oack = ack; /* * As long as we've allocated something, start using it. */ if (ack != NULL) { dsmp->dsm_orig_ack = oack; dsmp->dsm_ack = ack; dhcp_init_reboot(dsmp); /* next destination: dhcp_acknak() */ break; } /* * if not debugging, wait for a few seconds before * going into SELECTING. */ if (debug_level != 0 || !set_start_timer(dsmp)) { dhcp_selecting(dsmp); /* next destination: dhcp_requesting() */ } /* else next destination: dhcp_start() */ } break; case DHCP_STATUS: { dhcp_status_t status; dhcp_lease_t *dlp; status.if_began = monosec_to_time(dsmp->dsm_curstart_monosec); /* * We return information on just the first lease as being * representative of the lot. A better status mechanism is * needed. */ dlp = dsmp->dsm_leases; if (dlp == NULL || dlp->dl_lifs->lif_expire.dt_start == DHCP_PERM) { status.if_t1 = DHCP_PERM; status.if_t2 = DHCP_PERM; status.if_lease = DHCP_PERM; } else { status.if_t1 = status.if_began + dlp->dl_t1.dt_start; status.if_t2 = status.if_began + dlp->dl_t2.dt_start; status.if_lease = status.if_began + dlp->dl_lifs->lif_expire.dt_start; } status.version = DHCP_STATUS_VER; status.if_state = dsmp->dsm_state; status.if_dflags = dsmp->dsm_dflags; status.if_sent = dsmp->dsm_sent; status.if_recv = dsmp->dsm_received; status.if_bad_offers = dsmp->dsm_bad_offers; (void) strlcpy(status.if_name, dsmp->dsm_name, LIFNAMSIZ); send_data_reply(iap, 0, DHCP_TYPE_STATUS, &status, sizeof (dhcp_status_t)); break; } } } /* * dhcp_smach_set_msg_reqhost(): set dsm_msg_reqhost based on the message * content of a DHCP IPC message * * input: dhcp_smach_t *: the state machine instance; * ipc_action_t *: the decoded DHCP IPC message; * output: void */ static void dhcp_smach_set_msg_reqhost(dhcp_smach_t *dsmp, ipc_action_t *iap) { DHCP_OPT *d4o; dhcp_symbol_t *entry; char *value; if (dsmp->dsm_msg_reqhost != NULL) { dhcpmsg(MSG_DEBUG, "dhcp_smach_set_msg_reqhost: nullify former value, %s", dsmp->dsm_msg_reqhost); free(dsmp->dsm_msg_reqhost); dsmp->dsm_msg_reqhost = NULL; } /* * if a STANDARD/HOSTNAME was sent in the IPC request, then copy that * value into the state machine data if decoding succeeds. Otherwise, * log to indicate at what step the decoding stopped. */ if (dsmp->dsm_isv6) { dhcpmsg(MSG_DEBUG, "dhcp_smach_set_msg_reqhost: ipv6 is not" " handled"); return; } else if (iap->ia_request->data_type != DHCP_TYPE_OPTION) { dhcpmsg(MSG_DEBUG, "dhcp_smach_set_msg_reqhost: request type" " %d is not DHCP_TYPE_OPTION", iap->ia_request->data_type); return; } if (iap->ia_request->data_length <= DHCP_OPT_META_LEN) { dhcpmsg(MSG_WARNING, "dhcp_smach_set_msg_reqhost:" " DHCP_TYPE_OPTION ia_request buffer is short"); return; } d4o = (DHCP_OPT *)iap->ia_request->buffer; if (d4o->code != CD_HOSTNAME) { dhcpmsg(MSG_DEBUG, "dhcp_smach_set_msg_reqhost: ignoring DHCPv4" " option %u", d4o->code); return; } else if (iap->ia_request->data_length - DHCP_OPT_META_LEN != d4o->len) { dhcpmsg(MSG_WARNING, "dhcp_smach_set_msg_reqhost:" " unexpected DHCP_OPT buffer length %u for CD_HOSTNAME" " option length %u", iap->ia_request->data_length, d4o->len); return; } entry = inittab_getbycode(ITAB_CAT_STANDARD, ITAB_CONS_INFO, CD_HOSTNAME); if (entry == NULL) { dhcpmsg(MSG_WARNING, "dhcp_smach_set_msg_reqhost: error getting" " ITAB_CAT_STANDARD ITAB_CONS_INFO" " CD_HOSTNAME entry"); return; } value = inittab_decode(entry, d4o->value, d4o->len, /* just_payload */ B_TRUE); if (value == NULL) { dhcpmsg(MSG_WARNING, "dhcp_smach_set_msg_reqhost: error decoding" " CD_HOSTNAME value from DHCP_OPT"); } else { dhcpmsg(MSG_DEBUG, "dhcp_smach_set_msg_reqhost: host %s", value); free(dsmp->dsm_msg_reqhost); dsmp->dsm_msg_reqhost = value; } free(entry); } /* * dhcp_get_ack_or_state(): get a v4 option from the ACK or from the state * machine state for certain codes that are not ACKed (e.g., CD_CLIENT_ID) * * input: dhcp_smach_t *: the state machine instance; * PKT_LIST *: the decoded DHCP IPC message; * uint_t: the DHCP client option code; * boolean_t *: a pointer to a value that will be set to B_TRUE if * the return value must be freed (or else set to B_FALSE); * output: the option if found or else NULL. */ static DHCP_OPT * dhcp_get_ack_or_state(const dhcp_smach_t *dsmp, const PKT_LIST *plp, uint_t codenum, boolean_t *did_alloc) { DHCP_OPT *opt; *did_alloc = B_FALSE; if (codenum > DHCP_LAST_OPT) return (NULL); /* check the ACK first for all codes */ opt = plp->opts[codenum]; if (opt != NULL) return (opt); /* check the machine state also for certain codes */ switch (codenum) { case CD_CLIENT_ID: /* * CD_CLIENT_ID is not sent in an ACK, but it's possibly * available from the state machine data */ if (dsmp->dsm_cidlen > 0) { if ((opt = malloc(dsmp->dsm_cidlen + DHCP_OPT_META_LEN)) != NULL) { *did_alloc = B_TRUE; (void) encode_dhcp_opt(opt, B_FALSE /* is IPv6 */, CD_CLIENT_ID, dsmp->dsm_cid, dsmp->dsm_cidlen); } } break; default: break; } return (opt); } /* * check_rtm_addr(): determine if routing socket message matches interface * address * * input: const struct if_msghdr *: pointer to routing socket message * int: routing socket message length * boolean_t: set to B_TRUE if IPv6 * const in6_addr_t *: pointer to IP address * output: boolean_t: B_TRUE if address is a match */ static boolean_t check_rtm_addr(const struct ifa_msghdr *ifam, int msglen, boolean_t isv6, const in6_addr_t *addr) { const char *cp, *lim; uint_t flag; const struct sockaddr *sa; if (!(ifam->ifam_addrs & RTA_IFA)) return (B_FALSE); cp = (const char *)(ifam + 1); lim = (const char *)ifam + msglen; for (flag = 1; flag < RTA_IFA; flag <<= 1) { if (ifam->ifam_addrs & flag) { /* LINTED: alignment */ sa = (const struct sockaddr *)cp; if ((const char *)(sa + 1) > lim) return (B_FALSE); switch (sa->sa_family) { case AF_INET: cp += sizeof (struct sockaddr_in); break; case AF_LINK: cp += sizeof (struct sockaddr_dl); break; case AF_INET6: cp += sizeof (struct sockaddr_in6); break; default: cp += sizeof (struct sockaddr); break; } } } if (isv6) { const struct sockaddr_in6 *sin6; /* LINTED: alignment */ sin6 = (const struct sockaddr_in6 *)cp; if ((const char *)(sin6 + 1) > lim) return (B_FALSE); if (sin6->sin6_family != AF_INET6) return (B_FALSE); return (IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, addr)); } else { const struct sockaddr_in *sinp; ipaddr_t v4addr; /* LINTED: alignment */ sinp = (const struct sockaddr_in *)cp; if ((const char *)(sinp + 1) > lim) return (B_FALSE); if (sinp->sin_family != AF_INET) return (B_FALSE); IN6_V4MAPPED_TO_IPADDR(addr, v4addr); return (sinp->sin_addr.s_addr == v4addr); } } /* * is_rtm_v6(): determine if routing socket message is IPv6 * * input: struct ifa_msghdr *: pointer to routing socket message * int: message length * output: boolean_t */ static boolean_t is_rtm_v6(const struct ifa_msghdr *ifam, int msglen) { const char *cp, *lim; uint_t flag; const struct sockaddr *sa; cp = (const char *)(ifam + 1); lim = (const char *)ifam + msglen; for (flag = ifam->ifam_addrs; flag != 0; flag &= flag - 1) { /* LINTED: alignment */ sa = (const struct sockaddr *)cp; if ((const char *)(sa + 1) > lim) return (B_FALSE); switch (sa->sa_family) { case AF_INET: return (B_FALSE); case AF_LINK: cp += sizeof (struct sockaddr_dl); break; case AF_INET6: return (B_TRUE); default: cp += sizeof (struct sockaddr); break; } } return (B_FALSE); } /* * check_lif(): check the state of a given logical interface and its DHCP * lease. We've been told by the routing socket that the * corresponding ifIndex has changed. This may mean that DAD has * completed or failed. * * input: dhcp_lif_t *: pointer to the LIF * const struct ifa_msghdr *: routing socket message * int: size of routing socket message * output: boolean_t: B_TRUE if DAD has completed on this interface */ static boolean_t check_lif(dhcp_lif_t *lif, const struct ifa_msghdr *ifam, int msglen) { boolean_t isv6, dad_wait, unplumb; int fd; struct lifreq lifr; isv6 = lif->lif_pif->pif_isv6; fd = isv6 ? v6_sock_fd : v4_sock_fd; /* * Get the real (64 bit) logical interface flags. Note that the * routing socket message has flags, but these are just the lower 32 * bits. */ unplumb = B_FALSE; (void) memset(&lifr, 0, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, lif->lif_name, sizeof (lifr.lifr_name)); if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) { /* * Failing to retrieve flags means that the interface is gone. * It hasn't failed to verify with DAD, but we still have to * give up on it. */ lifr.lifr_flags = 0; if (errno == ENXIO) { lif->lif_plumbed = B_FALSE; dhcpmsg(MSG_INFO, "%s has been removed; abandoning", lif->lif_name); if (!isv6) discard_default_routes(lif->lif_smachs); } else { dhcpmsg(MSG_ERR, "unable to retrieve interface flags on %s", lif->lif_name); } unplumb = B_TRUE; } else if (!check_rtm_addr(ifam, msglen, isv6, &lif->lif_v6addr)) { /* * If the message is not about this logical interface, * then just ignore it. */ return (B_FALSE); } else if (lifr.lifr_flags & IFF_DUPLICATE) { dhcpmsg(MSG_ERROR, "interface %s has duplicate address", lif->lif_name); lif_mark_decline(lif, "duplicate address"); close_ip_lif(lif); (void) open_ip_lif(lif, INADDR_ANY, B_TRUE); } dad_wait = lif->lif_dad_wait; if (dad_wait) { dhcpmsg(MSG_VERBOSE, "check_lif: %s has finished DAD", lif->lif_name); lif->lif_dad_wait = B_FALSE; } if (unplumb) unplumb_lif(lif); return (dad_wait); } /* * check_main_lif(): check the state of a main logical interface for a state * machine. This is used only for DHCPv6. * * input: dhcp_smach_t *: pointer to the state machine * const struct ifa_msghdr *: routing socket message * int: size of routing socket message * output: boolean_t: B_TRUE if LIF is ok. */ static boolean_t check_main_lif(dhcp_smach_t *dsmp, const struct ifa_msghdr *ifam, int msglen) { dhcp_lif_t *lif = dsmp->dsm_lif; struct lifreq lifr; /* * Get the real (64 bit) logical interface flags. Note that the * routing socket message has flags, but these are just the lower 32 * bits. */ (void) memset(&lifr, 0, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, lif->lif_name, sizeof (lifr.lifr_name)); if (ioctl(v6_sock_fd, SIOCGLIFFLAGS, &lifr) == -1) { /* * Failing to retrieve flags means that the interface is gone. * Our state machine is now trash. */ if (errno == ENXIO) { dhcpmsg(MSG_INFO, "%s has been removed; abandoning", lif->lif_name); } else { dhcpmsg(MSG_ERR, "unable to retrieve interface flags on %s", lif->lif_name); } return (B_FALSE); } else if (!check_rtm_addr(ifam, msglen, B_TRUE, &lif->lif_v6addr)) { /* * If the message is not about this logical interface, * then just ignore it. */ return (B_TRUE); } else if (lifr.lifr_flags & IFF_DUPLICATE) { dhcpmsg(MSG_ERROR, "interface %s has duplicate address", lif->lif_name); return (B_FALSE); } else { return (B_TRUE); } } /* * process_link_up_down(): check the state of a physical interface for up/down * transitions; must go through INIT_REBOOT state if * the link flaps. * * input: dhcp_pif_t *: pointer to the physical interface to check * const struct if_msghdr *: routing socket message * output: none */ static void process_link_up_down(dhcp_pif_t *pif, const struct if_msghdr *ifm) { struct lifreq lifr; boolean_t isv6; int fd; /* * If the message implies no change of flags, then we're done; no need * to check further. Note that if we have multiple state machines on a * single physical interface, this test keeps us from issuing an ioctl * for each one. */ if ((ifm->ifm_flags & IFF_RUNNING) && pif->pif_running || !(ifm->ifm_flags & IFF_RUNNING) && !pif->pif_running) return; /* * We don't know what the real interface flags are, because the * if_index number is only 16 bits; we must go ask. */ isv6 = pif->pif_isv6; fd = isv6 ? v6_sock_fd : v4_sock_fd; (void) memset(&lifr, 0, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, pif->pif_name, sizeof (lifr.lifr_name)); if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1 || !(lifr.lifr_flags & IFF_RUNNING)) { /* * If we've lost the interface or it has gone down, then * nothing special to do; just turn off the running flag. */ pif_status(pif, B_FALSE); } else { /* * Interface has come back up: go through verification process. */ pif_status(pif, B_TRUE); } } /* * rtsock_event(): fetches routing socket messages and updates internal * interface state based on those messages. * * input: iu_eh_t *: unused * int: the routing socket file descriptor * (other arguments unused) * output: void */ /* ARGSUSED */ static void rtsock_event(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) { dhcp_smach_t *dsmp, *dsmnext; union { struct ifa_msghdr ifam; struct if_msghdr ifm; char buf[1024]; } msg; uint16_t ifindex; int msglen; boolean_t isv6; if ((msglen = read(fd, &msg, sizeof (msg))) <= 0) return; /* Note that the routing socket interface index is just 16 bits */ if (msg.ifm.ifm_type == RTM_IFINFO) { ifindex = msg.ifm.ifm_index; isv6 = (msg.ifm.ifm_flags & IFF_IPV6) ? B_TRUE : B_FALSE; } else if (msg.ifam.ifam_type == RTM_DELADDR || msg.ifam.ifam_type == RTM_NEWADDR) { ifindex = msg.ifam.ifam_index; isv6 = is_rtm_v6(&msg.ifam, msglen); } else { return; } for (dsmp = lookup_smach_by_uindex(ifindex, NULL, isv6); dsmp != NULL; dsmp = dsmnext) { DHCPSTATE oldstate; boolean_t lif_finished; boolean_t lease_removed; dhcp_lease_t *dlp, *dlnext; /* * Note that script_start can call dhcp_drop directly, and * that will do release_smach. */ dsmnext = lookup_smach_by_uindex(ifindex, dsmp, isv6); oldstate = dsmp->dsm_state; /* * Ignore state machines that are currently processing drop or * release; there is nothing more we can do for them. */ if (dsmp->dsm_droprelease) continue; /* * Look for link up/down notifications. These occur on a * physical interface basis. */ if (msg.ifm.ifm_type == RTM_IFINFO) { process_link_up_down(dsmp->dsm_lif->lif_pif, &msg.ifm); continue; } /* * Since we cannot trust the flags reported by the routing * socket (they're just 32 bits -- and thus never include * IFF_DUPLICATE), and we can't trust the ifindex (it's only 16 * bits and also doesn't reflect the alias in use), we get * flags on all matching interfaces, and go by that. */ lif_finished = B_FALSE; lease_removed = B_FALSE; for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlnext) { dhcp_lif_t *lif, *lifnext; uint_t nlifs = dlp->dl_nlifs; dlnext = dlp->dl_next; for (lif = dlp->dl_lifs; lif != NULL && nlifs > 0; lif = lifnext, nlifs--) { lifnext = lif->lif_next; if (check_lif(lif, &msg.ifam, msglen)) { dsmp->dsm_lif_wait--; lif_finished = B_TRUE; } } if (dlp->dl_nlifs == 0) { remove_lease(dlp); lease_removed = B_TRUE; } } if ((isv6 && !check_main_lif(dsmp, &msg.ifam, msglen)) || (!isv6 && !verify_lif(dsmp->dsm_lif))) { finished_smach(dsmp, DHCP_IPC_E_INVIF); continue; } /* * Ignore this state machine if nothing interesting has * happened. */ if (!lif_finished && dsmp->dsm_lif_down == 0 && (dsmp->dsm_leases != NULL || !lease_removed)) continue; /* * If we're still waiting for DAD to complete on some of the * configured LIFs, then don't send a response. */ if (dsmp->dsm_lif_wait != 0) { dhcpmsg(MSG_VERBOSE, "rtsock_event: %s still has %d " "LIFs waiting on DAD", dsmp->dsm_name, dsmp->dsm_lif_wait); continue; } /* * If we have some failed LIFs, then handle them now. We'll * remove them from the list. Any leases that become empty are * also removed as part of the decline-generation process. */ if (dsmp->dsm_lif_down != 0) send_declines(dsmp); if (dsmp->dsm_leases == NULL) { dsmp->dsm_bad_offers++; /* * For DHCPv6, we'll process the restart once we're * done sending Decline messages, because these are * supposed to be acknowledged. With DHCPv4, there's * no acknowledgment for a DECLINE, so after sending * it, we just restart right away. */ if (!dsmp->dsm_isv6) { dhcpmsg(MSG_VERBOSE, "rtsock_event: %s has no " "LIFs left", dsmp->dsm_name); dhcp_restart(dsmp); } } else { /* * If we're now up on at least some of the leases and * we were waiting for that, then kick off the rest of * configuration. Lease validation and DAD are done. */ dhcpmsg(MSG_VERBOSE, "rtsock_event: all LIFs verified " "on %s in %s state", dsmp->dsm_name, dhcp_state_to_string(oldstate)); if (oldstate == PRE_BOUND || oldstate == ADOPTING) dhcp_bound_complete(dsmp); if (oldstate == ADOPTING) dhcp_adopt_complete(dsmp); } } } /* * check_cmd_allowed(): check whether the requested command is allowed in the * state specified. * * input: DHCPSTATE: current state * dhcp_ipc_type_t: requested command * output: boolean_t: B_TRUE if command is allowed in this state */ boolean_t check_cmd_allowed(DHCPSTATE state, dhcp_ipc_type_t cmd) { return (ipc_cmd_allowed[state][cmd] != 0); } static boolean_t is_iscsi_active(void) { int fd; int active = 0; if ((fd = open(ISCSI_DRIVER_DEVCTL, O_RDONLY)) != -1) { if (ioctl(fd, ISCSI_IS_ACTIVE, &active) != 0) active = 0; (void) close(fd); } return (active != 0); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef AGENT_H #define AGENT_H #include #include #include /* * agent.h contains general symbols that should be available to all * source programs that are part of the agent. in general, files * specific to a given collection of code (such as interface.h or * dhcpmsg.h) are to be preferred to this dumping ground. use only * when necessary. */ #ifdef __cplusplus extern "C" { #endif /* * global variables: `tq' and `eh' represent the global timer queue * and event handler, as described in the README. `class_id' is our * vendor class id set early on in main(). `inactivity_id' is the * timer id of the global inactivity timer, which shuts down the agent * if there are no state machines to manage for DHCP_INACTIVITY_WAIT * seconds. `grandparent' is the pid of the original process when in * adopt mode. `rtsock_fd' is the global routing socket file descriptor. */ extern iu_tq_t *tq; extern iu_eh_t *eh; extern char *class_id; extern int class_id_len; extern iu_timer_id_t inactivity_id; extern pid_t grandparent; extern int rtsock_fd; boolean_t drain_script(iu_eh_t *, void *); boolean_t check_cmd_allowed(DHCPSTATE, dhcp_ipc_type_t); /* * global tunable parameters. an `I' in the preceding comment indicates * an implementation artifact; a `R' in the preceding comment indicates * that the value was suggested (or required) by RFC2131. */ /* I: how many seconds to wait before restarting DHCP on an interface */ #define DHCP_RESTART_WAIT 10 /* * I: the maximum number of milliseconds to wait before SELECTING on an * interface. RFC2131 recommends a random wait of between one and ten seconds, * to speed up DHCP at boot we wait between zero and two seconds. */ #define DHCP_SELECT_WAIT 2000 /* R: how many seconds before lease expiration we give up trying to rebind */ #define DHCP_REBIND_MIN 60 /* I: seconds to wait retrying dhcp_expire() if uncancellable async event */ #define DHCP_EXPIRE_WAIT 10 /* R: approximate percentage of lease time to wait until RENEWING state */ #define DHCP_T1_FACT .5 /* R: approximate percentage of lease time to wait until REBINDING state */ #define DHCP_T2_FACT .875 /* I: number of REQUEST attempts before assuming something is awry */ #define DHCP_MAX_REQUESTS 4 /* I: epsilon in seconds used to check if old and new lease times are same */ #define DHCP_LEASE_EPS 30 /* I: if lease is not being extended, seconds left before alerting user */ #define DHCP_LEASE_ERROR_THRESH (60*60) /* one hour */ /* I: how many seconds before bailing out if there's no work to do */ #define DHCP_INACTIVITY_WAIT (60*3) /* three minutes */ /* I: the maximum amount of seconds we use an adopted lease */ #define DHCP_ADOPT_LEASE_MAX (60*60) /* one hour */ /* I: number of seconds grandparent waits for child to finish adoption. */ #define DHCP_ADOPT_SLEEP 30 /* I: the maximum amount of milliseconds to wait for an ipc request */ #define DHCP_IPC_REQUEST_WAIT (3*1000) /* three seconds */ /* * DHCPv6 timer and retransmit values from RFC 3315. */ #define DHCPV6_SOL_MAX_DELAY 1000 /* Max delay of first Solicit; 1s */ #define DHCPV6_CNF_MAX_DELAY 1000 /* Max delay of first Confirm; 1s */ #define DHCPV6_INF_MAX_DELAY 1000 /* Max delay of first Info-req; 1s */ #define DHCPV6_SOL_TIMEOUT 1000 /* Initial Solicit timeout; 1s */ #define DHCPV6_REQ_TIMEOUT 1000 /* Initial Request timeout; 1s */ #define DHCPV6_CNF_TIMEOUT 1000 /* Initial Confirm timeout; 1s */ #define DHCPV6_REN_TIMEOUT 10000 /* Initial Renew timeout; 10s */ #define DHCPV6_REB_TIMEOUT 10000 /* Initial Rebind timeout; 10s */ #define DHCPV6_INF_TIMEOUT 1000 /* Initial Info-req timeout; 1s */ #define DHCPV6_REL_TIMEOUT 1000 /* Initial Release timeout; 1s */ #define DHCPV6_DEC_TIMEOUT 1000 /* Initial Decline timeout; 1s */ #define DHCPV6_SOL_MAX_RT 120000 /* Max Solicit timeout; 2m */ #define DHCPV6_REQ_MAX_RT 30000 /* Max Request timeout; 30s */ #define DHCPV6_CNF_MAX_RT 4000 /* Max Confirm timeout; 4s */ #define DHCPV6_REN_MAX_RT 600000 /* Max Renew timeout; 5m */ #define DHCPV6_REB_MAX_RT 600000 /* Max Rebind timeout; 5m */ #define DHCPV6_INF_MAX_RT 120000 /* Max Info-req timeout; 2m */ #define DHCPV6_CNF_MAX_RD 10000 /* Max Confirm duration; 10s */ #define DHCPV6_REQ_MAX_RC 10 /* Max Request attempts */ #define DHCPV6_REL_MAX_RC 5 /* Max Release attempts */ #define DHCPV6_DEC_MAX_RC 5 /* Max Decline attempts */ /* * reasons for why iu_handle_events() returned */ enum { DHCP_REASON_INACTIVITY, DHCP_REASON_SIGNAL, DHCP_REASON_TERMINATE }; #ifdef __cplusplus } #endif #endif /* AGENT_H */ /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include "async.h" #include "util.h" #include "interface.h" #include "script_handler.h" #include "states.h" /* * async_start(): starts an asynchronous command on a state machine * * input: dhcp_smach_t *: the state machine to start the async command on * dhcp_ipc_type_t: the command to start * boolean_t: B_TRUE if the command was started by a user * output: boolean: B_TRUE on success, B_FALSE on failure */ boolean_t async_start(dhcp_smach_t *dsmp, dhcp_ipc_type_t cmd, boolean_t user) { if (dsmp->dsm_async.as_present) { return (B_FALSE); } else { dsmp->dsm_async.as_cmd = cmd; dsmp->dsm_async.as_user = user; dsmp->dsm_async.as_present = B_TRUE; return (B_TRUE); } } /* * async_finish(): completes an asynchronous command * * input: dhcp_smach_t *: the state machine with the pending async command * output: void * note: should only be used when the command has no residual state to * clean up */ void async_finish(dhcp_smach_t *dsmp) { /* * be defensive here. the script may still be running if * the asynchronous action times out before it is killed by the * script helper process. */ if (dsmp->dsm_script_pid != -1) script_stop(dsmp); dsmp->dsm_async.as_present = B_FALSE; } /* * async_cancel(): cancels a pending asynchronous command * * input: dhcp_smach_t *: the state machine with the pending async command * output: boolean: B_TRUE if cancellation was successful, B_FALSE on failure */ boolean_t async_cancel(dhcp_smach_t *dsmp) { if (!dsmp->dsm_async.as_present) return (B_TRUE); if (dsmp->dsm_async.as_user) { dhcpmsg(MSG_DEBUG, "async_cancel: cannot abort command %d from user", (int)dsmp->dsm_async.as_cmd); return (B_FALSE); } else { async_finish(dsmp); dhcpmsg(MSG_DEBUG, "async_cancel: command %d aborted", (int)dsmp->dsm_async.as_cmd); return (B_TRUE); } } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef ASYNC_H #define ASYNC_H #include #include #include #include "common.h" /* * async.[ch] comprise the interface used to handle asynchronous DHCP * commands. see ipc_event() in agent.c for more documentation on * the treatment of asynchronous DHCP commands. see async.c for * documentation on how to use the exported functions. */ #ifdef __cplusplus extern "C" { #endif typedef struct async_action { dhcp_ipc_type_t as_cmd; /* command/action in progress */ boolean_t as_user; /* user-generated async cmd */ boolean_t as_present; /* async operation present */ } async_action_t; boolean_t async_start(dhcp_smach_t *, dhcp_ipc_type_t, boolean_t); void async_finish(dhcp_smach_t *); boolean_t async_cancel(dhcp_smach_t *); #ifdef __cplusplus } #endif #endif /* ASYNC_H */ /* * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2017, Chris Fraire . * Copyright 2019 Joshua M. Clulow * * BOUND state of the DHCP client state machine. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "states.h" #include "packet.h" #include "util.h" #include "agent.h" #include "interface.h" #include "script_handler.h" #include "defaults.h" /* * Possible outcomes for IPv6 binding attempt. */ enum v6_bind_result { v6Restart, /* report failure and restart state machine */ v6Resent, /* new Request message has been sent */ v6Done /* successful binding */ }; static enum v6_bind_result configure_v6_leases(dhcp_smach_t *); static boolean_t configure_v4_lease(dhcp_smach_t *); static boolean_t configure_v4_timers(dhcp_smach_t *); /* * bound_event_cb(): callback for script_start on the event EVENT_BOUND * * input: dhcp_smach_t *: the state machine configured * void *: unused * output: int: always 1 */ /* ARGSUSED1 */ static int bound_event_cb(dhcp_smach_t *dsmp, void *arg) { if (dsmp->dsm_ia.ia_fd != -1) ipc_action_finish(dsmp, DHCP_IPC_SUCCESS); else async_finish(dsmp); return (1); } /* * dhcp_bound(): configures an state machine and interfaces using information * contained in the ACK/Reply packet and sets up lease timers. * Before starting, the requested address is verified by * Duplicate Address Detection to make sure it's not in use. * * input: dhcp_smach_t *: the state machine to move to bound * PKT_LIST *: the ACK/Reply packet, or NULL to use dsmp->dsm_ack * output: boolean_t: B_TRUE on success, B_FALSE on failure */ boolean_t dhcp_bound(dhcp_smach_t *dsmp, PKT_LIST *ack) { DHCPSTATE oldstate; lease_t new_lease; dhcp_lif_t *lif; dhcp_lease_t *dlp; enum v6_bind_result v6b; if (ack != NULL) { /* If ack we're replacing is not the original, then free it */ if (dsmp->dsm_ack != dsmp->dsm_orig_ack) free_pkt_entry(dsmp->dsm_ack); dsmp->dsm_ack = ack; /* Save the first ack as the original */ if (dsmp->dsm_orig_ack == NULL) dsmp->dsm_orig_ack = ack; save_domainname(dsmp, ack); } oldstate = dsmp->dsm_state; switch (oldstate) { case ADOPTING: /* Note that adoption occurs only for IPv4 DHCP. */ /* Ignore BOOTP */ if (ack->opts[CD_DHCP_TYPE] == NULL) return (B_FALSE); /* * if we're adopting a lease, the lease timers * only provide an upper bound since we don't know * from what time they are relative to. assume we * have a lease time of at most DHCP_ADOPT_LEASE_MAX. */ (void) memcpy(&new_lease, ack->opts[CD_LEASE_TIME]->value, sizeof (lease_t)); new_lease = htonl(MIN(ntohl(new_lease), DHCP_ADOPT_LEASE_MAX)); (void) memcpy(ack->opts[CD_LEASE_TIME]->value, &new_lease, sizeof (lease_t)); /* * we have no idea when the REQUEST that generated * this ACK was sent, but for diagnostic purposes * we'll assume its close to the current time. */ dsmp->dsm_newstart_monosec = monosec(); if (dsmp->dsm_isv6) { if ((v6b = configure_v6_leases(dsmp)) != v6Done) return (v6b == v6Resent); } else { if (!configure_v4_lease(dsmp)) return (B_FALSE); if (!configure_v4_timers(dsmp)) return (B_FALSE); } dsmp->dsm_curstart_monosec = dsmp->dsm_newstart_monosec; write_lease_to_hostconf(dsmp); break; case SELECTING: case REQUESTING: case INIT_REBOOT: if (dsmp->dsm_isv6) { if ((v6b = configure_v6_leases(dsmp)) != v6Done) return (v6b == v6Resent); } else { if (!configure_v4_lease(dsmp)) return (B_FALSE); if (!configure_v4_timers(dsmp)) return (B_FALSE); if (!clear_lif_deprecated(dsmp->dsm_lif)) return (B_FALSE); } /* Stop sending requests now */ stop_pkt_retransmission(dsmp); /* * If we didn't end up with any usable leases, then we have a * problem. */ if (dsmp->dsm_leases == NULL) { dhcpmsg(MSG_WARNING, "dhcp_bound: no address lease established"); return (B_FALSE); } /* * If this is a Rapid-Commit (selecting state) or if we're * dealing with a reboot (init-reboot), then we will have a new * server ID to save. */ if (ack != NULL && (oldstate == SELECTING || oldstate == INIT_REBOOT) && dsmp->dsm_isv6 && !save_server_id(dsmp, ack)) { dhcpmsg(MSG_ERROR, "dhcp_bound: unable to save server ID on %s", dsmp->dsm_name); return (B_FALSE); } /* * We will continue configuring the interfaces via * dhcp_bound_complete, once kernel DAD completes. If no new * leases were created (which can happen on an init-reboot used * for link-up confirmation), then go straight to bound state. */ if (!set_smach_state(dsmp, PRE_BOUND)) return (B_FALSE); if (dsmp->dsm_lif_wait == 0) dhcp_bound_complete(dsmp); break; case PRE_BOUND: case BOUND: case INFORMATION: /* This is just a duplicate ack; silently ignore it */ return (B_TRUE); case RENEWING: case REBINDING: if (dsmp->dsm_isv6) { if ((v6b = configure_v6_leases(dsmp)) != v6Done) return (v6b == v6Resent); } else { if (!configure_v4_timers(dsmp)) return (B_FALSE); if (!clear_lif_deprecated(dsmp->dsm_lif)) return (B_FALSE); } /* * If some or all of the leases were torn down by the server, * then handle that as an expiry. When the script is done * running for the LOSS6 event, we'll end up back here. */ if ((lif = find_expired_lif(dsmp)) != NULL) { hold_lif(lif); dhcp_expire(NULL, lif); while ((lif = find_expired_lif(dsmp)) != NULL) { dlp = lif->lif_lease; unplumb_lif(lif); if (dlp->dl_nlifs == 0) remove_lease(dlp); } if (dsmp->dsm_leases == NULL) return (B_FALSE); } if (oldstate == REBINDING && dsmp->dsm_isv6 && !save_server_id(dsmp, ack)) { return (B_FALSE); } /* * Handle Renew/Rebind that fails to address one of our leases. * (Should just never happen, but RFC 3315 section 18.1.8 * requires it, and TAHI tests for it.) */ for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) { if (dlp->dl_stale && dlp->dl_nlifs > 0) break; } if (dlp != NULL) { dhcpmsg(MSG_DEBUG, "dhcp_bound: lease not updated; " "allow retransmit"); return (B_TRUE); } if (!set_smach_state(dsmp, BOUND)) return (B_FALSE); (void) script_start(dsmp, dsmp->dsm_isv6 ? EVENT_EXTEND6 : EVENT_EXTEND, bound_event_cb, NULL, NULL); dsmp->dsm_curstart_monosec = dsmp->dsm_newstart_monosec; write_lease_to_hostconf(dsmp); /* Stop sending requests now */ stop_pkt_retransmission(dsmp); break; case INFORM_SENT: if (dsmp->dsm_isv6 && !save_server_id(dsmp, ack)) { return (B_FALSE); } (void) bound_event_cb(dsmp, NULL); if (!set_smach_state(dsmp, INFORMATION)) return (B_FALSE); /* Stop sending requests now */ stop_pkt_retransmission(dsmp); break; default: /* something is really bizarre... */ dhcpmsg(MSG_DEBUG, "dhcp_bound: called in unexpected state: %s", dhcp_state_to_string(dsmp->dsm_state)); return (B_FALSE); } return (B_TRUE); } /* * dhcp_bound_complete(): complete interface configuration after DAD * * input: dhcp_smach_t *: the state machine now ready * output: none */ void dhcp_bound_complete(dhcp_smach_t *dsmp) { PKT_LIST *ack = dsmp->dsm_ack; DHCP_OPT *router_list; DHCPSTATE oldstate; dhcp_lif_t *lif = dsmp->dsm_lif; boolean_t ignore_mtu = B_FALSE; boolean_t manage_mtu; /* * Do bound state entry processing only if running IPv4. There's no * need for this with DHCPv6 because link-locals are used for I/O and * because DHCPv6 isn't entangled with routing. */ if (dsmp->dsm_isv6) { (void) set_smach_state(dsmp, BOUND); dhcpmsg(MSG_DEBUG, "dhcp_bound_complete: bound %s", dsmp->dsm_name); (void) script_start(dsmp, EVENT_BOUND6, bound_event_cb, NULL, NULL); dsmp->dsm_curstart_monosec = dsmp->dsm_newstart_monosec; write_lease_to_hostconf(dsmp); return; } manage_mtu = df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, DF_SET_MTU); /* * Check to see if the default route or MTU options appear in the * ignore list. */ router_list = ack->opts[CD_ROUTER]; for (int i = 0; i < dsmp->dsm_pillen; i++) { switch (dsmp->dsm_pil[i]) { case CD_MTU: ignore_mtu = B_TRUE; break; case CD_ROUTER: router_list = NULL; break; } } /* * If the server provides a valid MTU option, and the operator has not * disabled MTU management, configure the MTU on the interface now. */ if (manage_mtu) { DHCP_OPT *mtu; uint16_t mtuval = 0; if (!ignore_mtu && (mtu = ack->opts[CD_MTU]) != NULL && mtu->len == sizeof (uint16_t)) { (void) memcpy(&mtuval, mtu->value, sizeof (mtuval)); mtuval = ntohs(mtuval); set_lif_mtu(lif, mtuval); } else { /* * If no MTU value is provided, clear any value that * might have been requested previously. */ clear_lif_mtu(lif); } } /* * Add each provided router; we'll clean them up when the * state machine goes away or when our lease expires. * * Note that we do not handle default routers on IPv4 logicals; * see README for details. */ if (router_list != NULL && (router_list->len % sizeof (ipaddr_t)) == 0 && strchr(lif->lif_name, ':') == NULL && !lif->lif_pif->pif_under_ipmp) { dsmp->dsm_nrouters = router_list->len / sizeof (ipaddr_t); dsmp->dsm_routers = malloc(router_list->len); if (dsmp->dsm_routers == NULL) { dhcpmsg(MSG_ERR, "dhcp_bound_complete: cannot allocate " "default router list, ignoring default routers"); dsmp->dsm_nrouters = 0; } for (uint_t i = 0; i < dsmp->dsm_nrouters; i++) { (void) memcpy(&dsmp->dsm_routers[i].s_addr, router_list->value + (i * sizeof (ipaddr_t)), sizeof (ipaddr_t)); if (!add_default_route(lif->lif_pif->pif_index, &dsmp->dsm_routers[i])) { dhcpmsg(MSG_ERR, "dhcp_bound_complete: cannot " "add default router %s on %s", inet_ntoa( dsmp->dsm_routers[i]), dsmp->dsm_name); dsmp->dsm_routers[i].s_addr = htonl(INADDR_ANY); continue; } dhcpmsg(MSG_INFO, "added default router %s on %s", inet_ntoa(dsmp->dsm_routers[i]), dsmp->dsm_name); } } oldstate = dsmp->dsm_state; if (!set_smach_state(dsmp, BOUND)) { dhcpmsg(MSG_ERR, "dhcp_bound_complete: cannot set bound state on %s", dsmp->dsm_name); return; } dhcpmsg(MSG_DEBUG, "dhcp_bound_complete: bound %s", dsmp->dsm_name); /* * We're now committed to this binding, so if it came from BOOTP, set * the flag. */ if (ack->opts[CD_DHCP_TYPE] == NULL) dsmp->dsm_dflags |= DHCP_IF_BOOTP; /* * If the previous state was ADOPTING, event loop has not been started * at this time; so don't run the EVENT_BOUND script. */ if (oldstate != ADOPTING) { (void) script_start(dsmp, EVENT_BOUND, bound_event_cb, NULL, NULL); } dsmp->dsm_curstart_monosec = dsmp->dsm_newstart_monosec; write_lease_to_hostconf(dsmp); } /* * fuzzify(): adds some "fuzz" to a t1/t2 time, in accordance with RFC2131. * We use up to plus or minus 2% jitter in the time. This is a * small value, but the timers involved are typically long. A * common T1 value is one day, and the fuzz is up to 28.8 minutes; * plenty of time to make sure that individual clients don't renew * all at the same time. * * input: uint32_t: the number of seconds until lease expiration * double: the approximate percentage of that time to return * output: double: a number approximating (sec * pct) */ static double fuzzify(uint32_t sec, double pct) { return (sec * (pct + (drand48() - 0.5) / 25.0)); } /* * get_pkt_times(): pulls the lease times out of a v4 DHCP packet and stores * them as host byte-order relative times in the passed in * parameters. * * input: PKT_LIST *: the packet to pull the packet times from * lease_t *: where to store the relative lease time in hbo * lease_t *: where to store the relative t1 time in hbo * lease_t *: where to store the relative t2 time in hbo * output: void */ static void get_pkt_times(PKT_LIST *ack, lease_t *lease, lease_t *t1, lease_t *t2) { *lease = DHCP_PERM; *t1 = DHCP_PERM; *t2 = DHCP_PERM; if (ack->opts[CD_DHCP_TYPE] == NULL) { dhcpmsg(MSG_VERBOSE, "get_pkt_times: BOOTP response; infinite lease"); return; } if (ack->opts[CD_LEASE_TIME] == NULL) { dhcpmsg(MSG_VERBOSE, "get_pkt_times: no lease option provided"); return; } if (ack->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) { dhcpmsg(MSG_VERBOSE, "get_pkt_times: invalid lease option"); } (void) memcpy(lease, ack->opts[CD_LEASE_TIME]->value, sizeof (lease_t)); *lease = ntohl(*lease); if (*lease == DHCP_PERM) { dhcpmsg(MSG_VERBOSE, "get_pkt_times: infinite lease granted"); return; } if (ack->opts[CD_T1_TIME] != NULL && ack->opts[CD_T1_TIME]->len == sizeof (lease_t)) { (void) memcpy(t1, ack->opts[CD_T1_TIME]->value, sizeof (*t1)); *t1 = ntohl(*t1); } if (ack->opts[CD_T2_TIME] != NULL && ack->opts[CD_T2_TIME]->len == sizeof (lease_t)) { (void) memcpy(t2, ack->opts[CD_T2_TIME]->value, sizeof (*t2)); *t2 = ntohl(*t2); } if ((*t1 == DHCP_PERM) || (*t1 >= *lease)) *t1 = (lease_t)fuzzify(*lease, DHCP_T1_FACT); if ((*t2 == DHCP_PERM) || (*t2 > *lease) || (*t2 <= *t1)) *t2 = (lease_t)fuzzify(*lease, DHCP_T2_FACT); dhcpmsg(MSG_VERBOSE, "get_pkt_times: lease %u t1 %u t2 %u", *lease, *t1, *t2); } /* * configure_v4_timers(): configures the lease timers on a v4 state machine * * input: dhcp_smach_t *: the state machine to configure * output: boolean_t: B_TRUE on success, B_FALSE on failure */ static boolean_t configure_v4_timers(dhcp_smach_t *dsmp) { PKT_LIST *ack = dsmp->dsm_ack; lease_t lease, t1, t2; dhcp_lease_t *dlp; dhcp_lif_t *lif; /* v4 has just one lease per state machine, and one LIF */ dlp = dsmp->dsm_leases; lif = dlp->dl_lifs; /* * If it's DHCP, but there's no valid lease time, then complain, * decline the lease and return error. */ if (ack->opts[CD_DHCP_TYPE] != NULL && (ack->opts[CD_LEASE_TIME] == NULL || ack->opts[CD_LEASE_TIME]->len != sizeof (lease_t))) { lif_mark_decline(lif, "Missing or corrupted lease time"); send_declines(dsmp); dhcpmsg(MSG_WARNING, "configure_v4_timers: %s lease time in " "ACK on %s", ack->opts[CD_LEASE_TIME] == NULL ? "missing" : "corrupt", dsmp->dsm_name); return (B_FALSE); } /* Stop the T1 and T2 timers */ cancel_lease_timers(dlp); /* Stop the LEASE timer */ cancel_lif_timers(lif); /* * type has already been verified as ACK. if type is not set, * then we got a BOOTP packet. we now fetch the t1, t2, and * lease options out of the packet into variables. they are * returned as relative host-byte-ordered times. */ get_pkt_times(ack, &lease, &t1, &t2); /* * if the current lease is mysteriously close to the new * lease, warn the user. unless there's less than a minute * left, round to the closest minute. */ if (lif->lif_expire.dt_start != 0 && abs((dsmp->dsm_newstart_monosec + lease) - (dsmp->dsm_curstart_monosec + lif->lif_expire.dt_start)) < DHCP_LEASE_EPS) { const char *noext = "configure_v4_timers: lease renewed but " "time not extended"; int msg_level; uint_t minleft; if (lif->lif_expire.dt_start < DHCP_LEASE_ERROR_THRESH) msg_level = MSG_ERROR; else msg_level = MSG_VERBOSE; minleft = (lif->lif_expire.dt_start + 30) / 60; if (lif->lif_expire.dt_start < 60) { dhcpmsg(msg_level, "%s; expires in %d seconds", noext, lif->lif_expire.dt_start); } else if (minleft == 1) { dhcpmsg(msg_level, "%s; expires in 1 minute", noext); } else if (minleft > 120) { dhcpmsg(msg_level, "%s; expires in %d hours", noext, (minleft + 30) / 60); } else { dhcpmsg(msg_level, "%s; expires in %d minutes", noext, minleft); } } init_timer(&dlp->dl_t1, t1); init_timer(&dlp->dl_t2, t2); init_timer(&lif->lif_expire, lease); if (lease == DHCP_PERM) { dhcpmsg(MSG_INFO, "configure_v4_timers: %s acquired permanent lease", dsmp->dsm_name); return (B_TRUE); } dhcpmsg(MSG_INFO, "configure_v4_timers: %s acquired lease, expires %s", dsmp->dsm_name, monosec_to_string(dsmp->dsm_newstart_monosec + lease)); dhcpmsg(MSG_INFO, "configure_v4_timers: %s begins renewal at %s", dsmp->dsm_name, monosec_to_string(dsmp->dsm_newstart_monosec + dlp->dl_t1.dt_start)); dhcpmsg(MSG_INFO, "configure_v4_timers: %s begins rebinding at %s", dsmp->dsm_name, monosec_to_string(dsmp->dsm_newstart_monosec + dlp->dl_t2.dt_start)); /* * according to RFC2131, there is no minimum lease time, but don't * set up renew/rebind timers if lease is shorter than DHCP_REBIND_MIN. */ if (!schedule_lif_timer(lif, &lif->lif_expire, dhcp_expire)) goto failure; if (lease < DHCP_REBIND_MIN) { dhcpmsg(MSG_WARNING, "configure_v4_timers: lease on %s is for " "less than %d seconds!", dsmp->dsm_name, DHCP_REBIND_MIN); return (B_TRUE); } if (!schedule_lease_timer(dlp, &dlp->dl_t1, dhcp_renew)) goto failure; if (!schedule_lease_timer(dlp, &dlp->dl_t2, dhcp_rebind)) goto failure; return (B_TRUE); failure: cancel_lease_timers(dlp); cancel_lif_timers(lif); dhcpmsg(MSG_WARNING, "configure_v4_timers: cannot schedule lease timers"); return (B_FALSE); } /* * configure_v6_leases(): configures the IPv6 leases on a state machine from * the current DHCPv6 ACK. We need to scan the ACK, * create a lease for each IA_NA, and a new LIF for each * IAADDR. * * input: dhcp_smach_t *: the machine to configure (with a valid dsm_ack) * output: enum v6_bind_result: restart, resend, or done */ static enum v6_bind_result configure_v6_leases(dhcp_smach_t *dsmp) { const dhcpv6_option_t *d6o, *d6so, *d6sso; const char *optbase, *estr, *msg; uint_t olen, solen, ssolen, msglen; dhcpv6_ia_na_t d6in; dhcpv6_iaaddr_t d6ia; dhcp_lease_t *dlp; uint32_t shortest; dhcp_lif_t *lif; uint_t nlifs; boolean_t got_iana = B_FALSE; uint_t scode; for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) dlp->dl_stale = B_TRUE; d6o = NULL; while ((d6o = dhcpv6_pkt_option(dsmp->dsm_ack, d6o, DHCPV6_OPT_IA_NA, &olen)) != NULL) { if (olen < sizeof (d6in)) { dhcpmsg(MSG_WARNING, "configure_v6_leases: garbled IA_NA"); continue; } /* * Check the IAID. It should be for our controlling LIF. If a * single state machine needs to use multiple IAIDs, then this * will need to change. */ (void) memcpy(&d6in, d6o, sizeof (d6in)); d6in.d6in_iaid = ntohl(d6in.d6in_iaid); if (d6in.d6in_iaid != dsmp->dsm_lif->lif_iaid) { dhcpmsg(MSG_WARNING, "configure_v6_leases: ignored " "IA_NA for IAID %x (not %x)", d6in.d6in_iaid, dsmp->dsm_lif->lif_iaid); continue; } /* * See notes below; there's only one IA_NA and a single IAID * for now. */ if ((dlp = dsmp->dsm_leases) != NULL) dlp->dl_stale = B_FALSE; /* * Note that some bug-ridden servers will try to give us * multiple IA_NA options for a single IAID. We ignore * duplicates. */ if (got_iana) { dhcpmsg(MSG_WARNING, "configure_v6_leases: unexpected " "extra IA_NA ignored"); continue; } d6in.d6in_t1 = ntohl(d6in.d6in_t1); d6in.d6in_t2 = ntohl(d6in.d6in_t2); /* RFC 3315 required check for invalid T1/T2 combinations */ if (d6in.d6in_t1 > d6in.d6in_t2 && d6in.d6in_t2 != 0) { dhcpmsg(MSG_WARNING, "configure_v6_leases: ignored " "IA_NA with invalid T1 %u > T2 %u", d6in.d6in_t1, d6in.d6in_t2); continue; } /* * There may be a status code here. Process if present. */ optbase = (const char *)d6o + sizeof (d6in); olen -= sizeof (d6in); d6so = dhcpv6_find_option(optbase, olen, NULL, DHCPV6_OPT_STATUS_CODE, &solen); scode = dhcpv6_status_code(d6so, solen, &estr, &msg, &msglen); if (scode != DHCPV6_STAT_SUCCESS) { dhcpmsg(MSG_WARNING, "configure_v6_leases: IA_NA: %s: %.*s", estr, msglen, msg); } print_server_msg(dsmp, msg, msglen); /* * Other errors are possible here. According to RFC 3315 * section 18.1.8, we ignore the entire IA if it gives the "no * addresses" status code. We may try another server if we * like -- we instead opt to allow the addresses to expire and * then try a new server. * * If the status code is "no binding," then we must go back and * redo the Request. Surprisingly, it doesn't matter if it's * any other code. */ if (scode == DHCPV6_STAT_NOADDRS) { dhcpmsg(MSG_DEBUG, "configure_v6_leases: ignoring " "no-addrs status in IA_NA"); continue; } if (scode == DHCPV6_STAT_NOBINDING) { send_v6_request(dsmp); return (v6Resent); } /* * Find or create the lease structure. This part is simple, * because we support only IA_NA and a single IAID. This means * there's only one lease structure. The design supports * multiple lease structures so that IA_TA and IA_PD can be * added later. */ if ((dlp = dsmp->dsm_leases) == NULL && (dlp = insert_lease(dsmp)) == NULL) { dhcpmsg(MSG_ERROR, "configure_v6_leases: unable to " "allocate memory for lease"); return (v6Restart); } /* * Iterate over the IAADDR options contained within this IA_NA. */ shortest = DHCPV6_INFTIME; d6so = NULL; while ((d6so = dhcpv6_find_option(optbase, olen, d6so, DHCPV6_OPT_IAADDR, &solen)) != NULL) { if (solen < sizeof (d6ia)) { dhcpmsg(MSG_WARNING, "configure_v6_leases: garbled IAADDR"); continue; } (void) memcpy(&d6ia, d6so, sizeof (d6ia)); d6ia.d6ia_preflife = ntohl(d6ia.d6ia_preflife); d6ia.d6ia_vallife = ntohl(d6ia.d6ia_vallife); /* RFC 3315 required validity check */ if (d6ia.d6ia_preflife > d6ia.d6ia_vallife) { dhcpmsg(MSG_WARNING, "configure_v6_leases: ignored IAADDR with " "preferred lifetime %u > valid %u", d6ia.d6ia_preflife, d6ia.d6ia_vallife); continue; } /* * RFC 3315 allows a status code to be buried inside * the IAADDR option. Look for it, and process if * present. Process in a manner similar to that for * the IA itself; TAHI checks for this. Real servers * likely won't do this. */ d6sso = dhcpv6_find_option((const char *)d6so + sizeof (d6ia), solen - sizeof (d6ia), NULL, DHCPV6_OPT_STATUS_CODE, &ssolen); scode = dhcpv6_status_code(d6sso, ssolen, &estr, &msg, &msglen); print_server_msg(dsmp, msg, msglen); if (scode == DHCPV6_STAT_NOADDRS) { dhcpmsg(MSG_DEBUG, "configure_v6_leases: " "ignoring no-addrs status in IAADDR"); continue; } if (scode == DHCPV6_STAT_NOBINDING) { send_v6_request(dsmp); return (v6Resent); } if (scode != DHCPV6_STAT_SUCCESS) { dhcpmsg(MSG_WARNING, "configure_v6_leases: IAADDR: %s", estr); } /* * Locate the existing LIF within the lease associated * with this address, if any. */ lif = dlp->dl_lifs; for (nlifs = dlp->dl_nlifs; nlifs > 0; nlifs--, lif = lif->lif_next) { if (IN6_ARE_ADDR_EQUAL(&d6ia.d6ia_addr, &lif->lif_v6addr)) break; } /* * If the server has set the lifetime to zero, then * delete the LIF. Otherwise, set the new LIF expiry * time, adding the LIF if necessary. */ if (d6ia.d6ia_vallife == 0) { /* If it was found, then it's expired */ if (nlifs != 0) { dhcpmsg(MSG_DEBUG, "configure_v6_leases: lif %s has " "expired", lif->lif_name); lif->lif_expired = B_TRUE; } continue; } /* If it wasn't found, then create it now. */ if (nlifs == 0) { lif = plumb_lif(dsmp->dsm_lif->lif_pif, &d6ia.d6ia_addr); if (lif == NULL) continue; if (++dlp->dl_nlifs == 1) { dlp->dl_lifs = lif; } else { remque(lif); insque(lif, dlp->dl_lifs); } lif->lif_lease = dlp; lif->lif_dad_wait = _B_TRUE; dsmp->dsm_lif_wait++; } else { /* If it was found, cancel timer */ cancel_lif_timers(lif); if (d6ia.d6ia_preflife != 0 && !clear_lif_deprecated(lif)) { unplumb_lif(lif); continue; } } /* Set the new expiry timers */ init_timer(&lif->lif_preferred, d6ia.d6ia_preflife); init_timer(&lif->lif_expire, d6ia.d6ia_vallife); /* * If the preferred lifetime is over now, then the LIF * is deprecated. If it's the same as the expiry time, * then we don't need a separate timer for it. */ if (d6ia.d6ia_preflife == 0) { set_lif_deprecated(lif); } else if (d6ia.d6ia_preflife != DHCPV6_INFTIME && d6ia.d6ia_preflife != d6ia.d6ia_vallife && !schedule_lif_timer(lif, &lif->lif_preferred, dhcp_deprecate)) { unplumb_lif(lif); continue; } if (d6ia.d6ia_vallife != DHCPV6_INFTIME && !schedule_lif_timer(lif, &lif->lif_expire, dhcp_expire)) { unplumb_lif(lif); continue; } if (d6ia.d6ia_preflife < shortest) shortest = d6ia.d6ia_preflife; } if (dlp->dl_nlifs == 0) { dhcpmsg(MSG_WARNING, "configure_v6_leases: no IAADDRs found in IA_NA"); remove_lease(dlp); continue; } if (d6in.d6in_t1 == 0 && d6in.d6in_t2 == 0) { /* Default values from RFC 3315: 0.5 and 0.8 */ if ((d6in.d6in_t1 = shortest / 2) == 0) d6in.d6in_t1 = 1; d6in.d6in_t2 = shortest - shortest / 5; } cancel_lease_timers(dlp); init_timer(&dlp->dl_t1, d6in.d6in_t1); init_timer(&dlp->dl_t2, d6in.d6in_t2); if ((d6in.d6in_t1 != DHCPV6_INFTIME && !schedule_lease_timer(dlp, &dlp->dl_t1, dhcp_renew)) || (d6in.d6in_t2 != DHCPV6_INFTIME && !schedule_lease_timer(dlp, &dlp->dl_t2, dhcp_rebind))) { dhcpmsg(MSG_WARNING, "configure_v6_leases: unable to " "set renew/rebind timers"); } else { got_iana = B_TRUE; } } if (!got_iana) { dhcpmsg(MSG_WARNING, "configure_v6_leases: no usable IA_NA option found"); } return (v6Done); } /* * configure_v4_lease(): configures the IPv4 lease on a state machine from * the current DHCP ACK. There's only one lease and LIF * per state machine in IPv4. * * input: dhcp_smach_t *: the machine to configure (with a valid dsm_ack) * output: boolean_t: B_TRUE on success, B_FALSE on failure */ static boolean_t configure_v4_lease(dhcp_smach_t *dsmp) { struct lifreq lifr; struct sockaddr_in *sin; PKT_LIST *ack = dsmp->dsm_ack; dhcp_lease_t *dlp; dhcp_lif_t *lif; uint32_t addrhbo; struct in_addr inaddr; /* * if we're using DHCP, then we'll have a valid CD_SERVER_ID * (we checked in dhcp_acknak()); set it now so that * dsmp->dsm_server is valid in case we need to send_decline(). * note that we use comparisons against opts[CD_DHCP_TYPE] * since we haven't set DHCP_IF_BOOTP yet (we don't do that * until we're sure we want the offered address.) */ if (ack->opts[CD_DHCP_TYPE] != NULL) { (void) memcpy(&inaddr, ack->opts[CD_SERVER_ID]->value, sizeof (inaddr)); IN6_INADDR_TO_V4MAPPED(&inaddr, &dsmp->dsm_server); } /* * There needs to be exactly one lease for IPv4, and that lease * controls the main LIF for the state machine. If it doesn't exist * yet, then create it now. */ if ((dlp = dsmp->dsm_leases) == NULL && (dlp = insert_lease(dsmp)) == NULL) { dhcpmsg(MSG_ERROR, "configure_v4_lease: unable to allocate " "memory for lease"); return (B_FALSE); } if (dlp->dl_nlifs == 0) { dlp->dl_lifs = dsmp->dsm_lif; dlp->dl_nlifs = 1; /* The lease holds a reference on the LIF */ hold_lif(dlp->dl_lifs); dlp->dl_lifs->lif_lease = dlp; } lif = dlp->dl_lifs; IN6_INADDR_TO_V4MAPPED(&ack->pkt->yiaddr, &lif->lif_v6addr); addrhbo = ntohl(ack->pkt->yiaddr.s_addr); if ((addrhbo & IN_CLASSA_NET) == 0 || (addrhbo >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET || IN_CLASSD(addrhbo)) { dhcpmsg(MSG_ERROR, "configure_v4_lease: got invalid IP address %s for %s", inet_ntoa(ack->pkt->yiaddr), lif->lif_name); return (B_FALSE); } (void) memset(&lifr, 0, sizeof (struct lifreq)); (void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ); /* * bring the interface online. note that there is no optimal * order here: it is considered bad taste (and in > solaris 7, * likely illegal) to bring an interface up before it has an * ip address. however, due to an apparent bug in sun fddi * 5.0, fddi will not obtain a network routing entry unless * the interface is brought up before it has an ip address. * we take the lesser of the two evils; if fddi customers have * problems, they can get a newer fddi distribution which * fixes the problem. */ sin = (struct sockaddr_in *)&lifr.lifr_addr; sin->sin_family = AF_INET; (void) memset(&lif->lif_v6mask, 0xff, sizeof (lif->lif_v6mask)); if (ack->opts[CD_SUBNETMASK] != NULL && ack->opts[CD_SUBNETMASK]->len == sizeof (inaddr)) { (void) memcpy(&inaddr, ack->opts[CD_SUBNETMASK]->value, sizeof (inaddr)); } else { if (ack->opts[CD_SUBNETMASK] != NULL && ack->opts[CD_SUBNETMASK]->len != sizeof (inaddr)) { dhcpmsg(MSG_WARNING, "configure_v4_lease: specified " "subnet mask length is %d instead of %d, ignoring", ack->opts[CD_SUBNETMASK]->len, sizeof (ipaddr_t)); } else { dhcpmsg(MSG_WARNING, "configure_v4_lease: no IP " "netmask specified for %s, making best guess", lif->lif_name); } /* * no legitimate IP subnet mask specified.. use best * guess. recall that lif_addr is in network order, so * imagine it's 0x11223344: then when it is read into * a register on x86, it becomes 0x44332211, so we * must ntohl() it to convert it to 0x11223344 in * order to use the macros in . */ if (IN_CLASSA(addrhbo)) inaddr.s_addr = htonl(IN_CLASSA_NET); else if (IN_CLASSB(addrhbo)) inaddr.s_addr = htonl(IN_CLASSB_NET); else if (IN_CLASSC(addrhbo)) inaddr.s_addr = htonl(IN_CLASSC_NET); else { /* * Cant be Class D as that is multicast * Must be Class E */ inaddr.s_addr = htonl(IN_CLASSE_NET); } } lif->lif_v6mask._S6_un._S6_u32[3] = inaddr.s_addr; sin->sin_addr = inaddr; dhcpmsg(MSG_INFO, "configure_v4_lease: setting IP netmask to %s on %s", inet_ntoa(sin->sin_addr), lif->lif_name); if (ioctl(v4_sock_fd, SIOCSLIFNETMASK, &lifr) == -1) { dhcpmsg(MSG_ERR, "configure_v4_lease: cannot set IP netmask " "on %s", lif->lif_name); return (B_FALSE); } IN6_V4MAPPED_TO_INADDR(&lif->lif_v6addr, &sin->sin_addr); dhcpmsg(MSG_INFO, "configure_v4_lease: setting IP address to %s on %s", inet_ntoa(sin->sin_addr), lif->lif_name); if (ioctl(v4_sock_fd, SIOCSLIFADDR, &lifr) == -1) { dhcpmsg(MSG_ERR, "configure_v4_lease: cannot set IP address " "on %s", lif->lif_name); return (B_FALSE); } if (!lif->lif_dad_wait) { lif->lif_dad_wait = _B_TRUE; dsmp->dsm_lif_wait++; } if (ack->opts[CD_BROADCASTADDR] != NULL && ack->opts[CD_BROADCASTADDR]->len == sizeof (inaddr)) { (void) memcpy(&inaddr, ack->opts[CD_BROADCASTADDR]->value, sizeof (inaddr)); } else { if (ack->opts[CD_BROADCASTADDR] != NULL && ack->opts[CD_BROADCASTADDR]->len != sizeof (inaddr)) { dhcpmsg(MSG_WARNING, "configure_v4_lease: specified " "broadcast address length is %d instead of %d, " "ignoring", ack->opts[CD_BROADCASTADDR]->len, sizeof (inaddr)); } else { dhcpmsg(MSG_WARNING, "configure_v4_lease: no IP " "broadcast specified for %s, making best guess", lif->lif_name); } /* * no legitimate IP broadcast specified. compute it * from the IP address and netmask. */ IN6_V4MAPPED_TO_INADDR(&lif->lif_v6addr, &inaddr); inaddr.s_addr |= ~lif->lif_v6mask._S6_un._S6_u32[3]; } /* * the kernel will set the broadcast address for us as part of * bringing the interface up. since experience has shown that dhcp * servers sometimes provide a bogus broadcast address, we let the * kernel set it so that it's guaranteed to be correct. * * also, note any inconsistencies and save the broadcast address the * kernel set so that we can watch for changes to it. */ if (ioctl(v4_sock_fd, SIOCGLIFBRDADDR, &lifr) == -1) { dhcpmsg(MSG_ERR, "configure_v4_lease: cannot get broadcast " "address for %s", lif->lif_name); return (B_FALSE); } if (inaddr.s_addr != sin->sin_addr.s_addr) { dhcpmsg(MSG_WARNING, "configure_v4_lease: incorrect broadcast " "address %s specified for %s; ignoring", inet_ntoa(inaddr), lif->lif_name); } lif->lif_broadcast = sin->sin_addr.s_addr; dhcpmsg(MSG_INFO, "configure_v4_lease: using broadcast address %s on %s", inet_ntoa(inaddr), lif->lif_name); return (B_TRUE); } /* * save_server_id(): save off the new DHCPv6 Server ID * * input: dhcp_smach_t *: the state machine to use * PKT_LIST *: the packet with the Reply message * output: boolean_t: B_TRUE on success, B_FALSE on failure */ boolean_t save_server_id(dhcp_smach_t *dsmp, PKT_LIST *msg) { const dhcpv6_option_t *d6o; uint_t olen; d6o = dhcpv6_pkt_option(msg, NULL, DHCPV6_OPT_SERVERID, &olen); if (d6o == NULL) return (B_FALSE); olen -= sizeof (*d6o); free(dsmp->dsm_serverid); if ((dsmp->dsm_serverid = malloc(olen)) == NULL) { return (B_FALSE); } else { dsmp->dsm_serveridlen = olen; (void) memcpy(dsmp->dsm_serverid, d6o + 1, olen); return (B_TRUE); } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include /* sprintf() */ #include /* * opp_zalloc(): allocates and initializes a struct openpromio * * input: size_t: the size of the variable-length part of the openpromio * const char *: an initial value for oprom_array, if non-NULL * output: struct openpromio: the allocated, initialized openpromio */ static struct openpromio * opp_zalloc(size_t size, const char *prop) { struct openpromio *opp = malloc(sizeof (struct openpromio) + size); if (opp != NULL) { (void) memset(opp, 0, sizeof (struct openpromio) + size); opp->oprom_size = size; if (prop != NULL) (void) strcpy(opp->oprom_array, prop); } return (opp); } /* * goto_rootnode(): moves to the root of the devinfo tree * * input: int: an open descriptor to /dev/openprom * output: int: nonzero on success */ static int goto_rootnode(int prom_fd) { struct openpromio op = { sizeof (int), 0 }; /* zero it explicitly since a union is involved */ op.oprom_node = 0; return (ioctl(prom_fd, OPROMNEXT, &op) == 0); } /* * return_property(): returns the value of a given property * * input: int: an open descriptor to /dev/openprom * const char *: the property to look for in the current devinfo node * output: the value of that property (dynamically allocated) */ static char * return_property(int prom_fd, const char *prop) { int proplen; char *result; struct openpromio *opp = opp_zalloc(strlen(prop) + 1, prop); if (opp == NULL) return (NULL); if (ioctl(prom_fd, OPROMGETPROPLEN, opp) == -1) { free(opp); return (NULL); } proplen = opp->oprom_len; if (proplen > (strlen(prop) + 1)) { free(opp); opp = opp_zalloc(proplen, prop); if (opp == NULL) return (NULL); } if (ioctl(prom_fd, OPROMGETPROP, opp) == -1) { free(opp); return (NULL); } result = strdup(opp->oprom_array); free(opp); return (result); } /* * sanitize_class_id(): translates the class id into a canonical format, * so that it can be used easily with dhcptab(5). * * input: char *: the class id to canonicalize * output: void */ static void sanitize_class_id(char *src_ptr) { char *dst_ptr = src_ptr; /* remove all spaces and change all commas to periods */ while (*src_ptr != '\0') { switch (*src_ptr) { case ' ': break; case ',': *dst_ptr++ = '.'; break; default: *dst_ptr++ = *src_ptr; break; } src_ptr++; } *dst_ptr = '\0'; } /* * get_class_id(): retrieves the class id from the prom, then canonicalizes it * * input: void * output: char *: the class id (dynamically allocated and sanitized) */ char * get_class_id(void) { int prom_fd; char *name, *class_id = NULL; size_t len; prom_fd = open("/dev/openprom", O_RDONLY); if (prom_fd == -1) return (NULL); if (goto_rootnode(prom_fd) == 0) { (void) close(prom_fd); return (NULL); } /* * the `name' property is the same as the result of `uname -i', modulo * some stylistic issues we fix up via sanitize_class_id() below. */ name = return_property(prom_fd, "name"); (void) close(prom_fd); if (name == NULL) return (NULL); /* * if the name is not prefixed with a vendor name, add "SUNW," to make * it more likely to be globally unique; see PSARC/2004/674. */ if (strchr(name, ',') == NULL) { len = strlen(name) + sizeof ("SUNW,"); class_id = malloc(len); if (class_id == NULL) { free(name); return (NULL); } (void) snprintf(class_id, len, "SUNW,%s", name); free(name); } else { class_id = name; } sanitize_class_id(class_id); return (class_id); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 1999 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef CLASS_ID_H #define CLASS_ID_H /* * class_id.[ch] provides an interface for retrieving the class id * from the prom. see class_id.c for more details on how to use the * exported function. */ #ifdef __cplusplus extern "C" { #endif char *get_class_id(void); #ifdef __cplusplus } #endif #endif /* CLASS_ID_H */ /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _COMMON_H #define _COMMON_H #include /* * Common opaque structure definitions and values used throughout the dhcpagent * implementation. */ #ifdef __cplusplus extern "C" { #endif /* * Things (unfortunately) required because we're in an XPG environment. */ #define B_TRUE _B_TRUE #define B_FALSE _B_FALSE struct dhcp_smach_s; typedef struct dhcp_smach_s dhcp_smach_t; struct dhcp_lease_s; typedef struct dhcp_lease_s dhcp_lease_t; struct dhcp_lif_s; typedef struct dhcp_lif_s dhcp_lif_t; struct dhcp_pif_s; typedef struct dhcp_pif_s dhcp_pif_t; typedef int script_callback_t(dhcp_smach_t *, void *); struct dhcp_timer_s; typedef struct dhcp_timer_s dhcp_timer_t; struct dhcp_ipc_s; typedef struct dhcp_ipc_s dhcp_ipc_t; typedef int64_t monosec_t; /* see README for details */ #ifdef __cplusplus } #endif #endif /* _COMMON_H */ /* * 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. * Copyright (c) 2016-2017, Chris Fraire . * Copyright 2019 Joshua M. Clulow */ #include #include #include #include #include #include #include #include #include "common.h" #include "defaults.h" struct dhcp_default { const char *df_name; /* parameter name */ const char *df_default; /* default value */ int df_min; /* min value if type DF_INTEGER */ int df_max; /* max value if type DF_INTEGER */ }; /* * NOTE: Keep in the same order as tunable parameter constants in defaults.h */ static struct dhcp_default defaults[] = { { "RELEASE_ON_SIGTERM", "0", 0, 0 }, { "IGNORE_FAILED_ARP", "1", 0, -1 }, { "OFFER_WAIT", "3", 1, 20 }, { "ARP_WAIT", "1000", 0, -1 }, { "CLIENT_ID", NULL, 0, 0 }, { "PARAM_REQUEST_LIST", NULL, 0, 0 }, { "REQUEST_HOSTNAME", "1", 0, 0 }, { "DEBUG_LEVEL", "0", 0, 3 }, { "VERBOSE", "0", 0, 0 }, { "VERIFIED_LEASE_ONLY", "0", 0, 0 }, { "PARAM_IGNORE_LIST", NULL, 0, 0 }, { "REQUEST_FQDN", "1", 0, 0 }, { "V4_DEFAULT_IAID_DUID", "0", 0, 0 }, { "DNS_DOMAINNAME", NULL, 0, 0 }, { "ADOPT_DOMAINNAME", "0", 0, 0 }, { "SET_MTU", "1", 0, 0 }, }; /* * df_build_cache(): builds the defaults nvlist cache * * input: void * output: a pointer to an nvlist of the current defaults, or NULL on failure */ static nvlist_t * df_build_cache(void) { char entry[1024]; int i; char *param, *pastv6, *value, *end; FILE *fp; nvlist_t *nvlist; struct dhcp_default *defp; if ((fp = fopen(DHCP_AGENT_DEFAULTS, "r")) == NULL) return (NULL); if (nvlist_alloc(&nvlist, NV_UNIQUE_NAME, 0) != 0) { dhcpmsg(MSG_WARNING, "cannot build default value cache; " "using built-in defaults"); (void) fclose(fp); return (NULL); } while (fgets(entry, sizeof (entry), fp) != NULL) { for (i = 0; entry[i] == ' '; i++) ; end = strrchr(entry, '\n'); value = strchr(entry, '='); if (end == NULL || value == NULL || entry[i] == '#') continue; *end = '\0'; *value++ = '\0'; /* * to be compatible with the old defread()-based code * which ignored case, store the parameters (except for the * leading interface name) in upper case. */ if ((param = strchr(entry, '.')) == NULL) { pastv6 = param = entry; } else { pastv6 = ++param; if (strncasecmp(param, "v6.", 3) == 0) pastv6 += 3; } for (defp = defaults; (char *)defp < (char *)defaults + sizeof (defaults); defp++) { if (strcasecmp(pastv6, defp->df_name) == 0) { if (defp->df_max == -1) { dhcpmsg(MSG_WARNING, "parameter %s is " "obsolete; ignored", defp->df_name); } break; } } for (; *param != '\0'; param++) *param = toupper(*param); if (nvlist_add_string(nvlist, &entry[i], value) != 0) { dhcpmsg(MSG_WARNING, "cannot build default value cache;" " using built-in defaults"); nvlist_free(nvlist); nvlist = NULL; break; } } (void) fclose(fp); return (nvlist); } /* * df_get_string(): gets the string value of a given user-tunable parameter * * input: const char *: the interface the parameter applies to * boolean_t: B_TRUE for DHCPv6, B_FALSE for IPv4 DHCP * uint_t: the parameter number to look up * output: const char *: the parameter's value, or default if not set * (must be copied by caller to be kept) * NOTE: df_get_string() is both used by functions outside this source * file to retrieve strings from the defaults file, *and* * internally by other df_get_*() functions. */ const char * df_get_string(const char *if_name, boolean_t isv6, uint_t param) { char *value; char paramstr[256]; char name[256]; struct stat statbuf; static struct stat df_statbuf; static boolean_t df_unavail_msg = B_FALSE; static nvlist_t *df_nvlist = NULL; if (param >= (sizeof (defaults) / sizeof (*defaults))) return (NULL); if (stat(DHCP_AGENT_DEFAULTS, &statbuf) != 0) { if (!df_unavail_msg) { dhcpmsg(MSG_WARNING, "cannot access %s; using " "built-in defaults", DHCP_AGENT_DEFAULTS); df_unavail_msg = B_TRUE; } return (defaults[param].df_default); } /* * if our cached parameters are stale, rebuild. */ if (statbuf.st_mtime != df_statbuf.st_mtime || statbuf.st_size != df_statbuf.st_size) { df_statbuf = statbuf; nvlist_free(df_nvlist); df_nvlist = df_build_cache(); } if (isv6) { (void) snprintf(name, sizeof (name), ".V6.%s", defaults[param].df_name); (void) snprintf(paramstr, sizeof (paramstr), "%s%s", if_name, name); } else { (void) strlcpy(name, defaults[param].df_name, sizeof (name)); (void) snprintf(paramstr, sizeof (paramstr), "%s.%s", if_name, name); } /* * first look for `if_name.[v6.]param', then `[v6.]param'. if neither * has been set, use the built-in default. */ if (nvlist_lookup_string(df_nvlist, paramstr, &value) == 0 || nvlist_lookup_string(df_nvlist, name, &value) == 0) return (value); return (defaults[param].df_default); } /* * df_get_int(): gets the integer value of a given user-tunable parameter * * input: const char *: the interface the parameter applies to * boolean_t: B_TRUE for DHCPv6, B_FALSE for IPv4 DHCP * uint_t: the parameter number to look up * output: int: the parameter's value, or default if not set */ int df_get_int(const char *if_name, boolean_t isv6, uint_t param) { const char *value; int value_int; if (param >= (sizeof (defaults) / sizeof (*defaults))) return (0); value = df_get_string(if_name, isv6, param); if (value == NULL || !isdigit(*value)) goto failure; value_int = atoi(value); if (value_int > defaults[param].df_max || value_int < defaults[param].df_min) goto failure; return (value_int); failure: dhcpmsg(MSG_WARNING, "df_get_int: parameter `%s' is not between %d and " "%d, defaulting to `%s'", defaults[param].df_name, defaults[param].df_min, defaults[param].df_max, defaults[param].df_default); return (atoi(defaults[param].df_default)); } /* * df_get_bool(): gets the boolean value of a given user-tunable parameter * * input: const char *: the interface the parameter applies to * boolean_t: B_TRUE for DHCPv6, B_FALSE for IPv4 DHCP * uint_t: the parameter number to look up * output: boolean_t: B_TRUE if true, B_FALSE if false, default if not set */ boolean_t df_get_bool(const char *if_name, boolean_t isv6, uint_t param) { const char *value; if (param >= (sizeof (defaults) / sizeof (*defaults))) return (0); value = df_get_string(if_name, isv6, param); if (value != NULL) { if (strcasecmp(value, "true") == 0 || strcasecmp(value, "yes") == 0 || strcmp(value, "1") == 0) return (B_TRUE); if (strcasecmp(value, "false") == 0 || strcasecmp(value, "no") == 0 || strcmp(value, "0") == 0) return (B_FALSE); } dhcpmsg(MSG_WARNING, "df_get_bool: parameter `%s' has invalid value " "`%s', defaulting to `%s'", defaults[param].df_name, value != NULL ? value : "NULL", defaults[param].df_default); return ((atoi(defaults[param].df_default) == 0) ? B_FALSE : B_TRUE); } /* * 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. * Copyright (c) 2016-2017, Chris Fraire . * Copyright 2019 Joshua M. Clulow */ #ifndef DEFAULTS_H #define DEFAULTS_H #include /* * defaults.[ch] encapsulate the agent's interface to the dhcpagent * defaults file. see defaults.c for documentation on how to use the * exported functions. */ #ifdef __cplusplus extern "C" { #endif /* * Tunable parameters -- keep in the same order as defaults[] in defaults.c */ enum { DF_RELEASE_ON_SIGTERM, /* send RELEASE on each if upon SIGTERM */ _UNUSED_DF_IGNORE_FAILED_ARP, DF_OFFER_WAIT, /* how long to wait to collect offers */ _UNUSED_DF_ARP_WAIT, DF_CLIENT_ID, /* our client id */ DF_PARAM_REQUEST_LIST, /* our parameter request list */ DF_REQUEST_HOSTNAME, /* request hostname associated with interface */ DF_DEBUG_LEVEL, /* set debug level (undocumented) */ DF_VERBOSE, /* set verbose mode (undocumented) */ DF_VERIFIED_LEASE_ONLY, /* send RELEASE on SIGTERM and need verify */ DF_PARAM_IGNORE_LIST, /* our parameter ignore list */ DF_REQUEST_FQDN, /* request FQDN associated with interface */ DF_V4_DEFAULT_IAID_DUID, /* IAID/DUID if no DF_CLIENT_ID */ DF_DNS_DOMAINNAME, /* static domain name if not in --reqhost */ DF_ADOPT_DOMAINNAME, /* adopt DHCP domain if not in --reqhost */ DF_SET_MTU /* set interface MTU (option 26) */ }; #define DHCP_AGENT_DEFAULTS "/etc/default/dhcpagent" boolean_t df_get_bool(const char *, boolean_t, uint_t); int df_get_int(const char *, boolean_t, uint_t); const char *df_get_string(const char *, boolean_t, uint_t); #ifdef __cplusplus } #endif #endif /* DEFAULTS_H */ # # 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. # Copyright (c) 2016-2017, Chris Fraire . # Copyright 2019 Joshua M. Clulow # # # This file contains tunable parameters for dhcpagent(8). # # All parameters can be tuned for a specific interface by prepending # the interface name to the parameter name. For example, to make # VERIFIED_LEASE_ONLY happen on all interfaces except hme0, specify: # # hme0.VERIFIED_LEASE_ONLY=no # VERIFIED_LEASE_ONLY=yes # # An interface name alone specifies IPv4 DHCP. For DHCPv6, append ".v6". # Some examples: # # hme0.VERIFIED_LEASE_ONLY=no specify hme0 v4 behavior # hme0.v6.VERIFIED_LEASE_ONLY=no specify hme0 v6 behavior # VERIFIED_LEASE_ONLY=no match all v4 interfaces # .v6.VERIFIED_LEASE_ONLY=no match all v6 interfaces # By default, when the DHCP agent is sent a SIGTERM (typically when # the system is shut down), all managed addresses are dropped rather # than released. Dropping an address does not notify the DHCP server # that the address is no longer in use, leaving it possibly available # for subsequent use by the same client. If DHCP is later restarted # on the interface, the client will ask the server if it can continue # to use the address. If the server either grants the request, or # does not answer (and the lease has not yet expired), then the client # will use the original address. # # Similarly, when the system is suspended and then woken up or when # the link status transitions from down to up, DHCP will ask the server # to continue to use the managed address, in case the lease has changed. # # By uncommenting the following parameter-value pairs, all managed # addresses are released on SIGTERM instead, and any that may have been # saved but cannot be verified will not be used. When SIGTERM is # received, the DHCP server is notified that the address is available # for use, and the address will not be saved for a later restart. If # DHCP receives SIGTHAW or a link-up event, DHCP will attempt to verify # the previous lease, but if unable to do so, it will not attempt to # use that lease. This behavior is often preferred for roaming systems. # # VERIFIED_LEASE_ONLY=yes # .v6.VERIFIED_LEASE_ONLY=yes # By default, the DHCP agent waits 3 seconds to collect OFFER # responses to a DISCOVER. If it receives no OFFERs in this time, it # then waits for another 3 seconds, and so forth. To change this # behavior, set and uncomment the following parameter-value pair. # Note: this does not control the retransmission strategy for # DISCOVERs, which is formally specified in RFC 2131. This parameter # is specified in seconds. # # OFFER_WAIT= # By default, the DHCP agent does not send out a client identifier # (and hence, the chaddr field is used by the DHCP server as the # client identifier.) To make the DHCP agent send a client # identifier, set and uncomment the following parameter-value pair. # Note that by default this is treated as an NVT ASCII string. To # specify a binary value, prepend "0x" to a sequence of hexadecimal # digits (for example, the value 0xAABBCC11 would set the client # identifier to the 4-byte binary sequence 0xAA 0xBB 0xCC 0x11). # # CLIENT_ID= # By default, for an IPv4 interface that is not in an IP network # multipathing (IPMP) group, that is not IP over InfiniBand (IPoIB), and # that is not a logical interface, the DHCP agent will forgo sending a # client identifier unless CLIENT_ID is defined. # # To use a system-managed, RFC 3315-style (i.e., DHCPv6-style) binding # identifier as documented in RFC 4361, "Node-specific Client Identifiers # for DHCPv4," for all IPv4 interfaces (unless CLIENT_ID is defined), # uncomment the following line. # # V4_DEFAULT_IAID_DUID=yes # By default, the DHCP agent will try to request the Fully Qualified Domain # Name (FQDN) currently associated with the interface performing DHCP. The # hostname is defined by using the -h,--reqhost option of ipadm(8) or the # ncu ip-reqhost property of nwamcfg(8) or by flagging the interface as # primary so that nodename(5) is used as the hostname. # # A defined hostname will be used as the FQDN if it is "rooted" (i.e., if # it ends with a '.') or if it consists of at least three DNS labels (e.g., # srv.example.com). If the hostname is not an FQDN, then DNS_DOMAINNAME # will be appended if defined or ADOPT_DOMAINNAME discernment will be used # if active. If no FQDN can be determined, the option will not be used. # # If this REQUEST_FQDN option is enabled, an FQDN will be sent in messages # to the DHCP server along with RFC 4702 options to request that a # collaborating DNS server perform DNS updates for A and PTR resource # records. To prevent sending FQDN and DNS options, uncomment the line # below. # # If an FQDN is sent, REQUEST_HOSTNAME processing will not be done, per RFC # 4702 (3.1): "clients that send the Client FQDN option in their messages # MUST NOT also send the Host Name." # # REQUEST_FQDN=no # By default, the DHCP agent will not attempt to construct an FQDN from a # PQDN specified by the -h,--reqhost option of ipadm(8), by the ncu # ip-reqhost property of nwamcfg(8), or by nodename(5). Set and # uncomment the following parameter to indicate a domain name to be used by # the DHCP agent to construct if necessary an FQDN. # # DNS_DOMAINNAME= # By default, the DHCP agent will not attempt to use a domain name returned # by the DHCP server or the domain in resolv.conf(5) to construct an FQDN # from a PQDN specified by the -h,--reqhost option of ipadm(8), by the ncu # ip-reqhost property of nwamcfg(8), or by nodename(5). Set and uncomment # the following parameter to indicate that a returned DHCPv4 DNSdmain or the # domain from resolv.conf(5) should be adopted by the DHCP agent to # construct if necessary an FQDN. # # ADOPT_DOMAINNAME=yes # By default, the DHCP agent will try to request the hostname currently # associated with the interface performing DHCP. If this option is # enabled, the agent will attempt to use an -h,--reqhost option saved with # ipadm(8) or an ncu ip-reqhost property set with nwamcfg(8); or else # attempt to find a host name in /etc/hostname., which must contain a # line of the form # # inet name # # where "name" is a single RFC 1101-compliant token; or else use # nodename(5) for a DHCP interface flagged as primary. If found in any of # these configurations, the token will be used to request that host name # from the DHCP server. To prevent this, uncomment the following line. # # REQUEST_HOSTNAME=no # By default, the DHCP agent will set the MTU of the link if the MTU option # (26) is provided by the server. To prevent this, uncomment the following # line. # # SET_MTU=no # By default, a parameter request list requesting a subnet mask (1), router # (3), DNS server (6), hostname (12), DNS domain (15), MTU (26), broadcast # address (28), and encapsulated vendor options (43), is sent to the DHCP # server when the DHCP agent sends requests. However, if desired, this can be # changed by altering the following parameter-value pair. The numbers # correspond to the values defined in the IANA bootp-dhcp-parameters registry # at the time of this writing. Site and standard option names from # /etc/dhcp/inittab are also accepted. # PARAM_REQUEST_LIST=1,3,6,12,15,26,28,43 # The default DHCPv6 parameter request list has preference (7), unicast (12), # DNS addresses (23), DNS search list (24), NIS addresses (27), and # NIS domain (29). This may be changed by altering the following parameter- # value pair. The numbers correspond to the values defined in the IANA # dhcpv6-parameters registry at the time of this writing. Site and standard # option names from /etc/dhcp/inittab6 are also accepted. .v6.PARAM_REQUEST_LIST=7,12,23,24,27,29 # The parameter ignore list allows you to instruct the DHCP client to discard # optional parameters received from the DHCP server. The format is the same # as the request list above. When discarded, a parameter will not be acted # on by the DHCP client or returned to users via the dhcpinfo(1) command. PARAM_IGNORE_LIST= .v6.PARAM_IGNORE_LIST= # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (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 # msgid "vd:l:fa" msgid "" msgid "/" msgid "%s %02x" msgid "/dev/openprom" msgid "name" msgid "SUNW." msgid "SUNW.%s" msgid "RELEASE_ON_SIGTERM=" msgid "IGNORE_FAILED_ARP=" msgid "OFFER_WAIT=" msgid "ARP_WAIT=" msgid "CLIENT_ID=" msgid "PARAM_REQUEST_LIST=" msgid "%s:%s" msgid "0x" msgid "true" msgid "yes" msgid "false" msgid "no" msgid "NULL" msgid "/dev/" msgid "0123456789" msgid "pfmod" msgid "DHCP" msgid "BOOTP" msgid "DISCOVER" msgid "OFFER" msgid "REQUEST" msgid "DECLINE" msgid "ACK" msgid "NAK" msgid "RELEASE" msgid "INFORM" /* * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * * INFORM_SENT state of the client state machine. */ #include #include #include #include #include #include #include #include #include "agent.h" #include "states.h" #include "interface.h" #include "packet.h" static boolean_t stop_informing(dhcp_smach_t *, unsigned int); /* * dhcp_inform(): sends an INFORM packet and sets up reception for an ACK * * input: dhcp_smach_t *: the state machine to use * output: void * note: the INFORM cannot be sent successfully if the interface * does not have an IP address (this is mostly an issue for IPv4). * We switch into INFORM_SENT state before sending the packet so * that the packet-sending subsystem uses regular sockets and sets * the source address. (See set_smach_state.) */ void dhcp_inform(dhcp_smach_t *dsmp) { dhcp_pkt_t *dpkt; if (!set_smach_state(dsmp, INFORM_SENT)) goto failed; if (dsmp->dsm_isv6) { dpkt = init_pkt(dsmp, DHCPV6_MSG_INFO_REQ); /* Add required Option Request option */ (void) add_pkt_prl(dpkt, dsmp); dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers; (void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server, stop_informing, DHCPV6_INF_TIMEOUT, DHCPV6_INF_MAX_RT); } else { ipaddr_t server; /* * Assemble a DHCPREQUEST packet, without the Server ID option. * Fill in ciaddr, since we know this. dsm_server will be set * to the server's IP address, which will be the broadcast * address if we don't know it. The max DHCP message size * option is set to the interface max, minus the size of the * UDP and IP headers. */ dpkt = init_pkt(dsmp, INFORM); IN6_V4MAPPED_TO_INADDR(&dsmp->dsm_lif->lif_v6addr, &dpkt->pkt->ciaddr); (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, htons(dsmp->dsm_lif->lif_pif->pif_mtu - sizeof (struct udpiphdr))); if (class_id_len != 0) { (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len); } (void) add_pkt_prl(dpkt, dsmp); (void) add_pkt_opt(dpkt, CD_END, NULL, 0); IN6_V4MAPPED_TO_IPADDR(&dsmp->dsm_server, server); if (!send_pkt(dsmp, dpkt, server, stop_informing)) { dhcpmsg(MSG_ERROR, "dhcp_inform: send_pkt failed"); goto failed; } } return; failed: dsmp->dsm_dflags |= DHCP_IF_FAILED; ipc_action_finish(dsmp, DHCP_IPC_E_INT); (void) set_smach_state(dsmp, INIT); } /* * stop_informing(): decides when to stop retransmitting Information-Requests * * input: dhcp_smach_t *: the state machine Info-Reqs are being sent from * unsigned int: the number of requests sent so far * output: boolean_t: B_TRUE if retransmissions should stop */ /* ARGSUSED */ static boolean_t stop_informing(dhcp_smach_t *dsmp, unsigned int n_requests) { return (B_FALSE); } /* * 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. * Copyright (c) 2016-2017, Chris Fraire . * * INIT_REBOOT state of the DHCP client state machine. */ #include #include #include #include #include #include #include #include #include #include #include "agent.h" #include "packet.h" #include "states.h" #include "util.h" #include "interface.h" #include "defaults.h" static stop_func_t stop_init_reboot; /* * dhcp_init_reboot_v4(): attempts to reuse a cached configuration for a state * machine. * * input: dhcp_smach_t *: the state machine to examine for reuse * output: void */ static void dhcp_init_reboot_v4(dhcp_smach_t *dsmp) { dhcp_pkt_t *dpkt; /* * assemble DHCPREQUEST message. The max dhcp message size * option is set to the interface max, minus the size of the udp and * ip headers. */ dpkt = init_pkt(dsmp, REQUEST); (void) add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR, dsmp->dsm_ack->pkt->yiaddr.s_addr); (void) add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM)); (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, htons(dsmp->dsm_lif->lif_pif->pif_mtu - sizeof (struct udpiphdr))); if (class_id_len != 0) (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len); (void) add_pkt_prl(dpkt, dsmp); if (!dhcp_add_fqdn_opt(dpkt, dsmp)) (void) dhcp_add_hostname_opt(dpkt, dsmp); (void) add_pkt_opt(dpkt, CD_END, NULL, 0); (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), stop_init_reboot); } /* * dhcp_init_reboot_v6(): attempts to reuse a cached configuration for a state * machine. Create a Confirm message and multicast it * out. * * input: dhcp_smach_t *: the state machine to examine for reuse * output: void */ static void dhcp_init_reboot_v6(dhcp_smach_t *dsmp) { dhcp_pkt_t *dpkt; dhcpv6_option_t *d6o, *d6so, *popt; uint_t olen, solen; dhcpv6_ia_na_t d6in; dhcpv6_iaaddr_t d6ia; char *obase; /* * Assemble a Confirm message based on the current ack. */ dpkt = init_pkt(dsmp, DHCPV6_MSG_CONFIRM); /* * Loop over and copy IA_NAs and IAADDRs we have in our last ack. This * is what we'll be requesting. */ d6o = NULL; while ((d6o = dhcpv6_pkt_option(dsmp->dsm_ack, d6o, DHCPV6_OPT_IA_NA, &olen)) != NULL) { /* * Copy in IA_NA option from the ack. Note that we use zero * for all timers in accordance with RFC 3315. (It would make * some sense to say what we think the current timers are as * a hint to the server, but the RFC doesn't agree.) */ if (olen < sizeof (dhcpv6_ia_na_t)) continue; (void) memcpy(&d6in, d6o, sizeof (d6in)); d6in.d6in_t1 = 0; d6in.d6in_t2 = 0; popt = add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA, (char *)&d6in + sizeof (*d6o), sizeof (d6in) - sizeof (*d6o)); if (popt == NULL) goto failure; /* * Now loop over the IAADDR suboptions and add those. */ obase = (char *)d6o + sizeof (dhcpv6_ia_na_t); olen -= sizeof (dhcpv6_ia_na_t); d6so = NULL; while ((d6so = dhcpv6_find_option(obase, olen, d6so, DHCPV6_OPT_IAADDR, &solen)) != NULL) { if (solen < sizeof (dhcpv6_iaaddr_t)) continue; (void) memcpy(&d6ia, d6so, sizeof (d6ia)); d6ia.d6ia_preflife = 0; d6ia.d6ia_vallife = 0; if (add_pkt_subopt(dpkt, popt, DHCPV6_OPT_IAADDR, (char *)&d6ia + sizeof (*d6so), sizeof (d6ia) - sizeof (*d6so)) == NULL) goto failure; } } /* Add required Option Request option */ (void) add_pkt_prl(dpkt, dsmp); (void) send_pkt_v6(dsmp, dpkt, ipv6_all_dhcp_relay_and_servers, stop_init_reboot, DHCPV6_CNF_TIMEOUT, DHCPV6_CNF_MAX_RT); return; failure: if (!set_start_timer(dsmp)) dhcp_selecting(dsmp); } /* * dhcp_init_reboot(): attempts to reuse a cached configuration for a state * machine. * * input: dhcp_smach_t *: the state machine to examine for reuse * output: void */ void dhcp_init_reboot(dhcp_smach_t *dsmp) { dhcpmsg(MSG_VERBOSE, "%s has cached configuration - entering " "INIT_REBOOT", dsmp->dsm_name); if (!set_smach_state(dsmp, INIT_REBOOT)) { dhcpmsg(MSG_ERROR, "dhcp_init_reboot: cannot register to " "collect ACK/NAK packets, reverting to INIT on %s", dsmp->dsm_name); dsmp->dsm_dflags |= DHCP_IF_FAILED; (void) set_smach_state(dsmp, INIT); ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); return; } if (dsmp->dsm_isv6) dhcp_init_reboot_v6(dsmp); else dhcp_init_reboot_v4(dsmp); } /* * stop_init_reboot(): decides when to stop retransmitting REQUESTs * * input: dhcp_smach_t *: the state machine sending the REQUESTs * unsigned int: the number of REQUESTs sent so far * output: boolean_t: B_TRUE if retransmissions should stop */ static boolean_t stop_init_reboot(dhcp_smach_t *dsmp, unsigned int n_requests) { if (dsmp->dsm_isv6) { uint_t nowabs, maxabs; nowabs = NSEC2MSEC(gethrtime()); maxabs = NSEC2MSEC(dsmp->dsm_neg_hrtime) + DHCPV6_CNF_MAX_RD; if (nowabs < maxabs) { /* Cap the timer based on the maximum */ if (nowabs + dsmp->dsm_send_timeout > maxabs) dsmp->dsm_send_timeout = maxabs - nowabs; return (B_FALSE); } } else { if (n_requests < DHCP_MAX_REQUESTS) return (B_FALSE); } if (df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, DF_VERIFIED_LEASE_ONLY)) { dhcpmsg(MSG_INFO, "unable to verify existing lease on %s; restarting", dsmp->dsm_name); dhcp_selecting(dsmp); return (B_TRUE); } if (dsmp->dsm_isv6) { dhcpmsg(MSG_INFO, "no Reply to Confirm, using remainder of " "existing lease on %s", dsmp->dsm_name); } else { dhcpmsg(MSG_INFO, "no ACK/NAK to INIT_REBOOT REQUEST, " "using remainder of existing lease on %s", dsmp->dsm_name); } /* * We already stuck our old ack in dsmp->dsm_ack and relativized the * packet times, so we can just pretend that the server sent it to us * and move to bound. If that fails, fall back to selecting. */ if (dhcp_bound(dsmp, NULL)) { if (dsmp->dsm_isv6) { if (!save_server_id(dsmp, dsmp->dsm_ack)) goto failure; server_unicast_option(dsmp, dsmp->dsm_ack); } } else { failure: dhcpmsg(MSG_INFO, "unable to use saved lease on %s; restarting", dsmp->dsm_name); dhcp_selecting(dsmp); } return (B_TRUE); } /* * 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. * Copyright 2019 Joshua M. Clulow */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 *); static void update_pif_mtu(dhcp_pif_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_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; } /* * 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); /* * Unplumbing the last logical interface on top of this * physical interface should have returned the MTU to its * original value. */ 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); } } /* * 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, 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 we adjusted the MTU for this interface, put it back now: */ 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; } /* * 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); } static void update_pif_mtu(dhcp_pif_t *pif) { /* * Find the smallest requested MTU, if any, for the logical interfaces * on this physical interface: */ uint_t mtu = 0; for (dhcp_lif_t *lif = pif->pif_lifs; lif != NULL; lif = lif->lif_next) { if (lif->lif_mtu == 0) { /* * This logical interface has not requested a specific * MTU. */ continue; } if (mtu == 0 || mtu > lif->lif_mtu) { mtu = lif->lif_mtu; } } if (mtu == 0) { /* * There are no remaining requests for a specific MTU. Return * the interface to its original MTU. */ 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) { /* * The MTU is already correct. */ 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); } /* * Remove our prior request and update the physical interface. */ lif->lif_mtu = 0; update_pif_mtu(lif->lif_pif); } /* * 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); } /* * 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. * Copyright 2019 Joshua M. Clulow */ #ifndef INTERFACE_H #define INTERFACE_H /* * Interface.[ch] encapsulate all of the agent's knowledge of network * interfaces from the DHCP agent's perspective. See interface.c for * documentation on how to use the exported functions. Note that there are not * functional interfaces for manipulating all of the fields in a PIF or LIF -- * please read the comments in the structure definitions below for the rules on * accessing various fields. */ #ifdef __cplusplus extern "C" { #endif #include #include /* IFNAMSIZ */ #include #include #include #include #include "common.h" #include "util.h" #define V4_PART_OF_V6(v6) v6._S6_un._S6_u32[3] struct dhcp_pif_s { dhcp_pif_t *pif_next; /* Note: must be first */ dhcp_pif_t *pif_prev; dhcp_lif_t *pif_lifs; /* pointer to logical interface list */ uint32_t pif_index; /* interface index */ uint_t pif_mtu_orig; /* Original interface MTU */ uint_t pif_mtu; /* Current interface MTU */ uchar_t *pif_hwaddr; /* our link-layer address */ uchar_t pif_hwlen; /* our link-layer address len */ uchar_t pif_hwtype; /* type of link-layer */ boolean_t pif_isv6; boolean_t pif_running; /* interface is running */ uint_t pif_hold_count; /* reference count */ char pif_name[LIFNAMSIZ]; char pif_grifname[LIFNAMSIZ]; uint32_t pif_grindex; /* interface index for pif_grifname */ boolean_t pif_under_ipmp; /* is an ipmp underlying interface */ }; struct dhcp_lif_s { dhcp_lif_t *lif_next; /* Note: must be first */ dhcp_lif_t *lif_prev; dhcp_pif_t *lif_pif; /* backpointer to parent physical if */ dhcp_smach_t *lif_smachs; /* pointer to list of state machines */ dhcp_lease_t *lif_lease; /* backpointer to lease holding LIF */ uint64_t lif_flags; /* Interface flags (IFF_*) */ int lif_sock_ip_fd; /* Bound to addr.BOOTPC for src addr */ iu_event_id_t lif_packet_id; /* event packet id */ uint_t lif_mtu; /* Requested interface MTU */ uint_t lif_hold_count; /* reference count */ boolean_t lif_dad_wait; /* waiting for DAD resolution */ boolean_t lif_removed; /* removed from list */ boolean_t lif_plumbed; /* interface plumbed by dhcpagent */ boolean_t lif_expired; /* lease has evaporated */ const char *lif_declined; /* reason to refuse this address */ uint32_t lif_iaid; /* unique and stable identifier */ iu_event_id_t lif_iaid_id; /* for delayed writes to /etc */ /* * While in any states except ADOPTING, INIT, INFORMATION and * INFORM_SENT, the following three fields are equal to what we believe * the current address, netmask, and broadcast address on the interface * to be. This is so we can detect if the user changes them and * abandon the interface. */ in6_addr_t lif_v6addr; /* our IP address */ in6_addr_t lif_v6mask; /* our netmask */ in6_addr_t lif_v6peer; /* our broadcast or peer address */ dhcp_timer_t lif_preferred; /* lease preferred timer (v6 only) */ dhcp_timer_t lif_expire; /* lease expire timer */ char lif_name[LIFNAMSIZ]; }; #define lif_addr V4_PART_OF_V6(lif_v6addr) #define lif_netmask V4_PART_OF_V6(lif_v6mask) #define lif_peer V4_PART_OF_V6(lif_v6peer) #define lif_broadcast V4_PART_OF_V6(lif_v6peer) /* used by expired_lif_state to express state of DHCP interfaces */ typedef enum dhcp_expire_e { DHCP_EXP_NOLIFS, DHCP_EXP_NOEXP, DHCP_EXP_ALLEXP, DHCP_EXP_SOMEEXP } dhcp_expire_t; /* * A word on memory management and LIFs and PIFs: * * Since LIFs are often passed as context to callback functions, they cannot be * freed when the interface they represent is dropped or released (or when * those callbacks finally go off, they will be hosed). To handle this * situation, the structures are reference counted. Here are the rules for * managing these counts: * * A PIF is created through insert_pif(). Along with initializing the PIF, * this puts a hold on the PIF. A LIF is created through insert_lif(). This * also initializes the LIF and places a hold on it. The caller's hold on the * underlying PIF is transferred to the LIF. * * Whenever a lease is released or dropped (implicitly or explicitly), * remove_lif() is called, which sets the lif_removed flag and removes the * interface from the internal list of managed interfaces. Lastly, * remove_lif() calls release_lif() to remove the hold acquired in * insert_lif(). If this decrements the hold count on the interface to zero, * then free() is called and the hold on the PIF is dropped. If there are * holds other than the hold acquired in insert_lif(), the hold count will * still be > 0, and the interface will remain allocated (though dormant). * * Whenever a callback is scheduled against a LIF, another hold must be put on * the ifslist through hold_lif(). * * Whenever a callback is called back against a LIF, release_lif() must be * called to decrement the hold count, which may end up freeing the LIF if the * hold count becomes zero. * * Since some callbacks may take a long time to get called back (such as * timeout callbacks for lease expiration, etc), it is sometimes more * appropriate to cancel the callbacks and call release_lif() if the * cancellation succeeds. This is done in remove_lif() for the lease preferred * and expire callbacks. * * In general, a callback may also call verify_lif() when it gets called back * in addition to release_lif(), to make sure that the interface is still in * fact under the dhcpagent's control. To make coding simpler, there is a * third function, verify_smach(), which performs both the release_lif() and * the verify_lif() on all LIFs controlled by a state machine. */ extern dhcp_pif_t *v4root; extern dhcp_pif_t *v6root; dhcp_pif_t *insert_pif(const char *, boolean_t, int *); void hold_pif(dhcp_pif_t *); void release_pif(dhcp_pif_t *); dhcp_pif_t *lookup_pif_by_uindex(uint16_t, dhcp_pif_t *, boolean_t); dhcp_pif_t *lookup_pif_by_name(const char *, boolean_t); void pif_status(dhcp_pif_t *, boolean_t); dhcp_lif_t *insert_lif(dhcp_pif_t *, const char *, int *); void hold_lif(dhcp_lif_t *); void release_lif(dhcp_lif_t *); void remove_lif(dhcp_lif_t *); dhcp_lif_t *lookup_lif_by_name(const char *, const dhcp_pif_t *); boolean_t verify_lif(const dhcp_lif_t *); dhcp_lif_t *plumb_lif(dhcp_pif_t *, const in6_addr_t *); void unplumb_lif(dhcp_lif_t *); dhcp_lif_t *attach_lif(const char *, boolean_t, int *); int set_lif_dhcp(dhcp_lif_t *); void set_lif_deprecated(dhcp_lif_t *); boolean_t clear_lif_deprecated(dhcp_lif_t *); void set_lif_mtu(dhcp_lif_t *, uint_t); void clear_lif_mtu(dhcp_lif_t *); boolean_t open_ip_lif(dhcp_lif_t *, in_addr_t, boolean_t); void close_ip_lif(dhcp_lif_t *); void lif_mark_decline(dhcp_lif_t *, const char *); boolean_t schedule_lif_timer(dhcp_lif_t *, dhcp_timer_t *, iu_tq_callback_t *); void cancel_lif_timers(dhcp_lif_t *); dhcp_expire_t expired_lif_state(dhcp_smach_t *); dhcp_lif_t *find_expired_lif(dhcp_smach_t *); uint_t get_max_mtu(boolean_t); void remove_v6_strays(void); #ifdef __cplusplus } #endif #endif /* INTERFACE_H */ /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2016, Chris Fraire . */ #include #include #include #include #include "agent.h" #include "states.h" #include "interface.h" #include "ipc_action.h" #include "util.h" static iu_tq_callback_t ipc_action_timeout; /* * ipc_action_init(): initializes the ipc_action structure * * input: ipc_action_t *: the structure to initialize * output: void */ void ipc_action_init(ipc_action_t *ia) { ia->ia_cmd = 0; ia->ia_fd = -1; ia->ia_tid = -1; ia->ia_eid = -1; ia->ia_request = NULL; } /* * ipc_action_start(): starts an ipc_action request on a DHCP state machine * * input: dhcp_smach_t *: the state machine to start the action on * ipc_action_t *: request structure * output: B_TRUE if the request is started successfully, B_FALSE otherwise * original request is still valid on failure, consumed otherwise. */ boolean_t ipc_action_start(dhcp_smach_t *dsmp, ipc_action_t *iareq) { struct ipc_action *ia = &dsmp->dsm_ia; if (ia->ia_fd != -1 || ia->ia_tid != -1 || iareq->ia_fd == -1) { dhcpmsg(MSG_CRIT, "ipc_action_start: attempted restart on %s", dsmp->dsm_name); return (B_FALSE); } if (!async_cancel(dsmp)) { dhcpmsg(MSG_WARNING, "ipc_action_start: unable to cancel " "action on %s", dsmp->dsm_name); return (B_FALSE); } if (iareq->ia_request->timeout == DHCP_IPC_WAIT_DEFAULT) iareq->ia_request->timeout = DHCP_IPC_DEFAULT_WAIT; if (iareq->ia_request->timeout == DHCP_IPC_WAIT_FOREVER) { iareq->ia_tid = -1; } else { iareq->ia_tid = iu_schedule_timer(tq, iareq->ia_request->timeout, ipc_action_timeout, dsmp); if (iareq->ia_tid == -1) { dhcpmsg(MSG_ERROR, "ipc_action_start: failed to set " "timer for %s on %s", dhcp_ipc_type_to_string(iareq->ia_cmd), dsmp->dsm_name); return (B_FALSE); } hold_smach(dsmp); } *ia = *iareq; /* We've taken ownership, so the input request is now invalid */ ipc_action_init(iareq); dhcpmsg(MSG_DEBUG, "ipc_action_start: started %s (command %d) on %s," " buffer length %u", dhcp_ipc_type_to_string(ia->ia_cmd), ia->ia_cmd, dsmp->dsm_name, ia->ia_request == NULL ? 0 : ia->ia_request->data_length); dsmp->dsm_dflags |= DHCP_IF_BUSY; /* This cannot fail due to the async_cancel above */ (void) async_start(dsmp, ia->ia_cmd, B_TRUE); return (B_TRUE); } /* * ipc_action_finish(): completes an ipc_action request on an interface * * input: dhcp_smach_t *: the state machine to complete the action on * int: the reason why the action finished (nonzero on error) * output: void */ void ipc_action_finish(dhcp_smach_t *dsmp, int reason) { struct ipc_action *ia = &dsmp->dsm_ia; dsmp->dsm_dflags &= ~DHCP_IF_BUSY; if (dsmp->dsm_ia.ia_fd == -1) { dhcpmsg(MSG_ERROR, "ipc_action_finish: attempted to finish unknown action " "on %s", dsmp->dsm_name); return; } dhcpmsg(MSG_DEBUG, "ipc_action_finish: finished %s (command %d) on %s: %d", dhcp_ipc_type_to_string(ia->ia_cmd), (int)ia->ia_cmd, dsmp->dsm_name, reason); /* * if we can't cancel this timer, we're really in the * twilight zone. however, as long as we don't drop the * reference to the state machine, it shouldn't hurt us */ if (dsmp->dsm_ia.ia_tid != -1 && iu_cancel_timer(tq, dsmp->dsm_ia.ia_tid, NULL) == 1) { dsmp->dsm_ia.ia_tid = -1; release_smach(dsmp); } if (reason == 0) send_ok_reply(ia); else send_error_reply(ia, reason); async_finish(dsmp); } /* * ipc_action_timeout(): times out an ipc_action on a state machine (the * request continues asynchronously, however) * * input: iu_tq_t *: unused * void *: the dhcp_smach_t * the ipc_action was pending on * output: void */ /* ARGSUSED */ static void ipc_action_timeout(iu_tq_t *tq, void *arg) { dhcp_smach_t *dsmp = arg; struct ipc_action *ia = &dsmp->dsm_ia; dsmp->dsm_dflags &= ~DHCP_IF_BUSY; ia->ia_tid = -1; dhcpmsg(MSG_VERBOSE, "ipc timeout waiting for agent to complete " "%s (command %d) for %s", dhcp_ipc_type_to_string(ia->ia_cmd), ia->ia_cmd, dsmp->dsm_name); send_error_reply(ia, DHCP_IPC_E_TIMEOUT); async_finish(dsmp); release_smach(dsmp); } /* * send_ok_reply(): sends an "ok" reply to a request and closes the ipc * connection * * input: ipc_action_t *: the request to reply to * output: void * note: the request is freed (thus the request must be on the heap). */ void send_ok_reply(ipc_action_t *ia) { send_error_reply(ia, 0); } /* * send_error_reply(): sends an "error" reply to a request and closes the ipc * connection * * input: ipc_action_t *: the request to reply to * int: the error to send back on the ipc connection * output: void * note: the request is freed (thus the request must be on the heap). */ void send_error_reply(ipc_action_t *ia, int error) { send_data_reply(ia, error, DHCP_TYPE_NONE, NULL, 0); } /* * send_data_reply(): sends a reply to a request and closes the ipc connection * * input: ipc_action_t *: the request to reply to * int: the status to send back on the ipc connection (zero for * success, DHCP_IPC_E_* otherwise). * dhcp_data_type_t: the type of the payload in the reply * const void *: the payload for the reply, or NULL if there is no * payload * size_t: the size of the payload * output: void * note: the request is freed (thus the request must be on the heap). */ void send_data_reply(ipc_action_t *ia, int error, dhcp_data_type_t type, const void *buffer, size_t size) { dhcp_ipc_reply_t *reply; int retval; if (ia->ia_fd == -1 || ia->ia_request == NULL) return; reply = dhcp_ipc_alloc_reply(ia->ia_request, error, buffer, size, type); if (reply == NULL) { dhcpmsg(MSG_ERR, "send_data_reply: cannot allocate reply"); } else if ((retval = dhcp_ipc_send_reply(ia->ia_fd, reply)) != 0) { dhcpmsg(MSG_ERROR, "send_data_reply: dhcp_ipc_send_reply: %s", dhcp_ipc_strerror(retval)); } /* * free the request since we've now used it to send our reply. * we can also close the socket since the reply has been sent. */ free(reply); free(ia->ia_request); if (ia->ia_eid != -1) (void) iu_unregister_event(eh, ia->ia_eid, NULL); (void) dhcp_ipc_close(ia->ia_fd); ia->ia_request = NULL; ia->ia_fd = -1; ia->ia_eid = -1; } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef IPC_ACTION_H #define IPC_ACTION_H #include #include #include #include #include #include "common.h" /* * ipc_action.[ch] make up the interface used to control the current * pending interprocess communication transaction taking place. see * ipc_action.c for documentation on how to use the exported functions. */ #ifdef __cplusplus extern "C" { #endif typedef struct ipc_action { dhcp_ipc_type_t ia_cmd; /* command/action requested */ int ia_fd; /* ipc channel descriptor */ iu_timer_id_t ia_tid; /* ipc timer id */ iu_event_id_t ia_eid; /* ipc event ID */ dhcp_ipc_request_t *ia_request; /* ipc request pointer */ } ipc_action_t; void ipc_action_init(ipc_action_t *); boolean_t ipc_action_start(dhcp_smach_t *, ipc_action_t *); void ipc_action_finish(dhcp_smach_t *, int); void send_error_reply(ipc_action_t *, int); void send_ok_reply(ipc_action_t *); void send_data_reply(ipc_action_t *, int, dhcp_data_type_t, const void *, size_t); #ifdef __cplusplus } #endif #endif /* IPC_ACTION_H */ /* * 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. * Copyright (c) 2016, Chris Fraire . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "states.h" #include "interface.h" #include "agent.h" #include "packet.h" #include "util.h" int v6_sock_fd = -1; int v4_sock_fd = -1; const in6_addr_t ipv6_all_dhcp_relay_and_servers = { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02 }; /* * We have our own version of this constant because dhcpagent is compiled with * -lxnet. */ const in6_addr_t my_in6addr_any = IN6ADDR_ANY_INIT; static void retransmit(iu_tq_t *, void *); static void next_retransmission(dhcp_smach_t *, boolean_t, boolean_t); static boolean_t send_pkt_internal(dhcp_smach_t *); /* * pkt_send_type(): returns an integer representing the packet's type; only * for use with outbound packets. * * input: dhcp_pkt_t *: the packet to examine * output: uchar_t: the packet type (0 if unknown) */ static uchar_t pkt_send_type(const dhcp_pkt_t *dpkt) { const uchar_t *option; if (dpkt->pkt_isv6) return (((const dhcpv6_message_t *)dpkt->pkt)->d6m_msg_type); /* * this is a little dirty but it should get the job done. * assumes that the type is in the statically allocated part * of the options field. */ option = dpkt->pkt->options; for (;;) { if (*option == CD_PAD) { option++; continue; } if (*option == CD_END || option + 2 - dpkt->pkt->options >= sizeof (dpkt->pkt->options)) return (0); if (*option == CD_DHCP_TYPE) break; option++; option += *option + 1; } return (option[2]); } /* * pkt_recv_type(): returns an integer representing the packet's type; only * for use with inbound packets. * * input: dhcp_pkt_t *: the packet to examine * output: uchar_t: the packet type (0 if unknown) */ uchar_t pkt_recv_type(const PKT_LIST *plp) { if (plp->isv6) return (((const dhcpv6_message_t *)plp->pkt)->d6m_msg_type); else if (plp->opts[CD_DHCP_TYPE] != NULL) return (plp->opts[CD_DHCP_TYPE]->value[0]); else return (0); } /* * pkt_get_xid(): returns transaction ID from a DHCP packet. * * input: const PKT *: the packet to examine * output: uint_t: the transaction ID (0 if unknown) */ uint_t pkt_get_xid(const PKT *pkt, boolean_t isv6) { if (pkt == NULL) return (0); if (isv6) return (DHCPV6_GET_TRANSID((const dhcpv6_message_t *)pkt)); else return (pkt->xid); } /* * init_pkt(): initializes and returns a packet of a given type * * input: dhcp_smach_t *: the state machine that will send the packet * uchar_t: the packet type (DHCP message type) * output: dhcp_pkt_t *: a pointer to the initialized packet; may be NULL */ dhcp_pkt_t * init_pkt(dhcp_smach_t *dsmp, uchar_t type) { dhcp_pkt_t *dpkt = &dsmp->dsm_send_pkt; dhcp_lif_t *lif = dsmp->dsm_lif; dhcp_pif_t *pif = lif->lif_pif; uint_t mtu = pif->pif_mtu; uint32_t xid; boolean_t isv6; dpkt->pkt_isv6 = isv6 = pif->pif_isv6; /* * Since multiple dhcp leases may be maintained over the same pif * (e.g. "hme0" and "hme0:1"), make sure the xid is unique. * * Note that transaction ID zero is intentionally never assigned. * That's used to represent "no ID." Also note that transaction IDs * are only 24 bits long in DHCPv6. */ do { xid = mrand48(); if (isv6) xid &= 0xFFFFFF; } while (xid == 0 || lookup_smach_by_xid(xid, NULL, dpkt->pkt_isv6) != NULL); if (isv6) { dhcpv6_message_t *v6; if (mtu != dpkt->pkt_max_len && (v6 = realloc(dpkt->pkt, mtu)) != NULL) { /* LINTED: alignment known to be correct */ dpkt->pkt = (PKT *)v6; dpkt->pkt_max_len = mtu; } if (sizeof (*v6) > dpkt->pkt_max_len) { dhcpmsg(MSG_ERR, "init_pkt: cannot allocate v6 pkt: %u", mtu); return (NULL); } v6 = (dhcpv6_message_t *)dpkt->pkt; dpkt->pkt_cur_len = sizeof (*v6); (void) memset(v6, 0, dpkt->pkt_max_len); v6->d6m_msg_type = type; DHCPV6_SET_TRANSID(v6, xid); if (dsmp->dsm_cidlen > 0 && add_pkt_opt(dpkt, DHCPV6_OPT_CLIENTID, dsmp->dsm_cid, dsmp->dsm_cidlen) == NULL) { dhcpmsg(MSG_WARNING, "init_pkt: cannot insert client ID"); return (NULL); } /* For v6, time starts with the creation of a transaction */ dsmp->dsm_neg_hrtime = gethrtime(); dsmp->dsm_newstart_monosec = monosec(); } else { static uint8_t bootmagic[] = BOOTMAGIC; PKT *v4; if (mtu != dpkt->pkt_max_len && (v4 = realloc(dpkt->pkt, mtu)) != NULL) { dpkt->pkt = v4; dpkt->pkt_max_len = mtu; } if (offsetof(PKT, options) > dpkt->pkt_max_len) { dhcpmsg(MSG_ERR, "init_pkt: cannot allocate v4 pkt: %u", mtu); return (NULL); } v4 = dpkt->pkt; dpkt->pkt_cur_len = offsetof(PKT, options); (void) memset(v4, 0, dpkt->pkt_max_len); (void) memcpy(v4->cookie, bootmagic, sizeof (bootmagic)); if (pif->pif_hwlen <= sizeof (v4->chaddr)) { v4->hlen = pif->pif_hwlen; (void) memcpy(v4->chaddr, pif->pif_hwaddr, pif->pif_hwlen); } else { /* * The mac address does not fit in the chaddr * field, thus it can not be sent to the server, * thus server can not unicast the reply. Per * RFC 2131 4.4.1, client can set this bit in * DISCOVER/REQUEST. If the client is already * in a bound state, do not set this bit, as it * can respond to unicast responses from server * using the 'ciaddr' address. */ if (type == DISCOVER || (type == REQUEST && !is_bound_state(dsmp->dsm_state))) v4->flags = htons(BCAST_MASK); } v4->xid = xid; v4->op = BOOTREQUEST; v4->htype = pif->pif_hwtype; if (add_pkt_opt(dpkt, CD_DHCP_TYPE, &type, 1) == NULL) { dhcpmsg(MSG_WARNING, "init_pkt: cannot set DHCP packet type"); return (NULL); } if (dsmp->dsm_cidlen > 0 && add_pkt_opt(dpkt, CD_CLIENT_ID, dsmp->dsm_cid, dsmp->dsm_cidlen) == NULL) { dhcpmsg(MSG_WARNING, "init_pkt: cannot insert client ID"); return (NULL); } } return (dpkt); } /* * remove_pkt_opt(): removes the first instance of an option from a dhcp_pkt_t * * input: dhcp_pkt_t *: the packet to remove the option from * uint_t: the type of option being added * output: boolean_t: B_TRUE on success, B_FALSE on failure * note: currently does not work with DHCPv6 suboptions, or to remove * arbitrary option instances. */ boolean_t remove_pkt_opt(dhcp_pkt_t *dpkt, uint_t opt_type) { uchar_t *raw_pkt, *raw_end, *next; uint_t len; raw_pkt = (uchar_t *)dpkt->pkt; raw_end = raw_pkt + dpkt->pkt_cur_len; if (dpkt->pkt_isv6) { dhcpv6_option_t d6o; raw_pkt += sizeof (dhcpv6_message_t); opt_type = htons(opt_type); while (raw_pkt + sizeof (d6o) <= raw_end) { (void) memcpy(&d6o, raw_pkt, sizeof (d6o)); len = ntohs(d6o.d6o_len) + sizeof (d6o); if (len > raw_end - raw_pkt) break; next = raw_pkt + len; if (d6o.d6o_code == opt_type) { if (next < raw_end) { (void) memmove(raw_pkt, next, raw_end - next); } dpkt->pkt_cur_len -= len; return (B_TRUE); } raw_pkt = next; } } else { uchar_t *pstart, *padrun; raw_pkt += offsetof(PKT, options); pstart = raw_pkt; if (opt_type == CD_END || opt_type == CD_PAD) return (B_FALSE); padrun = NULL; while (raw_pkt + 1 <= raw_end) { if (*raw_pkt == CD_END) break; if (*raw_pkt == CD_PAD) { if (padrun == NULL) padrun = raw_pkt; raw_pkt++; continue; } if (raw_pkt + 2 > raw_end) break; len = raw_pkt[1]; if (len > raw_end - raw_pkt || len < 2) break; next = raw_pkt + len; if (*raw_pkt == opt_type) { if (next < raw_end) { int toadd = (4 + ((next-pstart)&3) - ((raw_pkt-pstart)&3)) & 3; int torem = 4 - toadd; if (torem != 4 && padrun != NULL && (raw_pkt - padrun) >= torem) { raw_pkt -= torem; dpkt->pkt_cur_len -= torem; } else if (toadd > 0) { (void) memset(raw_pkt, CD_PAD, toadd); raw_pkt += toadd; /* max is not an issue here */ dpkt->pkt_cur_len += toadd; } if (raw_pkt != next) { (void) memmove(raw_pkt, next, raw_end - next); } } dpkt->pkt_cur_len -= len; return (B_TRUE); } padrun = NULL; raw_pkt = next; } } return (B_FALSE); } /* * update_v6opt_len(): updates the length field of a DHCPv6 option. * * input: dhcpv6_option_t *: option to be updated * int: number of octets to add or subtract * output: boolean_t: B_TRUE on success, B_FALSE on failure */ boolean_t update_v6opt_len(dhcpv6_option_t *opt, int adjust) { dhcpv6_option_t optval; (void) memcpy(&optval, opt, sizeof (optval)); adjust += ntohs(optval.d6o_len); if (adjust < 0 || adjust > UINT16_MAX) { return (B_FALSE); } else { optval.d6o_len = htons(adjust); (void) memcpy(opt, &optval, sizeof (optval)); return (B_TRUE); } } /* * add_pkt_opt(): adds an option to a dhcp_pkt_t * * input: dhcp_pkt_t *: the packet to add the option to * uint_t: the type of option being added * const void *: the value of that option * uint_t: the length of the value of the option * output: void *: pointer to the option that was added, or NULL on failure. */ void * add_pkt_opt(dhcp_pkt_t *dpkt, uint_t opt_type, const void *opt_val, uint_t opt_len) { uchar_t *raw_pkt; size_t req_len; void *optr; raw_pkt = (uchar_t *)dpkt->pkt; optr = raw_pkt + dpkt->pkt_cur_len; if (dpkt->pkt_isv6) { req_len = opt_len + sizeof (dhcpv6_option_t); if (dpkt->pkt_cur_len + req_len > dpkt->pkt_max_len) { dhcpmsg(MSG_WARNING, "add_pkt_opt: not enough room for v6 option %u in " "packet (%u + %u > %u)", opt_type, dpkt->pkt_cur_len, req_len, dpkt->pkt_max_len); return (NULL); } } else { req_len = opt_len + DHCP_OPT_META_LEN; /* CD_END and CD_PAD options don't have a length field */ if (opt_type == CD_END || opt_type == CD_PAD) { req_len = 1; } else if (opt_val == NULL) { dhcpmsg(MSG_ERROR, "add_pkt_opt: option type %d is " "missing required value", opt_type); return (NULL); } if ((dpkt->pkt_cur_len + req_len) > dpkt->pkt_max_len) { dhcpmsg(MSG_WARNING, "add_pkt_opt: not enough room for v4 option %u in " "packet", opt_type); return (NULL); } } req_len = encode_dhcp_opt(&raw_pkt[dpkt->pkt_cur_len], dpkt->pkt_isv6, opt_type, opt_val, opt_len); dpkt->pkt_cur_len += req_len; return (optr); } /* * encode_dhcp_opt(): sets the fields of an allocated DHCP option buffer * * input: void *: the buffer allocated for enough space for * (DHCPv6) dhcpv6_option_t and value, or for * (DHCPv4) opt_type + length + value (length/value are * skipped for CD_END or CD_PAD); * boolean_t: a value indicating whether DHCPv6 or not; * uint_t: the type of option being added; * const void *: the value of that option; * uint_t: the length of the value of the option * output: size_t: the number of bytes written starting at opt. */ size_t encode_dhcp_opt(void *dopt, boolean_t isv6, uint_t opt_type, const void *opt_val, uint_t opt_len) { boolean_t do_copy_value = B_FALSE; size_t res_len = 0; uint8_t *pval; if (isv6) { dhcpv6_option_t d6o; d6o.d6o_code = htons(opt_type); d6o.d6o_len = htons(opt_len); (void) memcpy(dopt, &d6o, sizeof (d6o)); res_len += sizeof (d6o); do_copy_value = B_TRUE; } else { pval = (uint8_t *)dopt; pval[res_len++] = opt_type; if (opt_type != CD_END && opt_type != CD_PAD) { pval[res_len++] = opt_len; do_copy_value = B_TRUE; } } pval = (uint8_t *)dopt + res_len; if (do_copy_value && opt_len > 0) { (void) memcpy(pval, opt_val, opt_len); res_len += opt_len; } return (res_len); } /* * add_pkt_subopt(): adds an option to a dhcp_pkt_t option. DHCPv6-specific, * but could be extended to IPv4 DHCP if necessary. Assumes * that if the parent isn't a top-level option, the caller * will adjust any upper-level options recursively using * update_v6opt_len. * * input: dhcp_pkt_t *: the packet to add the suboption to * dhcpv6_option_t *: the start of the option to that should contain * it (parent) * uint_t: the type of suboption being added * const void *: the value of that option * uint_t: the length of the value of the option * output: void *: pointer to the suboption that was added, or NULL on * failure. */ void * add_pkt_subopt(dhcp_pkt_t *dpkt, dhcpv6_option_t *parentopt, uint_t opt_type, const void *opt_val, uint_t opt_len) { uchar_t *raw_pkt; int req_len; void *optr; dhcpv6_option_t d6o; uchar_t *optend; int olen; if (!dpkt->pkt_isv6) return (NULL); raw_pkt = (uchar_t *)dpkt->pkt; req_len = opt_len + sizeof (d6o); if (dpkt->pkt_cur_len + req_len > dpkt->pkt_max_len) { dhcpmsg(MSG_WARNING, "add_pkt_subopt: not enough room for v6 suboption %u in " "packet (%u + %u > %u)", opt_type, dpkt->pkt_cur_len, req_len, dpkt->pkt_max_len); return (NULL); } /* * Update the parent option to include room for this option, * and compute the insertion point. */ (void) memcpy(&d6o, parentopt, sizeof (d6o)); olen = ntohs(d6o.d6o_len); optend = (uchar_t *)(parentopt + 1) + olen; olen += req_len; d6o.d6o_len = htons(olen); (void) memcpy(parentopt, &d6o, sizeof (d6o)); /* * If there's anything at the end to move, then move it. Also bump up * the packet size. */ if (optend < raw_pkt + dpkt->pkt_cur_len) { (void) memmove(optend + req_len, optend, (raw_pkt + dpkt->pkt_cur_len) - optend); } dpkt->pkt_cur_len += req_len; /* * Now format the suboption and add it in. */ optr = optend; d6o.d6o_code = htons(opt_type); d6o.d6o_len = htons(opt_len); (void) memcpy(optend, &d6o, sizeof (d6o)); if (opt_len > 0) (void) memcpy(optend + sizeof (d6o), opt_val, opt_len); return (optr); } /* * add_pkt_opt16(): adds an option with a 16-bit value to a dhcp_pkt_t * * input: dhcp_pkt_t *: the packet to add the option to * uint_t: the type of option being added * uint16_t: the value of that option * output: void *: pointer to the option that was added, or NULL on failure. */ void * add_pkt_opt16(dhcp_pkt_t *dpkt, uint_t opt_type, uint16_t opt_value) { return (add_pkt_opt(dpkt, opt_type, &opt_value, 2)); } /* * add_pkt_opt32(): adds an option with a 32-bit value to a dhcp_pkt_t * * input: dhcp_pkt_t *: the packet to add the option to * uint_t: the type of option being added * uint32_t: the value of that option * output: void *: pointer to the option that was added, or NULL on failure. */ void * add_pkt_opt32(dhcp_pkt_t *dpkt, uint_t opt_type, uint32_t opt_value) { return (add_pkt_opt(dpkt, opt_type, &opt_value, 4)); } /* * add_pkt_prl(): adds the parameter request option to the packet * * input: dhcp_pkt_t *: the packet to add the option to * dhcp_smach_t *: state machine with request option * output: void *: pointer to the option that was added, or NULL on failure. */ void * add_pkt_prl(dhcp_pkt_t *dpkt, dhcp_smach_t *dsmp) { uint_t len; if (dsmp->dsm_prllen == 0) return (0); if (dpkt->pkt_isv6) { uint16_t *prl; /* * RFC 3315 requires that we include the option, even if we * have nothing to request. */ if (dsmp->dsm_prllen == 0) prl = NULL; else prl = alloca(dsmp->dsm_prllen * sizeof (uint16_t)); for (len = 0; len < dsmp->dsm_prllen; len++) prl[len] = htons(dsmp->dsm_prl[len]); return (add_pkt_opt(dpkt, DHCPV6_OPT_ORO, prl, len * sizeof (uint16_t))); } else { uint8_t *prl = alloca(dsmp->dsm_prllen); for (len = 0; len < dsmp->dsm_prllen; len++) prl[len] = dsmp->dsm_prl[len]; return (add_pkt_opt(dpkt, CD_REQUEST_LIST, prl, len)); } } /* * add_pkt_lif(): Adds CD_REQUESTED_IP_ADDR (IPv4 DHCP) or IA_NA and IAADDR * (DHCPv6) options to the packet to represent the given LIF. * * input: dhcp_pkt_t *: the packet to add the options to * dhcp_lif_t *: the logical interface to represent * int: status code (unused for IPv4 DHCP) * const char *: message to include with status option, or NULL * output: boolean_t: B_TRUE on success, B_FALSE on failure */ boolean_t add_pkt_lif(dhcp_pkt_t *dpkt, dhcp_lif_t *lif, int status, const char *msg) { if (lif->lif_pif->pif_isv6) { dhcp_smach_t *dsmp; dhcpv6_message_t *d6m; dhcpv6_ia_na_t d6in; dhcpv6_iaaddr_t d6ia; uint32_t iaid; uint16_t *statusopt; dhcpv6_option_t *d6o, *d6so; uint_t olen; /* * Currently, we support just one IAID related to the primary * LIF on the state machine. */ dsmp = lif->lif_lease->dl_smach; iaid = dsmp->dsm_lif->lif_iaid; iaid = htonl(iaid); d6m = (dhcpv6_message_t *)dpkt->pkt; /* * Find or create the IA_NA needed for this LIF. If we * supported IA_TA, we'd check the IFF_TEMPORARY bit here. */ d6o = NULL; while ((d6o = dhcpv6_find_option(d6m + 1, dpkt->pkt_cur_len - sizeof (*d6m), d6o, DHCPV6_OPT_IA_NA, &olen)) != NULL) { if (olen < sizeof (d6in)) continue; (void) memcpy(&d6in, d6o, sizeof (d6in)); if (d6in.d6in_iaid == iaid) break; } if (d6o == NULL) { d6in.d6in_iaid = iaid; d6in.d6in_t1 = 0; d6in.d6in_t2 = 0; d6o = add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA, (dhcpv6_option_t *)&d6in + 1, sizeof (d6in) - sizeof (*d6o)); if (d6o == NULL) return (B_FALSE); } /* * Now add the IAADDR suboption for this LIF. No need to * search here, as we know that this is unique. */ d6ia.d6ia_addr = lif->lif_v6addr; /* * For Release and Decline, we zero out the lifetime. For * Renew and Rebind, we report the original time as the * preferred and valid lifetimes. */ if (d6m->d6m_msg_type == DHCPV6_MSG_RELEASE || d6m->d6m_msg_type == DHCPV6_MSG_DECLINE) { d6ia.d6ia_preflife = 0; d6ia.d6ia_vallife = 0; } else { d6ia.d6ia_preflife = htonl(lif->lif_preferred.dt_start); d6ia.d6ia_vallife = htonl(lif->lif_expire.dt_start); } d6so = add_pkt_subopt(dpkt, d6o, DHCPV6_OPT_IAADDR, (dhcpv6_option_t *)&d6ia + 1, sizeof (d6ia) - sizeof (*d6o)); if (d6so == NULL) return (B_FALSE); /* * Add a status code suboption to the IAADDR to tell the server * why we're declining the address. Note that we must manually * update the enclosing IA_NA, as add_pkt_subopt doesn't know * how to do that. */ if (status != DHCPV6_STAT_SUCCESS || msg != NULL) { olen = sizeof (*statusopt) + (msg == NULL ? 0 : strlen(msg)); statusopt = alloca(olen); *statusopt = htons(status); if (msg != NULL) { (void) memcpy((char *)(statusopt + 1), msg, olen - sizeof (*statusopt)); } d6so = add_pkt_subopt(dpkt, d6so, DHCPV6_OPT_STATUS_CODE, statusopt, olen); if (d6so != NULL) { /* * Update for length of suboption header and * suboption contents. */ (void) update_v6opt_len(d6o, sizeof (*d6so) + olen); } } } else { /* * For DECLINE, we need to add the CD_REQUESTED_IP_ADDR option. * In all other cases (RELEASE and REQUEST), we need to set * ciadr. */ if (pkt_send_type(dpkt) == DECLINE) { if (!add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR, lif->lif_addr)) return (B_FALSE); } else { dpkt->pkt->ciaddr.s_addr = lif->lif_addr; } /* * It's not too worrisome if the message fails to fit in the * packet. The result will still be valid. */ if (msg != NULL) (void) add_pkt_opt(dpkt, CD_MESSAGE, msg, strlen(msg) + 1); } return (B_TRUE); } /* * free_pkt_entry(): frees a packet list list entry * * input: PKT_LIST *: the packet list entry to free * output: void */ void free_pkt_entry(PKT_LIST *plp) { if (plp != NULL) { free(plp->pkt); free(plp); } } /* * free_pkt_list(): frees an entire packet list * * input: PKT_LIST **: the packet list to free * output: void */ void free_pkt_list(PKT_LIST **head) { PKT_LIST *plp; while ((plp = *head) != NULL) { remque(plp); free_pkt_entry(plp); } } /* * send_pkt_internal(): sends a packet out on an interface * * input: dhcp_smach_t *: the state machine with a packet to send * output: boolean_t: B_TRUE if the packet is sent, B_FALSE otherwise */ static boolean_t send_pkt_internal(dhcp_smach_t *dsmp) { ssize_t n_bytes; dhcp_lif_t *lif = dsmp->dsm_lif; dhcp_pkt_t *dpkt = &dsmp->dsm_send_pkt; uchar_t ptype = pkt_send_type(dpkt); const char *pkt_name; struct iovec iov; struct msghdr msg; struct cmsghdr *cmsg; struct in6_pktinfo *ipi6; boolean_t ismcast; int msgtype; /* * Timer should not be running at the point we go to send a packet. */ if (dsmp->dsm_retrans_timer != -1) { dhcpmsg(MSG_CRIT, "send_pkt_internal: unexpected retransmit " "timer on %s", dsmp->dsm_name); stop_pkt_retransmission(dsmp); } pkt_name = pkt_type_to_string(ptype, dpkt->pkt_isv6); /* * if needed, schedule a retransmission timer, then attempt to * send the packet. if we fail, then log the error. our * return value should indicate whether or not we were * successful in sending the request, independent of whether * we could schedule a timer. */ if (dsmp->dsm_send_timeout != 0) { if ((dsmp->dsm_retrans_timer = iu_schedule_timer_ms(tq, dsmp->dsm_send_timeout, retransmit, dsmp)) == -1) dhcpmsg(MSG_WARNING, "send_pkt_internal: cannot " "schedule retransmit timer for %s packet", pkt_name); else hold_smach(dsmp); } if (dpkt->pkt_isv6) { hrtime_t delta; /* * Convert current time into centiseconds since transaction * started. This is what DHCPv6 expects to see in the Elapsed * Time option. */ delta = (gethrtime() - dsmp->dsm_neg_hrtime) / (NANOSEC / 100); if (delta > DHCPV6_FOREVER) delta = DHCPV6_FOREVER; (void) remove_pkt_opt(dpkt, DHCPV6_OPT_ELAPSED_TIME); (void) add_pkt_opt16(dpkt, DHCPV6_OPT_ELAPSED_TIME, htons(delta)); } else { /* * set the `pkt->secs' field depending on the type of packet. * it should be zero, except in the following cases: * * DISCOVER: set to the number of seconds since we started * trying to obtain a lease. * * INFORM: set to the number of seconds since we started * trying to get configuration parameters. * * REQUEST: if in the REQUESTING state, then same value as * DISCOVER, otherwise the number of seconds * since we started trying to obtain a lease. * * we also set `dsm_newstart_monosec', to the time we sent a * REQUEST or DISCOVER packet, so we know the lease start * time (the DISCOVER case is for handling BOOTP servers). */ switch (ptype) { case DISCOVER: dsmp->dsm_newstart_monosec = monosec(); dsmp->dsm_disc_secs = dsmp->dsm_newstart_monosec - hrtime_to_monosec(dsmp->dsm_neg_hrtime); dpkt->pkt->secs = htons(dsmp->dsm_disc_secs); break; case INFORM: dpkt->pkt->secs = htons(monosec() - hrtime_to_monosec(dsmp->dsm_neg_hrtime)); break; case REQUEST: dsmp->dsm_newstart_monosec = monosec(); if (dsmp->dsm_state == REQUESTING) { dpkt->pkt->secs = htons(dsmp->dsm_disc_secs); break; } dpkt->pkt->secs = htons(monosec() - hrtime_to_monosec(dsmp->dsm_neg_hrtime)); break; default: dpkt->pkt->secs = htons(0); break; } } if (dpkt->pkt_isv6) { struct sockaddr_in6 sin6; (void) memset(&iov, 0, sizeof (iov)); iov.iov_base = dpkt->pkt; iov.iov_len = dpkt->pkt_cur_len; (void) memset(&msg, 0, sizeof (msg)); msg.msg_name = &dsmp->dsm_send_dest.v6; msg.msg_namelen = sizeof (struct sockaddr_in6); msg.msg_iov = &iov; msg.msg_iovlen = 1; /* * If the address that's requested cannot be reached, then fall * back to the multcast address. */ if (IN6_IS_ADDR_MULTICAST(&dsmp->dsm_send_dest.v6.sin6_addr)) { ismcast = B_TRUE; } else { struct dstinforeq dinfo; struct strioctl str; ismcast = B_FALSE; (void) memset(&dinfo, 0, sizeof (dinfo)); dinfo.dir_daddr = dsmp->dsm_send_dest.v6.sin6_addr; str.ic_cmd = SIOCGDSTINFO; str.ic_timout = 0; str.ic_len = sizeof (dinfo); str.ic_dp = (char *)&dinfo; if (ioctl(v6_sock_fd, I_STR, &str) == -1) { dhcpmsg(MSG_ERR, "send_pkt_internal: ioctl SIOCGDSTINFO"); } else if (!dinfo.dir_dreachable) { char abuf[INET6_ADDRSTRLEN]; dhcpmsg(MSG_DEBUG, "send_pkt_internal: %s is " "not reachable; using multicast instead", inet_ntop(AF_INET6, &dinfo.dir_daddr, abuf, sizeof (abuf))); sin6 = dsmp->dsm_send_dest.v6; sin6.sin6_addr = ipv6_all_dhcp_relay_and_servers; msg.msg_name = &sin6; ismcast = B_TRUE; } } /* * Make room for our ancillary data option as well as a dummy * option used by CMSG_NXTHDR. */ msg.msg_controllen = sizeof (*cmsg) + _MAX_ALIGNMENT + sizeof (*ipi6) + _MAX_ALIGNMENT + sizeof (*cmsg); msg.msg_control = alloca(msg.msg_controllen); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = IPPROTO_IPV6; cmsg->cmsg_type = IPV6_PKTINFO; /* LINTED: alignment */ ipi6 = (struct in6_pktinfo *)CMSG_DATA(cmsg); if (ismcast) ipi6->ipi6_addr = lif->lif_v6addr; else ipi6->ipi6_addr = my_in6addr_any; if (lif->lif_pif->pif_under_ipmp) ipi6->ipi6_ifindex = lif->lif_pif->pif_grindex; else ipi6->ipi6_ifindex = lif->lif_pif->pif_index; cmsg->cmsg_len = (char *)(ipi6 + 1) - (char *)cmsg; /* * Now correct the control message length. */ cmsg = CMSG_NXTHDR(&msg, cmsg); msg.msg_controllen = (char *)cmsg - (char *)msg.msg_control; n_bytes = sendmsg(v6_sock_fd, &msg, 0); } else { n_bytes = sendto(lif->lif_sock_ip_fd, dpkt->pkt, dpkt->pkt_cur_len, 0, (struct sockaddr *)&dsmp->dsm_send_dest.v4, sizeof (struct sockaddr_in)); } if (n_bytes != dpkt->pkt_cur_len) { msgtype = (n_bytes == -1) ? MSG_ERR : MSG_WARNING; if (dsmp->dsm_retrans_timer == -1) dhcpmsg(msgtype, "send_pkt_internal: cannot send " "%s packet to server", pkt_name); else dhcpmsg(msgtype, "send_pkt_internal: cannot send " "%s packet to server (will retry in %u seconds)", pkt_name, dsmp->dsm_send_timeout / MILLISEC); return (B_FALSE); } dhcpmsg(MSG_VERBOSE, "sent %s xid %x packet out %s", pkt_name, pkt_get_xid(dpkt->pkt, dpkt->pkt_isv6), dsmp->dsm_name); dsmp->dsm_packet_sent++; dsmp->dsm_sent++; return (B_TRUE); } /* * send_pkt(): sends a packet out * * input: dhcp_smach_t *: the state machine sending the packet * dhcp_pkt_t *: the packet to send out * in_addr_t: the destination IP address for the packet * stop_func_t *: a pointer to function to indicate when to stop * retransmitting the packet (if NULL, packet is * not retransmitted) * output: boolean_t: B_TRUE if the packet was sent, B_FALSE otherwise */ boolean_t send_pkt(dhcp_smach_t *dsmp, dhcp_pkt_t *dpkt, in_addr_t dest, stop_func_t *stop) { /* * packets must be at least sizeof (PKT) or they may be dropped * by routers. pad out the packet in this case. */ dpkt->pkt_cur_len = MAX(dpkt->pkt_cur_len, sizeof (PKT)); dsmp->dsm_packet_sent = 0; (void) memset(&dsmp->dsm_send_dest.v4, 0, sizeof (dsmp->dsm_send_dest.v4)); dsmp->dsm_send_dest.v4.sin_addr.s_addr = dest; dsmp->dsm_send_dest.v4.sin_family = AF_INET; dsmp->dsm_send_dest.v4.sin_port = htons(IPPORT_BOOTPS); dsmp->dsm_send_stop_func = stop; /* * TODO: dispose of this gruesome assumption (there's no real * technical gain from doing so, but it would be cleaner) */ assert(dpkt == &dsmp->dsm_send_pkt); /* * clear out any packets which had been previously received * but not pulled off of the recv_packet queue. */ free_pkt_list(&dsmp->dsm_recv_pkt_list); if (stop == NULL) dsmp->dsm_send_timeout = 0; /* prevents retransmissions */ else next_retransmission(dsmp, B_TRUE, B_FALSE); return (send_pkt_internal(dsmp)); } /* * send_pkt_v6(): sends a DHCPv6 packet out * * input: dhcp_smach_t *: the state machine sending the packet * dhcp_pkt_t *: the packet to send out * in6_addr_t: the destination IPv6 address for the packet * stop_func_t *: a pointer to function to indicate when to stop * retransmitting the packet (if NULL, packet is * not retransmitted) * uint_t: Initial Retransmit Timer value * uint_t: Maximum Retransmit Timer value, zero if none * output: boolean_t: B_TRUE if the packet was sent, B_FALSE otherwise */ boolean_t send_pkt_v6(dhcp_smach_t *dsmp, dhcp_pkt_t *dpkt, in6_addr_t dest, stop_func_t *stop, uint_t irt, uint_t mrt) { dsmp->dsm_packet_sent = 0; (void) memset(&dsmp->dsm_send_dest.v6, 0, sizeof (dsmp->dsm_send_dest.v6)); dsmp->dsm_send_dest.v6.sin6_addr = dest; dsmp->dsm_send_dest.v6.sin6_family = AF_INET6; dsmp->dsm_send_dest.v6.sin6_port = htons(IPPORT_DHCPV6S); dsmp->dsm_send_stop_func = stop; /* * TODO: dispose of this gruesome assumption (there's no real * technical gain from doing so, but it would be cleaner) */ assert(dpkt == &dsmp->dsm_send_pkt); /* * clear out any packets which had been previously received * but not pulled off of the recv_packet queue. */ free_pkt_list(&dsmp->dsm_recv_pkt_list); if (stop == NULL) { dsmp->dsm_send_timeout = 0; /* prevents retransmissions */ } else { dsmp->dsm_send_timeout = irt; dsmp->dsm_send_tcenter = mrt; /* * This is quite ugly, but RFC 3315 section 17.1.2 requires * that the RAND value for the very first retransmission of a * Solicit message is strictly greater than zero. */ next_retransmission(dsmp, B_TRUE, pkt_send_type(dpkt) == DHCPV6_MSG_SOLICIT); } return (send_pkt_internal(dsmp)); } /* * retransmit(): retransmits the current packet on an interface * * input: iu_tq_t *: unused * void *: the dhcp_smach_t * (state machine) sending a packet * output: void */ /* ARGSUSED */ static void retransmit(iu_tq_t *tqp, void *arg) { dhcp_smach_t *dsmp = arg; dsmp->dsm_retrans_timer = -1; if (!verify_smach(dsmp)) return; /* * Check the callback to see if we should keep sending retransmissions. * Compute the next retransmission time first, so that the callback can * cap the value if need be. (Required for DHCPv6 Confirm messages.) * * Hold the state machine across the callback so that the called * function can remove the state machine from the system without * disturbing the string used subsequently for verbose logging. The * Release function destroys the state machine when the retry count * expires. */ next_retransmission(dsmp, B_FALSE, B_FALSE); hold_smach(dsmp); if (dsmp->dsm_send_stop_func(dsmp, dsmp->dsm_packet_sent)) { dhcpmsg(MSG_VERBOSE, "retransmit: time to stop on %s", dsmp->dsm_name); } else { dhcpmsg(MSG_VERBOSE, "retransmit: sending another on %s", dsmp->dsm_name); (void) send_pkt_internal(dsmp); } release_smach(dsmp); } /* * stop_pkt_retransmission(): stops retransmission of last sent packet * * input: dhcp_smach_t *: the state machine to stop retransmission on * output: void */ void stop_pkt_retransmission(dhcp_smach_t *dsmp) { if (dsmp->dsm_retrans_timer != -1 && iu_cancel_timer(tq, dsmp->dsm_retrans_timer, NULL) == 1) { dhcpmsg(MSG_VERBOSE, "stop_pkt_retransmission: stopped on %s", dsmp->dsm_name); dsmp->dsm_retrans_timer = -1; release_smach(dsmp); } } /* * retransmit_now(): force a packet retransmission right now. Used only with * the DHCPv6 UseMulticast status code. Use with caution; * triggered retransmissions can cause packet storms. * * input: dhcp_smach_t *: the state machine to force retransmission on * output: void */ void retransmit_now(dhcp_smach_t *dsmp) { stop_pkt_retransmission(dsmp); (void) send_pkt_internal(dsmp); } /* * alloc_pkt_entry(): Allocates a packet list entry with a given data area * size. * * input: size_t: size of data area for packet * boolean_t: B_TRUE for IPv6 * output: PKT_LIST *: allocated packet list entry */ PKT_LIST * alloc_pkt_entry(size_t psize, boolean_t isv6) { PKT_LIST *plp; if ((plp = calloc(1, sizeof (*plp))) == NULL || (plp->pkt = malloc(psize)) == NULL) { free(plp); plp = NULL; } else { plp->len = psize; plp->isv6 = isv6; } return (plp); } /* * sock_recvpkt(): read from the given socket into an allocated buffer and * handles any ancillary data options. * * input: int: file descriptor to read * PKT_LIST *: allocated buffer * output: ssize_t: number of bytes read, or -1 on error */ static ssize_t sock_recvpkt(int fd, PKT_LIST *plp) { struct iovec iov; struct msghdr msg; int64_t ctrl[8192 / sizeof (int64_t)]; ssize_t msglen; (void) memset(&iov, 0, sizeof (iov)); iov.iov_base = (caddr_t)plp->pkt; iov.iov_len = plp->len; (void) memset(&msg, 0, sizeof (msg)); msg.msg_name = &plp->pktfrom; msg.msg_namelen = sizeof (plp->pktfrom); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = ctrl; msg.msg_controllen = sizeof (ctrl); if ((msglen = recvmsg(fd, &msg, 0)) != -1) { struct cmsghdr *cmsg; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { struct sockaddr_in *sinp; struct sockaddr_in6 *sin6; struct in6_pktinfo *ipi6; switch (cmsg->cmsg_level) { case IPPROTO_IP: switch (cmsg->cmsg_type) { case IP_RECVDSTADDR: sinp = (struct sockaddr_in *) &plp->pktto; sinp->sin_family = AF_INET; (void) memcpy(&sinp->sin_addr.s_addr, CMSG_DATA(cmsg), sizeof (ipaddr_t)); break; case IP_RECVIF: (void) memcpy(&plp->ifindex, CMSG_DATA(cmsg), sizeof (uint_t)); break; } break; case IPPROTO_IPV6: switch (cmsg->cmsg_type) { case IPV6_PKTINFO: /* LINTED: alignment */ ipi6 = (struct in6_pktinfo *) CMSG_DATA(cmsg); sin6 = (struct sockaddr_in6 *) &plp->pktto; sin6->sin6_family = AF_INET6; (void) memcpy(&sin6->sin6_addr, &ipi6->ipi6_addr, sizeof (ipi6->ipi6_addr)); (void) memcpy(&plp->ifindex, &ipi6->ipi6_ifindex, sizeof (uint_t)); break; } } } } return (msglen); } /* * recv_pkt(): receives a single DHCP packet on a given file descriptor. * * input: int: the file descriptor to receive the packet from * int: the maximum packet size to allow * boolean_t: B_TRUE for IPv6 * output: PKT_LIST *: the received packet */ PKT_LIST * recv_pkt(int fd, int mtu, boolean_t isv6) { PKT_LIST *plp; ssize_t retval; if ((plp = alloc_pkt_entry(mtu, isv6)) == NULL) { dhcpmsg(MSG_ERROR, "recv_pkt: allocation failure; dropped packet"); return (NULL); } retval = sock_recvpkt(fd, plp); if (retval == -1) { dhcpmsg(MSG_ERR, "recv_pkt: recvfrom v%d failed, dropped", isv6 ? 6 : 4); goto failure; } plp->len = retval; if (isv6) { if (retval < sizeof (dhcpv6_message_t)) { dhcpmsg(MSG_WARNING, "recv_pkt: runt message"); goto failure; } } else { switch (dhcp_options_scan(plp, B_TRUE)) { case DHCP_WRONG_MSG_TYPE: dhcpmsg(MSG_WARNING, "recv_pkt: unexpected DHCP message"); goto failure; case DHCP_GARBLED_MSG_TYPE: dhcpmsg(MSG_WARNING, "recv_pkt: garbled DHCP message type"); goto failure; case DHCP_BAD_OPT_OVLD: dhcpmsg(MSG_WARNING, "recv_pkt: bad option overload"); goto failure; case 0: break; default: dhcpmsg(MSG_WARNING, "recv_pkt: packet corrupted, dropped"); goto failure; } } return (plp); failure: free_pkt_entry(plp); return (NULL); } /* * pkt_v4_match(): check if a given DHCPv4 message type is in a given set * * input: uchar_t: packet type * dhcp_message_type_t: bit-wise OR of DHCP_P* values. * output: boolean_t: B_TRUE if packet type is in the set */ boolean_t pkt_v4_match(uchar_t type, dhcp_message_type_t match_type) { /* * note: the ordering here allows direct indexing of the table * based on the RFC2131 packet type value passed in. */ static dhcp_message_type_t type_map[] = { DHCP_PUNTYPED, DHCP_PDISCOVER, DHCP_POFFER, DHCP_PREQUEST, DHCP_PDECLINE, DHCP_PACK, DHCP_PNAK, DHCP_PRELEASE, DHCP_PINFORM }; if (type < (sizeof (type_map) / sizeof (*type_map))) return ((type_map[type] & match_type) ? B_TRUE : B_FALSE); else return (B_FALSE); } /* * pkt_smach_enqueue(): enqueue a packet on a given state machine * * input: dhcp_smach_t: state machine * PKT_LIST *: packet to enqueue * output: none */ void pkt_smach_enqueue(dhcp_smach_t *dsmp, PKT_LIST *plp) { dhcpmsg(MSG_VERBOSE, "pkt_smach_enqueue: received %s %s packet on %s", pkt_type_to_string(pkt_recv_type(plp), dsmp->dsm_isv6), dsmp->dsm_isv6 ? "v6" : "v4", dsmp->dsm_name); /* add to front of list */ insque(plp, &dsmp->dsm_recv_pkt_list); } /* * next_retransmission(): computes the number of seconds until the next * retransmission, based on the algorithms in RFCs 2131 * 3315. * * input: dhcp_smach_t *: state machine that needs a new timer * boolean_t: B_TRUE if this is the first time sending the message * boolean_t: B_TRUE for positive RAND values only (RFC 3315 17.1.2) * output: none */ static void next_retransmission(dhcp_smach_t *dsmp, boolean_t first_send, boolean_t positive_only) { uint32_t timeout_ms; if (dsmp->dsm_isv6) { double randval; /* * The RFC specifies 0 to 10% jitter for the initial * solicitation, and plus or minus 10% jitter for all others. * This works out to 100 milliseconds on the shortest timer we * use. */ if (positive_only) randval = drand48() / 10.0; else randval = (drand48() - 0.5) / 5.0; /* The RFC specifies doubling *after* the first transmission */ timeout_ms = dsmp->dsm_send_timeout; if (!first_send) timeout_ms *= 2; timeout_ms += (int)(randval * dsmp->dsm_send_timeout); /* This checks the MRT (maximum retransmission time) */ if (dsmp->dsm_send_tcenter != 0 && timeout_ms > dsmp->dsm_send_tcenter) { timeout_ms = dsmp->dsm_send_tcenter + (uint_t)(randval * dsmp->dsm_send_tcenter); } dsmp->dsm_send_timeout = timeout_ms; } else { if (dsmp->dsm_state == RENEWING || dsmp->dsm_state == REBINDING) { monosec_t mono; timeout_ms = dsmp->dsm_state == RENEWING ? dsmp->dsm_leases->dl_t2.dt_start : dsmp->dsm_leases->dl_lifs->lif_expire.dt_start; timeout_ms += dsmp->dsm_curstart_monosec; mono = monosec(); if (mono > timeout_ms) timeout_ms = 0; else timeout_ms -= mono; timeout_ms *= MILLISEC / 2; } else { /* * Start at 4, and increase by a factor of 2 up to 64. */ if (first_send) { timeout_ms = 4 * MILLISEC; } else { timeout_ms = MIN(dsmp->dsm_send_tcenter << 1, 64 * MILLISEC); } } dsmp->dsm_send_tcenter = timeout_ms; /* * At each iteration, jitter the timeout by some fraction of a * second. */ dsmp->dsm_send_timeout = timeout_ms + ((lrand48() % (2 * MILLISEC)) - MILLISEC); } } /* * dhcp_ip_default(): open and bind the default IP sockets used for I/O and * interface control. * * input: none * output: B_TRUE on success */ boolean_t dhcp_ip_default(void) { int on = 1; if ((v4_sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { dhcpmsg(MSG_ERR, "dhcp_ip_default: unable to create IPv4 socket"); return (B_FALSE); } if (setsockopt(v4_sock_fd, IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof (on)) == -1) { dhcpmsg(MSG_ERR, "dhcp_ip_default: unable to enable IP_RECVDSTADDR"); return (B_FALSE); } if (setsockopt(v4_sock_fd, IPPROTO_IP, IP_RECVIF, &on, sizeof (on)) == -1) { dhcpmsg(MSG_ERR, "dhcp_ip_default: unable to enable IP_RECVIF"); return (B_FALSE); } if (!bind_sock(v4_sock_fd, IPPORT_BOOTPC, INADDR_ANY)) { dhcpmsg(MSG_ERROR, "dhcp_ip_default: unable to bind IPv4 socket to port %d", IPPORT_BOOTPC); return (B_FALSE); } if (iu_register_event(eh, v4_sock_fd, POLLIN, dhcp_acknak_global, NULL) == -1) { dhcpmsg(MSG_WARNING, "dhcp_ip_default: cannot register to " "receive IPv4 broadcasts"); return (B_FALSE); } if ((v6_sock_fd = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) { dhcpmsg(MSG_ERR, "dhcp_ip_default: unable to create IPv6 socket"); return (B_FALSE); } if (setsockopt(v6_sock_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof (on)) == -1) { dhcpmsg(MSG_ERR, "dhcp_ip_default: unable to enable IPV6_RECVPKTINFO"); return (B_FALSE); } if (!bind_sock_v6(v6_sock_fd, IPPORT_DHCPV6C, NULL)) { dhcpmsg(MSG_ERROR, "dhcp_ip_default: unable to bind IPv6 socket to port %d", IPPORT_DHCPV6C); return (B_FALSE); } if (iu_register_event(eh, v6_sock_fd, POLLIN, dhcp_acknak_global, NULL) == -1) { dhcpmsg(MSG_WARNING, "dhcp_ip_default: cannot register to " "receive IPv6 packets"); return (B_FALSE); } return (B_TRUE); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2016, Chris Fraire . */ #ifndef _PACKET_H #define _PACKET_H #include #include #include #include #include #include "common.h" /* * packet.[ch] contain routines for manipulating, setting, and * transmitting DHCP/BOOTP packets. see packet.c for descriptions on * how to use the exported functions. */ #ifdef __cplusplus extern "C" { #endif /* * data type for recv_pkt(). needed because we may want to wait for * several kinds of packets at once, and the existing enumeration of * DHCP packet types does not provide a way to do that easily. here, * we light a different bit in the enumeration for each type of packet * we want to receive. * * Note that for DHCPv6, types 4 (CONFIRM), 5 (RENEW), 6 (REBIND), 12 * (RELAY-FORW, and 13 (RELAY-REPL) are not in the table. They're never * received by a client, so there's no reason to process them. (SOLICIT, * REQUEST, DECLINE, RELEASE, and INFORMATION-REQUEST are also never seen by * clients, but are included for consistency.) * * Note also that the symbols are named for the DHCPv4 message types, and that * DHCPv6 has analogous message types. */ typedef enum { DHCP_PUNTYPED = 0x001, /* untyped (BOOTP) message */ DHCP_PDISCOVER = 0x002, /* in v6: SOLICIT (1) */ DHCP_POFFER = 0x004, /* in v6: ADVERTISE (2) */ DHCP_PREQUEST = 0x008, /* in v6: REQUEST (3) */ DHCP_PDECLINE = 0x010, /* in v6: DECLINE (9) */ DHCP_PACK = 0x020, /* in v6: REPLY (7), status == 0 */ DHCP_PNAK = 0x040, /* in v6: REPLY (7), status != 0 */ DHCP_PRELEASE = 0x080, /* in v6: RELEASE (8) */ DHCP_PINFORM = 0x100, /* in v6: INFORMATION-REQUEST (11) */ DHCP_PRECONFIG = 0x200 /* v6 only: RECONFIGURE (10) */ } dhcp_message_type_t; /* * A dhcp_pkt_t is used by the output-side packet manipulation functions. * While the structure is not strictly necessary, it allows a better separation * of functionality since metadata about the packet (such as its current * length) is stored along with the packet. * * Note that 'pkt' points to a dhcpv6_message_t if the packet is IPv6. */ typedef struct dhcp_pkt_s { PKT *pkt; /* the real underlying packet */ unsigned int pkt_max_len; /* its maximum length */ unsigned int pkt_cur_len; /* its current length */ boolean_t pkt_isv6; } dhcp_pkt_t; /* * a `stop_func_t' is used by parts of dhcpagent that use the * retransmission capability of send_pkt(). this makes it so the * callers of send_pkt() decide when to stop retransmitting, which * makes more sense than hardcoding their instance-specific cases into * packet.c */ typedef boolean_t stop_func_t(dhcp_smach_t *, unsigned int); /* * Default I/O and interface control sockets. */ extern int v6_sock_fd; extern int v4_sock_fd; extern const in6_addr_t ipv6_all_dhcp_relay_and_servers; extern const in6_addr_t my_in6addr_any; PKT_LIST *alloc_pkt_entry(size_t, boolean_t); void free_pkt_entry(PKT_LIST *); void free_pkt_list(PKT_LIST **); uchar_t pkt_recv_type(const PKT_LIST *); uint_t pkt_get_xid(const PKT *, boolean_t); dhcp_pkt_t *init_pkt(dhcp_smach_t *, uchar_t); boolean_t remove_pkt_opt(dhcp_pkt_t *, uint_t); boolean_t update_v6opt_len(dhcpv6_option_t *, int); void *add_pkt_opt(dhcp_pkt_t *, uint_t, const void *, uint_t); size_t encode_dhcp_opt(void *, boolean_t, uint_t, const void *, uint_t); void *add_pkt_subopt(dhcp_pkt_t *, dhcpv6_option_t *, uint_t, const void *, uint_t); void *add_pkt_opt16(dhcp_pkt_t *, uint_t, uint16_t); void *add_pkt_opt32(dhcp_pkt_t *, uint_t, uint32_t); void *add_pkt_prl(dhcp_pkt_t *, dhcp_smach_t *); boolean_t add_pkt_lif(dhcp_pkt_t *, dhcp_lif_t *, int, const char *); void stop_pkt_retransmission(dhcp_smach_t *); void retransmit_now(dhcp_smach_t *); PKT_LIST *recv_pkt(int, int, boolean_t); boolean_t pkt_v4_match(uchar_t, dhcp_message_type_t); void pkt_smach_enqueue(dhcp_smach_t *, PKT_LIST *); boolean_t send_pkt(dhcp_smach_t *, dhcp_pkt_t *, in_addr_t, stop_func_t *); boolean_t send_pkt_v6(dhcp_smach_t *, dhcp_pkt_t *, in6_addr_t, stop_func_t *, uint_t, uint_t); boolean_t dhcp_ip_default(void); #ifdef __cplusplus } #endif #endif /* _PACKET_H */ /* * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * * DECLINE/RELEASE configuration functionality for the DHCP client. */ #include #include #include #include #include #include #include #include #include #include #include "agent.h" #include "packet.h" #include "interface.h" #include "states.h" static boolean_t stop_release_decline(dhcp_smach_t *, unsigned int); /* * send_declines(): sends a DECLINE message (broadcasted for IPv4) to the * server to indicate a problem with the offered addresses. * The failing addresses are removed from the leases. * * input: dhcp_smach_t *: the state machine sending DECLINE * output: void */ void send_declines(dhcp_smach_t *dsmp) { dhcp_pkt_t *dpkt; dhcp_lease_t *dlp, *dlpn; uint_t nlifs; dhcp_lif_t *lif, *lifn; boolean_t got_one; /* * Create an empty DECLINE message. We'll stuff the information into * this message as we find it. */ if (dsmp->dsm_isv6) { if ((dpkt = init_pkt(dsmp, DHCPV6_MSG_DECLINE)) == NULL) return; (void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID, dsmp->dsm_serverid, dsmp->dsm_serveridlen); } else { ipaddr_t serverip; /* * If this ack is from BOOTP, then there's no way to send a * decline. Note that since we haven't bound yet, we can't * just check the BOOTP flag. */ if (dsmp->dsm_ack->opts[CD_DHCP_TYPE] == NULL) return; if ((dpkt = init_pkt(dsmp, DECLINE)) == NULL) return; IN6_V4MAPPED_TO_IPADDR(&dsmp->dsm_server, serverip); (void) add_pkt_opt32(dpkt, CD_SERVER_ID, serverip); } /* * Loop over the leases, looking for ones with now-broken LIFs. Add * each one found to the DECLINE message, and remove it from the list. * Also remove any completely declined leases. */ got_one = B_FALSE; for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlpn) { dlpn = dlp->dl_next; lif = dlp->dl_lifs; for (nlifs = dlp->dl_nlifs; nlifs > 0; nlifs--, lif = lifn) { lifn = lif->lif_next; if (lif->lif_declined != NULL) { (void) add_pkt_lif(dpkt, lif, DHCPV6_STAT_UNSPECFAIL, lif->lif_declined); unplumb_lif(lif); got_one = B_TRUE; } } if (dlp->dl_nlifs == 0) remove_lease(dlp); } if (!got_one) return; (void) set_smach_state(dsmp, DECLINING); if (dsmp->dsm_isv6) { (void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server, stop_release_decline, DHCPV6_DEC_TIMEOUT, 0); } else { (void) add_pkt_opt(dpkt, CD_END, NULL, 0); (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), NULL); } } /* * dhcp_release(): sends a RELEASE message to a DHCP server and removes * the all interfaces for the given state machine from DHCP * control. Called back by script handler. * * input: dhcp_smach_t *: the state machine to send the RELEASE on and remove * void *: an optional text explanation to send with the message * output: int: 1 on success, 0 on failure */ int dhcp_release(dhcp_smach_t *dsmp, void *arg) { const char *msg = arg; dhcp_pkt_t *dpkt; dhcp_lease_t *dlp; dhcp_lif_t *lif; ipaddr_t serverip; uint_t nlifs; if ((dsmp->dsm_dflags & DHCP_IF_BOOTP) || !check_cmd_allowed(dsmp->dsm_state, DHCP_RELEASE)) { ipc_action_finish(dsmp, DHCP_IPC_E_INT); return (0); } dhcpmsg(MSG_INFO, "releasing leases for state machine %s", dsmp->dsm_name); (void) set_smach_state(dsmp, RELEASING); (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); if (dsmp->dsm_isv6) { dpkt = init_pkt(dsmp, DHCPV6_MSG_RELEASE); (void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID, dsmp->dsm_serverid, dsmp->dsm_serveridlen); for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) { lif = dlp->dl_lifs; for (nlifs = dlp->dl_nlifs; nlifs > 0; nlifs--, lif = lif->lif_next) { (void) add_pkt_lif(dpkt, lif, DHCPV6_STAT_SUCCESS, NULL); } } /* * Must kill off the leases before attempting to tell the * server. */ deprecate_leases(dsmp); /* * For DHCPv6, this is a transaction, rather than just a * one-shot message. When this transaction is done, we'll * finish the invoking async operation. */ (void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server, stop_release_decline, DHCPV6_REL_TIMEOUT, 0); } else { if ((dlp = dsmp->dsm_leases) != NULL && dlp->dl_nlifs > 0) { dpkt = init_pkt(dsmp, RELEASE); if (msg != NULL) { (void) add_pkt_opt(dpkt, CD_MESSAGE, msg, strlen(msg) + 1); } lif = dlp->dl_lifs; (void) add_pkt_lif(dpkt, dlp->dl_lifs, 0, NULL); IN6_V4MAPPED_TO_IPADDR(&dsmp->dsm_server, serverip); (void) add_pkt_opt32(dpkt, CD_SERVER_ID, serverip); (void) add_pkt_opt(dpkt, CD_END, NULL, 0); (void) send_pkt(dsmp, dpkt, serverip, NULL); } /* * XXX this totally sucks, but since udp is best-effort, * without this delay, there's a good chance that the packet * that we just enqueued for sending will get pitched * when we canonize the interface through remove_smach. */ (void) usleep(500); deprecate_leases(dsmp); finished_smach(dsmp, DHCP_IPC_SUCCESS); } return (1); } /* * dhcp_drop(): drops the interface from DHCP control; callback from script * handler * * input: dhcp_smach_t *: the state machine dropping leases * void *: unused * output: int: always 1 */ /* ARGSUSED1 */ int dhcp_drop(dhcp_smach_t *dsmp, void *arg) { dhcpmsg(MSG_INFO, "dropping leases for state machine %s", dsmp->dsm_name); if (dsmp->dsm_state == PRE_BOUND || dsmp->dsm_state == BOUND || dsmp->dsm_state == RENEWING || dsmp->dsm_state == REBINDING) { if (dsmp->dsm_dflags & DHCP_IF_BOOTP) { dhcpmsg(MSG_INFO, "used bootp; not writing lease file for %s", dsmp->dsm_name); } else { write_lease_to_hostconf(dsmp); } } else { dhcpmsg(MSG_DEBUG, "%s in state %s; not saving lease", dsmp->dsm_name, dhcp_state_to_string(dsmp->dsm_state)); } deprecate_leases(dsmp); finished_smach(dsmp, DHCP_IPC_SUCCESS); return (1); } /* * stop_release_decline(): decides when to stop retransmitting RELEASE/DECLINE * messages for DHCPv6. When we stop, if there are no * more leases left, then restart the state machine. * * input: dhcp_smach_t *: the state machine messages are being sent from * unsigned int: the number of messages sent so far * output: boolean_t: B_TRUE if retransmissions should stop */ static boolean_t stop_release_decline(dhcp_smach_t *dsmp, unsigned int n_requests) { if (dsmp->dsm_state == RELEASING) { if (n_requests >= DHCPV6_REL_MAX_RC) { dhcpmsg(MSG_INFO, "no Reply to Release, finishing " "transaction on %s", dsmp->dsm_name); finished_smach(dsmp, DHCP_IPC_SUCCESS); return (B_TRUE); } else { return (B_FALSE); } } else { if (n_requests >= DHCPV6_DEC_MAX_RC) { dhcpmsg(MSG_INFO, "no Reply to Decline on %s", dsmp->dsm_name); if (dsmp->dsm_leases == NULL) { dhcpmsg(MSG_VERBOSE, "stop_release_decline: " "%s has no leases left", dsmp->dsm_name); dhcp_restart(dsmp); } return (B_TRUE); } else { return (B_FALSE); } } } /* * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016-2017, Chris Fraire . */ #include #include #include #include #include #include #include #include #include #include #include #include "packet.h" #include "agent.h" #include "script_handler.h" #include "interface.h" #include "states.h" #include "util.h" /* * Number of seconds to wait for a retry if the user is interacting with the * daemon. */ #define RETRY_DELAY 10 /* * If the renew timer fires within this number of seconds of the rebind timer, * then skip renew. This prevents us from sending back-to-back renew and * rebind messages -- a pointless activity. */ #define TOO_CLOSE 2 static boolean_t stop_extending(dhcp_smach_t *, unsigned int); /* * dhcp_renew(): attempts to renew a DHCP lease on expiration of the T1 timer. * * input: iu_tq_t *: unused * void *: the lease to renew (dhcp_lease_t) * output: void * * notes: The primary expense involved with DHCP (like most UDP protocols) is * with the generation and handling of packets, not the contents of * those packets. Thus, we try to reduce the number of packets that * are sent. It would be nice to just renew all leases here (each one * added has trivial added overhead), but the DHCPv6 RFC doesn't * explicitly allow that behavior. Rather than having that argument, * we settle for ones that are close in expiry to the one that fired. * For v4, we repeatedly reschedule the T1 timer to do the * retransmissions. For v6, we rely on the common timer computation * in packet.c. */ /* ARGSUSED */ void dhcp_renew(iu_tq_t *tqp, void *arg) { dhcp_lease_t *dlp = arg; dhcp_smach_t *dsmp = dlp->dl_smach; uint32_t t2; dhcpmsg(MSG_VERBOSE, "dhcp_renew: T1 timer expired on %s", dsmp->dsm_name); dlp->dl_t1.dt_id = -1; if (dsmp->dsm_state == RENEWING || dsmp->dsm_state == REBINDING) { dhcpmsg(MSG_DEBUG, "dhcp_renew: already renewing"); release_lease(dlp); return; } /* * Sanity check: don't send packets if we're past T2, or if we're * extremely close. */ t2 = dsmp->dsm_curstart_monosec + dlp->dl_t2.dt_start; if (monosec() + TOO_CLOSE >= t2) { dhcpmsg(MSG_DEBUG, "dhcp_renew: %spast T2 on %s", monosec() > t2 ? "" : "almost ", dsmp->dsm_name); release_lease(dlp); return; } /* * If there isn't an async event pending, or if we can cancel the one * that's there, then try to renew by sending an extension request. If * that fails, we'll try again when the next timer fires. */ if (!async_cancel(dsmp) || !async_start(dsmp, DHCP_EXTEND, B_FALSE) || !dhcp_extending(dsmp)) { if (monosec() + RETRY_DELAY < t2) { /* * Try again in RETRY_DELAY seconds; user command * should be gone. */ init_timer(&dlp->dl_t1, RETRY_DELAY); (void) set_smach_state(dsmp, BOUND); if (!schedule_lease_timer(dlp, &dlp->dl_t1, dhcp_renew)) { dhcpmsg(MSG_INFO, "dhcp_renew: unable to " "reschedule renewal around user command " "on %s; will wait for rebind", dsmp->dsm_name); } } else { dhcpmsg(MSG_DEBUG, "dhcp_renew: user busy on %s; will " "wait for rebind", dsmp->dsm_name); } } release_lease(dlp); } /* * dhcp_rebind(): attempts to renew a DHCP lease from the REBINDING state (T2 * timer expiry). * * input: iu_tq_t *: unused * void *: the lease to renew * output: void * notes: For v4, we repeatedly reschedule the T2 timer to do the * retransmissions. For v6, we rely on the common timer computation * in packet.c. */ /* ARGSUSED */ void dhcp_rebind(iu_tq_t *tqp, void *arg) { dhcp_lease_t *dlp = arg; dhcp_smach_t *dsmp = dlp->dl_smach; int nlifs; dhcp_lif_t *lif; boolean_t some_valid; uint32_t expiremax; DHCPSTATE oldstate; dhcpmsg(MSG_VERBOSE, "dhcp_rebind: T2 timer expired on %s", dsmp->dsm_name); dlp->dl_t2.dt_id = -1; if ((oldstate = dsmp->dsm_state) == REBINDING) { dhcpmsg(MSG_DEBUG, "dhcp_renew: already rebinding"); release_lease(dlp); return; } /* * Sanity check: don't send packets if we've already expired on all of * the addresses. We compute the maximum expiration time here, because * it won't matter for v4 (there's only one lease) and for v6 we need * to know when the last lease ages away. */ some_valid = B_FALSE; expiremax = monosec(); lif = dlp->dl_lifs; for (nlifs = dlp->dl_nlifs; nlifs > 0; nlifs--, lif = lif->lif_next) { uint32_t expire; expire = dsmp->dsm_curstart_monosec + lif->lif_expire.dt_start; if (expire > expiremax) { expiremax = expire; some_valid = B_TRUE; } } if (!some_valid) { dhcpmsg(MSG_DEBUG, "dhcp_rebind: all leases expired on %s", dsmp->dsm_name); release_lease(dlp); return; } /* * This is our first venture into the REBINDING state, so reset the * server address. We know the renew timer has already been cancelled * (or we wouldn't be here). */ if (dsmp->dsm_isv6) { dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers; } else { IN6_IPADDR_TO_V4MAPPED(htonl(INADDR_BROADCAST), &dsmp->dsm_server); } /* {Bound,Renew}->rebind transitions cannot fail */ (void) set_smach_state(dsmp, REBINDING); /* * If there isn't an async event pending, or if we can cancel the one * that's there, then try to rebind by sending an extension request. * If that fails, we'll clean up when the lease expires. */ if (!async_cancel(dsmp) || !async_start(dsmp, DHCP_EXTEND, B_FALSE) || !dhcp_extending(dsmp)) { if (monosec() + RETRY_DELAY < expiremax) { /* * Try again in RETRY_DELAY seconds; user command * should be gone. */ init_timer(&dlp->dl_t2, RETRY_DELAY); (void) set_smach_state(dsmp, oldstate); if (!schedule_lease_timer(dlp, &dlp->dl_t2, dhcp_rebind)) { dhcpmsg(MSG_INFO, "dhcp_rebind: unable to " "reschedule rebind around user command on " "%s; lease may expire", dsmp->dsm_name); } } else { dhcpmsg(MSG_WARNING, "dhcp_rebind: user busy on %s; " "will expire", dsmp->dsm_name); } } release_lease(dlp); } /* * dhcp_finish_expire(): finish expiration of a lease after the user script * runs. If this is the last lease, then restart DHCP. * The caller has a reference to the LIF, which will be * dropped. * * input: dhcp_smach_t *: the state machine to be restarted * void *: logical interface that has expired * output: int: always 1 */ static int dhcp_finish_expire(dhcp_smach_t *dsmp, void *arg) { dhcp_lif_t *lif = arg; dhcp_lease_t *dlp; dhcpmsg(MSG_DEBUG, "lease expired on %s; removing", lif->lif_name); dlp = lif->lif_lease; unplumb_lif(lif); if (dlp->dl_nlifs == 0) remove_lease(dlp); release_lif(lif); /* If some valid leases remain, then drive on */ if (dsmp->dsm_leases != NULL) { dhcpmsg(MSG_DEBUG, "dhcp_finish_expire: some leases remain on %s", dsmp->dsm_name); return (1); } (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); dhcpmsg(MSG_INFO, "last lease expired on %s -- restarting DHCP", dsmp->dsm_name); /* * in the case where the lease is less than DHCP_REBIND_MIN * seconds, we will never enter dhcp_renew() and thus the packet * counters will not be reset. in that case, reset them here. */ if (dsmp->dsm_state == BOUND) { dsmp->dsm_bad_offers = 0; dsmp->dsm_sent = 0; dsmp->dsm_received = 0; } deprecate_leases(dsmp); /* reset_smach() in dhcp_selecting() will clean up any leftover state */ dhcp_selecting(dsmp); return (1); } /* * dhcp_deprecate(): deprecates an address on a given logical interface when * the preferred lifetime expires. * * input: iu_tq_t *: unused * void *: the logical interface whose lease is expiring * output: void */ /* ARGSUSED */ void dhcp_deprecate(iu_tq_t *tqp, void *arg) { dhcp_lif_t *lif = arg; set_lif_deprecated(lif); release_lif(lif); } /* * dhcp_expire(): expires a lease on a given logical interface and, if there * are no more leases, restarts DHCP. * * input: iu_tq_t *: unused * void *: the logical interface whose lease has expired * output: void */ /* ARGSUSED */ void dhcp_expire(iu_tq_t *tqp, void *arg) { dhcp_lif_t *lif = arg; dhcp_smach_t *dsmp; const char *event; dhcpmsg(MSG_VERBOSE, "dhcp_expire: lease timer expired on %s", lif->lif_name); lif->lif_expire.dt_id = -1; if (lif->lif_lease == NULL) { release_lif(lif); return; } set_lif_deprecated(lif); dsmp = lif->lif_lease->dl_smach; if (!async_cancel(dsmp)) { dhcpmsg(MSG_WARNING, "dhcp_expire: cannot cancel current asynchronous command " "on %s", dsmp->dsm_name); /* * Try to schedule ourselves for callback. We're really * situation-critical here; there's not much hope for us if * this fails. */ init_timer(&lif->lif_expire, DHCP_EXPIRE_WAIT); if (schedule_lif_timer(lif, &lif->lif_expire, dhcp_expire)) return; dhcpmsg(MSG_CRIT, "dhcp_expire: cannot reschedule dhcp_expire " "to get called back, proceeding..."); } if (!async_start(dsmp, DHCP_START, B_FALSE)) dhcpmsg(MSG_WARNING, "dhcp_expire: cannot start asynchronous " "transaction on %s, continuing...", dsmp->dsm_name); /* * Determine if this state machine has any non-expired LIFs left in it. * If it doesn't, then this is an "expire" event. Otherwise, if some * valid leases remain, it's a "loss" event. The SOMEEXP case can * occur only with DHCPv6. */ if (expired_lif_state(dsmp) == DHCP_EXP_SOMEEXP) event = EVENT_LOSS6; else if (dsmp->dsm_isv6) event = EVENT_EXPIRE6; else event = EVENT_EXPIRE; /* * just march on if this fails; at worst someone will be able * to async_start() while we're actually busy with our own * asynchronous transaction. better than not having a lease. */ (void) script_start(dsmp, event, dhcp_finish_expire, lif, NULL); } /* * dhcp_extending(): sends a REQUEST (IPv4 DHCP) or Rebind/Renew (DHCPv6) to * extend a lease on a given state machine * * input: dhcp_smach_t *: the state machine to send the message from * output: boolean_t: B_TRUE if the extension request was sent */ boolean_t dhcp_extending(dhcp_smach_t *dsmp) { dhcp_pkt_t *dpkt; stop_pkt_retransmission(dsmp); /* * We change state here because this function is also called when * adopting a lease and on demand by the user. */ if (dsmp->dsm_state == BOUND) { dsmp->dsm_neg_hrtime = gethrtime(); dsmp->dsm_bad_offers = 0; dsmp->dsm_sent = 0; dsmp->dsm_received = 0; /* Bound->renew can't fail */ (void) set_smach_state(dsmp, RENEWING); } dhcpmsg(MSG_DEBUG, "dhcp_extending: sending request on %s", dsmp->dsm_name); if (dsmp->dsm_isv6) { dhcp_lease_t *dlp; dhcp_lif_t *lif; uint_t nlifs; uint_t irt, mrt; /* * Start constructing the Renew/Rebind message. Only Renew has * a server ID, as we still think our server might be * reachable. */ if (dsmp->dsm_state == RENEWING) { dpkt = init_pkt(dsmp, DHCPV6_MSG_RENEW); (void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID, dsmp->dsm_serverid, dsmp->dsm_serveridlen); irt = DHCPV6_REN_TIMEOUT; mrt = DHCPV6_REN_MAX_RT; } else { dpkt = init_pkt(dsmp, DHCPV6_MSG_REBIND); irt = DHCPV6_REB_TIMEOUT; mrt = DHCPV6_REB_MAX_RT; } /* * Loop over the leases, and add an IA_NA for each and an * IAADDR for each address. */ for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) { lif = dlp->dl_lifs; for (nlifs = dlp->dl_nlifs; nlifs > 0; nlifs--, lif = lif->lif_next) { (void) add_pkt_lif(dpkt, lif, DHCPV6_STAT_SUCCESS, NULL); } } /* Add required Option Request option */ (void) add_pkt_prl(dpkt, dsmp); return (send_pkt_v6(dsmp, dpkt, dsmp->dsm_server, stop_extending, irt, mrt)); } else { dhcp_lif_t *lif = dsmp->dsm_lif; ipaddr_t server; /* assemble the DHCPREQUEST message. */ dpkt = init_pkt(dsmp, REQUEST); dpkt->pkt->ciaddr.s_addr = lif->lif_addr; /* * The max dhcp message size option is set to the interface * max, minus the size of the udp and ip headers. */ (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, htons(lif->lif_pif->pif_mtu - sizeof (struct udpiphdr))); (void) add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM)); if (class_id_len != 0) { (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len); } (void) add_pkt_prl(dpkt, dsmp); /* * dsm_reqhost was set for this state machine in * dhcp_selecting() if the REQUEST_HOSTNAME option was set and * a host name was found. */ if (!dhcp_add_fqdn_opt(dpkt, dsmp) && dsmp->dsm_reqhost != NULL) { (void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost, strlen(dsmp->dsm_reqhost)); } (void) add_pkt_opt(dpkt, CD_END, NULL, 0); IN6_V4MAPPED_TO_IPADDR(&dsmp->dsm_server, server); return (send_pkt(dsmp, dpkt, server, stop_extending)); } } /* * stop_extending(): decides when to stop retransmitting v4 REQUEST or v6 * Renew/Rebind messages. If we're renewing, then stop if * T2 is soon approaching. * * input: dhcp_smach_t *: the state machine REQUESTs are being sent from * unsigned int: the number of REQUESTs sent so far * output: boolean_t: B_TRUE if retransmissions should stop */ /* ARGSUSED */ static boolean_t stop_extending(dhcp_smach_t *dsmp, unsigned int n_requests) { dhcp_lease_t *dlp; /* * If we're renewing and rebind time is soon approaching, then don't * schedule */ if (dsmp->dsm_state == RENEWING) { monosec_t t2; t2 = 0; for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) { if (dlp->dl_t2.dt_start > t2) t2 = dlp->dl_t2.dt_start; } t2 += dsmp->dsm_curstart_monosec; if (monosec() + TOO_CLOSE >= t2) { dhcpmsg(MSG_DEBUG, "stop_extending: %spast T2 on %s", monosec() > t2 ? "" : "almost ", dsmp->dsm_name); return (B_TRUE); } } /* * Note that returning B_TRUE cancels both this transmission and the * one that would occur at dsm_send_timeout, and that for v4 we cut the * time in half for each retransmission. Thus we check here against * half of the minimum. */ if (!dsmp->dsm_isv6 && dsmp->dsm_send_timeout < DHCP_REBIND_MIN * MILLISEC / 2) { dhcpmsg(MSG_DEBUG, "stop_extending: next retry would be in " "%d.%03d; stopping", dsmp->dsm_send_timeout / MILLISEC, dsmp->dsm_send_timeout % MILLISEC); return (B_TRUE); } /* Otherwise, w stop only when the next timer (rebind, expire) fires */ return (B_FALSE); } /* * 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. * Copyright (c) 2016-2017, Chris Fraire . * * REQUESTING state of the client state machine. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "states.h" #include "util.h" #include "packet.h" #include "interface.h" #include "agent.h" static PKT_LIST *select_best(dhcp_smach_t *); static void request_failed(dhcp_smach_t *); static stop_func_t stop_requesting; /* * send_v6_request(): sends a DHCPv6 Request message and switches to REQUESTING * state. This is a separate function because a NoBinding * response can also cause us to do this. * * input: dhcp_smach_t *: the state machine * output: none */ void send_v6_request(dhcp_smach_t *dsmp) { dhcp_pkt_t *dpkt; dhcpv6_ia_na_t d6in; dpkt = init_pkt(dsmp, DHCPV6_MSG_REQUEST); (void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID, dsmp->dsm_serverid, dsmp->dsm_serveridlen); /* Add an IA_NA option for our controlling LIF */ d6in.d6in_iaid = htonl(dsmp->dsm_lif->lif_iaid); d6in.d6in_t1 = htonl(0); d6in.d6in_t2 = htonl(0); (void) add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA, (dhcpv6_option_t *)&d6in + 1, sizeof (d6in) - sizeof (dhcpv6_option_t)); /* Add required Option Request option */ (void) add_pkt_prl(dpkt, dsmp); (void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server, stop_requesting, DHCPV6_REQ_TIMEOUT, DHCPV6_REQ_MAX_RT); /* For DHCPv6, state switch cannot fail */ (void) set_smach_state(dsmp, REQUESTING); } /* * server_unicast_option(): determines the server address to use based on the * DHCPv6 Server Unicast option present in the given * packet. * * input: dhcp_smach_t *: the state machine * PKT_LIST *: received packet (Advertisement or Reply) * output: none */ void server_unicast_option(dhcp_smach_t *dsmp, PKT_LIST *plp) { const dhcpv6_option_t *d6o; uint_t olen; d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_UNICAST, &olen); olen -= sizeof (*d6o); /* LINTED: no consequent */ if (d6o == NULL) { /* No Server Unicast option specified */ } else if (olen != sizeof (dsmp->dsm_server)) { dhcpmsg(MSG_WARNING, "server_unicast_option: %s has Server " "Unicast option with bad length", pkt_type_to_string(pkt_recv_type(plp), B_TRUE)); } else { in6_addr_t addr; (void) memcpy(&addr, d6o + 1, olen); if (IN6_IS_ADDR_UNSPECIFIED(&addr)) { dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " "to unspecified address ignored"); } else if (IN6_IS_ADDR_MULTICAST(&addr)) { dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " "to multicast address ignored"); } else if (IN6_IS_ADDR_V4COMPAT(&addr) || IN6_IS_ADDR_V4MAPPED(&addr)) { dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " "to invalid address ignored"); } else { dsmp->dsm_server = addr; } } } /* * dhcp_requesting(): checks if OFFER packets to come in from DHCP servers. * if so, chooses the best one, sends a REQUEST to the * server and registers an event handler to receive * the ACK/NAK. This may be called by the offer timer or * by any function that wants to check for offers after * canceling that timer. * * input: iu_tq_t *: timer queue; non-NULL if this is a timer callback * void *: the state machine receiving OFFER packets * output: void */ void dhcp_requesting(iu_tq_t *tqp, void *arg) { dhcp_smach_t *dsmp = arg; dhcp_pkt_t *dpkt; PKT_LIST *offer; lease_t lease; boolean_t isv6 = dsmp->dsm_isv6; /* * We assume here that if tqp is set, then this means we're being * called back by the offer wait timer. If so, then drop our hold * on the state machine. Otherwise, cancel the timer if it's running. */ if (tqp != NULL) { dhcpmsg(MSG_VERBOSE, "dhcp_requesting: offer wait timer on v%d %s", isv6 ? 6 : 4, dsmp->dsm_name); dsmp->dsm_offer_timer = -1; if (!verify_smach(dsmp)) return; } else { cancel_offer_timer(dsmp); } /* * select the best OFFER; all others pitched. */ offer = select_best(dsmp); if (offer == NULL) { dhcpmsg(MSG_VERBOSE, "no OFFERs/Advertisements on %s, waiting...", dsmp->dsm_name); /* * no acceptable OFFERs have come in. reschedule * ourself for callback. */ if ((dsmp->dsm_offer_timer = iu_schedule_timer(tq, dsmp->dsm_offer_wait, dhcp_requesting, dsmp)) == -1) { /* * ugh. the best we can do at this point is * revert back to INIT and wait for a user to * restart us. */ dhcpmsg(MSG_WARNING, "dhcp_requesting: cannot " "reschedule callback, reverting to INIT state on " "%s", dsmp->dsm_name); stop_pkt_retransmission(dsmp); (void) set_smach_state(dsmp, INIT); dsmp->dsm_dflags |= DHCP_IF_FAILED; ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); } else { hold_smach(dsmp); } return; } /* * With IPv4, the DHCPREQUEST packet we're about to transmit implicitly * declines all other offers we've received. We can no longer use any * cached offers, so we must discard them now. With DHCPv6, though, * we're permitted to hang onto the advertisements (offers) and try * them if the preferred one doesn't pan out. */ if (!isv6) free_pkt_list(&dsmp->dsm_recv_pkt_list); /* stop collecting packets. */ stop_pkt_retransmission(dsmp); /* * For IPv4, check to see whether we got an OFFER or a BOOTP packet. * If we got a BOOTP packet, go to the BOUND state now. */ if (!isv6 && offer->opts[CD_DHCP_TYPE] == NULL) { free_pkt_list(&dsmp->dsm_recv_pkt_list); if (!set_smach_state(dsmp, REQUESTING)) { dhcp_restart(dsmp); return; } if (!dhcp_bound(dsmp, offer)) { dhcpmsg(MSG_WARNING, "dhcp_requesting: dhcp_bound " "failed for %s", dsmp->dsm_name); dhcp_restart(dsmp); return; } return; } save_domainname(dsmp, offer); if (isv6) { const char *estr, *msg; const dhcpv6_option_t *d6o; uint_t olen, msglen; /* If there's a Status Code option, print the message */ d6o = dhcpv6_pkt_option(offer, NULL, DHCPV6_OPT_STATUS_CODE, &olen); (void) dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen); print_server_msg(dsmp, msg, msglen); /* Copy in the Server ID (guaranteed to be present now) */ if (!save_server_id(dsmp, offer)) goto failure; /* * Determine how to send this message. If the Advertisement * (offer) has the unicast option, then use the address * specified in the option. Otherwise, send via multicast. */ server_unicast_option(dsmp, offer); send_v6_request(dsmp); } else { /* if we got a message from the server, display it. */ if (offer->opts[CD_MESSAGE] != NULL) { print_server_msg(dsmp, (char *)offer->opts[CD_MESSAGE]->value, offer->opts[CD_MESSAGE]->len); } /* * assemble a DHCPREQUEST, with the ciaddr field set to 0, * since we got here from the INIT state. */ dpkt = init_pkt(dsmp, REQUEST); /* * Grab the lease out of the OFFER; we know it's valid because * select_best() already checked. The max dhcp message size * option is set to the interface max, minus the size of the * udp and ip headers. */ (void) memcpy(&lease, offer->opts[CD_LEASE_TIME]->value, sizeof (lease_t)); (void) add_pkt_opt32(dpkt, CD_LEASE_TIME, lease); (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, htons(dsmp->dsm_lif->lif_pif->pif_mtu - sizeof (struct udpiphdr))); (void) add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR, offer->pkt->yiaddr.s_addr); (void) add_pkt_opt(dpkt, CD_SERVER_ID, offer->opts[CD_SERVER_ID]->value, offer->opts[CD_SERVER_ID]->len); if (class_id_len != 0) { (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len); } (void) add_pkt_prl(dpkt, dsmp); /* * dsm_reqhost was set for this state machine in * dhcp_selecting() if the DF_REQUEST_HOSTNAME option set and a * host name was found */ if (!dhcp_add_fqdn_opt(dpkt, dsmp) && dsmp->dsm_reqhost != NULL) { (void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost, strlen(dsmp->dsm_reqhost)); } (void) add_pkt_opt(dpkt, CD_END, NULL, 0); /* * send out the REQUEST, trying retransmissions. either a NAK * or too many REQUEST attempts will revert us to SELECTING. */ if (!set_smach_state(dsmp, REQUESTING)) { dhcpmsg(MSG_ERROR, "dhcp_requesting: cannot switch to " "REQUESTING state; reverting to INIT on %s", dsmp->dsm_name); goto failure; } (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), stop_requesting); } /* all done with the offer */ free_pkt_entry(offer); return; failure: dsmp->dsm_dflags |= DHCP_IF_FAILED; (void) set_smach_state(dsmp, INIT); ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); free_pkt_list(&dsmp->dsm_recv_pkt_list); } /* * compute_points_v6(): compute the number of "points" for a given v6 * advertisement. * * input: const PKT_LIST *: packet to inspect * const dhcp_smach_t *: state machine that received the packet * output: int: -1 to discard, -2 to accept immediately, >=0 for preference. */ static int compute_points_v6(const PKT_LIST *pkt, const dhcp_smach_t *dsmp) { char abuf[INET6_ADDRSTRLEN]; int points = 0; const dhcpv6_option_t *d6o, *d6so; uint_t olen, solen; int i; const char *estr, *msg; uint_t msglen; /* * Look through the packet contents. Valid packets must have our * client ID and a server ID, which has already been checked by * dhcp_packet_lif. Bonus points for each option. */ /* One point for having a valid message. */ points++; /* * Per RFC 3315, if the Advertise message says, "yes, we have no * bananas today," then ignore the entire message. (Why it's just * _this_ error and no other is a bit of a mystery, but a standard is a * standard.) */ d6o = dhcpv6_pkt_option(pkt, NULL, DHCPV6_OPT_STATUS_CODE, &olen); if (dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen) == DHCPV6_STAT_NOADDRS) { dhcpmsg(MSG_INFO, "discard advertisement from %s on %s: no address status", inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&pkt->pktfrom)->sin6_addr, abuf, sizeof (abuf)), dsmp->dsm_name); return (-1); } /* Two points for each batch of offered IP addresses */ d6o = NULL; while ((d6o = dhcpv6_pkt_option(pkt, d6o, DHCPV6_OPT_IA_NA, &olen)) != NULL) { /* * Note that it's possible to have "no bananas" on an * individual IA. We must look for that here. * * RFC 3315 section 17.1.3 does not refer to the status code * embedded in the IA itself. However, the TAHI test suite * checks for this specific case. Because it's extremely * unlikely that any usable server is going to report that it * has no addresses on a network using DHCP for address * assignment, we allow such messages to be dropped. */ d6so = dhcpv6_find_option( (const char *)d6o + sizeof (dhcpv6_ia_na_t), olen - sizeof (dhcpv6_ia_na_t), NULL, DHCPV6_OPT_STATUS_CODE, &solen); if (dhcpv6_status_code(d6so, solen, &estr, &msg, &msglen) == DHCPV6_STAT_NOADDRS) return (-1); points += 2; } /* * Note that we drive on in the case where there are no addresses. The * hope here is that we'll at least get some useful configuration * information. */ /* One point for each requested option */ for (i = 0; i < dsmp->dsm_prllen; i++) { if (dhcpv6_pkt_option(pkt, NULL, dsmp->dsm_prl[i], NULL) != NULL) points++; } /* * Ten points for each point of "preference." Note: the value 255 is * special. It means "stop right now and select this server." */ d6o = dhcpv6_pkt_option(pkt, NULL, DHCPV6_OPT_PREFERENCE, &olen); if (d6o != NULL && olen == sizeof (*d6o) + 1) { int pref = *(const uchar_t *)(d6o + 1); if (pref == 255) return (-2); points += 10 * pref; } return (points); } /* * compute_points_v4(): compute the number of "points" for a given v4 offer. * * input: const PKT_LIST *: packet to inspect * const dhcp_smach_t *: state machine that received the packet * output: int: -1 to discard, >=0 for preference. */ static int compute_points_v4(const PKT_LIST *pkt) { int points = 0; if (pkt->opts[CD_DHCP_TYPE] == NULL) { dhcpmsg(MSG_VERBOSE, "compute_points_v4: valid BOOTP reply"); goto valid_offer; } if (pkt->opts[CD_LEASE_TIME] == NULL) { dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER without lease " "time"); return (-1); } if (pkt->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) { dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER with garbled " "lease time"); return (-1); } if (pkt->opts[CD_SERVER_ID] == NULL) { dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER without server " "id"); return (-1); } if (pkt->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER with garbled " "server id"); return (-1); } /* valid DHCP OFFER. see if we got our parameters. */ dhcpmsg(MSG_VERBOSE, "compute_points_v4: valid OFFER packet"); points += 30; valid_offer: if (pkt->rfc1048) points += 5; /* * also could be faked, though more difficult because the encapsulation * is hard to encode on a BOOTP server; plus there's not as much real * estate in the packet for options, so it's likely this option would * get dropped. */ if (pkt->opts[CD_VENDOR_SPEC] != NULL) points += 80; if (pkt->opts[CD_SUBNETMASK] != NULL) points++; if (pkt->opts[CD_ROUTER] != NULL) points++; if (pkt->opts[CD_HOSTNAME] != NULL) points += 5; return (points); } /* * select_best(): selects the best offer from a list of IPv4 OFFER packets or * DHCPv6 Advertise packets. * * input: dhcp_smach_t *: state machine with enqueued offers * output: PKT_LIST *: the best packet, or NULL if none are acceptable */ static PKT_LIST * select_best(dhcp_smach_t *dsmp) { PKT_LIST *current = dsmp->dsm_recv_pkt_list; PKT_LIST *next, *best = NULL; int points, best_points = -1; /* * pick out the best offer. point system. * what's important for IPv4? * * 0) DHCP (30 points) * 1) no option overload * 2) encapsulated vendor option (80 points) * 3) non-null sname and siaddr fields * 4) non-null file field * 5) hostname (5 points) * 6) subnetmask (1 point) * 7) router (1 point) */ for (; current != NULL; current = next) { next = current->next; points = current->isv6 ? compute_points_v6(current, dsmp) : compute_points_v4(current); /* * Just discard any unacceptable entries we encounter. */ if (points == -1) { remque(current); free_pkt_entry(current); continue; } dhcpmsg(MSG_DEBUG, "select_best: OFFER had %d points", points); /* Special case: stop now and select */ if (points == -2) { best = current; break; } if (points >= best_points) { best_points = points; best = current; } } if (best != NULL) { dhcpmsg(MSG_DEBUG, "select_best: most points: %d", best_points); remque(best); } else { dhcpmsg(MSG_DEBUG, "select_best: no valid OFFER/BOOTP reply"); } return (best); } /* * accept_v4_acknak(): determine what to do with a DHCPv4 ACK/NAK based on the * current state. If we're renewing or rebinding, the ACK * must be for the same address and must have a new lease * time. If it's a NAK, then our cache is garbage, and we * must restart. Finally, call dhcp_bound on accepted * ACKs. * * input: dhcp_smach_t *: the state machine to handle the ACK/NAK * PKT_LIST *: the ACK/NAK message * output: void */ static void accept_v4_acknak(dhcp_smach_t *dsmp, PKT_LIST *plp) { /* Account for received and processed messages */ dsmp->dsm_received++; if (*plp->opts[CD_DHCP_TYPE]->value == ACK) { if (dsmp->dsm_state != INFORM_SENT && dsmp->dsm_state != INFORMATION && (plp->opts[CD_LEASE_TIME] == NULL || plp->opts[CD_LEASE_TIME]->len != sizeof (lease_t))) { dhcpmsg(MSG_WARNING, "accept_v4_acknak: ACK packet on " "%s missing mandatory lease option, ignored", dsmp->dsm_name); dsmp->dsm_bad_offers++; free_pkt_entry(plp); return; } if ((dsmp->dsm_state == RENEWING || dsmp->dsm_state == REBINDING) && dsmp->dsm_leases->dl_lifs->lif_addr != plp->pkt->yiaddr.s_addr) { dhcpmsg(MSG_WARNING, "accept_v4_acknak: renewal ACK " "packet has a different IP address (%s), ignored", inet_ntoa(plp->pkt->yiaddr)); dsmp->dsm_bad_offers++; free_pkt_entry(plp); return; } } /* * looks good; cancel the retransmission timer and unregister * the acknak handler. ACK to BOUND, NAK back to SELECTING. */ stop_pkt_retransmission(dsmp); if (*plp->opts[CD_DHCP_TYPE]->value == NAK) { dhcpmsg(MSG_WARNING, "accept_v4_acknak: NAK on interface %s", dsmp->dsm_name); dsmp->dsm_bad_offers++; free_pkt_entry(plp); dhcp_restart(dsmp); /* * remove any bogus cached configuration we might have * around (right now would only happen if we got here * from INIT_REBOOT). */ (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); return; } if (plp->opts[CD_SERVER_ID] == NULL || plp->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { dhcpmsg(MSG_ERROR, "accept_v4_acknak: ACK with no valid " "server id on %s", dsmp->dsm_name); dsmp->dsm_bad_offers++; free_pkt_entry(plp); dhcp_restart(dsmp); return; } if (plp->opts[CD_MESSAGE] != NULL) { print_server_msg(dsmp, (char *)plp->opts[CD_MESSAGE]->value, plp->opts[CD_MESSAGE]->len); } dhcpmsg(MSG_VERBOSE, "accept_v4_acknak: ACK on %s", dsmp->dsm_name); if (!dhcp_bound(dsmp, plp)) { dhcpmsg(MSG_WARNING, "accept_v4_acknak: dhcp_bound failed " "for %s", dsmp->dsm_name); dhcp_restart(dsmp); } } /* * accept_v6_message(): determine what to do with a DHCPv6 message based on the * current state. * * input: dhcp_smach_t *: the state machine to handle the message * PKT_LIST *: the DHCPv6 message * const char *: type of message (for logging) * uchar_t: type of message (extracted from packet) * output: void */ static void accept_v6_message(dhcp_smach_t *dsmp, PKT_LIST *plp, const char *pname, uchar_t recv_type) { const dhcpv6_option_t *d6o; uint_t olen; const char *estr, *msg; uint_t msglen; int status; /* Account for received and processed messages */ dsmp->dsm_received++; /* We don't yet support Reconfigure at all. */ if (recv_type == DHCPV6_MSG_RECONFIGURE) { dhcpmsg(MSG_VERBOSE, "accept_v6_message: ignored Reconfigure " "on %s", dsmp->dsm_name); free_pkt_entry(plp); return; } /* * All valid DHCPv6 messages must have our Client ID specified. */ d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_CLIENTID, &olen); olen -= sizeof (*d6o); if (d6o == NULL || olen != dsmp->dsm_cidlen || memcmp(d6o + 1, dsmp->dsm_cid, olen) != 0) { dhcpmsg(MSG_VERBOSE, "accept_v6_message: discarded %s on %s: %s Client ID", pname, dsmp->dsm_name, d6o == NULL ? "no" : "wrong"); free_pkt_entry(plp); return; } /* * All valid DHCPv6 messages must have a Server ID specified. * * If this is a Reply and it's not in response to Solicit, Confirm, * Rebind, or Information-Request, then it must also match the Server * ID we're expecting. * * For Reply in the Solicit, Confirm, Rebind, and Information-Request * cases, the Server ID needs to be saved. This is done inside of * dhcp_bound(). */ d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_SERVERID, &olen); if (d6o == NULL) { dhcpmsg(MSG_DEBUG, "accept_v6_message: discarded %s on %s: no Server ID", pname, dsmp->dsm_name); free_pkt_entry(plp); return; } if (recv_type == DHCPV6_MSG_REPLY && dsmp->dsm_state != SELECTING && dsmp->dsm_state != INIT_REBOOT && dsmp->dsm_state != REBINDING && dsmp->dsm_state != INFORM_SENT) { olen -= sizeof (*d6o); if (olen != dsmp->dsm_serveridlen || memcmp(d6o + 1, dsmp->dsm_serverid, olen) != 0) { dhcpmsg(MSG_DEBUG, "accept_v6_message: discarded %s on " "%s: wrong Server ID", pname, dsmp->dsm_name); free_pkt_entry(plp); return; } } /* * Break out of the switch if the input message needs to be discarded. * Return from the function if the message has been enqueued or * consumed. */ switch (dsmp->dsm_state) { case SELECTING: /* A Reply message signifies a Rapid-Commit. */ if (recv_type == DHCPV6_MSG_REPLY) { if (dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_RAPID_COMMIT, &olen) == NULL) { dhcpmsg(MSG_DEBUG, "accept_v6_message: Reply " "on %s lacks Rapid-Commit; ignoring", dsmp->dsm_name); break; } dhcpmsg(MSG_VERBOSE, "accept_v6_message: rapid-commit Reply on %s", dsmp->dsm_name); cancel_offer_timer(dsmp); goto rapid_commit; } /* Otherwise, we're looking for Advertisements. */ if (recv_type != DHCPV6_MSG_ADVERTISE) break; /* * Special case: if this advertisement has preference 255, then * we must stop right now and select this server. */ d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_PREFERENCE, &olen); if (d6o != NULL && olen == sizeof (*d6o) + 1 && *(const uchar_t *)(d6o + 1) == 255) { pkt_smach_enqueue(dsmp, plp); dhcpmsg(MSG_DEBUG, "accept_v6_message: preference 255;" " immediate Request on %s", dsmp->dsm_name); dhcp_requesting(NULL, dsmp); } else { pkt_smach_enqueue(dsmp, plp); } return; case PRE_BOUND: case BOUND: /* * Not looking for anything in these states. (If we * implemented reconfigure, that might go here.) */ break; case REQUESTING: case INIT_REBOOT: case RENEWING: case REBINDING: case INFORM_SENT: /* * We're looking for Reply messages. */ if (recv_type != DHCPV6_MSG_REPLY) break; dhcpmsg(MSG_VERBOSE, "accept_v6_message: received Reply message on %s", dsmp->dsm_name); rapid_commit: /* * Extract the status code option. If one is present and the * request failed, then try to go to another advertisement in * the list or restart the selection machinery. */ d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, &olen); status = dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen); /* * Check for the UseMulticast status code. If this is present, * and if we were actually using unicast, then drop back and * try again. If we weren't using unicast, then just pretend * we never saw this message -- the peer is confused. (TAHI * does this.) */ if (status == DHCPV6_STAT_USEMCAST) { if (IN6_IS_ADDR_MULTICAST( &dsmp->dsm_send_dest.v6.sin6_addr)) { break; } else { free_pkt_entry(plp); dsmp->dsm_send_dest.v6.sin6_addr = ipv6_all_dhcp_relay_and_servers; retransmit_now(dsmp); return; } } print_server_msg(dsmp, msg, msglen); /* * We treat NoBinding at the top level as "success." Granted, * this doesn't make much sense, but the TAHI test suite does * this. NoBinding really only makes sense in the context of a * specific IA, as it refers to the GUID:IAID binding, so * ignoring it at the top level is safe. */ if (status == DHCPV6_STAT_SUCCESS || status == DHCPV6_STAT_NOBINDING) { if (dhcp_bound(dsmp, plp)) { /* * dhcp_bound will stop retransmission on * success, if that's called for. */ server_unicast_option(dsmp, plp); } else { stop_pkt_retransmission(dsmp); dhcpmsg(MSG_WARNING, "accept_v6_message: " "dhcp_bound failed for %s", dsmp->dsm_name); (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); dhcp_restart(dsmp); } } else { dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", estr); stop_pkt_retransmission(dsmp); free_pkt_entry(plp); if (dsmp->dsm_state == INFORM_SENT) { (void) set_smach_state(dsmp, INIT); ipc_action_finish(dsmp, DHCP_IPC_E_SRVFAILED); } else { (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); request_failed(dsmp); } } return; case DECLINING: /* * We're looking for Reply messages. */ if (recv_type != DHCPV6_MSG_REPLY) break; stop_pkt_retransmission(dsmp); /* * Extract the status code option. Note that it's not a * failure if the server reports an error. */ d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, &olen); if (dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen) == DHCPV6_STAT_SUCCESS) { print_server_msg(dsmp, msg, msglen); } else { dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", estr); } free_pkt_entry(plp); if (dsmp->dsm_leases == NULL) { dhcpmsg(MSG_VERBOSE, "accept_v6_message: %s has no " "leases left", dsmp->dsm_name); dhcp_restart(dsmp); } else if (dsmp->dsm_lif_wait == 0) { (void) set_smach_state(dsmp, BOUND); } else { (void) set_smach_state(dsmp, PRE_BOUND); } return; case RELEASING: /* * We're looking for Reply messages. */ if (recv_type != DHCPV6_MSG_REPLY) break; stop_pkt_retransmission(dsmp); /* * Extract the status code option. */ d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, &olen); if (dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen) == DHCPV6_STAT_SUCCESS) { print_server_msg(dsmp, msg, msglen); } else { dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", estr); } free_pkt_entry(plp); finished_smach(dsmp, DHCP_IPC_SUCCESS); return; } /* * Break from above switch means that the message must be discarded. */ dhcpmsg(MSG_VERBOSE, "accept_v6_message: discarded v6 %s on %s; state %s", pname, dsmp->dsm_name, dhcp_state_to_string(dsmp->dsm_state)); free_pkt_entry(plp); } /* * dhcp_acknak_global(): Processes reception of an ACK or NAK packet on the * global socket -- broadcast packets for IPv4, all * packets for DHCPv6. * * input: iu_eh_t *: unused * int: the global file descriptor the ACK/NAK arrived on * short: unused * iu_event_id_t: unused * void *: unused * output: void */ /* ARGSUSED */ void dhcp_acknak_global(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) { PKT_LIST *plp; dhcp_pif_t *pif; uchar_t recv_type; const char *pname; uint_t xid; dhcp_smach_t *dsmp; boolean_t isv6 = (fd == v6_sock_fd); struct sockaddr_in sin; const char *reason; size_t sinlen = sizeof (sin); int sock; plp = recv_pkt(fd, get_max_mtu(isv6), isv6); if (plp == NULL) return; recv_type = pkt_recv_type(plp); pname = pkt_type_to_string(recv_type, isv6); /* * Find the corresponding state machine and pif. * * Note that DHCPv6 Reconfigure would be special: it's not the reply to * any transaction, and thus we would need to search on transaction ID * zero (all state machines) to find the match. However, Reconfigure * is not yet supported. */ xid = pkt_get_xid(plp->pkt, isv6); for (dsmp = lookup_smach_by_xid(xid, NULL, isv6); dsmp != NULL; dsmp = lookup_smach_by_xid(xid, dsmp, isv6)) { pif = dsmp->dsm_lif->lif_pif; if (pif->pif_index == plp->ifindex || pif->pif_under_ipmp && pif->pif_grindex == plp->ifindex) break; } if (dsmp == NULL) { dhcpmsg(MSG_VERBOSE, "dhcp_acknak_global: ignored v%d %s packet" " on ifindex %d: unknown state machine", isv6 ? 6 : 4, pname, plp->ifindex); free_pkt_entry(plp); return; } if (!isv6 && !pkt_v4_match(recv_type, DHCP_PACK|DHCP_PNAK)) { reason = "not ACK or NAK"; goto drop; } /* * For IPv4, most packets will be handled by dhcp_packet_lif(). The * only exceptions are broadcast packets sent when lif_sock_ip_fd has * bound to something other than INADDR_ANY. */ if (!isv6) { sock = dsmp->dsm_lif->lif_sock_ip_fd; if (getsockname(sock, (struct sockaddr *)&sin, &sinlen) != -1 && sin.sin_addr.s_addr == INADDR_ANY) { reason = "handled by lif_sock_ip_fd"; goto drop; } } /* * We've got a packet; make sure it's acceptable and cancel the REQUEST * retransmissions. */ if (isv6) accept_v6_message(dsmp, plp, pname, recv_type); else accept_v4_acknak(dsmp, plp); return; drop: dhcpmsg(MSG_VERBOSE, "dhcp_acknak_global: ignored v%d %s packet for %s " "received on global socket: %s", isv6 ? 6 : 4, pname, pif->pif_name, reason); free_pkt_entry(plp); } /* * request_failed(): Attempt to request an address has failed. Take an * appropriate action. * * input: dhcp_smach_t *: state machine that has failed * output: void */ static void request_failed(dhcp_smach_t *dsmp) { PKT_LIST *offer; dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers; if ((offer = select_best(dsmp)) != NULL) { insque(offer, &dsmp->dsm_recv_pkt_list); dhcp_requesting(NULL, dsmp); } else { dhcpmsg(MSG_INFO, "no offers left on %s; restarting", dsmp->dsm_name); dhcp_selecting(dsmp); } } /* * dhcp_packet_lif(): Processes reception of an ACK, NAK, or OFFER packet on * a given logical interface for IPv4 (only). * * input: iu_eh_t *: unused * int: the file descriptor the packet arrived on * short: unused * iu_event_id_t: the id of this event callback with the handler * void *: pointer to logical interface receiving message * output: void */ /* ARGSUSED */ void dhcp_packet_lif(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) { dhcp_lif_t *lif = arg; PKT_LIST *plp; uchar_t recv_type; const char *pname; uint_t xid; dhcp_smach_t *dsmp; if ((plp = recv_pkt(fd, lif->lif_pif->pif_mtu, B_FALSE)) == NULL) return; recv_type = pkt_recv_type(plp); pname = pkt_type_to_string(recv_type, B_FALSE); if (!pkt_v4_match(recv_type, DHCP_PACK | DHCP_PNAK | DHCP_PUNTYPED | DHCP_POFFER)) { dhcpmsg(MSG_VERBOSE, "dhcp_packet_lif: ignored v4 %s packet " "received via LIF %s", pname, lif->lif_name); free_pkt_entry(plp); return; } /* * Find the corresponding state machine. */ xid = pkt_get_xid(plp->pkt, B_FALSE); for (dsmp = lookup_smach_by_xid(xid, NULL, B_FALSE); dsmp != NULL; dsmp = lookup_smach_by_xid(xid, dsmp, B_FALSE)) { if (dsmp->dsm_lif == lif) break; } if (dsmp == NULL) goto drop; if (pkt_v4_match(recv_type, DHCP_PACK|DHCP_PNAK)) { /* * We've got an ACK/NAK; make sure it's acceptable and cancel * the REQUEST retransmissions. */ accept_v4_acknak(dsmp, plp); } else { if (is_bound_state(dsmp->dsm_state)) goto drop; /* * Must be an OFFER or a BOOTP message: enqueue it for later * processing by select_best(). */ pkt_smach_enqueue(dsmp, plp); } return; drop: dhcpmsg(MSG_VERBOSE, "dhcp_packet_lif: ignored %s packet xid " "%x received via LIF %s; %s", pname, xid, lif->lif_name, dsmp == NULL ? "unknown state machine" : "bound"); free_pkt_entry(plp); } /* * dhcp_restart(): restarts DHCP (from INIT) on a given state machine, but only * if we're leasing addresses. Doesn't restart for information- * only interfaces. * * input: dhcp_smach_t *: the state machine to restart DHCP on * output: void */ void dhcp_restart(dhcp_smach_t *dsmp) { if (dsmp->dsm_state == INFORM_SENT || dsmp->dsm_state == INFORMATION) return; /* * As we're returning to INIT state, we need to discard any leases we * may have, and (for v4) canonize the LIF. There's a bit of tension * between keeping around a possibly still working address, and obeying * the RFCs. A more elaborate design would be to mark the addresses as * DEPRECATED, and then start a removal timer. Such a design would * probably compromise testing. */ deprecate_leases(dsmp); if (!set_start_timer(dsmp)) { dhcpmsg(MSG_ERROR, "dhcp_restart: cannot schedule dhcp_start, " "reverting to INIT state on %s", dsmp->dsm_name); (void) set_smach_state(dsmp, INIT); dsmp->dsm_dflags |= DHCP_IF_FAILED; ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); } else { dhcpmsg(MSG_DEBUG, "dhcp_restart: restarting DHCP on %s", dsmp->dsm_name); } } /* * stop_requesting(): decides when to stop retransmitting REQUESTs * * input: dhcp_smach_t *: the state machine REQUESTs are being sent from * unsigned int: the number of REQUESTs sent so far * output: boolean_t: B_TRUE if retransmissions should stop */ static boolean_t stop_requesting(dhcp_smach_t *dsmp, unsigned int n_requests) { uint_t maxreq; maxreq = dsmp->dsm_isv6 ? DHCPV6_REQ_MAX_RC : DHCP_MAX_REQUESTS; if (n_requests >= maxreq) { dhcpmsg(MSG_INFO, "no ACK/NAK/Reply to REQUEST on %s", dsmp->dsm_name); request_failed(dsmp); return (B_TRUE); } else { return (B_FALSE); } } /* * 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 #include #include #include #include #include #include #include #include #include #include #include "agent.h" #include "script_handler.h" #include "states.h" #include "interface.h" /* * scripts are directly managed by a script helper process. dhcpagent creates * the helper process and it, in turn, creates a process to run the script * dhcpagent owns one end of a pipe and the helper process owns the other end * the helper process calls waitpid to wait for the script to exit. an alarm * is set for SCRIPT_TIMEOUT seconds. If the alarm fires, SIGTERM is sent to * the script process and a second alarm is set for SCRIPT_TIMEOUT_GRACE. if * the second alarm fires, SIGKILL is sent to forcefully kill the script. when * script exits, the helper process notifies dhcpagent by closing its end * of the pipe. */ unsigned int script_count; /* * the signal to send to the script process. it is a global variable * to this file as sigterm_handler needs it. */ static int script_signal = SIGTERM; /* * script's absolute timeout value. the first timeout is set to SCRIPT_TIMEOUT * seconds from the time it is started. SIGTERM is sent on the first timeout * the second timeout is set to SCRIPT_TIMEOUT_GRACE from the first timeout * and SIGKILL is sent on the second timeout. */ static time_t timeout; /* * sigalarm_handler(): signal handler for SIGALRM * * input: int: signal the handler was called with * output: void */ /* ARGSUSED */ static void sigalarm_handler(int sig) { time_t now; /* set a another alarm if it fires too early */ now = time(NULL); if (now < timeout) (void) alarm(timeout - now); } /* * sigterm_handler(): signal handler for SIGTERM, fired when dhcpagent wants * to stop the script * input: int: signal the handler was called with * output: void */ /* ARGSUSED */ static void sigterm_handler(int sig) { if (script_signal != SIGKILL) { /* send SIGKILL SCRIPT_TIMEOUT_GRACE seconds from now */ script_signal = SIGKILL; timeout = time(NULL) + SCRIPT_TIMEOUT_GRACE; (void) alarm(SCRIPT_TIMEOUT_GRACE); } } /* * run_script(): it forks a process to execute the script * * input: dhcp_smach_t *: the state machine * const char *: the event name * int: the pipe end owned by the script helper process * output: void */ static void run_script(dhcp_smach_t *dsmp, const char *event, int fd) { int n; char c; char *path; char *name; pid_t pid; time_t now; if ((pid = fork()) == -1) return; if (pid == 0) { path = SCRIPT_PATH; name = strrchr(path, '/') + 1; /* close all files */ closefrom(0); /* redirect stdin, stdout and stderr to /dev/null */ if ((n = open("/dev/null", O_RDWR)) < 0) _exit(127); (void) dup2(n, STDOUT_FILENO); (void) dup2(n, STDERR_FILENO); (void) execl(path, name, dsmp->dsm_name, event, NULL); _exit(127); } /* * the first timeout fires SCRIPT_TIMEOUT seconds from now. */ timeout = time(NULL) + SCRIPT_TIMEOUT; (void) sigset(SIGALRM, sigalarm_handler); (void) alarm(SCRIPT_TIMEOUT); /* * pass script's pid to dhcpagent. */ (void) write(fd, &pid, sizeof (pid)); for (;;) { if (waitpid(pid, NULL, 0) >= 0) { /* script has exited */ c = SCRIPT_OK; break; } if (errno != EINTR) return; now = time(NULL); if (now >= timeout) { (void) kill(pid, script_signal); if (script_signal == SIGKILL) { c = SCRIPT_KILLED; break; } script_signal = SIGKILL; timeout = now + SCRIPT_TIMEOUT_GRACE; (void) alarm(SCRIPT_TIMEOUT_GRACE); } } (void) write(fd, &c, 1); } /* * script_init(): initialize script state on a given state machine * * input: dhcp_smach_t *: the state machine * output: void */ void script_init(dhcp_smach_t *dsmp) { dsmp->dsm_script_pid = -1; dsmp->dsm_script_helper_pid = -1; dsmp->dsm_script_event_id = -1; dsmp->dsm_script_fd = -1; dsmp->dsm_script_callback = NULL; dsmp->dsm_script_event = NULL; dsmp->dsm_callback_arg = NULL; } /* * script_cleanup(): cleanup helper function * * input: dhcp_smach_t *: the state machine * output: void */ static void script_cleanup(dhcp_smach_t *dsmp) { /* * We must clear dsm_script_pid prior to invoking the callback or we * could get in an infinite loop via async_finish(). */ dsmp->dsm_script_pid = -1; dsmp->dsm_script_helper_pid = -1; if (dsmp->dsm_script_fd != -1) { assert(dsmp->dsm_script_event_id != -1); (void) iu_unregister_event(eh, dsmp->dsm_script_event_id, NULL); (void) close(dsmp->dsm_script_fd); assert(dsmp->dsm_script_callback != NULL); dsmp->dsm_script_callback(dsmp, dsmp->dsm_callback_arg); script_init(dsmp); script_count--; release_smach(dsmp); /* hold from script_start() */ } } /* * script_exit(): does cleanup and invokes the callback when the script exits * * input: eh_t *: unused * int: the end of pipe owned by dhcpagent * short: unused * eh_event_id_t: unused * void *: the state machine * output: void */ /* ARGSUSED */ static void script_exit(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) { char c; if (read(fd, &c, 1) <= 0) c = SCRIPT_FAILED; if (c == SCRIPT_OK) dhcpmsg(MSG_DEBUG, "script ok"); else if (c == SCRIPT_KILLED) dhcpmsg(MSG_DEBUG, "script killed"); else dhcpmsg(MSG_DEBUG, "script failed"); script_cleanup(arg); } /* * script_start(): tries to start a script. * if a script is already running, it's stopped first. * * * input: dhcp_smach_t *: the state machine * const char *: the event name * script_callback_t: callback function * void *: data to the callback function * output: boolean_t: B_TRUE if script starts successfully * int *: the returned value of the callback function if script * starts unsuccessfully */ boolean_t script_start(dhcp_smach_t *dsmp, const char *event, script_callback_t *callback, void *arg, int *status) { int n; int fds[2]; pid_t pid; iu_event_id_t event_id; assert(callback != NULL); if (dsmp->dsm_script_pid != -1) { /* script is running, stop it */ dhcpmsg(MSG_DEBUG, "script_start: stopping ongoing script"); script_stop(dsmp); } if (access(SCRIPT_PATH, X_OK) == -1) { /* script does not exist */ goto out; } /* * dhcpagent owns one end of the pipe and script helper process * owns the other end. dhcpagent reads on the pipe; and the helper * process notifies it when the script exits. */ if (pipe(fds) < 0) { dhcpmsg(MSG_ERROR, "script_start: can't create pipe"); goto out; } if ((pid = fork()) < 0) { dhcpmsg(MSG_ERROR, "script_start: can't fork"); (void) close(fds[0]); (void) close(fds[1]); goto out; } if (pid == 0) { /* * SIGCHLD is ignored in dhcpagent, the helper process * needs it. it calls waitpid to wait for the script to exit. */ (void) close(fds[0]); (void) sigset(SIGCHLD, SIG_DFL); (void) sigset(SIGTERM, sigterm_handler); run_script(dsmp, event, fds[1]); exit(0); } (void) close(fds[1]); /* get the script's pid */ if (read(fds[0], &dsmp->dsm_script_pid, sizeof (pid_t)) != sizeof (pid_t)) { (void) kill(pid, SIGKILL); dsmp->dsm_script_pid = -1; (void) close(fds[0]); goto out; } dsmp->dsm_script_helper_pid = pid; event_id = iu_register_event(eh, fds[0], POLLIN, script_exit, dsmp); if (event_id == -1) { (void) close(fds[0]); script_stop(dsmp); goto out; } script_count++; dsmp->dsm_script_event_id = event_id; dsmp->dsm_script_callback = callback; dsmp->dsm_script_event = event; dsmp->dsm_callback_arg = arg; dsmp->dsm_script_fd = fds[0]; hold_smach(dsmp); return (B_TRUE); out: /* callback won't be called in script_exit, so call it here */ n = callback(dsmp, arg); if (status != NULL) *status = n; return (B_FALSE); } /* * script_stop(): stops the script if it is running * * input: dhcp_smach_t *: the state machine * output: void */ void script_stop(dhcp_smach_t *dsmp) { if (dsmp->dsm_script_pid != -1) { assert(dsmp->dsm_script_helper_pid != -1); /* * sends SIGTERM to the script and asks the helper process * to send SIGKILL if it does not exit after * SCRIPT_TIMEOUT_GRACE seconds. */ (void) kill(dsmp->dsm_script_pid, SIGTERM); (void) kill(dsmp->dsm_script_helper_pid, SIGTERM); } script_cleanup(dsmp); } /* * 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. */ #ifndef SCRIPT_HANDLER_H #define SCRIPT_HANDLER_H #include "common.h" #ifdef __cplusplus extern "C" { #endif /* * The signal SIGTERM is sent to a script process if it does not exit after * SCRIPT_TIMEOUT seconds; and the signal SIGKILL is sent if it is still alive * SCRIPT_TIMEOUT_GRACE seconds after SIGTERM is sent. (SCRIPT_TIMEOUT + * SCRIPT_TIMEOUT_GRACE) should be less than DHCP_ASYNC_WAIT. */ #define SCRIPT_TIMEOUT 55 #define SCRIPT_TIMEOUT_GRACE 3 /* * script exit status as dhcpagent sees it, for debug purpose only. * * SCRIPT_OK: script exits ok, no timeout * SCRIPT_KILLED: script timeout, killed * SCRIPT_FAILED: unknown status */ enum { SCRIPT_OK, SCRIPT_KILLED, SCRIPT_FAILED }; /* * event names for script. */ #define EVENT_BOUND "BOUND" #define EVENT_EXTEND "EXTEND" #define EVENT_EXPIRE "EXPIRE" #define EVENT_DROP "DROP" #define EVENT_INFORM "INFORM" #define EVENT_RELEASE "RELEASE" #define EVENT_BOUND6 "BOUND6" #define EVENT_EXTEND6 "EXTEND6" #define EVENT_EXPIRE6 "EXPIRE6" #define EVENT_DROP6 "DROP6" #define EVENT_INFORM6 "INFORM6" #define EVENT_LOSS6 "LOSS6" #define EVENT_RELEASE6 "RELEASE6" /* * script location. */ #define SCRIPT_PATH "/etc/dhcp/eventhook" /* * the number of running scripts. */ extern unsigned int script_count; void script_init(dhcp_smach_t *); boolean_t script_start(dhcp_smach_t *, const char *, script_callback_t *, void *, int *); void script_stop(dhcp_smach_t *); #ifdef __cplusplus } #endif #endif /* SCRIPT_HANDLER_H */ /* * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016-2017, Chris Fraire . * * SELECTING state of the client state machine. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "states.h" #include "agent.h" #include "util.h" #include "interface.h" #include "packet.h" #include "defaults.h" static stop_func_t stop_selecting; /* * dhcp_start(): starts DHCP on a state machine * * input: iu_tq_t *: unused * void *: the state machine on which to start DHCP * output: void */ /* ARGSUSED */ static void dhcp_start(iu_tq_t *tqp, void *arg) { dhcp_smach_t *dsmp = arg; dsmp->dsm_start_timer = -1; (void) set_smach_state(dsmp, INIT); if (verify_smach(dsmp)) { dhcpmsg(MSG_VERBOSE, "starting DHCP on %s", dsmp->dsm_name); dhcp_selecting(dsmp); } } /* * set_start_timer(): sets a random timer to start a DHCP state machine * * input: dhcp_smach_t *: the state machine on which to start DHCP * output: boolean_t: B_TRUE if a timer is now running */ boolean_t set_start_timer(dhcp_smach_t *dsmp) { if (dsmp->dsm_start_timer != -1) return (B_TRUE); dsmp->dsm_start_timer = iu_schedule_timer_ms(tq, lrand48() % DHCP_SELECT_WAIT, dhcp_start, dsmp); if (dsmp->dsm_start_timer == -1) return (B_FALSE); hold_smach(dsmp); return (B_TRUE); } /* * dhcp_selecting(): sends a DISCOVER and sets up reception of OFFERs for * IPv4, or sends a Solicit and sets up reception of * Advertisements for DHCPv6. * * input: dhcp_smach_t *: the state machine on which to send the DISCOVER * output: void */ void dhcp_selecting(dhcp_smach_t *dsmp) { dhcp_pkt_t *dpkt; /* * We first set up to collect OFFER/Advertise packets as they arrive. * We then send out DISCOVER/Solicit probes. Then we wait a * user-tunable number of seconds before seeing if OFFERs/ * Advertisements have come in response to our DISCOVER/Solicit. If * none have come in, we continue to wait, sending out our DISCOVER/ * Solicit probes with exponential backoff. If no OFFER/Advertisement * is ever received, we will wait forever (note that since we're * event-driven though, we're still able to service other state * machines). * * Note that we do an reset_smach() here because we may be landing in * dhcp_selecting() as a result of restarting DHCP, so the state * machine may not be fresh. */ reset_smach(dsmp); if (!set_smach_state(dsmp, SELECTING)) { dhcpmsg(MSG_ERROR, "dhcp_selecting: cannot switch to SELECTING state; " "reverting to INIT on %s", dsmp->dsm_name); goto failed; } /* Remove the stale hostconf file, if there is any */ (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); dsmp->dsm_offer_timer = iu_schedule_timer(tq, dsmp->dsm_offer_wait, dhcp_requesting, dsmp); if (dsmp->dsm_offer_timer == -1) { dhcpmsg(MSG_ERROR, "dhcp_selecting: cannot schedule to read " "%s packets", dsmp->dsm_isv6 ? "Advertise" : "OFFER"); goto failed; } hold_smach(dsmp); /* * Assemble and send the DHCPDISCOVER or Solicit message. * * If this fails, we'll wait for the select timer to go off * before trying again. */ if (dsmp->dsm_isv6) { dhcpv6_ia_na_t d6in; if ((dpkt = init_pkt(dsmp, DHCPV6_MSG_SOLICIT)) == NULL) { dhcpmsg(MSG_ERROR, "dhcp_selecting: unable to set up " "Solicit packet"); return; } /* Add an IA_NA option for our controlling LIF */ d6in.d6in_iaid = htonl(dsmp->dsm_lif->lif_iaid); d6in.d6in_t1 = htonl(0); d6in.d6in_t2 = htonl(0); (void) add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA, (dhcpv6_option_t *)&d6in + 1, sizeof (d6in) - sizeof (dhcpv6_option_t)); /* Option Request option for desired information */ (void) add_pkt_prl(dpkt, dsmp); /* Enable Rapid-Commit */ (void) add_pkt_opt(dpkt, DHCPV6_OPT_RAPID_COMMIT, NULL, 0); /* xxx add Reconfigure Accept */ (void) send_pkt_v6(dsmp, dpkt, ipv6_all_dhcp_relay_and_servers, stop_selecting, DHCPV6_SOL_TIMEOUT, DHCPV6_SOL_MAX_RT); } else { if ((dpkt = init_pkt(dsmp, DISCOVER)) == NULL) { dhcpmsg(MSG_ERROR, "dhcp_selecting: unable to set up " "DISCOVER packet"); return; } /* * The max DHCP message size option is set to the interface * MTU, minus the size of the UDP and IP headers. */ (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, htons(dsmp->dsm_lif->lif_pif->pif_mtu - sizeof (struct udpiphdr))); (void) add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM)); if (class_id_len != 0) { (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len); } (void) add_pkt_prl(dpkt, dsmp); if (!dhcp_add_fqdn_opt(dpkt, dsmp)) (void) dhcp_add_hostname_opt(dpkt, dsmp); (void) add_pkt_opt(dpkt, CD_END, NULL, 0); (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), stop_selecting); } return; failed: (void) set_smach_state(dsmp, INIT); dsmp->dsm_dflags |= DHCP_IF_FAILED; ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); } /* * stop_selecting(): decides when to stop retransmitting DISCOVERs -- only when * abandoning the state machine. For DHCPv6, this timer may * go off before the offer wait timer. If so, then this is a * good time to check for valid Advertisements, so cancel the * timer and go check. * * input: dhcp_smach_t *: the state machine DISCOVERs are being sent on * unsigned int: the number of DISCOVERs sent so far * output: boolean_t: B_TRUE if retransmissions should stop */ /* ARGSUSED1 */ static boolean_t stop_selecting(dhcp_smach_t *dsmp, unsigned int n_discovers) { /* * If we're using v4 and the underlying LIF we're trying to configure * has been touched by the user, then bail out. */ if (!dsmp->dsm_isv6 && !verify_lif(dsmp->dsm_lif)) { finished_smach(dsmp, DHCP_IPC_E_UNKIF); return (B_TRUE); } if (dsmp->dsm_recv_pkt_list != NULL) { dhcp_requesting(NULL, dsmp); if (dsmp->dsm_state != SELECTING) return (B_TRUE); } return (B_FALSE); } /* * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016-2017, Chris Fraire . * * This module contains core functions for managing DHCP state machine * instances. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "agent.h" #include "states.h" #include "interface.h" #include "defaults.h" #include "script_handler.h" static uint_t global_smach_count; static uchar_t *global_duid; static size_t global_duidlen; /* * iaid_retry(): attempt to write LIF IAID again * * input: iu_tq_t *: ignored * void *: pointer to LIF * output: none */ /* ARGSUSED */ static void iaid_retry(iu_tq_t *tqp, void *arg) { dhcp_lif_t *lif = arg; if (write_stable_iaid(lif->lif_name, lif->lif_iaid) == -1) { if (errno != EROFS) { dhcpmsg(MSG_ERR, "iaid_retry: unable to write out IAID for %s", lif->lif_name); release_lif(lif); } else { lif->lif_iaid_id = iu_schedule_timer(tq, 60, iaid_retry, lif); } } else { release_lif(lif); } } /* * parse_param_list(): parse a parameter list. * * input: const char *: parameter list string with comma-separated entries * uint_t *: return parameter; number of entries decoded * const char *: name of parameter list for logging purposes * dhcp_smach_t *: smach pointer for logging * output: uint16_t *: allocated array of parameters, or NULL if none. */ static uint16_t * parse_param_list(const char *param_list, uint_t *param_cnt, const char *param_name, dhcp_smach_t *dsmp) { int i, maxparam; char tsym[DSYM_MAX_SYM_LEN + 1]; uint16_t *params; const char *cp; dhcp_symbol_t *entry; *param_cnt = 0; if (param_list == NULL) return (NULL); for (maxparam = 1, i = 0; param_list[i] != '\0'; i++) { if (param_list[i] == ',') maxparam++; } params = malloc(maxparam * sizeof (*params)); if (params == NULL) { dhcpmsg(MSG_WARNING, "cannot allocate parameter %s list for %s (continuing)", param_name, dsmp->dsm_name); return (NULL); } for (i = 0; i < maxparam; ) { if (isspace(*param_list)) param_list++; /* extract the next element on the list */ cp = strchr(param_list, ','); if (cp == NULL || cp - param_list >= sizeof (tsym)) (void) strlcpy(tsym, param_list, sizeof (tsym)); else (void) strlcpy(tsym, param_list, cp - param_list + 1); /* LINTED -- do nothing with blanks on purpose */ if (tsym[0] == '\0') { ; } else if (isalpha(tsym[0])) { entry = inittab_getbyname(ITAB_CAT_SITE | ITAB_CAT_STANDARD | (dsmp->dsm_isv6 ? ITAB_CAT_V6 : 0), ITAB_CONS_INFO, tsym); if (entry == NULL) { dhcpmsg(MSG_INFO, "ignored unknown %s list " "entry '%s' for %s", param_name, tsym, dsmp->dsm_name); } else { params[i++] = entry->ds_code; free(entry); } } else { params[i++] = strtoul(tsym, NULL, 0); } if (cp == NULL) break; param_list = cp + 1; } *param_cnt = i; return (params); } /* * insert_smach(): Create a state machine instance on a given logical * interface. The state machine holds the caller's LIF * reference on success, and frees it on failure. * * input: dhcp_lif_t *: logical interface name * int *: set to DHCP_IPC_E_* if creation fails * output: dhcp_smach_t *: state machine instance */ dhcp_smach_t * insert_smach(dhcp_lif_t *lif, int *error) { dhcp_smach_t *dsmp, *alt_primary; boolean_t isv6; const char *plist; if ((dsmp = calloc(1, sizeof (*dsmp))) == NULL) { dhcpmsg(MSG_ERR, "cannot allocate state machine entry for %s", lif->lif_name); remove_lif(lif); release_lif(lif); *error = DHCP_IPC_E_MEMORY; return (NULL); } dsmp->dsm_name = lif->lif_name; dsmp->dsm_lif = lif; dsmp->dsm_hold_count = 1; dsmp->dsm_state = INIT; dsmp->dsm_dflags = DHCP_IF_REMOVED; /* until added to list */ isv6 = lif->lif_pif->pif_isv6; /* * Now that we have a controlling LIF, we need to assign an IAID to * that LIF. */ if (lif->lif_iaid == 0 && (lif->lif_iaid = read_stable_iaid(lif->lif_name)) == 0) { static uint32_t iaidctr = 0x80000000u; /* * If this is a logical interface, then use an arbitrary seed * value. Otherwise, use the ifIndex. */ lif->lif_iaid = make_stable_iaid(lif->lif_name, strchr(lif->lif_name, ':') != NULL ? iaidctr++ : lif->lif_pif->pif_index); dhcpmsg(MSG_INFO, "insert_smach: manufactured IAID %u for v%d %s", lif->lif_iaid, isv6 ? 6 : 4, lif->lif_name); hold_lif(lif); iaid_retry(NULL, lif); } if (isv6) { dsmp->dsm_dflags |= DHCP_IF_V6; dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers; /* * With DHCPv6, we do all of our I/O using the common * v6_sock_fd. There's no need for per-interface file * descriptors because we have IPV6_PKTINFO. */ } else { IN6_IPADDR_TO_V4MAPPED(htonl(INADDR_BROADCAST), &dsmp->dsm_server); /* * With IPv4 DHCP, we use a socket per lif. */ if (!open_ip_lif(lif, INADDR_ANY, B_TRUE)) { dhcpmsg(MSG_ERR, "unable to open socket for %s", lif->lif_name); /* This will also dispose of the LIF */ release_smach(dsmp); *error = DHCP_IPC_E_SOCKET; return (NULL); } } script_init(dsmp); ipc_action_init(&dsmp->dsm_ia); dsmp->dsm_neg_hrtime = gethrtime(); dsmp->dsm_offer_timer = -1; dsmp->dsm_start_timer = -1; dsmp->dsm_retrans_timer = -1; /* * Initialize the parameter request and ignore lists, if any. */ plist = df_get_string(dsmp->dsm_name, isv6, DF_PARAM_REQUEST_LIST); dsmp->dsm_prl = parse_param_list(plist, &dsmp->dsm_prllen, "request", dsmp); plist = df_get_string(dsmp->dsm_name, isv6, DF_PARAM_IGNORE_LIST); dsmp->dsm_pil = parse_param_list(plist, &dsmp->dsm_pillen, "ignore", dsmp); dsmp->dsm_offer_wait = df_get_int(dsmp->dsm_name, isv6, DF_OFFER_WAIT); /* * If there is no primary of this type, and there is one of the other, * then make this one primary if it's on the same named PIF. */ if (primary_smach(isv6) == NULL && (alt_primary = primary_smach(!isv6)) != NULL) { if (strcmp(lif->lif_pif->pif_name, alt_primary->dsm_lif->lif_pif->pif_name) == 0) { dhcpmsg(MSG_DEBUG, "insert_smach: making %s primary for v%d", dsmp->dsm_name, isv6 ? 6 : 4); dsmp->dsm_dflags |= DHCP_IF_PRIMARY; } } /* * We now have at least one state machine running, so cancel any * running inactivity timer. */ if (inactivity_id != -1 && iu_cancel_timer(tq, inactivity_id, NULL) == 1) inactivity_id = -1; dsmp->dsm_dflags &= ~DHCP_IF_REMOVED; insque(dsmp, &lif->lif_smachs); global_smach_count++; dhcpmsg(MSG_DEBUG2, "insert_smach: inserted %s", dsmp->dsm_name); return (dsmp); } /* * hold_smach(): acquires a hold on a state machine * * input: dhcp_smach_t *: the state machine to acquire a hold on * output: void */ void hold_smach(dhcp_smach_t *dsmp) { dsmp->dsm_hold_count++; dhcpmsg(MSG_DEBUG2, "hold_smach: hold count on %s: %d", dsmp->dsm_name, dsmp->dsm_hold_count); } /* * free_smach(): frees the memory occupied by a state machine * * input: dhcp_smach_t *: the DHCP state machine to free * output: void */ static void free_smach(dhcp_smach_t *dsmp) { dhcpmsg(MSG_DEBUG, "free_smach: freeing state machine %s", dsmp->dsm_name); deprecate_leases(dsmp); remove_lif(dsmp->dsm_lif); release_lif(dsmp->dsm_lif); free_pkt_list(&dsmp->dsm_recv_pkt_list); if (dsmp->dsm_ack != dsmp->dsm_orig_ack) free_pkt_entry(dsmp->dsm_orig_ack); free_pkt_entry(dsmp->dsm_ack); free(dsmp->dsm_send_pkt.pkt); free(dsmp->dsm_cid); free(dsmp->dsm_prl); free(dsmp->dsm_pil); free(dsmp->dsm_routers); free(dsmp->dsm_reqhost); free(dsmp->dsm_msg_reqhost); free(dsmp->dsm_dhcp_domainname); free(dsmp); /* no big deal if this fails */ if (global_smach_count == 0 && inactivity_id == -1) { inactivity_id = iu_schedule_timer(tq, DHCP_INACTIVITY_WAIT, inactivity_shutdown, NULL); } } /* * release_smach(): releases a hold previously acquired on a state machine. * If the hold count reaches 0, the state machine is freed. * * input: dhcp_smach_t *: the state machine entry to release the hold on * output: void */ void release_smach(dhcp_smach_t *dsmp) { if (dsmp->dsm_hold_count == 0) { dhcpmsg(MSG_CRIT, "release_smach: extraneous release"); return; } if (dsmp->dsm_hold_count == 1 && !(dsmp->dsm_dflags & DHCP_IF_REMOVED)) { dhcpmsg(MSG_CRIT, "release_smach: missing removal"); return; } if (--dsmp->dsm_hold_count == 0) { free_smach(dsmp); } else { dhcpmsg(MSG_DEBUG2, "release_smach: hold count on %s: %d", dsmp->dsm_name, dsmp->dsm_hold_count); } } /* * next_smach(): state machine iterator function * * input: dhcp_smach_t *: current state machine (or NULL for list start) * boolean_t: B_TRUE if DHCPv6, B_FALSE otherwise * output: dhcp_smach_t *: next state machine in list */ dhcp_smach_t * next_smach(dhcp_smach_t *dsmp, boolean_t isv6) { dhcp_lif_t *lif; dhcp_pif_t *pif; if (dsmp != NULL) { if (dsmp->dsm_next != NULL) return (dsmp->dsm_next); if ((lif = dsmp->dsm_lif) != NULL) lif = lif->lif_next; for (; lif != NULL; lif = lif->lif_next) { if (lif->lif_smachs != NULL) return (lif->lif_smachs); } if ((pif = dsmp->dsm_lif->lif_pif) != NULL) pif = pif->pif_next; } else { pif = isv6 ? v6root : v4root; } for (; pif != NULL; pif = pif->pif_next) { for (lif = pif->pif_lifs; lif != NULL; lif = lif->lif_next) { if (lif->lif_smachs != NULL) return (lif->lif_smachs); } } return (NULL); } /* * primary_smach(): loop through all state machines of the given type (v4 or * v6) in the system, and locate the one that's primary. * * input: boolean_t: B_TRUE for IPv6 * output: dhcp_smach_t *: the primary state machine */ dhcp_smach_t * primary_smach(boolean_t isv6) { dhcp_smach_t *dsmp; for (dsmp = next_smach(NULL, isv6); dsmp != NULL; dsmp = next_smach(dsmp, isv6)) { if (dsmp->dsm_dflags & DHCP_IF_PRIMARY) break; } return (dsmp); } /* * info_primary_smach(): loop through all state machines of the given type (v4 * or v6) in the system, and locate the one that should * be considered "primary" for dhcpinfo. * * input: boolean_t: B_TRUE for IPv6 * output: dhcp_smach_t *: the dhcpinfo primary state machine */ dhcp_smach_t * info_primary_smach(boolean_t isv6) { dhcp_smach_t *bestdsm = NULL; dhcp_smach_t *dsmp; for (dsmp = next_smach(NULL, isv6); dsmp != NULL; dsmp = next_smach(dsmp, isv6)) { /* * If there is a primary, then something previously went wrong * with verification, because the caller uses primary_smach() * before calling this routine. There's nothing else we can do * but return failure, as the designated primary must be bad. */ if (dsmp->dsm_dflags & DHCP_IF_PRIMARY) return (NULL); /* If we have no information, then we're not primary. */ if (dsmp->dsm_ack == NULL) continue; /* * Among those interfaces that have DHCP information, the * "primary" is the one that sorts lexically first. */ if (bestdsm == NULL || strcmp(dsmp->dsm_name, bestdsm->dsm_name) < 0) bestdsm = dsmp; } return (bestdsm); } /* * make_primary(): designate a given state machine as being the primary * instance on the primary interface. Note that the user often * thinks in terms of a primary "interface" (rather than just * an instance), so we go to lengths here to keep v4 and v6 in * sync. * * input: dhcp_smach_t *: the primary state machine * output: none */ void make_primary(dhcp_smach_t *dsmp) { dhcp_smach_t *old_primary, *alt_primary; dhcp_pif_t *pif; if ((old_primary = primary_smach(dsmp->dsm_isv6)) != NULL) old_primary->dsm_dflags &= ~DHCP_IF_PRIMARY; dsmp->dsm_dflags |= DHCP_IF_PRIMARY; /* * Find the primary for the other protocol. */ alt_primary = primary_smach(!dsmp->dsm_isv6); /* * If it's on a different interface, then cancel that. If it's on the * same interface, then we're done. */ if (alt_primary != NULL) { if (strcmp(alt_primary->dsm_lif->lif_pif->pif_name, dsmp->dsm_lif->lif_pif->pif_name) == 0) return; alt_primary->dsm_dflags &= ~DHCP_IF_PRIMARY; } /* * We need a new primary for the other protocol. If the PIF exists, * there must be at least one state machine. Just choose the first for * consistency with insert_smach(). */ if ((pif = lookup_pif_by_name(dsmp->dsm_lif->lif_pif->pif_name, !dsmp->dsm_isv6)) != NULL) { pif->pif_lifs->lif_smachs->dsm_dflags |= DHCP_IF_PRIMARY; } } /* * lookup_smach(): finds a state machine by name and type; used for dispatching * user commands. * * input: const char *: the name of the state machine * boolean_t: B_TRUE if DHCPv6, B_FALSE otherwise * output: dhcp_smach_t *: the state machine found */ dhcp_smach_t * lookup_smach(const char *smname, boolean_t isv6) { dhcp_smach_t *dsmp; for (dsmp = next_smach(NULL, isv6); dsmp != NULL; dsmp = next_smach(dsmp, isv6)) { if (strcmp(dsmp->dsm_name, smname) == 0) break; } return (dsmp); } /* * lookup_smach_by_uindex(): iterate through running state machines by * truncated interface index. * * input: uint16_t: the interface index (truncated) * dhcp_smach_t *: the previous state machine, or NULL for start * boolean_t: B_TRUE for DHCPv6, B_FALSE for IPv4 DHCP * output: dhcp_smach_t *: next state machine, or NULL at end of list */ dhcp_smach_t * lookup_smach_by_uindex(uint16_t ifindex, dhcp_smach_t *dsmp, boolean_t isv6) { dhcp_pif_t *pif; dhcp_lif_t *lif; /* * If the user gives us a state machine, then check that the next one * available is on the same physical interface. If so, then go ahead * and return that. */ if (dsmp != NULL) { pif = dsmp->dsm_lif->lif_pif; if ((dsmp = next_smach(dsmp, isv6)) == NULL) return (NULL); if (pif == dsmp->dsm_lif->lif_pif) return (dsmp); } else { /* Otherwise, start at the beginning of the list */ pif = NULL; } /* * Find the next physical interface with the same truncated interface * index, and return the first state machine on that. If there are no * more physical interfaces that match, then we're done. */ do { pif = lookup_pif_by_uindex(ifindex, pif, isv6); if (pif == NULL) return (NULL); for (lif = pif->pif_lifs; lif != NULL; lif = lif->lif_next) { if ((dsmp = lif->lif_smachs) != NULL) break; } } while (dsmp == NULL); return (dsmp); } /* * lookup_smach_by_xid(): iterate through running state machines by transaction * id. Transaction ID zero means "all state machines." * * input: uint32_t: the transaction id to look up * dhcp_smach_t *: the previous state machine, or NULL for start * boolean_t: B_TRUE if DHCPv6, B_FALSE otherwise * output: dhcp_smach_t *: next state machine, or NULL at end of list */ dhcp_smach_t * lookup_smach_by_xid(uint32_t xid, dhcp_smach_t *dsmp, boolean_t isv6) { for (dsmp = next_smach(dsmp, isv6); dsmp != NULL; dsmp = next_smach(dsmp, isv6)) { if (xid == 0 || pkt_get_xid(dsmp->dsm_send_pkt.pkt, isv6) == xid) break; } return (dsmp); } /* * lookup_smach_by_event(): find a state machine busy with a particular event * ID. This is used only for error handling. * * input: iu_event_id_t: the event id to look up * output: dhcp_smach_t *: matching state machine, or NULL if none */ dhcp_smach_t * lookup_smach_by_event(iu_event_id_t eid) { dhcp_smach_t *dsmp; boolean_t isv6 = B_FALSE; for (;;) { for (dsmp = next_smach(NULL, isv6); dsmp != NULL; dsmp = next_smach(dsmp, isv6)) { if ((dsmp->dsm_dflags & DHCP_IF_BUSY) && eid == dsmp->dsm_ia.ia_eid) return (dsmp); } if (isv6) break; isv6 = B_TRUE; } return (dsmp); } /* * cancel_offer_timer(): stop the offer polling timer on a given state machine * * input: dhcp_smach_t *: state machine on which to stop polling for offers * output: none */ void cancel_offer_timer(dhcp_smach_t *dsmp) { int retval; if (dsmp->dsm_offer_timer != -1) { retval = iu_cancel_timer(tq, dsmp->dsm_offer_timer, NULL); dsmp->dsm_offer_timer = -1; if (retval == 1) release_smach(dsmp); } } /* * cancel_smach_timers(): stop all of the timers related to a given state * machine, including lease and LIF expiry. * * input: dhcp_smach_t *: state machine to cancel * output: none * note: this function assumes that the iu timer functions are synchronous * and thus don't require any protection or ordering on cancellation. */ void cancel_smach_timers(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) { cancel_lease_timers(dlp); lif = dlp->dl_lifs; nlifs = dlp->dl_nlifs; for (; nlifs > 0; nlifs--, lif = lif->lif_next) cancel_lif_timers(lif); } cancel_offer_timer(dsmp); stop_pkt_retransmission(dsmp); if (dsmp->dsm_start_timer != -1) { (void) iu_cancel_timer(tq, dsmp->dsm_start_timer, NULL); dsmp->dsm_start_timer = -1; release_smach(dsmp); } } /* * remove_smach(): removes a given state machine from the system. marks it * for being freed (but may not actually free it). * * input: dhcp_smach_t *: the state machine to remove * output: void */ void remove_smach(dhcp_smach_t *dsmp) { if (dsmp->dsm_dflags & DHCP_IF_REMOVED) return; dhcpmsg(MSG_DEBUG2, "remove_smach: removing %s", dsmp->dsm_name); dsmp->dsm_dflags |= DHCP_IF_REMOVED; remque(dsmp); global_smach_count--; /* * if we have long term timers, cancel them so that state machine * resources can be reclaimed in a reasonable amount of time. */ cancel_smach_timers(dsmp); /* Drop the hold that the LIF's state machine list had on us */ release_smach(dsmp); } /* * finished_smach(): we're finished with a given state machine; remove it from * the system and tell the user (who may have initiated the * removal process). Note that we remove it from the system * first to allow back-to-back drop and create invocations. * * input: dhcp_smach_t *: the state machine to remove * int: error for IPC * output: void */ void finished_smach(dhcp_smach_t *dsmp, int error) { hold_smach(dsmp); remove_smach(dsmp); if (dsmp->dsm_ia.ia_fd != -1) ipc_action_finish(dsmp, error); else (void) async_cancel(dsmp); release_smach(dsmp); } /* * is_bound_state(): checks if a state indicates the client is bound * * input: DHCPSTATE: the state to check * output: boolean_t: B_TRUE if the state is bound, B_FALSE if not */ boolean_t is_bound_state(DHCPSTATE state) { return (state == BOUND || state == REBINDING || state == INFORMATION || state == RELEASING || state == INFORM_SENT || state == RENEWING); } /* * set_smach_state(): changes state and updates I/O * * input: dhcp_smach_t *: the state machine to change * DHCPSTATE: the new state * output: boolean_t: B_TRUE on success, B_FALSE on failure */ boolean_t set_smach_state(dhcp_smach_t *dsmp, DHCPSTATE state) { dhcp_lif_t *lif = dsmp->dsm_lif; if (dsmp->dsm_state != state) { dhcpmsg(MSG_DEBUG, "set_smach_state: changing from %s to %s on %s", dhcp_state_to_string(dsmp->dsm_state), dhcp_state_to_string(state), dsmp->dsm_name); /* * For IPv4, when we're in a bound state our socket must be * bound to our address. Otherwise, our socket must be bound * to INADDR_ANY. For IPv6, no such change is necessary. */ if (!dsmp->dsm_isv6) { if (is_bound_state(dsmp->dsm_state)) { if (!is_bound_state(state)) { close_ip_lif(lif); if (!open_ip_lif(lif, INADDR_ANY, B_FALSE)) return (B_FALSE); } } else { if (is_bound_state(state)) { close_ip_lif(lif); if (!open_ip_lif(lif, ntohl(lif->lif_addr), B_FALSE)) return (B_FALSE); } } } dsmp->dsm_state = state; } return (B_TRUE); } /* * duid_retry(): attempt to write DUID again * * input: iu_tq_t *: ignored * void *: ignored * output: none */ /* ARGSUSED */ static void duid_retry(iu_tq_t *tqp, void *arg) { if (write_stable_duid(global_duid, global_duidlen) == -1) { if (errno != EROFS) { dhcpmsg(MSG_ERR, "duid_retry: unable to write out DUID"); } else { (void) iu_schedule_timer(tq, 60, duid_retry, NULL); } } } /* * get_smach_cid(): gets the client ID for a given state machine. * * input: dhcp_smach_t *: the state machine to set up * output: int: DHCP_IPC_SUCCESS or one of DHCP_IPC_E_* on failure. */ int get_smach_cid(dhcp_smach_t *dsmp) { uchar_t *client_id; uint_t client_id_len; dhcp_lif_t *lif = dsmp->dsm_lif; dhcp_pif_t *pif = lif->lif_pif; const char *value; size_t slen; /* * Look in defaults file for the client-id. If present, this takes * precedence over all other forms of ID. */ dhcpmsg(MSG_DEBUG, "get_smach_cid: getting default client-id " "property on %s", dsmp->dsm_name); value = df_get_string(dsmp->dsm_name, pif->pif_isv6, DF_CLIENT_ID); if (value != NULL) { /* * The Client ID string can have one of three basic forms: * , * 0x * * * The first form is an RFC 3315 DUID. This is legal for both * IPv4 DHCP and DHCPv6. For IPv4, an RFC 4361 Client ID is * constructed from this value. * * The second and third forms are legal for IPv4 only. This is * a raw Client ID, in hex or ASCII string format. */ if (isdigit(*value) && value[strspn(value, "0123456789")] == ',') { char *cp; ulong_t duidtype; ulong_t subtype; errno = 0; duidtype = strtoul(value, &cp, 0); if (value == cp || errno != 0 || *cp != ',' || duidtype > 65535) { dhcpmsg(MSG_ERR, "get_smach_cid: cannot parse " "DUID type in %s", value); goto no_specified_id; } value = cp + 1; switch (duidtype) { case DHCPV6_DUID_LL: case DHCPV6_DUID_LLT: { int num; char chr; errno = 0; subtype = strtoul(value, &cp, 0); if (value == cp || errno != 0 || *cp != ',' || subtype > 65535) { dhcpmsg(MSG_ERR, "get_smach_cid: " "cannot parse MAC type in %s", value); goto no_specified_id; } value = cp + 1; client_id_len = pif->pif_isv6 ? 1 : 5; for (; *cp != '\0'; cp++) { if (*cp == ':') client_id_len++; else if (!isxdigit(*cp)) break; } if (duidtype == DHCPV6_DUID_LL) { duid_llt_t *dllt; time_t now; client_id_len += sizeof (*dllt); dllt = malloc(client_id_len); if (dllt == NULL) goto alloc_failure; dsmp->dsm_cid = (uchar_t *)dllt; dllt->dllt_dutype = htons(duidtype); dllt->dllt_hwtype = htons(subtype); now = time(NULL) - DUID_TIME_BASE; dllt->dllt_time = htonl(now); cp = (char *)(dllt + 1); } else { duid_ll_t *dll; client_id_len += sizeof (*dll); dll = malloc(client_id_len); if (dll == NULL) goto alloc_failure; dsmp->dsm_cid = (uchar_t *)dll; dll->dll_dutype = htons(duidtype); dll->dll_hwtype = htons(subtype); cp = (char *)(dll + 1); } num = 0; while ((chr = *value) != '\0') { if (isdigit(chr)) { num = (num << 4) + chr - '0'; } else if (isxdigit(chr)) { num = (num << 4) + 10 + chr - (isupper(chr) ? 'A' : 'a'); } else if (chr == ':') { *cp++ = num; num = 0; } else { break; } } break; } case DHCPV6_DUID_EN: { duid_en_t *den; errno = 0; subtype = strtoul(value, &cp, 0); if (value == cp || errno != 0 || *cp != ',') { dhcpmsg(MSG_ERR, "get_smach_cid: " "cannot parse enterprise in %s", value); goto no_specified_id; } value = cp + 1; slen = strlen(value); client_id_len = (slen + 1) / 2; den = malloc(sizeof (*den) + client_id_len); if (den == NULL) goto alloc_failure; den->den_dutype = htons(duidtype); DHCPV6_SET_ENTNUM(den, subtype); if (hexascii_to_octet(value, slen, den + 1, &client_id_len) != 0) { dhcpmsg(MSG_ERROR, "get_smach_cid: " "cannot parse hex string in %s", value); free(den); goto no_specified_id; } dsmp->dsm_cid = (uchar_t *)den; break; } default: slen = strlen(value); client_id_len = (slen + 1) / 2; cp = malloc(client_id_len); if (cp == NULL) goto alloc_failure; if (hexascii_to_octet(value, slen, cp, &client_id_len) != 0) { dhcpmsg(MSG_ERROR, "get_smach_cid: " "cannot parse hex string in %s", value); free(cp); goto no_specified_id; } dsmp->dsm_cid = (uchar_t *)cp; break; } dsmp->dsm_cidlen = client_id_len; if (!pif->pif_isv6) { (void) memmove(dsmp->dsm_cid + 5, dsmp->dsm_cid, client_id_len - 5); dsmp->dsm_cid[0] = 255; dsmp->dsm_cid[1] = lif->lif_iaid >> 24; dsmp->dsm_cid[2] = lif->lif_iaid >> 16; dsmp->dsm_cid[3] = lif->lif_iaid >> 8; dsmp->dsm_cid[4] = lif->lif_iaid; } return (DHCP_IPC_SUCCESS); } if (pif->pif_isv6) { dhcpmsg(MSG_ERROR, "get_smach_cid: client ID for %s invalid: %s", dsmp->dsm_name, value); } else if (strncasecmp("0x", value, 2) == 0 && value[2] != '\0') { /* skip past the 0x and convert the value to binary */ value += 2; slen = strlen(value); client_id_len = (slen + 1) / 2; dsmp->dsm_cid = malloc(client_id_len); if (dsmp->dsm_cid == NULL) goto alloc_failure; if (hexascii_to_octet(value, slen, dsmp->dsm_cid, &client_id_len) == 0) { dsmp->dsm_cidlen = client_id_len; return (DHCP_IPC_SUCCESS); } dhcpmsg(MSG_WARNING, "get_smach_cid: cannot convert " "hex value for Client ID on %s", dsmp->dsm_name); } else { client_id_len = strlen(value); dsmp->dsm_cid = malloc(client_id_len); if (dsmp->dsm_cid == NULL) goto alloc_failure; dsmp->dsm_cidlen = client_id_len; (void) memcpy(dsmp->dsm_cid, value, client_id_len); return (DHCP_IPC_SUCCESS); } } no_specified_id: /* * There was either no user-specified Client ID value, or we were * unable to parse it. We need to determine if a Client ID is required * and, if so, generate one. * * If it's IPv4, not in an IPMP group, not a logical interface, * and a DHCP default for DF_V4_DEFAULT_IAID_DUID is not affirmative, * then we need to preserve backward-compatibility by avoiding * new-fangled DUID/IAID construction. (Note: even for IPMP test * addresses, we construct a DUID/IAID since we may renew a lease for * an IPMP test address on any functioning IP interface in the group.) */ if (!pif->pif_isv6 && pif->pif_grifname[0] == '\0' && strchr(dsmp->dsm_name, ':') == NULL && !df_get_bool(dsmp->dsm_name, pif->pif_isv6, DF_V4_DEFAULT_IAID_DUID)) { if (pif->pif_hwtype == ARPHRD_IB) { /* * This comes from the DHCP over IPoIB specification. * In the absence of an user specified client id, IPoIB * automatically uses the required format, with the * unique 4 octet value set to 0 (since IPoIB driver * allows only a single interface on a port with a * specific GID to belong to an IP subnet (PSARC * 2001/289, FWARC 2002/702). * * Type Client-Identifier * +-----+-----+-----+-----+-----+----....----+ * | 0 | 0 (4 octets) | GID (16 octets)| * +-----+-----+-----+-----+-----+----....----+ */ dsmp->dsm_cidlen = 1 + 4 + 16; dsmp->dsm_cid = client_id = malloc(dsmp->dsm_cidlen); if (dsmp->dsm_cid == NULL) goto alloc_failure; /* * Pick the GID from the mac address. The format * of the hardware address is: * +-----+-----+-----+-----+----....----+ * | QPN (4 octets) | GID (16 octets)| * +-----+-----+-----+-----+----....----+ */ (void) memcpy(client_id + 5, pif->pif_hwaddr + 4, pif->pif_hwlen - 4); (void) memset(client_id, 0, 5); } return (DHCP_IPC_SUCCESS); } /* * Now check for a saved DUID. If there is one, then use it. If there * isn't, then generate a new one. For IPv4, we need to construct the * RFC 4361 Client ID with this value and the LIF's IAID. */ if (global_duid == NULL && (global_duid = read_stable_duid(&global_duidlen)) == NULL) { global_duid = make_stable_duid(pif->pif_name, &global_duidlen); if (global_duid == NULL) goto alloc_failure; duid_retry(NULL, NULL); } if (pif->pif_isv6) { dsmp->dsm_cid = malloc(global_duidlen); if (dsmp->dsm_cid == NULL) goto alloc_failure; (void) memcpy(dsmp->dsm_cid, global_duid, global_duidlen); dsmp->dsm_cidlen = global_duidlen; } else { dsmp->dsm_cid = malloc(5 + global_duidlen); if (dsmp->dsm_cid == NULL) goto alloc_failure; dsmp->dsm_cid[0] = 255; dsmp->dsm_cid[1] = lif->lif_iaid >> 24; dsmp->dsm_cid[2] = lif->lif_iaid >> 16; dsmp->dsm_cid[3] = lif->lif_iaid >> 8; dsmp->dsm_cid[4] = lif->lif_iaid; (void) memcpy(dsmp->dsm_cid + 5, global_duid, global_duidlen); dsmp->dsm_cidlen = 5 + global_duidlen; } return (DHCP_IPC_SUCCESS); alloc_failure: dhcpmsg(MSG_ERR, "get_smach_cid: cannot allocate Client Id for %s", dsmp->dsm_name); return (DHCP_IPC_E_MEMORY); } /* * smach_count(): returns the number of state machines running * * input: void * output: uint_t: the number of state machines */ uint_t smach_count(void) { return (global_smach_count); } /* * discard_default_routes(): removes a state machine's default routes alone. * * input: dhcp_smach_t *: the state machine whose default routes need to be * discarded * output: void */ void discard_default_routes(dhcp_smach_t *dsmp) { free(dsmp->dsm_routers); dsmp->dsm_routers = NULL; dsmp->dsm_nrouters = 0; } /* * remove_default_routes(): removes a state machine's default routes from the * kernel and from the state machine. * * input: dhcp_smach_t *: the state machine whose default routes need to be * removed * output: void */ void remove_default_routes(dhcp_smach_t *dsmp) { int idx; uint32_t ifindex; if (dsmp->dsm_routers != NULL) { ifindex = dsmp->dsm_lif->lif_pif->pif_index; for (idx = dsmp->dsm_nrouters - 1; idx >= 0; idx--) { if (del_default_route(ifindex, &dsmp->dsm_routers[idx])) { dhcpmsg(MSG_DEBUG, "remove_default_routes: " "removed %s from %s", inet_ntoa(dsmp->dsm_routers[idx]), dsmp->dsm_name); } else { dhcpmsg(MSG_INFO, "remove_default_routes: " "unable to remove %s from %s", inet_ntoa(dsmp->dsm_routers[idx]), dsmp->dsm_name); } } discard_default_routes(dsmp); } } /* * reset_smach(): resets a state machine to its initial state * * input: dhcp_smach_t *: the state machine to reset * output: void */ void reset_smach(dhcp_smach_t *dsmp) { dsmp->dsm_dflags &= ~DHCP_IF_FAILED; remove_default_routes(dsmp); clear_lif_mtu(dsmp->dsm_lif); free_pkt_list(&dsmp->dsm_recv_pkt_list); free_pkt_entry(dsmp->dsm_ack); if (dsmp->dsm_orig_ack != dsmp->dsm_ack) free_pkt_entry(dsmp->dsm_orig_ack); dsmp->dsm_ack = dsmp->dsm_orig_ack = NULL; free(dsmp->dsm_reqhost); dsmp->dsm_reqhost = NULL; /* * Do not reset dsm_msg_reqhost here. Unlike dsm_reqhost coming from * /etc/host.*, dsm_msg_reqhost comes externally, and it survives until * it is reset from another external message. */ free(dsmp->dsm_dhcp_domainname); dsmp->dsm_dhcp_domainname = NULL; cancel_smach_timers(dsmp); (void) set_smach_state(dsmp, INIT); if (dsmp->dsm_isv6) { dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers; } else { IN6_IPADDR_TO_V4MAPPED(htonl(INADDR_BROADCAST), &dsmp->dsm_server); } dsmp->dsm_neg_hrtime = gethrtime(); /* * We must never get here with a script running, since it means we're * resetting an smach that is still in the middle of another state * transition with a pending dsm_script_callback. */ assert(dsmp->dsm_script_pid == -1); } /* * refresh_smach(): refreshes a given state machine, as though awakened from * hibernation or by lower layer "link up." * * input: dhcp_smach_t *: state machine to refresh * output: void */ void refresh_smach(dhcp_smach_t *dsmp) { if (dsmp->dsm_state == BOUND || dsmp->dsm_state == RENEWING || dsmp->dsm_state == REBINDING || dsmp->dsm_state == INFORMATION) { dhcpmsg(MSG_WARNING, "refreshing state on %s", dsmp->dsm_name); cancel_smach_timers(dsmp); if (dsmp->dsm_state == INFORMATION) dhcp_inform(dsmp); else dhcp_init_reboot(dsmp); } } /* * refresh_smachs(): refreshes all finite leases under DHCP control * * input: iu_eh_t *: unused * int: unused * void *: unused * output: void */ /* ARGSUSED */ void refresh_smachs(iu_eh_t *eh, int sig, void *arg) { boolean_t isv6 = B_FALSE; dhcp_smach_t *dsmp; for (;;) { for (dsmp = next_smach(NULL, isv6); dsmp != NULL; dsmp = next_smach(dsmp, isv6)) { refresh_smach(dsmp); } if (isv6) break; isv6 = B_TRUE; } } /* * nuke_smach_list(): delete the state machine list. For use when the * dhcpagent is exiting. * * input: none * output: none */ void nuke_smach_list(void) { boolean_t isv6 = B_FALSE; dhcp_smach_t *dsmp, *dsmp_next; for (;;) { for (dsmp = next_smach(NULL, isv6); dsmp != NULL; dsmp = dsmp_next) { int status; dsmp_next = next_smach(dsmp, isv6); /* If we're already dropping or releasing, skip */ if (dsmp->dsm_droprelease) continue; dsmp->dsm_droprelease = B_TRUE; cancel_smach_timers(dsmp); /* * If the script is started by script_start, dhcp_drop * and dhcp_release should and will only be called * after the script exits. */ if (df_get_bool(dsmp->dsm_name, isv6, DF_RELEASE_ON_SIGTERM) || df_get_bool(dsmp->dsm_name, isv6, DF_VERIFIED_LEASE_ONLY)) { if (script_start(dsmp, isv6 ? EVENT_RELEASE6 : EVENT_RELEASE, dhcp_release, "DHCP agent is exiting", &status)) { continue; } if (status == 1) continue; } (void) script_start(dsmp, isv6 ? EVENT_DROP6 : EVENT_DROP, dhcp_drop, NULL, NULL); } if (isv6) break; isv6 = B_TRUE; } } /* * insert_lease(): Create a lease structure on a given state machine. The * lease holds a reference to the state machine. * * input: dhcp_smach_t *: state machine * output: dhcp_lease_t *: newly-created lease */ dhcp_lease_t * insert_lease(dhcp_smach_t *dsmp) { dhcp_lease_t *dlp; if ((dlp = calloc(1, sizeof (*dlp))) == NULL) return (NULL); dlp->dl_smach = dsmp; dlp->dl_hold_count = 1; init_timer(&dlp->dl_t1, 0); init_timer(&dlp->dl_t2, 0); insque(dlp, &dsmp->dsm_leases); dhcpmsg(MSG_DEBUG2, "insert_lease: new lease for %s", dsmp->dsm_name); return (dlp); } /* * hold_lease(): acquires a hold on a lease * * input: dhcp_lease_t *: the lease to acquire a hold on * output: void */ void hold_lease(dhcp_lease_t *dlp) { dlp->dl_hold_count++; dhcpmsg(MSG_DEBUG2, "hold_lease: hold count on lease for %s: %d", dlp->dl_smach->dsm_name, dlp->dl_hold_count); } /* * release_lease(): releases a hold previously acquired on a lease. * If the hold count reaches 0, the lease is freed. * * input: dhcp_lease_t *: the lease to release the hold on * output: void */ void release_lease(dhcp_lease_t *dlp) { if (dlp->dl_hold_count == 0) { dhcpmsg(MSG_CRIT, "release_lease: extraneous release"); return; } if (dlp->dl_hold_count == 1 && !dlp->dl_removed) { dhcpmsg(MSG_CRIT, "release_lease: missing removal"); return; } if (--dlp->dl_hold_count == 0) { dhcpmsg(MSG_DEBUG, "release_lease: freeing lease on state machine %s", dlp->dl_smach->dsm_name); free(dlp); } else { dhcpmsg(MSG_DEBUG2, "release_lease: hold count on lease for %s: %d", dlp->dl_smach->dsm_name, dlp->dl_hold_count); } } /* * remove_lease(): removes a given lease from the state machine and drops the * state machine's hold on the lease. * * input: dhcp_lease_t *: the lease to remove * output: void */ void remove_lease(dhcp_lease_t *dlp) { if (dlp->dl_removed) { dhcpmsg(MSG_CRIT, "remove_lease: extraneous removal"); } else { dhcp_lif_t *lif, *lifnext; uint_t nlifs; dhcpmsg(MSG_DEBUG, "remove_lease: removed lease from state machine %s", dlp->dl_smach->dsm_name); dlp->dl_removed = B_TRUE; remque(dlp); cancel_lease_timers(dlp); lif = dlp->dl_lifs; nlifs = dlp->dl_nlifs; for (; nlifs > 0; nlifs--, lif = lifnext) { lifnext = lif->lif_next; unplumb_lif(lif); } release_lease(dlp); } } /* * cancel_lease_timer(): cancels a lease-related timer * * input: dhcp_lease_t *: the lease to operate on * dhcp_timer_t *: the timer to cancel * output: void */ static void cancel_lease_timer(dhcp_lease_t *dlp, dhcp_timer_t *dt) { if (dt->dt_id == -1) return; if (cancel_timer(dt)) { release_lease(dlp); } else { dhcpmsg(MSG_WARNING, "cancel_lease_timer: cannot cancel timer"); } } /* * cancel_lease_timers(): cancels an lease's pending timers * * input: dhcp_lease_t *: the lease to operate on * output: void */ void cancel_lease_timers(dhcp_lease_t *dlp) { cancel_lease_timer(dlp, &dlp->dl_t1); cancel_lease_timer(dlp, &dlp->dl_t2); } /* * schedule_lease_timer(): schedules a lease-related timer * * input: dhcp_lease_t *: the lease 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_lease_timer(dhcp_lease_t *dlp, 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_lease(dlp); } if (schedule_timer(dt, expire, dlp)) { hold_lease(dlp); return (B_TRUE); } else { dhcpmsg(MSG_WARNING, "schedule_lease_timer: cannot schedule timer"); return (B_FALSE); } } /* * deprecate_leases(): remove all of the leases from a given state machine * * input: dhcp_smach_t *: the state machine * output: none */ void deprecate_leases(dhcp_smach_t *dsmp) { dhcp_lease_t *dlp; /* * note that due to infelicities in the routing code, any default * routes must be removed prior to canonizing or deprecating the LIF. */ remove_default_routes(dsmp); while ((dlp = dsmp->dsm_leases) != NULL) remove_lease(dlp); } /* * verify_smach(): if the state machine is in a bound state, then verify the * standing of the configured interfaces. Abandon those that * the user has modified. If we end up with no valid leases, * then just terminate the state machine. * * input: dhcp_smach_t *: the state machine * output: boolean_t: B_TRUE if the state machine is still valid. * note: assumes caller holds a state machine reference; as with most * callback functions. */ boolean_t verify_smach(dhcp_smach_t *dsmp) { dhcp_lease_t *dlp, *dlpn; if (dsmp->dsm_dflags & DHCP_IF_REMOVED) { release_smach(dsmp); return (B_FALSE); } if (!dsmp->dsm_isv6) { /* * If this is DHCPv4, then verify the main LIF. */ if (!verify_lif(dsmp->dsm_lif)) goto smach_terminate; } /* * If we're not in one of the bound states, then there are no LIFs to * verify here. */ if (dsmp->dsm_state != BOUND && dsmp->dsm_state != RENEWING && dsmp->dsm_state != REBINDING) { release_smach(dsmp); return (B_TRUE); } for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlpn) { dhcp_lif_t *lif, *lifnext; uint_t nlifs; dlpn = dlp->dl_next; lif = dlp->dl_lifs; nlifs = dlp->dl_nlifs; for (; nlifs > 0; lif = lifnext, nlifs--) { lifnext = lif->lif_next; if (!verify_lif(lif)) { /* * User has manipulated the interface. Even * if we plumbed it, we must now disown it. */ lif->lif_plumbed = B_FALSE; remove_lif(lif); } } if (dlp->dl_nlifs == 0) remove_lease(dlp); } /* * If there are leases left, then everything's ok. */ if (dsmp->dsm_leases != NULL) { release_smach(dsmp); return (B_TRUE); } smach_terminate: finished_smach(dsmp, DHCP_IPC_E_INVIF); release_smach(dsmp); return (B_FALSE); } /* * 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. * Copyright (c) 2016-2017, Chris Fraire . */ #ifndef STATES_H #define STATES_H #include #include #include #include #include "common.h" #include "ipc_action.h" #include "async.h" #include "packet.h" #include "util.h" /* * interfaces for state transition/action functions. these functions * can be found in suitably named .c files, such as inform.c, select.c, * renew.c, etc. */ #ifdef __cplusplus extern "C" { #endif /* * DHCP state machine representation: includes all of the information used for * a state machine instance. For IPv4, this represents a single logical * interface and (usually) a leased address. For IPv6, it represents a * DUID+IAID combination. Note that if multiple DUID+IAID instances are one * day allowed per interface, this will need to become a list. */ struct dhcp_smach_s { dhcp_smach_t *dsm_next; /* Note: must be first */ dhcp_smach_t *dsm_prev; /* * The name of the state machine. This is currently just a pointer to * the controlling LIF's name, but could be otherwise. */ const char *dsm_name; dhcp_lif_t *dsm_lif; /* Controlling LIF */ uint_t dsm_hold_count; /* reference count */ dhcp_lease_t *dsm_leases; /* List of leases */ uint_t dsm_lif_wait; /* LIFs waiting on DAD */ uint_t dsm_lif_down; /* LIFs failed */ /* * each state machine can have at most one pending asynchronous * action, which is represented in a `struct async_action'. * if that asynchronous action was a result of a user request, * then the `struct ipc_action' is used to hold information * about the user request. these structures are opaque to * users of the ifslist, and the functional interfaces * provided in async.[ch] and ipc_action.[ch] should be used * to maintain them. */ ipc_action_t dsm_ia; async_action_t dsm_async; uchar_t *dsm_cid; /* client id */ uchar_t dsm_cidlen; /* client id len */ /* * current state of the machine */ DHCPSTATE dsm_state; boolean_t dsm_droprelease; /* soon to call finished_smach */ uint16_t dsm_dflags; /* DHCP_IF_* (shared with IPC) */ uint16_t *dsm_prl; /* if non-NULL, param request list */ uint_t dsm_prllen; /* param request list len */ uint16_t *dsm_pil; /* if non-NULL, param ignore list */ uint_t dsm_pillen; /* param ignore list len */ uint_t dsm_nrouters; /* the number of default routers */ struct in_addr *dsm_routers; /* an array of default routers */ in6_addr_t dsm_server; /* our DHCP server */ uchar_t *dsm_serverid; /* server DUID for v6 */ uint_t dsm_serveridlen; /* DUID length */ /* * We retain the very first ack obtained on the state machine to * provide access to options which were originally assigned by * the server but may not have been included in subsequent * acks, as there are servers which do this and customers have * had unsatisfactory results when using our agent with them. * ipc_event() in agent.c provides a fallback to the original * ack when the current ack doesn't have the information * requested. * * Note that neither of these is actually a list of packets. There's * exactly one packet here, so use free_pkt_entry. */ PKT_LIST *dsm_ack; PKT_LIST *dsm_orig_ack; /* * other miscellaneous variables set or needed in the process * of acquiring a lease. */ int dsm_offer_wait; /* seconds between sending offers */ iu_timer_id_t dsm_offer_timer; /* timer associated with offer wait */ /* * time we sent the DISCOVER relative to dsm_neg_hrtime, so that the * REQUEST can have the same pkt->secs. */ uint16_t dsm_disc_secs; /* * this is a chain of packets which have been received on this * state machine over some interval of time. the packets may have * to meet some criteria in order to be put on this list. in * general, packets are put on this list through recv_pkt() */ PKT_LIST *dsm_recv_pkt_list; /* * these three fields are initially zero, and get incremented * as the ifslist goes from INIT -> BOUND. if and when the * ifslist moves to the RENEWING state, these fields are * reset, so they always either indicate the number of packets * sent, received, and declined while obtaining the current * lease (if BOUND), or the number of packets sent, received, * and declined while attempting to obtain a future lease * (if any other state). */ uint32_t dsm_sent; uint32_t dsm_received; uint32_t dsm_bad_offers; /* * dsm_send_pkt.pkt is dynamically allocated to be as big a * packet as we can send out on this state machine. the remainder * of this information is needed to make it easy to handle * retransmissions. note that other than dsm_bad_offers, all * of these fields are maintained internally in send_pkt(), * and consequently should never need to be modified by any * other functions. */ dhcp_pkt_t dsm_send_pkt; union { struct sockaddr_in v4; struct sockaddr_in6 v6; } dsm_send_dest; /* * For v4, dsm_send_tcenter is used to track the central timer value in * milliseconds (4000, 8000, 16000, 32000, 64000), and dsm_send_timeout * is that value plus the +/- 1000 millisecond fuzz. * * For v6, dsm_send_tcenter is the MRT (maximum retransmit timer) * value, and dsm_send_timeout must be set to the IRT (initial * retransmit timer) value by the sender. */ uint_t dsm_send_timeout; uint_t dsm_send_tcenter; stop_func_t *dsm_send_stop_func; uint32_t dsm_packet_sent; iu_timer_id_t dsm_retrans_timer; /* * The host name we've been asked to request is remembered * here between the DISCOVER and the REQUEST. (v4 only) */ char *dsm_reqhost; /* * The host name we've been asked by IPC message (e.g., * `ipadm -T dhcp -h ...') to request is remembered here until it is * reset by another external message. */ char *dsm_msg_reqhost; /* * The domain name returned for v4 DNSdmain is decoded here for use * (if configured and needed) to determine an FQDN. */ char *dsm_dhcp_domainname; /* * V4 and V6 use slightly different timers. For v4, we must count * seconds from the point where we first try to configure the * interface. For v6, only seconds while performing a transaction * matter. * * In v4, `dsm_neg_hrtime' represents the time since DHCP started * configuring the interface, and is used for computing the pkt->secs * field in v4. In v6, it represents the time since the current * transaction (if any) was started, and is used for the ELAPSED_TIME * option. * * `dsm_newstart_monosec' represents the time the ACKed REQUEST was * sent, which represents the start time of a new batch of leases. * When the lease time actually begins (and thus becomes current), * `dsm_curstart_monosec' is set to `dsm_newstart_monosec'. */ hrtime_t dsm_neg_hrtime; monosec_t dsm_newstart_monosec; monosec_t dsm_curstart_monosec; int dsm_script_fd; pid_t dsm_script_pid; pid_t dsm_script_helper_pid; const char *dsm_script_event; iu_event_id_t dsm_script_event_id; void *dsm_callback_arg; script_callback_t *dsm_script_callback; iu_timer_id_t dsm_start_timer; }; #define dsm_isv6 dsm_lif->lif_pif->pif_isv6 #define dsm_hwtype dsm_lif->lif_pif->pif_hwtype struct dhcp_lease_s { dhcp_lease_t *dl_next; /* Note: must be first */ dhcp_lease_t *dl_prev; dhcp_smach_t *dl_smach; /* back pointer to state machine */ dhcp_lif_t *dl_lifs; /* LIFs configured by this lease */ uint_t dl_nlifs; /* Number of configured LIFs */ uint_t dl_hold_count; /* reference counter */ boolean_t dl_removed; /* Set if removed from list */ boolean_t dl_stale; /* not updated by Renew/bind */ /* * the following fields are set when a lease is acquired, and * may be updated over the lifetime of the lease. they are * all reset by reset_smach(). */ dhcp_timer_t dl_t1; /* relative renewal start time, hbo */ dhcp_timer_t dl_t2; /* relative rebinding start time, hbo */ }; /* The IU event callback functions */ iu_eh_callback_t dhcp_acknak_global; iu_eh_callback_t dhcp_packet_lif; /* Common state-machine related routines throughout dhcpagent */ boolean_t dhcp_adopt(void); void dhcp_adopt_complete(dhcp_smach_t *); boolean_t dhcp_bound(dhcp_smach_t *, PKT_LIST *); void dhcp_bound_complete(dhcp_smach_t *); int dhcp_drop(dhcp_smach_t *, void *); void dhcp_deprecate(iu_tq_t *, void *); void dhcp_expire(iu_tq_t *, void *); boolean_t dhcp_extending(dhcp_smach_t *); void dhcp_inform(dhcp_smach_t *); void dhcp_init_reboot(dhcp_smach_t *); void dhcp_rebind(iu_tq_t *, void *); int dhcp_release(dhcp_smach_t *, void *); void dhcp_renew(iu_tq_t *, void *); void dhcp_requesting(iu_tq_t *, void *); void dhcp_restart(dhcp_smach_t *); void dhcp_selecting(dhcp_smach_t *); boolean_t set_start_timer(dhcp_smach_t *); void send_declines(dhcp_smach_t *); void send_v6_request(dhcp_smach_t *); boolean_t save_server_id(dhcp_smach_t *, PKT_LIST *); void server_unicast_option(dhcp_smach_t *, PKT_LIST *); /* State machine support functions in states.c */ dhcp_smach_t *insert_smach(dhcp_lif_t *, int *); void hold_smach(dhcp_smach_t *); void release_smach(dhcp_smach_t *); void remove_smach(dhcp_smach_t *); dhcp_smach_t *next_smach(dhcp_smach_t *, boolean_t); dhcp_smach_t *primary_smach(boolean_t); dhcp_smach_t *info_primary_smach(boolean_t); void make_primary(dhcp_smach_t *); dhcp_smach_t *lookup_smach(const char *, boolean_t); dhcp_smach_t *lookup_smach_by_uindex(uint16_t, dhcp_smach_t *, boolean_t); dhcp_smach_t *lookup_smach_by_xid(uint32_t, dhcp_smach_t *, boolean_t); dhcp_smach_t *lookup_smach_by_event(iu_event_id_t); void finished_smach(dhcp_smach_t *, int); boolean_t set_smach_state(dhcp_smach_t *, DHCPSTATE); int get_smach_cid(dhcp_smach_t *); boolean_t verify_smach(dhcp_smach_t *); uint_t smach_count(void); void reset_smach(dhcp_smach_t *); void refresh_smachs(iu_eh_t *, int, void *); void refresh_smach(dhcp_smach_t *); void nuke_smach_list(void); boolean_t schedule_smach_timer(dhcp_smach_t *, int, uint32_t, iu_tq_callback_t *); void cancel_offer_timer(dhcp_smach_t *); void cancel_smach_timers(dhcp_smach_t *); void discard_default_routes(dhcp_smach_t *); void remove_default_routes(dhcp_smach_t *); boolean_t is_bound_state(DHCPSTATE); /* Lease-related support functions in states.c */ dhcp_lease_t *insert_lease(dhcp_smach_t *); void hold_lease(dhcp_lease_t *); void release_lease(dhcp_lease_t *); void remove_lease(dhcp_lease_t *); void deprecate_leases(dhcp_smach_t *); void cancel_lease_timers(dhcp_lease_t *); boolean_t schedule_lease_timer(dhcp_lease_t *, dhcp_timer_t *, iu_tq_callback_t *); #ifdef __cplusplus } #endif #endif /* STATES_H */ /* * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016-2017, Chris Fraire . */ #include #include #include #include #include #include /* struct in_addr */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "states.h" #include "agent.h" #include "interface.h" #include "util.h" #include "packet.h" #include "defaults.h" /* * this file contains utility functions that have no real better home * of their own. they can largely be broken into six categories: * * o conversion functions -- functions to turn integers into strings, * or to convert between units of a similar measure. * * o time and timer functions -- functions to handle time measurement * and events. * * o ipc-related functions -- functions to simplify the generation of * ipc messages to the agent's clients. * * o signal-related functions -- functions to clean up the agent when * it receives a signal. * * o routing table manipulation functions * * o true miscellany -- anything else */ #define ETCNODENAME "/etc/nodename" static boolean_t is_fqdn(const char *); static boolean_t dhcp_assemble_fqdn(char *fqdnbuf, size_t buflen, dhcp_smach_t *dsmp); /* * pkt_type_to_string(): stringifies a packet type * * input: uchar_t: a DHCP packet type value, RFC 2131 or 3315 * boolean_t: B_TRUE if IPv6 * output: const char *: the stringified packet type */ const char * pkt_type_to_string(uchar_t type, boolean_t isv6) { /* * note: the ordering in these arrays allows direct indexing of the * table based on the RFC packet type value passed in. */ static const char *v4types[] = { "BOOTP", "DISCOVER", "OFFER", "REQUEST", "DECLINE", "ACK", "NAK", "RELEASE", "INFORM" }; static const char *v6types[] = { NULL, "SOLICIT", "ADVERTISE", "REQUEST", "CONFIRM", "RENEW", "REBIND", "REPLY", "RELEASE", "DECLINE", "RECONFIGURE", "INFORMATION-REQUEST", "RELAY-FORW", "RELAY-REPL" }; if (isv6) { if (type >= sizeof (v6types) / sizeof (*v6types) || v6types[type] == NULL) return (""); else return (v6types[type]); } else { if (type >= sizeof (v4types) / sizeof (*v4types) || v4types[type] == NULL) return (""); else return (v4types[type]); } } /* * monosec_to_string(): converts a monosec_t into a date string * * input: monosec_t: the monosec_t to convert * output: const char *: the corresponding date string */ const char * monosec_to_string(monosec_t monosec) { time_t time = monosec_to_time(monosec); char *time_string = ctime(&time); /* strip off the newline -- ugh, why, why, why.. */ time_string[strlen(time_string) - 1] = '\0'; return (time_string); } /* * monosec(): returns a monotonically increasing time in seconds that * is not affected by stime(2) or adjtime(2). * * input: void * output: monosec_t: the number of seconds since some time in the past */ monosec_t monosec(void) { return (gethrtime() / NANOSEC); } /* * monosec_to_time(): converts a monosec_t into real wall time * * input: monosec_t: the absolute monosec_t to convert * output: time_t: the absolute time that monosec_t represents in wall time */ time_t monosec_to_time(monosec_t abs_monosec) { return (abs_monosec - monosec()) + time(NULL); } /* * hrtime_to_monosec(): converts a hrtime_t to monosec_t * * input: hrtime_t: the time to convert * output: monosec_t: the time in monosec_t */ monosec_t hrtime_to_monosec(hrtime_t hrtime) { return (hrtime / NANOSEC); } /* * print_server_msg(): prints a message from a DHCP server * * input: dhcp_smach_t *: the state machine the message is associated with * const char *: the string to display * uint_t: length of string * output: void */ void print_server_msg(dhcp_smach_t *dsmp, const char *msg, uint_t msglen) { if (msglen > 0) { dhcpmsg(MSG_INFO, "%s: message from server: %.*s", dsmp->dsm_name, msglen, msg); } } /* * alrm_exit(): Signal handler for SIGARLM. terminates grandparent. * * input: int: signal the handler was called with. * * output: void */ static void alrm_exit(int sig) { int exitval; if (sig == SIGALRM && grandparent != 0) exitval = EXIT_SUCCESS; else exitval = EXIT_FAILURE; _exit(exitval); } /* * daemonize(): daemonizes the process * * input: void * output: int: 1 on success, 0 on failure */ int daemonize(void) { /* * We've found that adoption takes sufficiently long that * a dhcpinfo run after dhcpagent -a is started may occur * before the agent is ready to process the request. * The result is an error message and an unhappy user. * * The initial process now sleeps for DHCP_ADOPT_SLEEP, * unless interrupted by a SIGALRM, in which case it * exits immediately. This has the effect that the * grandparent doesn't exit until the dhcpagent is ready * to process requests. This defers the the balance of * the system start-up script processing until the * dhcpagent is ready to field requests. * * grandparent is only set for the adopt case; other * cases do not require the wait. */ if (grandparent != 0) (void) signal(SIGALRM, alrm_exit); switch (fork()) { case -1: return (0); case 0: if (grandparent != 0) (void) signal(SIGALRM, SIG_DFL); /* * setsid() makes us lose our controlling terminal, * and become both a session leader and a process * group leader. */ (void) setsid(); /* * under POSIX, a session leader can accidentally * (through open(2)) acquire a controlling terminal if * it does not have one. just to be safe, fork again * so we are not a session leader. */ switch (fork()) { case -1: return (0); case 0: (void) signal(SIGHUP, SIG_IGN); (void) chdir("/"); (void) umask(022); closefrom(0); break; default: _exit(EXIT_SUCCESS); } break; default: if (grandparent != 0) { (void) signal(SIGCHLD, SIG_IGN); /* * Note that we're not the agent here, so the DHCP * logging subsystem hasn't been configured yet. */ syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: " "waiting for adoption to complete."); if (sleep(DHCP_ADOPT_SLEEP) == 0) { syslog(LOG_WARNING | LOG_DAEMON, "dhcpagent: daemonize: timed out awaiting " "adoption."); } syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: " "wait finished"); } _exit(EXIT_SUCCESS); } return (1); } /* * update_default_route(): update the interface's default route * * input: int: the type of message; either RTM_ADD or RTM_DELETE * struct in_addr: the default gateway to use * const char *: the interface associated with the route * int: any additional flags (besides RTF_STATIC and RTF_GATEWAY) * output: boolean_t: B_TRUE on success, B_FALSE on failure */ static boolean_t update_default_route(uint32_t ifindex, int type, struct in_addr *gateway_nbo, int flags) { struct { struct rt_msghdr rm_mh; struct sockaddr_in rm_dst; struct sockaddr_in rm_gw; struct sockaddr_in rm_mask; struct sockaddr_dl rm_ifp; } rtmsg; (void) memset(&rtmsg, 0, sizeof (rtmsg)); rtmsg.rm_mh.rtm_version = RTM_VERSION; rtmsg.rm_mh.rtm_msglen = sizeof (rtmsg); rtmsg.rm_mh.rtm_type = type; rtmsg.rm_mh.rtm_pid = getpid(); rtmsg.rm_mh.rtm_flags = RTF_GATEWAY | RTF_STATIC | flags; rtmsg.rm_mh.rtm_addrs = RTA_GATEWAY | RTA_DST | RTA_NETMASK | RTA_IFP; rtmsg.rm_gw.sin_family = AF_INET; rtmsg.rm_gw.sin_addr = *gateway_nbo; rtmsg.rm_dst.sin_family = AF_INET; rtmsg.rm_dst.sin_addr.s_addr = htonl(INADDR_ANY); rtmsg.rm_mask.sin_family = AF_INET; rtmsg.rm_mask.sin_addr.s_addr = htonl(0); rtmsg.rm_ifp.sdl_family = AF_LINK; rtmsg.rm_ifp.sdl_index = ifindex; return (write(rtsock_fd, &rtmsg, sizeof (rtmsg)) == sizeof (rtmsg)); } /* * add_default_route(): add the default route to the given gateway * * input: const char *: the name of the interface associated with the route * struct in_addr: the default gateway to add * output: boolean_t: B_TRUE on success, B_FALSE otherwise */ boolean_t add_default_route(uint32_t ifindex, struct in_addr *gateway_nbo) { return (update_default_route(ifindex, RTM_ADD, gateway_nbo, RTF_UP)); } /* * del_default_route(): deletes the default route to the given gateway * * input: const char *: the name of the interface associated with the route * struct in_addr: if not INADDR_ANY, the default gateway to remove * output: boolean_t: B_TRUE on success, B_FALSE on failure */ boolean_t del_default_route(uint32_t ifindex, struct in_addr *gateway_nbo) { if (gateway_nbo->s_addr == htonl(INADDR_ANY)) /* no router */ return (B_TRUE); return (update_default_route(ifindex, RTM_DELETE, gateway_nbo, 0)); } /* * inactivity_shutdown(): shuts down agent if there are no state machines left * to manage * * input: iu_tq_t *: unused * void *: unused * output: void */ /* ARGSUSED */ void inactivity_shutdown(iu_tq_t *tqp, void *arg) { if (smach_count() > 0) /* shouldn't happen, but... */ return; dhcpmsg(MSG_VERBOSE, "inactivity_shutdown: timed out"); iu_stop_handling_events(eh, DHCP_REASON_INACTIVITY, NULL, NULL); } /* * graceful_shutdown(): shuts down the agent gracefully * * input: int: the signal that caused graceful_shutdown to be called * output: void */ void graceful_shutdown(int sig) { iu_stop_handling_events(eh, (sig == SIGTERM ? DHCP_REASON_TERMINATE : DHCP_REASON_SIGNAL), drain_script, NULL); } /* * bind_sock(): binds a socket to a given IP address and port number * * input: int: the socket to bind * in_port_t: the port number to bind to, host byte order * in_addr_t: the address to bind to, host byte order * output: boolean_t: B_TRUE on success, B_FALSE on failure */ boolean_t bind_sock(int fd, in_port_t port_hbo, in_addr_t addr_hbo) { struct sockaddr_in sin; int on = 1; (void) memset(&sin, 0, sizeof (struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = htons(port_hbo); sin.sin_addr.s_addr = htonl(addr_hbo); (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int)); return (bind(fd, (struct sockaddr *)&sin, sizeof (sin)) == 0); } /* * bind_sock_v6(): binds a socket to a given IP address and port number * * input: int: the socket to bind * in_port_t: the port number to bind to, host byte order * in6_addr_t: the address to bind to, network byte order * output: boolean_t: B_TRUE on success, B_FALSE on failure */ boolean_t bind_sock_v6(int fd, in_port_t port_hbo, const in6_addr_t *addr_nbo) { struct sockaddr_in6 sin6; int on = 1; (void) memset(&sin6, 0, sizeof (struct sockaddr_in6)); sin6.sin6_family = AF_INET6; sin6.sin6_port = htons(port_hbo); if (addr_nbo != NULL) { (void) memcpy(&sin6.sin6_addr, addr_nbo, sizeof (sin6.sin6_addr)); } (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int)); return (bind(fd, (struct sockaddr *)&sin6, sizeof (sin6)) == 0); } /* * iffile_to_hostname(): return the hostname contained on a line of the form * * [ ^I]*inet[ ^I]+hostname[\n]*\0 * * in the file located at the specified path * * input: const char *: the path of the file to look in for the hostname * output: const char *: the hostname at that path, or NULL on failure */ #define IFLINE_MAX 1024 /* maximum length of a hostname. line */ const char * iffile_to_hostname(const char *path) { FILE *fp; static char ifline[IFLINE_MAX]; fp = fopen(path, "r"); if (fp == NULL) return (NULL); /* * /etc/hostname. may contain multiple ifconfig commands, but each * such command is on a separate line (see the "while read ifcmds" code * in /etc/init.d/inetinit). Thus we will read the file a line at a * time, searching for a line of the form * * [ ^I]*inet[ ^I]+hostname[\n]*\0 * * extract the host name from it, and check it for validity. */ while (fgets(ifline, sizeof (ifline), fp) != NULL) { char *p; if ((p = strstr(ifline, "inet")) != NULL) { if ((p != ifline) && !isspace(p[-1])) { (void) fclose(fp); return (NULL); } p += 4; /* skip over "inet" and expect spaces or tabs */ if ((*p == '\n') || (*p == '\0')) { (void) fclose(fp); return (NULL); } if (isspace(*p)) { char *nlptr; /* no need to read more of the file */ (void) fclose(fp); while (isspace(*p)) p++; if ((nlptr = strrchr(p, '\n')) != NULL) *nlptr = '\0'; if (strlen(p) > MAXHOSTNAMELEN) { dhcpmsg(MSG_WARNING, "iffile_to_hostname:" " host name too long"); return (NULL); } if (ipadm_is_valid_hostname(p)) { return (p); } else { dhcpmsg(MSG_WARNING, "iffile_to_hostname:" " host name not valid"); return (NULL); } } else { (void) fclose(fp); return (NULL); } } } (void) fclose(fp); return (NULL); } /* * init_timer(): set up a DHCP timer * * input: dhcp_timer_t *: the timer to set up * output: void */ void init_timer(dhcp_timer_t *dt, lease_t startval) { dt->dt_id = -1; dt->dt_start = startval; } /* * cancel_timer(): cancel a DHCP timer * * input: dhcp_timer_t *: the timer to cancel * output: boolean_t: B_TRUE on success, B_FALSE otherwise */ boolean_t cancel_timer(dhcp_timer_t *dt) { if (dt->dt_id == -1) return (B_TRUE); if (iu_cancel_timer(tq, dt->dt_id, NULL) == 1) { dt->dt_id = -1; return (B_TRUE); } return (B_FALSE); } /* * schedule_timer(): schedule a DHCP timer. Note that it must not be already * running, and that we can't cancel here. If it were, and * we did, we'd leak a reference to the callback argument. * * input: dhcp_timer_t *: the timer to schedule * output: boolean_t: B_TRUE on success, B_FALSE otherwise */ boolean_t schedule_timer(dhcp_timer_t *dt, iu_tq_callback_t *cbfunc, void *arg) { if (dt->dt_id != -1) return (B_FALSE); dt->dt_id = iu_schedule_timer(tq, dt->dt_start, cbfunc, arg); return (dt->dt_id != -1); } /* * dhcpv6_status_code(): report on a DHCPv6 status code found in an option * buffer. * * input: const dhcpv6_option_t *: pointer to option * uint_t: option length * const char **: error string (nul-terminated) * const char **: message from server (unterminated) * uint_t *: length of server message * output: int: -1 on error, or >= 0 for a DHCPv6 status code */ int dhcpv6_status_code(const dhcpv6_option_t *d6o, uint_t olen, const char **estr, const char **msg, uint_t *msglenp) { uint16_t status; static const char *v6_status[] = { NULL, "Unknown reason", "Server has no addresses available", "Client record unavailable", "Prefix inappropriate for link", "Client must use multicast", "No prefix available" }; static char sbuf[32]; *estr = ""; *msg = ""; *msglenp = 0; if (d6o == NULL) return (0); olen -= sizeof (*d6o); if (olen < 2) { *estr = "garbled status code"; return (-1); } *msg = (const char *)(d6o + 1) + 2; *msglenp = olen - 2; (void) memcpy(&status, d6o + 1, sizeof (status)); status = ntohs(status); if (status > 0) { if (status > DHCPV6_STAT_NOPREFIX) { (void) snprintf(sbuf, sizeof (sbuf), "status %u", status); *estr = sbuf; } else { *estr = v6_status[status]; } } return (status); } void write_lease_to_hostconf(dhcp_smach_t *dsmp) { PKT_LIST *plp[2]; const char *hcfile; hcfile = ifname_to_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); plp[0] = dsmp->dsm_ack; plp[1] = dsmp->dsm_orig_ack; if (write_hostconf(dsmp->dsm_name, plp, 2, monosec_to_time(dsmp->dsm_curstart_monosec), dsmp->dsm_isv6) != -1) { dhcpmsg(MSG_DEBUG, "wrote lease to %s", hcfile); } else if (errno == EROFS) { dhcpmsg(MSG_DEBUG, "%s is on a read-only file " "system; not saving lease", hcfile); } else { dhcpmsg(MSG_ERR, "cannot write %s (reboot will " "not use cached configuration)", hcfile); } } /* * Try to get a string from the first line of a file, up to but not * including any space (0x20) or newline. * * input: const char *: file name; * char *: allocated buffer space; * size_t: space available in buf; * output: boolean_t: B_TRUE if a non-empty string was written to buf; * B_FALSE otherwise. */ static boolean_t dhcp_get_oneline(const char *filename, char *buf, size_t buflen) { char value[SYS_NMLN], *c; int fd, i; if ((fd = open(filename, O_RDONLY)) <= 0) { dhcpmsg(MSG_DEBUG, "dhcp_get_oneline: could not open %s", filename); *buf = '\0'; } else { if ((i = read(fd, value, SYS_NMLN - 1)) <= 0) { dhcpmsg(MSG_WARNING, "dhcp_get_oneline: no line in %s", filename); *buf = '\0'; } else { value[i] = '\0'; if ((c = strchr(value, '\n')) != NULL) *c = '\0'; if ((c = strchr(value, ' ')) != NULL) *c = '\0'; if (strlcpy(buf, value, buflen) >= buflen) { dhcpmsg(MSG_WARNING, "dhcp_get_oneline: too" " long value, %s", value); *buf = '\0'; } } (void) close(fd); } return (*buf != '\0'); } /* * Try to get the hostname from the /etc/nodename file. uname(2) cannot * be used, because that is initialized after DHCP has solicited, in order * to allow for the possibility that utsname.nodename can be set from * DHCP Hostname. Here, though, we want to send a value specified * advance of DHCP, so read /etc/nodename directly. * * input: char *: allocated buffer space; * size_t: space available in buf; * output: boolean_t: B_TRUE if a non-empty string was written to buf; * B_FALSE otherwise. */ static boolean_t dhcp_get_nodename(char *buf, size_t buflen) { return (dhcp_get_oneline(ETCNODENAME, buf, buflen)); } /* * dhcp_add_hostname_opt(): Set CD_HOSTNAME option if REQUEST_HOSTNAME is * affirmative and if 1) dsm_msg_reqhost is available; * or 2) hostname is read from an extant * /etc/hostname. file; or 3) interface is * primary and nodename(5) is defined. * * input: dhcp_pkt_t *: pointer to DHCP message being constructed; * dhcp_smach_t *: pointer to interface DHCP state machine; * output: B_TRUE if a client hostname was added; B_FALSE otherwise. */ boolean_t dhcp_add_hostname_opt(dhcp_pkt_t *dpkt, dhcp_smach_t *dsmp) { const char *reqhost; char nodename[MAXNAMELEN]; if (!df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, DF_REQUEST_HOSTNAME)) return (B_FALSE); dhcpmsg(MSG_DEBUG, "dhcp_add_hostname_opt: DF_REQUEST_HOSTNAME"); if (dsmp->dsm_msg_reqhost != NULL && ipadm_is_valid_hostname(dsmp->dsm_msg_reqhost)) { reqhost = dsmp->dsm_msg_reqhost; } else { char hostfile[PATH_MAX + 1]; (void) snprintf(hostfile, sizeof (hostfile), "/etc/hostname.%s", dsmp->dsm_name); reqhost = iffile_to_hostname(hostfile); } if (reqhost == NULL && (dsmp->dsm_dflags & DHCP_IF_PRIMARY) && dhcp_get_nodename(nodename, sizeof (nodename))) { reqhost = nodename; } if (reqhost != NULL) { free(dsmp->dsm_reqhost); if ((dsmp->dsm_reqhost = strdup(reqhost)) == NULL) dhcpmsg(MSG_WARNING, "dhcp_add_hostname_opt: cannot" " allocate memory for host name option"); } if (dsmp->dsm_reqhost != NULL) { dhcpmsg(MSG_DEBUG, "dhcp_add_hostname_opt: host %s for %s", dsmp->dsm_reqhost, dsmp->dsm_name); (void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost, strlen(dsmp->dsm_reqhost)); return (B_FALSE); } else { dhcpmsg(MSG_DEBUG, "dhcp_add_hostname_opt: no hostname for %s", dsmp->dsm_name); } return (B_TRUE); } /* * dhcp_add_fqdn_opt(): Set client FQDN option if dhcp_assemble_fqdn() * initializes an FQDN, or else do nothing. * * input: dhcp_pkt_t *: pointer to DHCP message being constructed; * dhcp_smach_t *: pointer to interface DHCP state machine; * output: B_TRUE if a client FQDN was added; B_FALSE otherwise. */ boolean_t dhcp_add_fqdn_opt(dhcp_pkt_t *dpkt, dhcp_smach_t *dsmp) { /* * RFC 4702 section 2: * * The format of the Client FQDN option is: * * Code Len Flags RCODE1 RCODE2 Domain Name * +------+------+------+------+------+------+-- * | 81 | n | | | | ... * +------+------+------+------+------+------+-- * * Code and Len are distinct, and the remainder is in a single buffer, * opt81, for Flags + (unused) RCODE1 and RCODE2 (all octets) and a * potentially maximum-length domain name. * * The format of the Flags field is: * * 0 1 2 3 4 5 6 7 * +-+-+-+-+-+-+-+-+ * | MBZ |N|E|O|S| * +-+-+-+-+-+-+-+-+ * * where MBZ is ignored and NEOS are: * * S = 1 to request that "the server SHOULD perform the A RR (FQDN-to- * address) DNS updates; * * O = 0, for a server-only response bit; * * E = 1 to indicate the domain name is in "canonical wire format, * without compression (i.e., ns_name_pton2) .... This encoding SHOULD * be used by clients ...."; * * N = 0 to request that "the server SHALL perform DNS updates [of the * PTR RR]." (1 would request SHALL NOT update). */ const uint8_t S_BIT_POS = 7; const uint8_t E_BIT_POS = 5; const uint8_t S_BIT = 1 << (7 - S_BIT_POS); const uint8_t E_BIT = 1 << (7 - E_BIT_POS); const size_t OPT_FQDN_METALEN = 3; char fqdnbuf[MAXNAMELEN]; uchar_t enc_fqdnbuf[MAXNAMELEN]; uint8_t fqdnopt[MAXNAMELEN + OPT_FQDN_METALEN]; uint_t fqdncode; size_t len, metalen; if (dsmp->dsm_isv6) return (B_FALSE); if (!dhcp_assemble_fqdn(fqdnbuf, sizeof (fqdnbuf), dsmp)) return (B_FALSE); /* encode the FQDN in canonical wire format */ if (ns_name_pton2(fqdnbuf, enc_fqdnbuf, sizeof (enc_fqdnbuf), &len) < 0) { dhcpmsg(MSG_WARNING, "dhcp_add_fqdn_opt: error encoding domain" " name %s", fqdnbuf); return (B_FALSE); } dhcpmsg(MSG_DEBUG, "dhcp_add_fqdn_opt: interface FQDN is %s" " for %s", fqdnbuf, dsmp->dsm_name); bzero(fqdnopt, sizeof (fqdnopt)); fqdncode = CD_CLIENTFQDN; metalen = OPT_FQDN_METALEN; *fqdnopt = S_BIT | E_BIT; (void) memcpy(fqdnopt + metalen, enc_fqdnbuf, len); (void) add_pkt_opt(dpkt, fqdncode, fqdnopt, metalen + len); return (B_TRUE); } /* * dhcp_adopt_domainname(): Set namebuf if either dsm_dhcp_domainname or * resolv's "default domain (deprecated)" is defined. * * input: char *: pointer to buffer to which domain name will be written; * size_t length of buffer; * dhcp_smach_t *: pointer to interface DHCP state machine; * output: B_TRUE if namebuf was set to a valid domain name; B_FALSE * otherwise. */ static boolean_t dhcp_adopt_domainname(char *namebuf, size_t buflen, dhcp_smach_t *dsmp) { const char *domainname; struct __res_state res_state; int lasterrno; domainname = dsmp->dsm_dhcp_domainname; if (ipadm_is_nil_hostname(domainname)) { /* * fall back to resolv's "default domain (deprecated)" */ bzero(&res_state, sizeof (struct __res_state)); if ((lasterrno = res_ninit(&res_state)) != 0) { dhcpmsg(MSG_WARNING, "dhcp_adopt_domainname: error %d" " initializing resolver", lasterrno); return (B_FALSE); } domainname = NULL; if (!ipadm_is_nil_hostname(res_state.defdname)) domainname = res_state.defdname; /* N.b. res_state.defdname survives the following call */ res_ndestroy(&res_state); } if (domainname == NULL) return (B_FALSE); if (strlcpy(namebuf, domainname, buflen) >= buflen) { dhcpmsg(MSG_WARNING, "dhcp_adopt_domainname: too long adopted domain" " name %s for %s", domainname, dsmp->dsm_name); return (B_FALSE); } return (B_TRUE); } /* * dhcp_pick_domainname(): Set namebuf if DNS_DOMAINNAME is defined in * /etc/default/dhcpagent or if dhcp_adopt_domainname() * succeeds. * * input: char *: pointer to buffer to which domain name will be written; * size_t length of buffer; * dhcp_smach_t *: pointer to interface DHCP state machine; * output: B_TRUE if namebuf was set to a valid domain name; B_FALSE * otherwise. */ static boolean_t dhcp_pick_domainname(char *namebuf, size_t buflen, dhcp_smach_t *dsmp) { const char *domainname; /* * Try to use a static DNS_DOMAINNAME if defined in * /etc/default/dhcpagent. */ domainname = df_get_string(dsmp->dsm_name, dsmp->dsm_isv6, DF_DNS_DOMAINNAME); if (!ipadm_is_nil_hostname(domainname)) { if (strlcpy(namebuf, domainname, buflen) >= buflen) { dhcpmsg(MSG_WARNING, "dhcp_pick_domainname: too long" " DNS_DOMAINNAME %s for %s", domainname, dsmp->dsm_name); return (B_FALSE); } return (B_TRUE); } else if (df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, DF_ADOPT_DOMAINNAME)) { return (dhcp_adopt_domainname(namebuf, buflen, dsmp)); } else { return (B_FALSE); } } /* * dhcp_assemble_fqdn(): Set fqdnbuf if REQUEST_FQDN is set and * either a host name was sent in the IPC message (e.g., * from ipadm(8) -h,--reqhost) or the interface is * primary and a nodename(5) is defined. If the host * name is not already fully qualified per is_fqdn(), * then dhcp_pick_domainname() is tried to select a * domain to be used to construct an FQDN. * * input: char *: pointer to buffer to which FQDN will be written; * size_t length of buffer; * dhcp_smach_t *: pointer to interface DHCP state machine; * output: B_TRUE if fqdnbuf was assigned a valid FQDN; B_FALSE otherwise. */ static boolean_t dhcp_assemble_fqdn(char *fqdnbuf, size_t buflen, dhcp_smach_t *dsmp) { char nodename[MAXNAMELEN], *reqhost; size_t pos, len; if (!df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, DF_REQUEST_FQDN)) return (B_FALSE); dhcpmsg(MSG_DEBUG, "dhcp_assemble_fqdn: DF_REQUEST_FQDN"); /* It's convenient to ensure fqdnbuf is always null-terminated */ bzero(fqdnbuf, buflen); reqhost = dsmp->dsm_msg_reqhost; if (ipadm_is_nil_hostname(reqhost) && (dsmp->dsm_dflags & DHCP_IF_PRIMARY) && dhcp_get_nodename(nodename, sizeof (nodename))) { reqhost = nodename; } if (ipadm_is_nil_hostname(reqhost)) { dhcpmsg(MSG_DEBUG, "dhcp_assemble_fqdn: no interface reqhost for %s", dsmp->dsm_name); return (B_FALSE); } if ((pos = strlcpy(fqdnbuf, reqhost, buflen)) >= buflen) { dhcpmsg(MSG_WARNING, "dhcp_assemble_fqdn: too long reqhost %s" " for %s", reqhost, dsmp->dsm_name); return (B_FALSE); } /* * If not yet FQDN, construct if possible */ if (!is_fqdn(reqhost)) { char domainname[MAXNAMELEN]; size_t needdots; if (!dhcp_pick_domainname(domainname, sizeof (domainname), dsmp)) { dhcpmsg(MSG_DEBUG, "dhcp_assemble_fqdn: no domain name for %s", dsmp->dsm_name); return (B_FALSE); } /* * Finish constructing FQDN. Account for space needed to hold a * separator '.' and a terminating '.'. */ len = strlen(domainname); needdots = 1 + (domainname[len - 1] != '.'); if (pos + len + needdots >= buflen) { dhcpmsg(MSG_WARNING, "dhcp_assemble_fqdn: too long" " FQDN %s.%s for %s", fqdnbuf, domainname, dsmp->dsm_name); return (B_FALSE); } /* add separator and then domain name */ fqdnbuf[pos++] = '.'; if (strlcpy(fqdnbuf + pos, domainname, buflen - pos) >= buflen - pos) { /* shouldn't get here as we checked above */ return (B_FALSE); } pos += len; /* ensure the final character is '.' */ if (needdots > 1) fqdnbuf[pos++] = '.'; /* following is already zeroed */ } if (!ipadm_is_valid_hostname(fqdnbuf)) { dhcpmsg(MSG_WARNING, "dhcp_assemble_fqdn: invalid FQDN %s" " for %s", fqdnbuf, dsmp->dsm_name); return (B_FALSE); } return (B_TRUE); } /* * is_fqdn() : Determine if the `hostname' can be considered as a Fully * Qualified Domain Name by being "rooted" (i.e., ending in '.') * or by containing at least three DNS labels (e.g., * srv.example.com). * * input: const char *: the hostname to inspect; * output: boolean_t: B_TRUE if `hostname' is not NULL satisfies the * criteria above; otherwise, B_FALSE; */ boolean_t is_fqdn(const char *hostname) { const char *c; size_t i; if (hostname == NULL) return (B_FALSE); i = strlen(hostname); if (i > 0 && hostname[i - 1] == '.') return (B_TRUE); c = hostname; i = 0; while ((c = strchr(c, '.')) != NULL) { ++i; ++c; } /* at least two separators is inferred to be fully-qualified */ return (i >= 2); } /* * terminate_at_space(): Reset the first space, 0x20, to 0x0 in the * specified string. * * input: char *: NULL or a null-terminated string; * output: void. */ static void terminate_at_space(char *value) { if (value != NULL) { char *sp; sp = strchr(value, ' '); if (sp != NULL) *sp = '\0'; } } /* * get_offered_domainname_v4(): decode a defined v4 DNSdmain value if it * exists to return a copy of the domain * name. * * input: dhcp_smach_t *: the state machine REQUESTs are being sent from; * PKT_LIST *: the best packet to be used to construct a REQUEST; * output: char *: NULL or a copy of the domain name ('\0' terminated); */ static char * get_offered_domainname_v4(PKT_LIST *offer) { char *domainname = NULL; DHCP_OPT *opt; if ((opt = offer->opts[CD_DNSDOMAIN]) != NULL) { uchar_t *valptr; dhcp_symbol_t *symp; valptr = (uchar_t *)opt + DHCP_OPT_META_LEN; symp = inittab_getbycode( ITAB_CAT_STANDARD, ITAB_CONS_INFO, opt->code); if (symp != NULL) { domainname = inittab_decode(symp, valptr, opt->len, B_TRUE); terminate_at_space(domainname); free(symp); } } return (domainname); } /* * save_domainname(): assign dsm_dhcp_domainname from * get_offered_domainname_v4 or leave the field NULL if no * option is present. * * input: dhcp_smach_t *: the state machine REQUESTs are being sent from; * PKT_LIST *: the best packet to be used to construct a REQUEST; * output: void */ void save_domainname(dhcp_smach_t *dsmp, PKT_LIST *offer) { char *domainname = NULL; free(dsmp->dsm_dhcp_domainname); dsmp->dsm_dhcp_domainname = NULL; if (!dsmp->dsm_isv6) { domainname = get_offered_domainname_v4(offer); } dsmp->dsm_dhcp_domainname = domainname; } /* * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016-2017, Chris Fraire . */ #ifndef UTIL_H #define UTIL_H #include #include #include #include #include #include #include "common.h" #include "packet.h" /* * general utility functions which have no better home. see util.c * for documentation on how to use the exported functions. */ #ifdef __cplusplus extern "C" { #endif struct dhcp_timer_s { iu_timer_id_t dt_id; lease_t dt_start; /* Initial timer value */ }; /* conversion functions */ const char *pkt_type_to_string(uchar_t, boolean_t); const char *monosec_to_string(monosec_t); time_t monosec_to_time(monosec_t); monosec_t hrtime_to_monosec(hrtime_t); /* shutdown handlers */ void graceful_shutdown(int); void inactivity_shutdown(iu_tq_t *, void *); /* timer functions */ void init_timer(dhcp_timer_t *, lease_t); boolean_t cancel_timer(dhcp_timer_t *); boolean_t schedule_timer(dhcp_timer_t *, iu_tq_callback_t *, void *); /* miscellaneous */ boolean_t add_default_route(uint32_t, struct in_addr *); boolean_t del_default_route(uint32_t, struct in_addr *); int daemonize(void); monosec_t monosec(void); void print_server_msg(dhcp_smach_t *, const char *, uint_t); boolean_t bind_sock(int, in_port_t, in_addr_t); boolean_t bind_sock_v6(int, in_port_t, const in6_addr_t *); const char *iffile_to_hostname(const char *); int dhcpv6_status_code(const dhcpv6_option_t *, uint_t, const char **, const char **, uint_t *); void write_lease_to_hostconf(dhcp_smach_t *); boolean_t dhcp_add_hostname_opt(dhcp_pkt_t *, dhcp_smach_t *); boolean_t dhcp_add_fqdn_opt(dhcp_pkt_t *, dhcp_smach_t *); void save_domainname(dhcp_smach_t *, PKT_LIST *); #ifdef __cplusplus } #endif #endif /* UTIL_H */