summaryrefslogtreecommitdiff
path: root/usr/src/cmd/cmd-inet/sbin/dhcpagent
diff options
context:
space:
mode:
authorstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
committerstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
commit7c478bd95313f5f23a4c958a745db2134aa03244 (patch)
treec871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/cmd/cmd-inet/sbin/dhcpagent
downloadillumos-gate-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz
OpenSolaris Launch
Diffstat (limited to 'usr/src/cmd/cmd-inet/sbin/dhcpagent')
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/Makefile89
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/README459
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/adopt.c185
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/agent.c845
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/agent.h124
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/arp_check.c235
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/arp_check.h54
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/async.c296
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/async.h67
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/bound.c567
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/class_id.c205
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/class_id.h48
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/defaults.c307
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/defaults.h70
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/dhcpagent.dfl101
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/dhcpagent.xcl55
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/dlpi_io.c629
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/dlpi_io.h80
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/dlprims.c208
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/dlprims.h53
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/inc.flg30
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/inform.c148
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/init_reboot.c158
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/interface.c1197
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/interface.h385
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/ipc_action.c187
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/ipc_action.h71
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/packet.c729
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/packet.h115
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/release.c160
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/renew.c367
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/request.c492
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/script_handler.c371
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/script_handler.h84
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/select.c226
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/states.h70
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/util.c757
-rw-r--r--usr/src/cmd/cmd-inet/sbin/dhcpagent/util.h84
38 files changed, 10308 insertions, 0 deletions
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/Makefile b/usr/src/cmd/cmd-inet/sbin/dhcpagent/Makefile
new file mode 100644
index 0000000000..0403bd19a1
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/Makefile
@@ -0,0 +1,89 @@
+#
+# 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.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+PROG = dhcpagent
+ROOTFS_PROG = $(PROG)
+LOCOBJS = adopt.o agent.o arp_check.o async.o bound.o class_id.o defaults.o \
+ dlpi_io.o dlprims.o inform.o init_reboot.o \
+ interface.o ipc_action.o packet.o release.o renew.o request.o \
+ script_handler.o select.o util.o
+COMDIR = $(SRC)/common/net/dhcp
+COMOBJS = ipv4_sum.o udp_sum.o
+INETDIR = $(SRC)/cmd/cmd-inet/common
+
+include ../../../Makefile.cmd
+
+DFLTD = $(ROOTETC)/default
+ETCDFLTPROG = $(PROG:%=$(DFLTD)/%)
+$(ETCDFLTPROG) := FILEMODE = 0444
+$(ETCDFLTPROG) := OWNER = root
+$(ETCDFLTPROG) := GROUP = sys
+
+OBJS = $(COMOBJS) $(LOCOBJS)
+SRCS = $(COMOBJS:%.o=$(COMDIR)/%.c) $(LOCOBJS:%.o=%.c)
+
+POFILES = $(LOCOBJS:%.o=%.po)
+XGETFLAGS += -a -x dhcpagent.xcl
+
+#
+# to compile a debug version, do a `make COPTFLAG="-g -XO0"'
+#
+
+CPPFLAGS += -I$(COMDIR) -I$(INETDIR)
+LDLIBS += -lsocket -lnvpair -lnsl -ldhcpagent -ldhcputil -linetutil -ldevinfo
+
+.KEEP_STATE:
+
+all: $(ROOTFS_PROG) $(PROG).dfl
+
+install: all $(ROOTSBINPROG) $(ETCDFLTPROG)
+
+$(PROG): $(OBJS)
+ $(LINK.c) -o $@ $(OBJS) $(LDLIBS)
+ $(POST_PROCESS)
+
+%.o: $(COMDIR)/%.c
+ $(COMPILE.c) $(OUTPUT_OPTION) $<
+ $(POST_PROCESS_O)
+
+%.o: $(INETDIR)/%.c
+ $(COMPILE.c) $(OUTPUT_OPTION) $<
+ $(POST_PROCESS_O)
+
+$(DFLTD)/%: %.dfl
+ $(INS.rename)
+
+$(POFILE): $(POFILES)
+ $(RM) $@; $(CAT) $(POFILES) > $@; $(RM) $(POFILES)
+
+clean:
+ $(RM) $(OBJS)
+
+lint: lint_SRCS
+
+include ../../../Makefile.targ
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/README b/usr/src/cmd/cmd-inet/sbin/dhcpagent/README
new file mode 100644
index 0000000000..b331b57470
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/README
@@ -0,0 +1,459 @@
+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.
+
+Architectural Overview for the DHCP agent
+Peter Memishian
+ident "%Z%%M% %I% %E% SMI"
+
+INTRODUCTION
+============
+
+The Solaris DHCP agent (dhcpagent) is an RFC2131-compliant DHCP client
+implementation. 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, 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, especially when threads cannot
+be used to attack the problem (since the filesystems containing the
+thread libraries may not be available when the agent starts).
+Instead, the problem must be solved using an event-driven model, which
+while tried-and-true, 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 20 source files, most with a
+companion header file. While the largest source file is around 700
+lines, most are much shorter. The source files can largely be broken
+up into three groups:
+
+ * Source files, which along with their companion header files,
+ define an abstract "object" that is used by other parts of
+ the system. Examples include "timer_queue.c", which along
+ with "timer_queue.h" provide a Timer Queue 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 which 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 "dlpi_io.c", which provides a useful interface
+ to DLPI-related i/o 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.
+
+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 DHCP-related state (such as when it acquired
+a lease). This state is stored in a a structure called an `ifslist',
+which is a poor name (since it suggests implementation artifacts but
+not purpose) but has historical precedent. Another way to think about
+an `ifslist' is that it provides all of the context necessary to
+perform DHCP on a given interface: the state the interface is in, the
+last packet DHCP packet received on that interface, and so forth. As
+one can imagine, the `ifslist' structure is quite complicated and rules
+governing accessing its fields are equally convoluted -- see the
+comments in interface.h for more information.
+
+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 registering a
+callback with 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 interface this packet is for
+(it must get back its context). This could be handled through an
+ad-hoc mapping of file descriptors to interfaces, 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, this context
+pointer is always the `ifslist', but for reasons of decoupling and
+generality, the timer queue and event handler objects allow a generic
+(void *) context argument.
+
+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, ifslists 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 interface), eventually checks to see if an acceptable OFFER
+has come in, and so forth. To some degree, the notion of the current
+state of an interface (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 interface, an IPC event comes
+in requesting that the interface be RELEASED, the agent knows to send
+back an error since the interface 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 interface can have at most one.
+The `async_action' structure is embedded in the `ifslist' structure,
+which is fine since there can be at most one pending transaction per
+interface. 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 RELEASE operation is synchronous
+(not asynchronous) since after the RELEASE is sent no reply is
+expected from the DHCP server. Also, note that there can be
+synchronous operations intermixed with asynchronous operations
+although it's not recommended.
+
+When the agent realizes it must perform an asynchronous transaction,
+it first calls async_pending() to see if there is already one pending;
+if so, 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, async_start() is called to begin
+one.
+
+When the transaction is complete, async_finish() must be called to
+complete the asynchronous action on that interface. If the
+transaction is unable to complete within a certain amount of time
+(more on this later), async_timeout() is invoked which attempts to
+cancel the asynchronous action with async_cancel(). If the event is
+not cancellable it is left pending, although this means that no future
+asynchronous actions can be performed on the interface until the
+transaction voluntarily calls async_finish(). While this may seem
+suboptimal, cancellation here is quite analogous to thread
+cancellation, which is generally considered a difficult problem.
+
+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. 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. Only
+IPC-generated asynchronous transactions have a valid `ipc_action'
+object. Note that since there can be at most one asynchronous action
+per interface, there can also be at most one `ipc_action' per
+interface (this means it can also conveniently be embedded inside the
+`ifslist' structure).
+
+One of the main purposes of the `ipc_action' object is to timeout user
+events. This is not the same as timing out the transaction; for
+instance, 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. However,
+even after the command times out for the user, the asynchronous
+transaction continues until async_timeout() occurs.
+
+It is worth understanding these timeouts since the relationship is
+subtle but powerful. The `async_action' timer specifies how long the
+agent will try to perform the transaction; the `ipc_action' timer
+specifies how long the user is willing to wait for the action to
+complete. If when the `async_action' timer fires and async_timeout()
+is called, there is no associated `ipc_action' (either because the
+transaction was not initiated by a user or because the user already
+timed out), then async_cancel() proceeds as described previously. If,
+on the other hand, the user is still waiting for the transaction to
+complete, then async_timeout() is rescheduled and the transaction is
+left pending. While this behavior might seem odd, it adheres to the
+principles of least surprise: when a user is willing to wait for a
+transaction to complete, the agent should try for as long as they're
+willing to wait. On the other hand, if the agent were to take that
+stance with its internal transactions, it would block out
+user-requested operations if the internal transaction never completed
+(perhaps because the server never sent an ACK in response to our lease
+extension REQUEST).
+
+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 `ifslist', 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() can be used to transmit the
+packet to a given IP address.
+
+The send_pkt() function is actually quite complicated; for one, it
+must internally use either DLPI or sockets depending on the state of
+the interface; for two, it 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 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 init_reboot.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; for instance, after sending a REQUEST, either an ACK or a NAK is
+acceptable. Also, before calling recv_pkt(), the caller must know
+that there is data to be read from the socket (this can be
+accomplished by using the event handler), otherwise recv_pkt() will
+block, which is clearly not acceptable.
+
+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(1M)). 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_ifslist().
+
+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 8, 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 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 physical interfaces, any provided default routes
+are also configured. Since logical interfaces cannot be stored in the
+kernel routing 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 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 and RELEASE. 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.
+
+The 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.
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/adopt.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/adopt.c
new file mode 100644
index 0000000000..c8e8fd3b30
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/adopt.c
@@ -0,0 +1,185 @@
+/*
+ * 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.
+ *
+ * ADOPTING state of the client state machine.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/sockio.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/systeminfo.h>
+#include <netinet/inetutil.h>
+#include <netinet/dhcp.h>
+#include <dhcpmsg.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 *);
+
+/*
+ * dhcp_adopt(): adopts the interface managed by the kernel for diskless boot
+ *
+ * input: void
+ * output: int: nonzero on success, zero on failure
+ */
+
+int
+dhcp_adopt(void)
+{
+ int retval;
+ dhcp_kcache_t *kcache = NULL;
+ size_t kcache_size;
+ PKT_LIST *plp = NULL;
+ struct ifslist *ifsp;
+
+ 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 = calloc(1, sizeof (PKT_LIST));
+ if (plp == NULL)
+ goto failure;
+
+ plp->len = strlen(kcache->dk_ack) / 2;
+ plp->pkt = malloc(plp->len);
+ if (plp->pkt == 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.
+ */
+
+ ifsp = insert_ifs(kcache->dk_if_name, B_TRUE, &retval);
+ if (ifsp == NULL)
+ goto failure;
+
+ ifsp->if_state = ADOPTING;
+ ifsp->if_dflags |= DHCP_IF_PRIMARY;
+
+ /*
+ * move to BOUND and use the information in our ACK packet
+ */
+
+ if (dhcp_bound(ifsp, plp) == 0) {
+ dhcpmsg(MSG_CRIT, "dhcp_adopt: cannot use cached packet");
+ goto failure;
+ }
+
+ if (async_start(ifsp, DHCP_EXTEND, B_FALSE) == 0) {
+ dhcpmsg(MSG_CRIT, "dhcp_adopt: async_start failed");
+ goto failure;
+ }
+
+ if (dhcp_extending(ifsp) == 0) {
+ dhcpmsg(MSG_CRIT, "dhcp_adopt: cannot send renew request");
+ goto failure;
+ }
+
+ free(kcache);
+ return (1);
+
+failure:
+ free(kcache);
+ if (plp != NULL)
+ free(plp->pkt);
+ free(plp);
+ return (0);
+}
+
+/*
+ * 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);
+}
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/agent.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/agent.c
new file mode 100644
index 0000000000..ee7a987003
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/agent.c
@@ -0,0 +1,845 @@
+/*
+ * 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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+#include <locale.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <dhcp_hostconf.h>
+#include <dhcp_symbol.h>
+#include <dhcpagent_ipc.h>
+#include <dhcpmsg.h>
+#include <netinet/dhcp.h>
+
+#include "async.h"
+#include "agent.h"
+#include "script_handler.h"
+#include "util.h"
+#include "class_id.h"
+#include "states.h"
+#include "packet.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;
+
+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;
+
+/*
+ * 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 interface 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 },
+ /* 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, 0, 1, 1, 1 },
+ /* INIT_REBOOT */ { 1, 0, 1, 0, 1, 1, 0, 0 },
+ /* ADOPTING */ { 1, 0, 1, 0, 0, 1, 0, 0 },
+ /* INFORM_SENT */ { 1, 0, 1, 0, 0, 1, 1, 0 }
+};
+
+int
+main(int argc, char **argv)
+{
+ boolean_t is_daemon = B_TRUE;
+ boolean_t is_verbose = B_FALSE;
+ int ipc_fd;
+ int c;
+ struct rlimit rl;
+
+ /*
+ * -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);
+ }
+
+ 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_ifslist, 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");
+
+ /*
+ * 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);
+ }
+
+ /*
+ * if the -a (adopt) option was specified, try to adopt the
+ * kernel-managed interface before we start. Our grandparent
+ * will be waiting for us to finish this, so signal him when
+ * we're done.
+ */
+
+ if (do_adopt) {
+ int result;
+
+ result = dhcp_adopt();
+
+ if (grandparent != (pid_t)0) {
+ dhcpmsg(MSG_DEBUG, "adoption complete, signalling "
+ "parent (%i) to exit.", grandparent);
+ (void) kill(grandparent, SIGALRM);
+ }
+
+ if (result == 0)
+ return (EXIT_FAILURE);
+ }
+
+ /*
+ * 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;
+ if (do_adopt == B_FALSE) /* see 4291141 */
+ nuke_ifslist(B_TRUE);
+ }
+ 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: unused
+ * 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)
+{
+ dhcp_ipc_request_t *request;
+ struct ifslist *ifsp, *primary_ifsp;
+ int error, is_priv = (int)arg;
+ PKT_LIST *plp[2];
+ dhcp_ipc_type_t cmd;
+
+ (void) iu_unregister_event(eh, id, NULL);
+
+ if (dhcp_ipc_recv_request(fd, &request, DHCP_IPC_REQUEST_WAIT) != 0) {
+ dhcpmsg(MSG_ERROR, "ipc_event: dhcp_ipc_recv_request failed");
+ (void) dhcp_ipc_close(fd);
+ return;
+ }
+
+ cmd = DHCP_IPC_CMD(request->message_type);
+ if (cmd >= DHCP_NIPC) {
+ send_error_reply(request, DHCP_IPC_E_CMD_UNKNOWN, &fd);
+ return;
+ }
+
+ /* return EPERM for any of the privileged actions */
+
+ if (!is_priv) {
+ switch (cmd) {
+
+ case DHCP_STATUS:
+ case DHCP_PING:
+ case DHCP_GET_TAG:
+ break;
+
+ default:
+ dhcpmsg(MSG_WARNING, "ipc_event: privileged ipc "
+ "command (%i) attempted on %s", cmd,
+ request->ifname);
+
+ send_error_reply(request, DHCP_IPC_E_PERM, &fd);
+ return;
+ }
+ }
+
+ /*
+ * try to locate the ifs associated with this command. if the
+ * command is DHCP_START or DHCP_INFORM, then if there isn't
+ * an ifs already, make one (there may already be one from a
+ * previous failed attempt to START or INFORM). otherwise,
+ * verify the interface is still valid.
+ */
+
+ ifsp = lookup_ifs(request->ifname);
+
+ switch (cmd) {
+
+ case DHCP_START: /* FALLTHRU */
+ case DHCP_INFORM:
+ /*
+ * it's possible that the interface already exists, but
+ * has been abandoned. usually in those cases we should
+ * return DHCP_IPC_E_UNKIF, but that makes little sense
+ * in the case of "start" or "inform", so just ignore
+ * the abandoned interface and start over anew.
+ */
+
+ if (ifsp != NULL && verify_ifs(ifsp) == 0)
+ ifsp = NULL;
+
+ /*
+ * as part of initializing the ifs, insert_ifs()
+ * creates a DLPI stream at ifsp->if_dlpi_fd.
+ */
+
+ if (ifsp == NULL) {
+ ifsp = insert_ifs(request->ifname, B_FALSE, &error);
+ if (ifsp == NULL) {
+ send_error_reply(request, error, &fd);
+ return;
+ }
+ }
+ break;
+
+ default:
+ if (ifsp == NULL) {
+ if (request->ifname[0] == '\0')
+ error = DHCP_IPC_E_NOPRIMARY;
+ else
+ error = DHCP_IPC_E_UNKIF;
+
+ send_error_reply(request, error, &fd);
+ return;
+ }
+ break;
+ }
+
+ if (verify_ifs(ifsp) == 0) {
+ send_error_reply(request, DHCP_IPC_E_UNKIF, &fd);
+ return;
+ }
+
+ if (ifsp->if_dflags & DHCP_IF_BOOTP) {
+ switch (cmd) {
+
+ case DHCP_EXTEND:
+ case DHCP_RELEASE:
+ case DHCP_INFORM:
+ send_error_reply(request, DHCP_IPC_E_BOOTP, &fd);
+ return;
+
+ default:
+ break;
+ }
+ }
+
+ /*
+ * verify that the interface 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 (!ipc_cmd_allowed[ifsp->if_state][cmd]) {
+ send_error_reply(request, DHCP_IPC_E_OUTSTATE, &fd);
+ return;
+ }
+
+ if ((request->message_type & DHCP_PRIMARY) && is_priv) {
+ if ((primary_ifsp = lookup_ifs("")) != NULL)
+ primary_ifsp->if_dflags &= ~DHCP_IF_PRIMARY;
+ ifsp->if_dflags |= DHCP_IF_PRIMARY;
+ }
+
+ /*
+ * current design dictates that there can be only one
+ * outstanding transaction per interface -- this simplifies
+ * the code considerably and also fits well with RFC2131.
+ * it is worth classifying the different DHCP commands into
+ * synchronous (those which we will handle now and be done
+ * with) and asynchronous (those which require transactions
+ * and will be completed at an indeterminate time in the
+ * future):
+ *
+ * DROP: removes the agent's management of an interface.
+ * asynchronous as the script program may be invoked.
+ *
+ * PING: checks to see if the agent controls an interface.
+ * synchronous, since no packets need to be sent
+ * to the DHCP server.
+ *
+ * STATUS: returns information about the an interface.
+ * synchronous, since no packets need to be sent
+ * to the DHCP server.
+ *
+ * RELEASE: releases the agent's management of an interface
+ * and brings the interface 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 an interface. asynchronous since
+ * the agent needs to wait for OFFERs, ACKs, etc.
+ *
+ * INFORM: obtains configuration parameters for an 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
+ * interface. the current asynchronous command is managed by
+ * the async_* api: async_start(), async_finish(),
+ * async_timeout(), async_cancel(), and async_pending().
+ * async_start() starts management of a new asynchronous
+ * command on an interface, which should only be done after
+ * async_pending() is called to check that there are no
+ * pending asynchronous commands on that interface. when the
+ * command is completed, async_finish() should be called. all
+ * asynchronous commands have an associated timer, which calls
+ * async_timeout() when it times out. if async_timeout()
+ * decides that the asynchronous command should be cancelled
+ * (see below), it calls async_cancel() to attempt
+ * cancellation.
+ *
+ * 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 interface):
+ *
+ * o when an asynchronous command is requested, then
+ * async_pending() is called to see if there is already
+ * an asynchronous event. if so, the command does not
+ * proceed, and if there is an associated ipc_action,
+ * the user command is sent DHCP_IPC_E_PEND.
+ *
+ * o otherwise, the 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 interface 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.
+ *
+ * o if async_timeout() fires before command completion,
+ * then if the command was internal to the agent, it
+ * is cancelled. otherwise, if it was a user command,
+ * then if the user is still waiting for the command
+ * to complete, the command continues and async_timeout()
+ * is rescheduled.
+ */
+
+ switch (cmd) {
+
+ case DHCP_DROP: /* FALLTHRU */
+ case DHCP_RELEASE: /* FALLTHRU */
+ case DHCP_EXTEND: /* FALLTHRU */
+ case DHCP_INFORM: /* FALLTHRU */
+ case DHCP_START:
+ /*
+ * if shutdown request has been received, send back an error.
+ */
+ if (shutdown_started) {
+ send_error_reply(request, DHCP_IPC_E_OUTSTATE, &fd);
+ return;
+ }
+
+ if (async_pending(ifsp)) {
+ send_error_reply(request, DHCP_IPC_E_PEND, &fd);
+ return;
+ }
+
+ if (ipc_action_start(ifsp, request, fd) == 0) {
+ dhcpmsg(MSG_WARNING, "ipc_event: ipc_action_start "
+ "failed for %s", ifsp->if_name);
+ send_error_reply(request, DHCP_IPC_E_MEMORY, &fd);
+ return;
+ }
+
+ if (async_start(ifsp, cmd, B_TRUE) == 0) {
+ ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY);
+ return;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ switch (cmd) {
+
+ case DHCP_DROP:
+ (void) script_start(ifsp, EVENT_DROP, dhcp_drop, NULL, NULL);
+ return;
+
+ case DHCP_EXTEND:
+ (void) dhcp_extending(ifsp);
+ break;
+
+ case DHCP_GET_TAG: {
+ dhcp_optnum_t optnum;
+ DHCP_OPT *opt = NULL;
+ boolean_t did_alloc = B_FALSE;
+ PKT_LIST *ack = ifsp->if_ack;
+
+ /*
+ * verify the request makes sense.
+ */
+
+ if (request->data_type != DHCP_TYPE_OPTNUM ||
+ request->data_length != sizeof (dhcp_optnum_t)) {
+ send_error_reply(request, DHCP_IPC_E_PROTO, &fd);
+ return;
+ }
+
+ (void) memcpy(&optnum, request->buffer, sizeof (dhcp_optnum_t));
+load_option:
+ switch (optnum.category) {
+
+ case DSYM_SITE: /* FALLTHRU */
+ case DSYM_STANDARD:
+ if (optnum.code <= DHCP_LAST_OPT)
+ opt = ack->opts[optnum.code];
+ break;
+
+ case DSYM_VENDOR:
+ /*
+ * 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 (optnum.code + optnum.size > sizeof (PKT))
+ break;
+
+ /* + 2 to account for option code and length byte */
+ opt = malloc(optnum.size + 2);
+ if (opt == NULL) {
+ send_error_reply(request, DHCP_IPC_E_MEMORY,
+ &fd);
+ return;
+ }
+
+ did_alloc = B_TRUE;
+ opt->len = optnum.size;
+ opt->code = optnum.code;
+ (void) memcpy(&opt->value, (caddr_t)ack->pkt +
+ opt->code, opt->len);
+
+ break;
+
+ default:
+ send_error_reply(request, DHCP_IPC_E_PROTO, &fd);
+ return;
+ }
+
+ /*
+ * return the option payload, if there was one. the "+ 2"
+ * accounts for the option code number and length byte.
+ */
+
+ if (opt != NULL) {
+ send_data_reply(request, &fd, 0, DHCP_TYPE_OPTION, opt,
+ opt->len + 2);
+
+ if (did_alloc)
+ free(opt);
+ return;
+ } else if (ack != ifsp->if_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 = ifsp->if_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(request, &fd);
+ return;
+ }
+
+ case DHCP_INFORM:
+ dhcp_inform(ifsp);
+ /* next destination: dhcp_acknak() */
+ return;
+
+ case DHCP_PING:
+ if (ifsp->if_dflags & DHCP_IF_FAILED)
+ send_error_reply(request, DHCP_IPC_E_FAILEDIF, &fd);
+ else
+ send_ok_reply(request, &fd);
+ return;
+
+ case DHCP_RELEASE:
+ (void) script_start(ifsp, EVENT_RELEASE, dhcp_release,
+ "Finished with lease.", NULL);
+ return;
+
+ case DHCP_START:
+ assert(ifsp->if_state == INIT);
+ (void) canonize_ifs(ifsp);
+
+ /*
+ * 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(ifsp->if_name, plp, 2);
+ if (error != -1) {
+ ifsp->if_orig_ack = ifsp->if_ack = plp[0];
+ if (error > 1) {
+ /*
+ * Return indicated we had more than one packet
+ * second one is the original ack. Older
+ * versions of the agent wrote only one ack
+ * to the file, we now keep both the first
+ * ack as well as the last one.
+ */
+ ifsp->if_orig_ack = plp[1];
+ }
+ dhcp_init_reboot(ifsp);
+ /* next destination: dhcp_acknak() */
+ return;
+ }
+
+ /*
+ * if not debugging, wait for a few seconds before
+ * going into SELECTING.
+ */
+
+ if (debug_level == 0) {
+ if (iu_schedule_timer_ms(tq,
+ lrand48() % DHCP_SELECT_WAIT, dhcp_start, ifsp)
+ != -1) {
+ hold_ifs(ifsp);
+ /* next destination: dhcp_start() */
+ return;
+ }
+ }
+
+ dhcp_selecting(ifsp);
+ /* next destination: dhcp_requesting() */
+ return;
+
+ case DHCP_STATUS: {
+ dhcp_status_t status;
+
+ status.if_began = monosec_to_time(ifsp->if_curstart_monosec);
+
+ if (ifsp->if_lease == DHCP_PERM) {
+ status.if_t1 = DHCP_PERM;
+ status.if_t2 = DHCP_PERM;
+ status.if_lease = DHCP_PERM;
+ } else {
+ status.if_t1 = status.if_began + ifsp->if_t1;
+ status.if_t2 = status.if_began + ifsp->if_t2;
+ status.if_lease = status.if_began + ifsp->if_lease;
+ }
+
+ status.version = DHCP_STATUS_VER;
+ status.if_state = ifsp->if_state;
+ status.if_dflags = ifsp->if_dflags;
+ status.if_sent = ifsp->if_sent;
+ status.if_recv = ifsp->if_received;
+ status.if_bad_offers = ifsp->if_bad_offers;
+
+ (void) strlcpy(status.if_name, ifsp->if_name, IFNAMSIZ);
+
+ send_data_reply(request, &fd, 0, DHCP_TYPE_STATUS, &status,
+ sizeof (dhcp_status_t));
+ return;
+ }
+
+ default:
+ return;
+ }
+}
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/agent.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/agent.h
new file mode 100644
index 0000000000..1e068b1186
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/agent.h
@@ -0,0 +1,124 @@
+/*
+ * 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.
+ */
+
+#ifndef AGENT_H
+#define AGENT_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <libinetutil.h>
+
+/*
+ * 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 interfaces to manage for DHCP_INACTIVITY_WAIT
+ * seconds. `grandparent' is the pid of the original process when in
+ * adopt mode.
+ */
+
+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;
+
+boolean_t drain_script(iu_eh_t *, void *);
+
+/*
+ * 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*24*2) /* two days */
+
+/* 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 */
+
+/*
+ * reasons for why iu_handle_events() returned
+ */
+enum { DHCP_REASON_INACTIVITY, DHCP_REASON_SIGNAL, DHCP_REASON_TERMINATE };
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AGENT_H */
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/arp_check.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/arp_check.c
new file mode 100644
index 0000000000..f4925468d8
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/arp_check.c
@@ -0,0 +1,235 @@
+/*
+ * 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.
+ */
+
+#pragma ident "%W% %E% SMI"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <poll.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <net/if_arp.h>
+#include <sys/dlpi.h>
+#include <stddef.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/pfmod.h>
+#include <dhcpmsg.h>
+#include <stddef.h>
+
+#include "defaults.h"
+#include "util.h"
+#include "interface.h"
+#include "dlpi_io.h"
+#include "arp_check.h"
+
+/*
+ * the struct arp_info is used by arp_reply_filter() to build a filter
+ * that only receives replies from the ARPed IP address.
+ */
+
+struct arp_info {
+
+ uchar_t send_addr_offset; /* from start of ARP frame */
+ in_addr_t send_addr; /* arped IP address */
+};
+
+/*
+ * arp_reply_filter(): builds a filter that permits ARP replies to our request
+ *
+ * input: ushort_t *: a place to store the packet filter code
+ * void *: a struct arp_info containing the requested IP address
+ * output: ushort_t *: two bytes past the last byte of the filter
+ */
+
+static ushort_t *
+arp_reply_filter(ushort_t *pfp, void *arg)
+{
+ struct arp_info *ai = (struct arp_info *)arg;
+
+ *pfp++ = ENF_PUSHWORD + (offsetof(struct arphdr, ar_op) / 2);
+ *pfp++ = ENF_PUSHLIT | ENF_EQ;
+ *pfp++ = htons(ARPOP_REPLY);
+
+ /*
+ * make sure this ARP reply is from the target IP address,
+ * which will be the "sender" IP address in the reply (even in
+ * the case of proxy ARP). the position of sender IP address
+ * depends on the link layer; so we can be link-layer
+ * independent, these values are calculated in arp_check().
+ *
+ * the byteorder issues here are *really* subtle. suppose
+ * that the network address is 0x11223344 (as stored in the
+ * packet read off the wire) by an intel machine. then notice
+ * that since the packet filter operates 16 bits at a time
+ * that the high-order word will load as 0x2211 and the
+ * low-order word will load as 0x4433. so send_addr has the
+ * register value 0x44332211 on intel since that will store to
+ * the network address 0x11223344 in memory. thus, to compare
+ * the low-order word, we must first ntohl() send_addr, which
+ * changes its register-value to 0x11223344, and then mask
+ * off the high-order bits, getting 0x3344, and then convert
+ * that to network order, getting 0x4433, which is what we
+ * want. the same logic applies to the high-order word. you
+ * are not expected to understand this.
+ */
+
+ *pfp++ = ENF_PUSHWORD + (ai->send_addr_offset / 2) + 1;
+ *pfp++ = ENF_PUSHLIT | ENF_EQ;
+ *pfp++ = htons(ntohl(ai->send_addr) & 0xffff);
+ *pfp++ = ENF_AND;
+
+ *pfp++ = ENF_PUSHWORD + (ai->send_addr_offset / 2);
+ *pfp++ = ENF_PUSHLIT | ENF_EQ;
+ *pfp++ = htons(ntohl(ai->send_addr) >> 16);
+ *pfp++ = ENF_AND;
+
+ return (pfp);
+}
+
+/*
+ * arp_check(): checks to see if a given IP address is already in use
+ *
+ * input: struct ifslist *: the interface to send the ARP request on
+ * in_addr_t: the IP address to send from, network order
+ * in_addr_t: the IP address to check on, network order
+ * uchar_t *: a scratch buffer that holds the hardware address
+ * of the machine that replied to our ARP request,
+ * if there was one.
+ * uint32_t: the length of the buffer
+ * uint32_t: how long to wait for an ARP reply, in milliseconds
+ * output: int: 1 if the IP address is in use, 0 if not in use.
+ */
+
+int
+arp_check(struct ifslist *ifsp, in_addr_t send_addr, in_addr_t target_addr,
+ uchar_t *target_hwaddr, uint32_t target_hwlen, uint32_t timeout_msec)
+{
+ uint32_t buf[DLPI_BUF_MAX / sizeof (uint32_t)];
+ dl_info_ack_t *dlia = (dl_info_ack_t *)buf;
+ int fd;
+ struct arphdr *arp_pkt = NULL;
+ uchar_t *arp_daddr = NULL;
+ caddr_t arp_payload;
+ uchar_t arp_dlen;
+ size_t offset;
+ struct pollfd pollfd;
+ int retval;
+ struct arp_info ai;
+ unsigned int arp_pkt_len;
+
+ fd = dlpi_open(ifsp->if_name, dlia, sizeof (buf), ETHERTYPE_ARP);
+ if (fd == -1)
+ goto failure;
+
+ /*
+ * the packet consists of an ARP header, two IP addresses
+ * and two hardware addresses (each ifsp->if_hwlen bytes long).
+ */
+
+ arp_pkt_len = sizeof (struct arphdr) + (sizeof (ipaddr_t) * 2) +
+ (ifsp->if_hwlen * 2);
+
+ arp_pkt = malloc(arp_pkt_len);
+ arp_daddr = build_broadcast_dest(dlia, &arp_dlen);
+ if (arp_pkt == NULL || arp_daddr == NULL)
+ goto failure;
+
+ (void) memset(arp_pkt, 0xff, arp_pkt_len);
+
+ arp_pkt->ar_hrd = htons(ifsp->if_hwtype);
+ arp_pkt->ar_pro = htons(ETHERTYPE_IP);
+ arp_pkt->ar_hln = ifsp->if_hwlen;
+ arp_pkt->ar_pln = sizeof (ipaddr_t);
+ arp_pkt->ar_op = htons(ARPOP_REQUEST);
+
+ arp_payload = (caddr_t)&arp_pkt[1];
+ (void) memcpy(arp_payload, ifsp->if_hwaddr, ifsp->if_hwlen);
+ offset = ifsp->if_hwlen;
+
+ /*
+ * while we're at the appropriate offset for sender IP address,
+ * store it for use by the packet filter.
+ */
+
+ ai.send_addr = target_addr;
+ ai.send_addr_offset = offset + sizeof (struct arphdr);
+
+ (void) memcpy(&arp_payload[offset], &send_addr, sizeof (ipaddr_t));
+ offset += ifsp->if_hwlen + sizeof (ipaddr_t);
+ (void) memcpy(&arp_payload[offset], &target_addr, sizeof (ipaddr_t));
+
+ /*
+ * install the packet filter, send our ARP request, and wait
+ * for a reply. waiting usually isn't a good idea since the
+ * design of the agent is nonblocking. however, we can
+ * tolerate short waits (< 5 seconds).
+ */
+
+ set_packet_filter(fd, arp_reply_filter, &ai, "ARP reply");
+
+ if (dlpi_send_link(fd, arp_pkt, arp_pkt_len, arp_daddr, arp_dlen) == -1)
+ goto failure;
+
+ pollfd.fd = fd;
+ pollfd.events = POLLIN;
+
+ retval = poll(&pollfd, 1, timeout_msec);
+ if (retval > 0 && target_hwaddr != NULL) {
+
+ /*
+ * try to grab the hardware address. if we fail, we'll
+ * just end up with some misleading diagnostics. the
+ * hardware address is at the start of the payload.
+ */
+
+ if (dlpi_recv_link(fd, arp_pkt, arp_pkt_len, DLPI_RECV_SHORT) ==
+ arp_pkt_len)
+ (void) memcpy(target_hwaddr, arp_payload, target_hwlen);
+ }
+
+ free(arp_daddr);
+ free(arp_pkt);
+ (void) close(fd);
+ return ((retval == 0) ? 0 : 1);
+
+failure:
+ free(arp_daddr);
+ free(arp_pkt);
+ (void) close(fd);
+
+ if (df_get_bool(ifsp->if_name, DF_IGNORE_FAILED_ARP)) {
+ dhcpmsg(MSG_WARNING, "arp_check: cannot send ARP request: "
+ "assuming address is available");
+ return (0);
+ }
+
+ dhcpmsg(MSG_WARNING, "arp_check: cannot send ARP request: "
+ "assuming address is unavailable");
+ return (1);
+}
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/arp_check.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/arp_check.h
new file mode 100644
index 0000000000..c3fff1ba0c
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/arp_check.h
@@ -0,0 +1,54 @@
+/*
+ * 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 ARP_CHECK_H
+#define ARP_CHECK_H
+
+#pragma ident "%W% %E% SMI"
+
+#include <sys/types.h>
+#include <netinet/in.h>
+
+#include "interface.h"
+
+/*
+ * arp_check.[ch] provide an interface for checking whether a given IP
+ * address is currently in use. see arp_check.c for documentation on
+ * how to use the exported function.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int arp_check(struct ifslist *, in_addr_t, in_addr_t, uchar_t *,
+ uint32_t, uint32_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ARP_CHECK_H */
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/async.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/async.c
new file mode 100644
index 0000000000..d7fb37970d
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/async.c
@@ -0,0 +1,296 @@
+/*
+ * 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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dhcpmsg.h>
+#include <libinetutil.h>
+
+#include "async.h"
+#include "util.h"
+#include "agent.h"
+#include "interface.h"
+#include "script_handler.h"
+
+static void async_timeout(iu_tq_t *, void *);
+
+/*
+ * async_pending(): checks to see if an async command is pending. if a stale
+ * async command is found, cancellation is attempted.
+ *
+ * input: struct ifslist *: the interface to check for an async command on
+ * output: boolean_t: B_TRUE if async command is pending, B_FALSE if not
+ */
+
+boolean_t
+async_pending(struct ifslist *ifsp)
+{
+ if (!(ifsp->if_dflags & DHCP_IF_BUSY))
+ return (B_FALSE);
+
+ /*
+ * if the command was not started by the user (i.e., was
+ * started internal to the agent), then it will timeout in
+ * async_timeout() -- don't shoot it here.
+ */
+
+ if (!ifsp->if_async.as_user)
+ return (B_TRUE);
+
+ if (ifsp->if_script_pid != -1)
+ return (B_TRUE);
+
+ /*
+ * user command -- see if they went away. if they went away,
+ * either a timeout was already sent to them or they
+ * control-c'd out.
+ */
+
+ if (ipc_action_pending(ifsp))
+ return (B_TRUE);
+
+ /*
+ * it appears they went away. try to cancel their pending
+ * command. if we can't cancel it, we leave their command
+ * pending and it's just gonna have to complete its business
+ * in any case, cancel the ipc_action timer, since we know
+ * they've gone away.
+ */
+
+ dhcpmsg(MSG_DEBUG, "async_pending: async command left, attempting "
+ "cancellation");
+
+ ipc_action_cancel_timer(ifsp);
+ return (async_cancel(ifsp) ? B_FALSE : B_TRUE);
+}
+
+/*
+ * async_start(): starts an asynchronous command on an interface
+ *
+ * input: struct ifslist *: the interface 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: int: 1 on success, 0 on failure
+ */
+
+int
+async_start(struct ifslist *ifsp, dhcp_ipc_type_t cmd, boolean_t user)
+{
+ iu_timer_id_t tid;
+
+ if (async_pending(ifsp))
+ return (0);
+
+ tid = iu_schedule_timer(tq, DHCP_ASYNC_WAIT, async_timeout, ifsp);
+ if (tid == -1)
+ return (0);
+
+ hold_ifs(ifsp);
+
+ ifsp->if_async.as_tid = tid;
+ ifsp->if_async.as_cmd = cmd;
+ ifsp->if_async.as_user = user;
+ ifsp->if_dflags |= DHCP_IF_BUSY;
+
+ return (1);
+}
+
+
+/*
+ * async_finish(): completes an asynchronous command
+ *
+ * input: struct ifslist *: the interface 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(struct ifslist *ifsp)
+{
+ /*
+ * 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 (ifsp->if_script_pid != -1)
+ script_stop(ifsp);
+
+ /*
+ * in case async_timeout() has already called async_cancel(),
+ * and to be idempotent, check the DHCP_IF_BUSY flag
+ */
+
+ if (!(ifsp->if_dflags & DHCP_IF_BUSY))
+ return;
+
+ if (ifsp->if_async.as_tid == -1) {
+ ifsp->if_dflags &= ~DHCP_IF_BUSY;
+ return;
+ }
+
+ if (iu_cancel_timer(tq, ifsp->if_async.as_tid, NULL) == 1) {
+ ifsp->if_dflags &= ~DHCP_IF_BUSY;
+ ifsp->if_async.as_tid = -1;
+ (void) release_ifs(ifsp);
+ return;
+ }
+
+ /*
+ * if we can't cancel this timer, we'll just leave the
+ * interface busy and when the timeout finally fires, we'll
+ * mark it free, which will just cause a minor nuisance.
+ */
+
+ dhcpmsg(MSG_WARNING, "async_finish: cannot cancel async timer");
+}
+
+/*
+ * async_cancel(): cancels a pending asynchronous command
+ *
+ * input: struct ifslist *: the interface with the pending async command
+ * output: int: 1 if cancellation was successful, 0 on failure
+ */
+
+int
+async_cancel(struct ifslist *ifsp)
+{
+ boolean_t do_reset = B_FALSE;
+
+ /*
+ * we decide how to cancel the command depending on our
+ * current state, since commands such as EXTEND may in fact
+ * cause us to enter back into SELECTING (if a NAK results
+ * from the EXTEND).
+ */
+
+ switch (ifsp->if_state) {
+
+ case BOUND:
+ case INFORMATION:
+ break;
+
+ case RENEWING: /* FALLTHRU */
+ case REBINDING: /* FALLTHRU */
+ case INFORM_SENT:
+
+ /*
+ * these states imply that we've sent a packet and we're
+ * awaiting an ACK or NAK. just cancel the wait.
+ */
+
+ if (unregister_acknak(ifsp) == 0)
+ return (0);
+
+ break;
+
+ case INIT: /* FALLTHRU */
+ case SELECTING: /* FALLTHRU */
+ case REQUESTING: /* FALLTHRU */
+ case INIT_REBOOT:
+
+ /*
+ * these states imply we're still trying to get a lease.
+ * just return to a clean slate (INIT) -- but not until
+ * after we've finished the asynchronous command!
+ */
+
+ do_reset = B_TRUE;
+ break;
+
+ default:
+ dhcpmsg(MSG_WARNING, "async_cancel: cancellation in unexpected "
+ "state %d", ifsp->if_state);
+ return (0);
+ }
+
+ async_finish(ifsp);
+ dhcpmsg(MSG_DEBUG, "async_cancel: asynchronous command (%d) aborted",
+ ifsp->if_async.as_cmd);
+ if (do_reset)
+ reset_ifs(ifsp);
+
+ return (1);
+}
+
+/*
+ * async_timeout(): expires stale asynchronous commands
+ *
+ * input: iu_tq_t *: the timer queue on which the timeout went off
+ * void *: the interface with the pending async command
+ * output: void
+ */
+
+static void
+async_timeout(iu_tq_t *tq, void *arg)
+{
+ struct ifslist *ifsp = (struct ifslist *)arg;
+
+ if (check_ifs(ifsp) == 0) {
+ (void) release_ifs(ifsp);
+ return;
+ }
+
+ /* we've expired now */
+ ifsp->if_async.as_tid = -1;
+
+ /*
+ * if the command was generated internally to the agent, try
+ * to cancel it immediately. otherwise, if the user has gone
+ * away, we cancel it in async_pending(). otherwise, we let
+ * it live.
+ */
+
+ if (!ifsp->if_async.as_user) {
+ (void) async_cancel(ifsp);
+ return;
+ }
+
+ if (async_pending(ifsp)) {
+
+ ifsp->if_async.as_tid = iu_schedule_timer(tq, DHCP_ASYNC_WAIT,
+ async_timeout, ifsp);
+
+ if (ifsp->if_async.as_tid != -1) {
+ hold_ifs(ifsp);
+ dhcpmsg(MSG_DEBUG, "async_timeout: asynchronous "
+ "command %d still pending", ifsp->if_async.as_cmd);
+ return;
+ }
+
+ /*
+ * what can we do but cancel it? we can't get called
+ * back again and otherwise we'll end up in the
+ * twilight zone with the interface permanently busy
+ */
+
+ ipc_action_finish(ifsp, DHCP_IPC_E_INT);
+ (void) async_cancel(ifsp);
+ }
+}
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/async.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/async.h
new file mode 100644
index 0000000000..05c3d7d21a
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/async.h
@@ -0,0 +1,67 @@
+/*
+ * 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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef ASYNC_H
+#define ASYNC_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <libinetutil.h>
+#include <dhcpagent_ipc.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
+
+struct ifslist; /* forward declaration */
+
+struct async_action {
+
+ dhcp_ipc_type_t as_cmd; /* command/action in progress */
+ iu_timer_id_t as_tid; /* async timer id */
+ boolean_t as_user; /* user-generated async cmd */
+};
+
+#define DHCP_ASYNC_WAIT 60 /* seconds */
+
+boolean_t async_pending(struct ifslist *);
+int async_start(struct ifslist *, dhcp_ipc_type_t, boolean_t);
+void async_finish(struct ifslist *);
+int async_cancel(struct ifslist *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ASYNC_H */
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/bound.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/bound.c
new file mode 100644
index 0000000000..883383bb4a
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/bound.c
@@ -0,0 +1,567 @@
+/*
+ * 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.
+ *
+ * BOUND state of the DHCP client state machine.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <sys/sockio.h>
+#include <unistd.h>
+#include <time.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <sys/sysmacros.h>
+#include <dhcp_hostconf.h>
+#include <dhcpmsg.h>
+#include <stdio.h> /* snprintf */
+
+#include "defaults.h"
+#include "arp_check.h"
+#include "states.h"
+#include "packet.h"
+#include "util.h"
+#include "agent.h"
+#include "interface.h"
+#include "script_handler.h"
+
+#define IS_DHCP(plp) ((plp)->opts[CD_DHCP_TYPE] != NULL)
+
+static int configure_if(struct ifslist *);
+static int configure_timers(struct ifslist *);
+
+/*
+ * bound_event_cb(): callback for script_start on the event EVENT_BOUND
+ *
+ * input: struct ifslist *: the interface configured
+ * const char *: unused
+ * output: int: always 1
+ */
+
+/* ARGSUSED */
+static int
+bound_event_cb(struct ifslist *ifsp, const char *msg)
+{
+ ipc_action_finish(ifsp, DHCP_IPC_SUCCESS);
+ async_finish(ifsp);
+ return (1);
+}
+
+/*
+ * dhcp_bound(): configures an interface and ifs using information contained
+ * in the ACK packet and sets up lease timers. before starting,
+ * the requested address is arped to make sure it's not in use.
+ *
+ * input: struct ifslist *: the interface to move to bound
+ * PKT_LIST *: the ACK packet, or NULL if it should use ifsp->if_ack
+ * output: int: 0 on failure, 1 on success
+ */
+
+int
+dhcp_bound(struct ifslist *ifsp, PKT_LIST *ack)
+{
+ lease_t cur_lease, new_lease;
+ int msg_level;
+
+ if (ack != NULL) {
+ /* If ack we're replacing is not the original, then free it */
+ if (ifsp->if_ack != ifsp->if_orig_ack)
+ free_pkt_list(&ifsp->if_ack);
+ ifsp->if_ack = ack;
+ /* Save the first ack as the original */
+ if (ifsp->if_orig_ack == NULL)
+ ifsp->if_orig_ack = ack;
+ }
+
+ switch (ifsp->if_state) {
+
+ case ADOPTING:
+
+ /*
+ * if we're adopting an interface, 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.
+ */
+
+ if (!IS_DHCP(ifsp->if_ack))
+ return (0);
+
+ (void) memcpy(&new_lease,
+ ifsp->if_ack->opts[CD_LEASE_TIME]->value, sizeof (lease_t));
+
+ new_lease = htonl(MIN(ntohl(new_lease), DHCP_ADOPT_LEASE_MAX));
+
+ (void) memcpy(ifsp->if_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.
+ */
+
+ ifsp->if_newstart_monosec = monosec();
+
+ /* FALLTHRU into REQUESTING/INIT_REBOOT */
+
+ case REQUESTING:
+ case INIT_REBOOT:
+
+ if (configure_if(ifsp) == 0)
+ return (0);
+
+ if (configure_timers(ifsp) == 0)
+ return (0);
+
+ /*
+ * if the state is ADOPTING, event loop has not been started
+ * at this time; so don''t run the script.
+ */
+
+ if (ifsp->if_state != ADOPTING) {
+ (void) script_start(ifsp, EVENT_BOUND, bound_event_cb,
+ NULL, NULL);
+ }
+
+ break;
+
+ case RENEWING:
+ case REBINDING:
+ case BOUND:
+
+ cur_lease = ifsp->if_lease;
+ if (configure_timers(ifsp) == 0)
+ return (0);
+
+ /*
+ * if the current lease is mysteriously close
+ * to the new lease, warn the user...
+ */
+
+ if (abs((ifsp->if_newstart_monosec + ifsp->if_lease) -
+ (ifsp->if_curstart_monosec + cur_lease)) < DHCP_LEASE_EPS) {
+
+ if (ifsp->if_lease < DHCP_LEASE_ERROR_THRESH)
+ msg_level = MSG_ERROR;
+ else
+ msg_level = MSG_VERBOSE;
+
+ dhcpmsg(msg_level, "lease renewed but lease time not "
+ "extended (expires in %d seconds)", ifsp->if_lease);
+ }
+
+
+ (void) script_start(ifsp, EVENT_EXTEND, bound_event_cb,
+ NULL, NULL);
+
+ break;
+
+ case INFORM_SENT:
+
+ (void) bound_event_cb(ifsp, NULL);
+ ifsp->if_state = INFORMATION;
+ break;
+
+ default:
+ /* something is really bizarre... */
+ dhcpmsg(MSG_DEBUG, "dhcp_bound: called in unexpected state");
+ return (0);
+ }
+
+ if (ifsp->if_state != INFORMATION) {
+ ifsp->if_state = BOUND;
+ ifsp->if_curstart_monosec = ifsp->if_newstart_monosec;
+ }
+
+ /*
+ * remove any stale hostconf file that might be lying around for
+ * this interface. (in general, it's harmless, since we'll write a
+ * fresh one when we exit anyway, but just to reduce confusion..)
+ */
+
+ (void) remove_hostconf(ifsp->if_name);
+ return (1);
+}
+
+/*
+ * configure_timers(): configures the lease timers on an interface
+ *
+ * input: struct ifslist *: the interface to configure (with a valid if_ack)
+ * output: int: 1 on success, 0 on failure
+ */
+
+int
+configure_timers(struct ifslist *ifsp)
+{
+ lease_t lease, t1, t2;
+
+ if (ifsp->if_ack->opts[CD_DHCP_TYPE] != NULL &&
+ (ifsp->if_ack->opts[CD_LEASE_TIME] == NULL ||
+ ifsp->if_ack->opts[CD_LEASE_TIME]->len != sizeof (lease_t))) {
+ send_decline(ifsp, "Missing or corrupted lease time",
+ &ifsp->if_ack->pkt->yiaddr);
+ dhcpmsg(MSG_WARNING, "configure_timers: missing or corrupted "
+ "lease time in ACK on %s", ifsp->if_name);
+ return (0);
+ }
+
+ cancel_ifs_timers(ifsp);
+
+ /*
+ * 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(ifsp->if_ack, &lease, &t1, &t2);
+
+ ifsp->if_t1 = t1;
+ ifsp->if_t2 = t2;
+ ifsp->if_lease = lease;
+
+ if (ifsp->if_lease == DHCP_PERM) {
+ dhcpmsg(MSG_INFO, "%s acquired permanent lease", ifsp->if_name);
+ return (1);
+ }
+
+ dhcpmsg(MSG_INFO, "%s acquired lease, expires %s", ifsp->if_name,
+ monosec_to_string(ifsp->if_newstart_monosec + ifsp->if_lease));
+
+ dhcpmsg(MSG_INFO, "%s begins renewal at %s", ifsp->if_name,
+ monosec_to_string(ifsp->if_newstart_monosec + ifsp->if_t1));
+
+ dhcpmsg(MSG_INFO, "%s begins rebinding at %s", ifsp->if_name,
+ monosec_to_string(ifsp->if_newstart_monosec + ifsp->if_t2));
+
+ /*
+ * 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_ifs_timer(ifsp, DHCP_LEASE_TIMER, lease, dhcp_expire) == 0)
+ goto failure;
+
+ if (lease < DHCP_REBIND_MIN) {
+ dhcpmsg(MSG_WARNING, "dhcp_bound: lease on %s is for "
+ "less than %d seconds!", ifsp->if_name, DHCP_REBIND_MIN);
+ return (1);
+ }
+
+ if (schedule_ifs_timer(ifsp, DHCP_T1_TIMER, t1, dhcp_renew) == 0)
+ goto failure;
+
+ if (schedule_ifs_timer(ifsp, DHCP_T2_TIMER, t2, dhcp_rebind) == 0)
+ goto failure;
+
+ return (1);
+
+failure:
+ cancel_ifs_timers(ifsp);
+ dhcpmsg(MSG_WARNING, "dhcp_bound: cannot schedule lease timers");
+ return (0);
+}
+
+/*
+ * configure_if(): configures an interface with DHCP parameters from an ACK
+ *
+ * input: struct ifslist *: the interface to configure (with a valid if_ack)
+ * output: int: 1 on success, 0 on failure
+ */
+
+static int
+configure_if(struct ifslist *ifsp)
+{
+ struct ifreq ifr;
+ struct sockaddr_in *sin;
+ PKT_LIST *ack = ifsp->if_ack;
+ DHCP_OPT *router_list;
+ uchar_t *target_hwaddr;
+ int i;
+ char in_use[256] = "IP address already in use by";
+
+ /*
+ * if we're using DHCP, then we'll have a valid CD_SERVER_ID
+ * (we checked in dhcp_acknak()); set it now so that
+ * ifsp->if_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 (ifsp->if_ack->opts[CD_DHCP_TYPE] != NULL)
+ (void) memcpy(&ifsp->if_server.s_addr,
+ ack->opts[CD_SERVER_ID]->value, sizeof (ipaddr_t));
+
+ /* no big deal if this fails; we'll just have less diagnostics */
+ target_hwaddr = malloc(ifsp->if_hwlen);
+
+ if (arp_check(ifsp, 0, ack->pkt->yiaddr.s_addr, target_hwaddr,
+ ifsp->if_hwlen, df_get_int(ifsp->if_name, DF_ARP_WAIT)) == 1) {
+
+ for (i = 0; i < ifsp->if_hwlen; i++)
+ (void) snprintf(in_use, sizeof (in_use), "%s %02x",
+ in_use, target_hwaddr[i]);
+
+ dhcpmsg(MSG_ERROR, in_use);
+
+ if (ifsp->if_ack->opts[CD_DHCP_TYPE] != NULL)
+ send_decline(ifsp, in_use, &ack->pkt->yiaddr);
+
+ ifsp->if_bad_offers++;
+ free(target_hwaddr);
+ return (0);
+ }
+ free(target_hwaddr);
+
+ ifsp->if_addr.s_addr = ack->pkt->yiaddr.s_addr;
+ if (ifsp->if_addr.s_addr == htonl(INADDR_ANY)) {
+ dhcpmsg(MSG_ERROR, "configure_if: got invalid IP address");
+ return (0);
+ }
+
+ (void) memset(&ifr, 0, sizeof (struct ifreq));
+ (void) strlcpy(ifr.ifr_name, ifsp->if_name, IFNAMSIZ);
+
+ /*
+ * 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.
+ */
+
+ /* LINTED [ifr_addr is a sockaddr which will be aligned] */
+ sin = (struct sockaddr_in *)&ifr.ifr_addr;
+ sin->sin_family = AF_INET;
+
+ if (ack->opts[CD_SUBNETMASK] != NULL &&
+ ack->opts[CD_SUBNETMASK]->len == sizeof (ipaddr_t)) {
+
+ (void) memcpy(&ifsp->if_netmask.s_addr,
+ ack->opts[CD_SUBNETMASK]->value, sizeof (ipaddr_t));
+
+ } else {
+
+ if (ack->opts[CD_SUBNETMASK] != NULL &&
+ ack->opts[CD_SUBNETMASK]->len != sizeof (ipaddr_t))
+ dhcpmsg(MSG_WARNING, "configure_if: specified subnet "
+ "mask length is %d instead of %d, ignoring",
+ ack->opts[CD_SUBNETMASK]->len, sizeof (ipaddr_t));
+
+ /*
+ * no legitimate IP subnet mask specified.. use best
+ * guess. recall that if_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 <netinet/in.h>.
+ */
+
+ if (IN_CLASSA(ntohl(ifsp->if_addr.s_addr)))
+ ifsp->if_netmask.s_addr = htonl(IN_CLASSA_NET);
+ else if (IN_CLASSB(ntohl(ifsp->if_addr.s_addr)))
+ ifsp->if_netmask.s_addr = htonl(IN_CLASSB_NET);
+ else if (IN_CLASSC(ntohl(ifsp->if_addr.s_addr)))
+ ifsp->if_netmask.s_addr = htonl(IN_CLASSC_NET);
+ else /* must be class d */
+ ifsp->if_netmask.s_addr = htonl(IN_CLASSD_NET);
+
+ dhcpmsg(MSG_WARNING, "configure_if: no IP netmask specified "
+ "for %s, making best guess", ifsp->if_name);
+ }
+
+ dhcpmsg(MSG_INFO, "setting IP netmask to %s on %s",
+ inet_ntoa(ifsp->if_netmask), ifsp->if_name);
+
+ sin->sin_addr = ifsp->if_netmask;
+ if (ioctl(ifsp->if_sock_fd, SIOCSIFNETMASK, &ifr) == -1) {
+ dhcpmsg(MSG_ERR, "cannot set IP netmask on %s", ifsp->if_name);
+ return (0);
+ }
+
+ dhcpmsg(MSG_INFO, "setting IP address to %s on %s",
+ inet_ntoa(ifsp->if_addr), ifsp->if_name);
+
+ sin->sin_addr = ifsp->if_addr;
+ if (ioctl(ifsp->if_sock_fd, SIOCSIFADDR, &ifr) == -1) {
+ dhcpmsg(MSG_ERR, "configure_if: cannot set IP address on %s",
+ ifsp->if_name);
+ return (0);
+ }
+
+ if (ack->opts[CD_BROADCASTADDR] != NULL &&
+ ack->opts[CD_BROADCASTADDR]->len == sizeof (ipaddr_t)) {
+
+ (void) memcpy(&ifsp->if_broadcast.s_addr,
+ ack->opts[CD_BROADCASTADDR]->value, sizeof (ipaddr_t));
+
+ } else {
+
+ if (ack->opts[CD_BROADCASTADDR] != NULL &&
+ ack->opts[CD_BROADCASTADDR]->len != sizeof (ipaddr_t))
+ dhcpmsg(MSG_WARNING, "configure_if: specified "
+ "broadcast address length is %d instead of %d, "
+ "ignoring", ack->opts[CD_BROADCASTADDR]->len,
+ sizeof (ipaddr_t));
+
+ /*
+ * no legitimate IP broadcast specified. compute it
+ * from the IP address and netmask.
+ */
+
+ ifsp->if_broadcast.s_addr = ifsp->if_addr.s_addr &
+ ifsp->if_netmask.s_addr | ~ifsp->if_netmask.s_addr;
+
+ dhcpmsg(MSG_WARNING, "configure_if: no IP broadcast specified "
+ "for %s, making best guess", ifsp->if_name);
+ }
+
+ if (ioctl(ifsp->if_sock_fd, SIOCGIFFLAGS, &ifr) == -1) {
+ dhcpmsg(MSG_ERR, "configure_if: cannot get interface flags for "
+ "%s", ifsp->if_name);
+ return (0);
+ }
+
+ ifr.ifr_flags |= IFF_UP;
+
+ if (ioctl(ifsp->if_sock_fd, SIOCSIFFLAGS, &ifr) == -1) {
+ dhcpmsg(MSG_ERR, "configure_if: cannot set interface flags for "
+ "%s", ifsp->if_name);
+ return (0);
+ }
+
+ /*
+ * 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(ifsp->if_sock_fd, SIOCGIFBRDADDR, &ifr) == -1) {
+ dhcpmsg(MSG_ERR, "configure_if: cannot get broadcast address "
+ "for %s", ifsp->if_name);
+ return (0);
+ }
+
+ if (ifsp->if_broadcast.s_addr != sin->sin_addr.s_addr) {
+ dhcpmsg(MSG_WARNING, "incorrect broadcast address %s specified "
+ "for %s; ignoring", inet_ntoa(ifsp->if_broadcast),
+ ifsp->if_name);
+ }
+
+ ifsp->if_broadcast = sin->sin_addr;
+ dhcpmsg(MSG_INFO, "using broadcast address %s on %s",
+ inet_ntoa(ifsp->if_broadcast), ifsp->if_name);
+
+ /*
+ * add each provided router; we'll clean them up when the
+ * interface goes away or when our lease expires.
+ */
+
+ router_list = ack->opts[CD_ROUTER];
+ if (router_list && (router_list->len % sizeof (ipaddr_t)) == 0) {
+
+ ifsp->if_nrouters = router_list->len / sizeof (ipaddr_t);
+ ifsp->if_routers = malloc(router_list->len);
+ if (ifsp->if_routers == NULL) {
+ dhcpmsg(MSG_ERR, "configure_if: cannot allocate "
+ "default router list, ignoring default routers");
+ ifsp->if_nrouters = 0;
+ }
+
+ for (i = 0; i < ifsp->if_nrouters; i++) {
+
+ (void) memcpy(&ifsp->if_routers[i].s_addr,
+ router_list->value + (i * sizeof (ipaddr_t)),
+ sizeof (ipaddr_t));
+
+ if (add_default_route(ifsp->if_name,
+ &ifsp->if_routers[i]) == 0) {
+ dhcpmsg(MSG_ERR, "configure_if: cannot add "
+ "default router %s on %s", inet_ntoa(
+ ifsp->if_routers[i]), ifsp->if_name);
+ ifsp->if_routers[i].s_addr = htonl(INADDR_ANY);
+ continue;
+ }
+
+ dhcpmsg(MSG_INFO, "added default router %s on %s",
+ inet_ntoa(ifsp->if_routers[i]), ifsp->if_name);
+ }
+ }
+
+ ifsp->if_sock_ip_fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (ifsp->if_sock_ip_fd == -1) {
+ dhcpmsg(MSG_ERR, "configure_if: cannot create socket on %s",
+ ifsp->if_name);
+ return (0);
+ }
+
+ if (bind_sock(ifsp->if_sock_ip_fd, IPPORT_BOOTPC,
+ ntohl(ifsp->if_addr.s_addr)) == 0) {
+ dhcpmsg(MSG_ERR, "configure_if: cannot bind socket on %s",
+ ifsp->if_name);
+ return (0);
+ }
+
+ /*
+ * we wait until here to bind if_sock_fd because it turns out
+ * the kernel has difficulties doing binds before interfaces
+ * are up (although it may work sometimes, it doesn't work all
+ * the time.) that's okay, because we don't use if_sock_fd
+ * for receiving data until we're BOUND anyway.
+ */
+
+ if (bind_sock(ifsp->if_sock_fd, IPPORT_BOOTPC, INADDR_BROADCAST) == 0) {
+ dhcpmsg(MSG_ERR, "configure_if: cannot bind broadcast socket "
+ "on %s", ifsp->if_name);
+ return (0);
+ }
+
+ /*
+ * we'll be using if_sock_fd for the remainder of the lease;
+ * blackhole if_dlpi_fd.
+ */
+
+ set_packet_filter(ifsp->if_dlpi_fd, blackhole_filter, 0, "blackhole");
+
+ if (ack->opts[CD_DHCP_TYPE] == NULL)
+ ifsp->if_dflags |= DHCP_IF_BOOTP;
+
+ dhcpmsg(MSG_DEBUG, "configure_if: bound ifsp->if_sock_ip_fd");
+ return (1);
+}
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/class_id.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/class_id.c
new file mode 100644
index 0000000000..3e5a533466
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/class_id.c
@@ -0,0 +1,205 @@
+/*
+ * 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.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/openpromio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h> /* sprintf() */
+#include <unistd.h>
+
+/*
+ * 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(4).
+ *
+ * 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);
+}
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/class_id.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/class_id.h
new file mode 100644
index 0000000000..d827fdc588
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/class_id.h
@@ -0,0 +1,48 @@
+/*
+ * 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
+
+#pragma ident "%W% %E% SMI"
+
+/*
+ * 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 */
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/defaults.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/defaults.c
new file mode 100644
index 0000000000..6edf5d6da5
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/defaults.c
@@ -0,0 +1,307 @@
+/*
+ * 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.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <netinet/inetutil.h>
+#include <netinet/dhcp.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <dhcpmsg.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <libnvpair.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, 0 },
+ { "OFFER_WAIT", "3", 1, 20 },
+ { "ARP_WAIT", "1000", 100, 4000 },
+ { "CLIENT_ID", NULL, 0, 0 },
+ { "PARAM_REQUEST_LIST", NULL, 0, 0 },
+ { "REQUEST_HOSTNAME", "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, *value, *end;
+ FILE *fp;
+ nvlist_t *nvlist;
+
+ 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)
+ param = entry;
+ else
+ param++;
+
+ 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
+ * unsigned int: 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, unsigned int p)
+{
+ char *value;
+ char param[256];
+ struct stat statbuf;
+ static struct stat df_statbuf;
+ static boolean_t df_unavail_msg = B_FALSE;
+ static nvlist_t *df_nvlist = NULL;
+
+ if (p >= (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[p].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;
+ if (df_nvlist != NULL)
+ nvlist_free(df_nvlist);
+ df_nvlist = df_build_cache();
+ }
+
+ (void) snprintf(param, sizeof (param), "%s.%s", if_name,
+ defaults[p].df_name);
+
+ /*
+ * first look for `if_name.param', then `param'. if neither
+ * has been set, use the built-in default.
+ */
+
+ if (nvlist_lookup_string(df_nvlist, param, &value) == 0 ||
+ nvlist_lookup_string(df_nvlist, defaults[p].df_name, &value) == 0)
+ return (value);
+
+ return (defaults[p].df_default);
+}
+
+/*
+ * df_get_octet(): gets the integer value of a given user-tunable parameter
+ *
+ * input: const char *: the interface the parameter applies to
+ * unsigned int: the parameter number to look up
+ * unsigned int *: the length of the returned value
+ * output: uchar_t *: a pointer to byte array (default value if not set)
+ * (must be copied by caller to be kept)
+ */
+
+uchar_t *
+df_get_octet(const char *if_name, unsigned int p, unsigned int *len)
+{
+ const char *value;
+ static uchar_t octet_value[256]; /* as big as defread() returns */
+
+ if (p >= (sizeof (defaults) / sizeof (*defaults)))
+ return (NULL);
+
+ value = df_get_string(if_name, p);
+ if (value == NULL)
+ goto do_default;
+
+ if (strncasecmp("0x", value, 2) != 0) {
+ *len = strlen(value); /* no NUL */
+ return ((uchar_t *)value);
+ }
+
+ /* skip past the 0x and convert the value to binary */
+ value += 2;
+ *len = sizeof (octet_value);
+ if (hexascii_to_octet(value, strlen(value), octet_value, len) != 0) {
+ dhcpmsg(MSG_WARNING, "df_get_octet: cannot convert value "
+ "for parameter `%s', using default", defaults[p].df_name);
+ goto do_default;
+ }
+ return (octet_value);
+
+do_default:
+ if (defaults[p].df_default == NULL) {
+ *len = 0;
+ return (NULL);
+ }
+
+ *len = strlen(defaults[p].df_default); /* no NUL */
+ return ((uchar_t *)defaults[p].df_default);
+}
+
+/*
+ * df_get_int(): gets the integer value of a given user-tunable parameter
+ *
+ * input: const char *: the interface the parameter applies to
+ * unsigned int: 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, unsigned int p)
+{
+ const char *value;
+ int value_int;
+
+ if (p >= (sizeof (defaults) / sizeof (*defaults)))
+ return (0);
+
+ value = df_get_string(if_name, p);
+ if (value == NULL || !isdigit(*value))
+ goto failure;
+
+ value_int = atoi(value);
+ if (value_int > defaults[p].df_max || value_int < defaults[p].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[p].df_name, defaults[p].df_min,
+ defaults[p].df_max, defaults[p].df_default);
+ return (atoi(defaults[p].df_default));
+}
+
+/*
+ * df_get_bool(): gets the boolean value of a given user-tunable parameter
+ *
+ * input: const char *: the interface the parameter applies to
+ * unsigned int: 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, unsigned int p)
+{
+ const char *value;
+
+ if (p >= (sizeof (defaults) / sizeof (*defaults)))
+ return (0);
+
+ value = df_get_string(if_name, p);
+ 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[p].df_name,
+ value ? value : "NULL", defaults[p].df_default);
+
+ return ((atoi(defaults[p].df_default) == 0) ? B_FALSE : B_TRUE);
+}
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/defaults.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/defaults.h
new file mode 100644
index 0000000000..4d58c2072d
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/defaults.h
@@ -0,0 +1,70 @@
+/*
+ * 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-2001 by Sun Microsystems, Inc.
+ * All rights reserved.
+ */
+
+#ifndef DEFAULTS_H
+#define DEFAULTS_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+
+/*
+ * 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 */
+ DF_IGNORE_FAILED_ARP, /* what to do if agent can't ARP */
+ DF_OFFER_WAIT, /* how long to wait to collect offers */
+ DF_ARP_WAIT, /* how long to wait for an ARP reply */
+ DF_CLIENT_ID, /* our client id */
+ DF_PARAM_REQUEST_LIST, /* our parameter request list */
+ DF_REQUEST_HOSTNAME /* request hostname associated with interface */
+};
+
+#define DHCP_AGENT_DEFAULTS "/etc/default/dhcpagent"
+
+boolean_t df_get_bool(const char *, unsigned int);
+int df_get_int(const char *, unsigned int);
+const char *df_get_string(const char *, unsigned int);
+uchar_t *df_get_octet(const char *, unsigned int, unsigned int *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DEFAULTS_H */
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/dhcpagent.dfl b/usr/src/cmd/cmd-inet/sbin/dhcpagent/dhcpagent.dfl
new file mode 100644
index 0000000000..4299f09136
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/dhcpagent.dfl
@@ -0,0 +1,101 @@
+#ident "%Z%%M% %I% %E% SMI"
+#
+# Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# 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
+#
+
+#
+# This file contains tunable parameters for dhcpagent(1M).
+#
+
+# All parameters can be tuned for a specific interface by prepending
+# the interface name to the parameter name. For example, to make
+# RELEASE_ON_SIGTERM happen on all interfaces except hme0, specify:
+#
+# hme0.RELEASE_ON_SIGTERM=no
+# RELEASE_ON_SIGTERM=yes
+
+# By default, when the DHCP agent is sent a SIGTERM, all managed
+# interfaces are dropped. By uncommenting the following
+# parameter-value pair, all managed interfaces are released instead.
+#
+# RELEASE_ON_SIGTERM=yes
+
+# When the DHCP agent gets an ACK from the server, it sends an ARP
+# request to verify that a given IP address is not already in use. If
+# an ARP reply is received, the DHCP agent declines the server's
+# offer. However, if the DHCP agent is unable to send the ARP request
+# packet for whatever reason, it assumes the address is available. To
+# be more cautious, uncomment the following parameter-value pair.
+#
+# IGNORE_FAILED_ARP=no
+
+# 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 waits 1000 milliseconds to collect ARP
+# replies to an ARP request when verifying that an IP address is not
+# in use. To change this behavior, set and uncomment the following
+# parameter-value pair. This parameter is specified in milliseconds.
+#
+# ARP_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, 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 find a host name in /etc/hostname.<if>,
+# which must contain a line of the form
+#
+# inet name
+#
+# where "name" is a single RFC 1101-compliant token. If found, 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, a parameter request list requesting a subnet mask (1),
+# router (3), DNS server (6), hostname (12), DNS domain (15), 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 RFC 2132.
+#
+PARAM_REQUEST_LIST=1,3,6,12,15,28,43
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/dhcpagent.xcl b/usr/src/cmd/cmd-inet/sbin/dhcpagent/dhcpagent.xcl
new file mode 100644
index 0000000000..58f30f85aa
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/dhcpagent.xcl
@@ -0,0 +1,55 @@
+#
+# 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"
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/dlpi_io.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/dlpi_io.c
new file mode 100644
index 0000000000..2c74b6bc88
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/dlpi_io.c
@@ -0,0 +1,629 @@
+/*
+ * 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.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/pfmod.h>
+#include <sys/socket.h>
+#include <net/if.h> /* IFNAMSIZ */
+#include <netinet/in_systm.h> /* n_long (ip.h) */
+#include <netinet/in.h> /* in_addr (ip.h) */
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+#include <stropts.h>
+#include <string.h> /* strpbrk */
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/uio.h>
+#include <sys/dlpi.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <dhcpmsg.h>
+#include <libinetutil.h>
+
+#include "agent.h"
+#include "dlprims.h"
+#include "dlpi_io.h"
+#include "interface.h"
+#include "v4_sum_impl.h"
+
+/*
+ * dlpi_open(): opens a DLPI stream to the given interface and returns
+ * information purpose about that interface.
+ *
+ * input: const char *: the name of the interface to open
+ * dl_info_ack_t *: a place to store information about the interface
+ * size_t: the size of dl_info_ack_t
+ * t_uscalar_t: the sap to bind to on this interface
+ * output: int: the open file descriptor on success, -1 on failure
+ */
+
+int
+dlpi_open(const char *if_name, dl_info_ack_t *dlia, size_t dlia_size,
+ t_uscalar_t dl_sap)
+{
+ char device_name[sizeof ("/dev/") + IFNAMSIZ];
+ int fd;
+ ifspec_t ifsp;
+ int is_style2 = 0;
+
+ if (!ifparse_ifspec(if_name, &ifsp)) {
+ dhcpmsg(MSG_ERROR, "dlpi_open: invalid interface name");
+ return (-1);
+ }
+
+ if (ifsp.ifsp_modcnt != 0) {
+ dhcpmsg(MSG_ERROR, "dlpi_open: modules cannot be specified "
+ "with an interface name");
+ return (-1);
+ }
+
+ /* try dlpi style1 interface first; if it fails, try style 2 */
+ (void) snprintf(device_name, sizeof (device_name),
+ "/dev/%s%d", ifsp.ifsp_devnm, ifsp.ifsp_ppa);
+ if ((fd = open(device_name, O_RDWR)) == -1) {
+ dhcpmsg(MSG_DEBUG, "dlpi_open: open on `%s'", device_name);
+
+ /* try style 2 interface */
+ (void) snprintf(device_name, sizeof (device_name),
+ "/dev/%s", ifsp.ifsp_devnm);
+ fd = open(device_name, O_RDWR);
+
+ /*
+ * temporary hack: if the style-2 open of the /dev link fails,
+ * try the corresponding /devices/pseudo path. this allows a
+ * diskless boot to succeed without necessarily pre-creating the
+ * /dev links, by taking advantage of devfs's ability to create
+ * /devices nodes for h/w devices on demand. this is to avoid
+ * the need to fiddle with packaging scripts to boot off a new
+ * NIC device. when /dev links are created on demand, this
+ * work-around may be removed.
+ */
+
+ {
+ const char prefix[] = "/devices/pseudo/clone@0:";
+ char path[sizeof (prefix) + IFNAMSIZ];
+ if (fd == -1 && errno == ENOENT) {
+ (void) snprintf(path, sizeof (path), "%s%s",
+ prefix, ifsp.ifsp_devnm);
+ fd = open(path, O_RDWR);
+ }
+ }
+
+ if (fd == -1) {
+ dhcpmsg(MSG_ERR, "dlpi_open: open on `%s'",
+ device_name);
+ return (-1);
+ }
+ is_style2 = 1;
+ }
+
+ /*
+ * okay, so we've got an open DLPI stream now. make sure that
+ * it's DL_VERSION_2, DL_STYLE2, and that it's connectionless.
+ * from there, attach to the appropriate ppa, bind to dl_sap,
+ * and get ready to roll.
+ */
+
+ if (dlinforeq(fd, dlia, dlia_size) != 0) {
+ dhcpmsg(MSG_ERR, "dlpi_open: DL_INFO_REQ on %s", device_name);
+ (void) close(fd);
+ return (-1);
+ }
+
+ if (dlia->dl_version != DL_VERSION_2) {
+ dhcpmsg(MSG_ERROR, "dlpi_open: %s is DLPI version %d, not 2",
+ device_name, dlia->dl_version);
+ (void) close(fd);
+ return (-1);
+ }
+
+ if (is_style2 && dlia->dl_provider_style != DL_STYLE2) {
+ dhcpmsg(MSG_ERROR, "dlpi_open: %s is DL_STYLE%d, not DL_STYLE2",
+ device_name, dlia->dl_provider_style);
+ (void) close(fd);
+ return (-1);
+ }
+
+ if ((dlia->dl_service_mode & DL_CLDLS) == 0) {
+ dhcpmsg(MSG_ERROR, "dlpi_open: %s is %#x, not DL_CLDLS, which "
+ "is not supported", device_name, dlia->dl_service_mode);
+ (void) close(fd);
+ return (-1);
+ }
+
+ if (is_style2 && dlattachreq(fd, ifsp.ifsp_ppa) == -1) {
+ dhcpmsg(MSG_ERR, "dlpi_open: DL_ATTACH_REQ on %s", device_name);
+ (void) close(fd);
+ return (-1);
+ }
+
+ if (dlbindreq(fd, dl_sap, 0, DL_CLDLS, 0) == -1) {
+ dhcpmsg(MSG_ERR, "dlpi_open: DL_BIND_REQ on %s", device_name);
+ (void) close(fd);
+ return (-1);
+ }
+
+ /*
+ * we call this again since some of the information obtained
+ * previously was not valid since we had not yet attached (in
+ * particular, our MAC address) (but we needed to check the
+ * STYLE before we did the attach)
+ */
+
+ if (dlinforeq(fd, dlia, dlia_size) != 0) {
+ dhcpmsg(MSG_ERR, "dlpi_open: DL_INFO_REQ on %s", device_name);
+ (void) close(fd);
+ return (-1);
+ }
+
+ if (ioctl(fd, I_PUSH, "pfmod") == -1) {
+ dhcpmsg(MSG_ERR, "dlpi_open: cannot push pfmod on stream");
+ (void) close(fd);
+ return (-1);
+ }
+
+ (void) ioctl(fd, I_FLUSH, FLUSHR);
+ return (fd);
+}
+
+/*
+ * dlpi_close(): closes a previously opened DLPI stream
+ *
+ * input: int: the file descriptor of the DLPI stream
+ * output: int: 0 on success, -1 on failure
+ */
+
+int
+dlpi_close(int fd)
+{
+ /* don't bother dismantling. it will happen automatically */
+ return (close(fd));
+}
+
+/*
+ * dlpi_recvfrom(): receives data on a DLPI stream
+ *
+ * input: int: the socket to receive the data on
+ * void *: a buffer to store the data in
+ * size_t: the size of the buffer
+ * struct sockaddr_in *: if non-NULL, sender's IP address is filled in
+ * output: ssize_t: the number of bytes read on success, -1 on failure
+ */
+
+ssize_t
+dlpi_recvfrom(int fd, void *buffer, size_t buf_len, struct sockaddr_in *from)
+{
+ struct ip *ip;
+ struct udphdr *udphdr;
+ void *data_buffer;
+ ssize_t data_length;
+
+ data_length = buf_len + sizeof (struct ip) + sizeof (struct udphdr);
+ data_buffer = malloc(data_length);
+
+ if (data_buffer == NULL) {
+ dhcpmsg(MSG_ERR, "dlpi_recvfrom: cannot allocate packet");
+ return (-1);
+ }
+
+ data_length = dlpi_recv_link(fd, data_buffer, data_length, 0);
+ if (data_length == -1)
+ return (-1);
+
+ /*
+ * since we're just pulling data off the wire, what we have
+ * may look nothing like a DHCP packet. note that this
+ * shouldn't happen (pfmod should have tossed it already).
+ */
+
+ if (data_length < sizeof (struct ip) + sizeof (struct udphdr)) {
+ dhcpmsg(MSG_WARNING, "dlpi_recvfrom: dropped short packet");
+ free(data_buffer);
+ return (-1);
+ }
+
+ /*
+ * verify checksums
+ */
+
+ ip = (struct ip *)data_buffer;
+ if (ipv4cksum((uint16_t *)ip, ip->ip_hl << 2) != 0) {
+ dhcpmsg(MSG_WARNING, "dlpi_recvfrom: dropped packet with bad "
+ "ipv4 checksum");
+ free(data_buffer);
+ return (-1);
+ }
+
+ udphdr = (struct udphdr *)&ip[1];
+ if ((udphdr->uh_sum != 0) &&
+ (udp_chksum(udphdr, &ip->ip_src, &ip->ip_dst, ip->ip_p) != 0)) {
+ dhcpmsg(MSG_WARNING, "dlpi_recvfrom: dropped packet with bad "
+ "UDP checksum");
+ free(data_buffer);
+ return (-1);
+ }
+
+ data_length -= (sizeof (struct ip) + sizeof (struct udphdr));
+ (void) memcpy(buffer, &udphdr[1], data_length);
+
+ if (from != NULL) {
+ from->sin_addr = ip->ip_dst;
+ from->sin_port = udphdr->uh_sport;
+ }
+
+ free(data_buffer);
+ return (data_length);
+}
+
+/*
+ * dlpi_recv_link(): receives raw data on a DLPI stream
+ *
+ * input: int: the DLPI stream to receive the data on
+ * void *: a buffer to store the data in
+ * size_t: the size of the buffer
+ * uint32_t: flags (see dlpi_io.h)
+ * output: ssize_t: the number of bytes received on success, -1 on failure
+ */
+
+ssize_t
+dlpi_recv_link(int fd, void *data_buffer, size_t data_length, uint32_t flags)
+{
+ int getmsg_flags = 0;
+ struct strbuf ctrl, data;
+ char ctrlbuf[1024];
+
+ ctrl.maxlen = sizeof (ctrlbuf);
+ ctrl.buf = ctrlbuf;
+
+ data.maxlen = data_length;
+ data.buf = data_buffer;
+
+ switch (getmsg(fd, &ctrl, &data, &getmsg_flags)) {
+
+ case MORECTL:
+ case MOREDATA:
+ case MOREDATA|MORECTL:
+
+ (void) ioctl(fd, I_FLUSH, FLUSHR);
+
+ if ((flags & DLPI_RECV_SHORT) == 0)
+ dhcpmsg(MSG_WARNING, "dlpi_recv_link: discarding stray "
+ "data on streamhead");
+ break;
+
+ case -1:
+ dhcpmsg(MSG_ERR, "dlpi_recv_link: getmsg");
+ return (-1);
+
+ default:
+ break;
+ }
+
+ return (data.len);
+}
+
+
+/*
+ * dlpi_sendto(): sends UDP packets on a DLPI stream
+ *
+ * input: int: the socket to send the packet on
+ * void *: a buffer to send
+ * size_t: the size of the buffer
+ * struct sockaddr_in *: the IP address to send the data to
+ * uchar_t *: the link-layer destination address
+ * size_t: the size of the link-layer destination address
+ * output: ssize_t: the number of bytes sent on success, -1 on failure
+ */
+
+ssize_t
+dlpi_sendto(int fd, void *buffer, size_t buf_len, struct sockaddr_in *to,
+ uchar_t *dl_to, size_t dl_to_len)
+{
+ struct ip *ip;
+ struct udphdr *udphdr;
+ void *data_buffer;
+ size_t data_length;
+ static uint16_t ip_id = 0;
+
+ /*
+ * TODO: someday we might want to support `to' not being
+ * the same as INADDR_BROADCAST. we don't need the support
+ * right now, but it's annoying to have a general interface
+ * that only supports a specific function.
+ */
+
+ if (to->sin_addr.s_addr != htonl(INADDR_BROADCAST)) {
+ dhcpmsg(MSG_ERROR, "dlpi_sendto: send to unicast address");
+ return (-1);
+ }
+
+ /*
+ * we allocate one extra byte here in case the UDP checksum
+ * routine needs it to get the packet length to be even.
+ */
+
+ data_length = sizeof (struct ip) + sizeof (struct udphdr) + buf_len;
+ data_buffer = calloc(1, data_length + 1);
+ if (data_buffer == NULL) {
+ dhcpmsg(MSG_ERR, "dlpi_sendto: cannot allocate packet");
+ return (-1);
+ }
+
+ ip = (struct ip *)data_buffer;
+ udphdr = (struct udphdr *)&ip[1];
+
+ (void) memcpy(&udphdr[1], buffer, buf_len);
+
+ /*
+ * build the ipv4 header. assume that our source address is 0
+ * (since we wouldn't be using DLPI if we could actually send
+ * packets an easier way). note that we only need to set nonzero
+ * fields since we got calloc()'d memory above.
+ */
+
+ /*
+ * From a purist's perspective, we should set the TTL to 1 for
+ * limited broadcasts. But operational experience (cisco routers)
+ * has shown that doing so results in the relay agent dropping our
+ * packets. These same devices (ciscos) also don't set the TTL
+ * to MAXTTL on the unicast side of the relay agent. Thus, the only
+ * safe thing to do is to always set the ttl to MAXTTL. Sigh.
+ */
+
+ ip->ip_ttl = MAXTTL;
+
+ ip->ip_v = 4;
+ ip->ip_hl = sizeof (struct ip) / 4;
+ ip->ip_id = htons(ip_id++);
+ ip->ip_off = htons(IP_DF);
+ ip->ip_p = IPPROTO_UDP;
+ ip->ip_len = htons(data_length);
+ ip->ip_dst = to->sin_addr;
+ ip->ip_src.s_addr = htonl(INADDR_ANY);
+ ip->ip_sum = ipv4cksum((uint16_t *)ip, sizeof (struct ip));
+
+ udphdr->uh_ulen = htons(sizeof (struct udphdr) + buf_len);
+ udphdr->uh_sport = htons(IPPORT_BOOTPC);
+ udphdr->uh_dport = htons(IPPORT_BOOTPS);
+ udphdr->uh_sum = udp_chksum(udphdr, &ip->ip_src, &ip->ip_dst, ip->ip_p);
+
+ if (dlpi_send_link(fd, data_buffer, data_length, dl_to, dl_to_len)
+ == -1) {
+ free(data_buffer);
+ dhcpmsg(MSG_ERR, "dlpi_sendto: dlpi_send_link");
+ return (-1);
+ }
+
+ free(data_buffer);
+ return (buf_len);
+}
+
+/*
+ * dlpi_send_link(): sends raw data down a DLPI stream
+ *
+ * input: int: the DLPI stream to send the data on
+ * void *: the raw data to send
+ * size_t: the size of the raw data
+ * uchar_t *: the link-layer destination address
+ * size_t: the size of the link-layer destination address
+ * output: ssize_t: 0 on success, -1 on failure
+ */
+
+ssize_t
+dlpi_send_link(int fd, void *data_buffer, size_t data_length,
+ uchar_t *dest_addr, size_t dest_addr_length)
+{
+ struct strbuf ctrl, data;
+ ssize_t retval;
+ dl_unitdata_req_t *dl_req;
+
+ /*
+ * allocate the control part of the message and fill it in.
+ * all we really indicate is the destination address
+ */
+
+ dl_req = malloc(sizeof (dl_unitdata_req_t) + data_length);
+ if (dl_req == NULL) {
+ dhcpmsg(MSG_ERR, "dlpi_send_link: dl_unitdata_req allocation");
+ return (-1);
+ }
+
+ ctrl.len = sizeof (dl_unitdata_req_t) + data_length;
+ ctrl.buf = (caddr_t)dl_req;
+
+ data.len = data_length;
+ data.buf = data_buffer;
+
+ dl_req->dl_primitive = DL_UNITDATA_REQ;
+ dl_req->dl_priority.dl_min = 0;
+ dl_req->dl_priority.dl_max = 0;
+ dl_req->dl_dest_addr_offset = sizeof (dl_unitdata_req_t);
+ dl_req->dl_dest_addr_length = dest_addr_length;
+ (void) memcpy(&dl_req[1], dest_addr, dest_addr_length);
+
+ retval = putmsg(fd, &ctrl, &data, 0);
+ free(dl_req);
+ return (retval);
+}
+
+/*
+ * set_packet_filter(): sets the current packet filter on a DLPI stream
+ *
+ * input: int: the DLPI stream to set the packet filter on
+ * filter_func_t *: the filter to use
+ * void *: an argument to pass to the filter function
+ * const char *: a text description of the filter's purpose
+ * output: void
+ */
+
+void
+set_packet_filter(int fd, filter_func_t *filter, void *arg,
+ const char *filter_name)
+{
+ struct strioctl sioc;
+ struct packetfilt pf;
+ ushort_t *pfp = pf.Pf_Filter;
+
+ pf.Pf_FilterLen = filter(pfp, arg) - pf.Pf_Filter;
+
+ sioc.ic_cmd = PFIOCSETF;
+ sioc.ic_timout = DLPI_TIMEOUT;
+ sioc.ic_len = sizeof (struct packetfilt);
+ sioc.ic_dp = (caddr_t)&pf;
+
+ /*
+ * if this ioctl() fails, we're really hosed. the best we can
+ * really do is play on.
+ */
+
+ if (ioctl(fd, I_STR, &sioc) == -1)
+ dhcpmsg(MSG_ERR, "set_packet_filter: PFIOCSETF");
+ else
+ dhcpmsg(MSG_DEBUG, "set_packet_filter: set filter %#x "
+ "(%s filter)", filter, filter_name);
+
+ /*
+ * clean out any potential cruft on the descriptor that
+ * appeared before we were able to set the filter
+ */
+
+ (void) ioctl(fd, I_FLUSH, FLUSHR);
+}
+
+/*
+ * dhcp_filter(): builds a packet filter that permits only DHCP/BOOTP messages
+ *
+ * input: ushort_t *: a place to store the packet filter code
+ * void *: not used
+ * output: ushort_t *: two bytes past the last byte in the packet filter
+ */
+
+/* ARGSUSED */
+ushort_t *
+dhcp_filter(ushort_t *pfp, void *arg)
+{
+ /*
+ * only pass up UDP packets -- 8th byte is the ttl/proto field
+ */
+
+ *pfp++ = ENF_PUSHWORD + 4;
+ *pfp++ = ENF_PUSHLIT | ENF_AND;
+ *pfp++ = htons(0xff);
+ *pfp++ = ENF_PUSHLIT | ENF_CAND;
+ *pfp++ = htons(IPPROTO_UDP);
+
+ /*
+ * make sure the IP packet doesn't have any options. 2nd
+ * nibble is the header length field.
+ * TODO: if we decide to handle options, this code goes away.
+ */
+
+ *pfp++ = ENF_PUSHWORD + 0;
+ *pfp++ = ENF_PUSHLIT | ENF_AND;
+ *pfp++ = htons(0x0f00); /* only care about 2nd nibble */
+ *pfp++ = ENF_PUSHLIT | ENF_CAND;
+ *pfp++ = htons(0x0500); /* which should be 5 * 4 = 20 */
+
+ /*
+ * if there's a fragment offset, or if the IP_MF bit is lit,
+ * pitch the packet. this pitches all fragments.
+ * TODO: if we decide to handle fragments, this code goes away.
+ */
+
+ *pfp++ = ENF_PUSHWORD + 3;
+ *pfp++ = ENF_PUSHLIT | ENF_AND;
+ *pfp++ = htons(0x1fff | IP_MF);
+ *pfp++ = ENF_PUSHZERO | ENF_CAND;
+
+ /*
+ * make sure the packet is for the DHCP client port -- 22nd
+ * byte is the UDP port number.
+ */
+
+ *pfp++ = ENF_PUSHWORD + 11;
+ *pfp++ = ENF_PUSHLIT | ENF_CAND;
+ *pfp++ = htons(IPPORT_BOOTPC);
+
+ return (pfp);
+}
+
+/*
+ * blackhole_filter(): builds a packet filter that tosses all messages
+ *
+ * input: ushort_t *: a place to store the packet filter code
+ * void *: not used
+ * output: ushort_t *: two bytes past the last byte in the packet filter
+ */
+
+/* ARGSUSED */
+ushort_t *
+blackhole_filter(ushort_t *pfp, void *arg)
+{
+ *pfp++ = ENF_PUSHZERO;
+ return (pfp);
+}
+
+/*
+ * build_broadcast_dest(): builds a DLPI destination address for the broadcast
+ * address for use in DL_UNITDATA_REQs
+ *
+ * input: dl_info_ack_t *: information about the interface
+ * uchar_t *: set to the length of the returned address
+ * output: uchar_t *: the broadcast address (dynamically allocated)
+ */
+
+uchar_t *
+build_broadcast_dest(dl_info_ack_t *dlia, uchar_t *length)
+{
+ uchar_t sap_len = abs(dlia->dl_sap_length);
+ caddr_t dl_sap;
+ uchar_t *dest_addr;
+
+ *length = dlia->dl_brdcst_addr_length + sap_len;
+ dest_addr = malloc(*length);
+ if (dest_addr == NULL)
+ return (NULL);
+
+ if (dlia->dl_sap_length > 0) { /* sap before */
+ dl_sap = (caddr_t)dlia + dlia->dl_addr_offset;
+ (void) memcpy(dest_addr, dl_sap, sap_len);
+ (void) memcpy(dest_addr + sap_len, (caddr_t)dlia +
+ dlia->dl_brdcst_addr_offset, dlia->dl_brdcst_addr_length);
+ } else {
+ dl_sap = (caddr_t)dlia + dlia->dl_addr_offset +
+ (dlia->dl_addr_length - sap_len);
+ (void) memcpy(dest_addr, (caddr_t)dlia +
+ dlia->dl_brdcst_addr_offset, dlia->dl_brdcst_addr_length);
+ (void) memcpy(dest_addr + dlia->dl_brdcst_addr_length,
+ dl_sap, sap_len);
+ }
+
+ return (dest_addr);
+}
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/dlpi_io.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/dlpi_io.h
new file mode 100644
index 0000000000..fdfe57be48
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/dlpi_io.h
@@ -0,0 +1,80 @@
+/*
+ * 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 DLPI_IO_H
+#define DLPI_IO_H
+
+#pragma ident "%W% %E% SMI"
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/dlpi.h>
+
+/*
+ * dlpi_io.[ch] contain the interface the agent uses to interact with
+ * DLPI. it makes use of dlprims.c (and should be its only consumer).
+ * see dlpi_io.c for documentation on how to use the exported
+ * functions.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * buffer size to be used in control part of DLPI messages, in bytes
+ */
+#define DLPI_BUF_MAX 256
+
+/*
+ * timeout to be used on DLPI-related operations, in seconds
+ */
+#define DLPI_TIMEOUT 5
+
+/*
+ * flags for dlpi_recv_link()
+ */
+#define DLPI_RECV_SHORT 0x01 /* short reads are expected */
+
+typedef ushort_t *filter_func_t(ushort_t *, void *);
+
+filter_func_t dhcp_filter, blackhole_filter;
+uchar_t *build_broadcast_dest(dl_info_ack_t *, uchar_t *);
+void set_packet_filter(int, filter_func_t *, void *, const char *);
+int dlpi_open(const char *, dl_info_ack_t *, size_t, t_uscalar_t);
+int dlpi_close(int);
+ssize_t dlpi_recvfrom(int, void *, size_t, struct sockaddr_in *);
+ssize_t dlpi_recv_link(int, void *, size_t, uint32_t);
+ssize_t dlpi_send_link(int, void *, size_t, uchar_t *, size_t);
+ssize_t dlpi_sendto(int, void *, size_t, struct sockaddr_in *,
+ uchar_t *, size_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DLPI_IO_H */
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/dlprims.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/dlprims.c
new file mode 100644
index 0000000000..3d0cf75273
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/dlprims.c
@@ -0,0 +1,208 @@
+/*
+ * 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) 1996-1999 by Sun Microsystems, Inc.
+ * All rights reserved.
+ *
+ * heavily cannibalized from
+ *
+ * #ident "@(#)dlprims.c 1.12 97/03/27 SMI"
+ */
+
+#pragma ident "%W% %E% SMI"
+
+/*
+ * TODO: get rid of this code as soon as possible and replace it with a
+ * version from a standard library. this stuff is barf-o-riffic.
+ */
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/dlpi.h>
+#include <stropts.h>
+#include <sys/poll.h>
+
+#include "dlpi_io.h"
+
+static int strgetmsg(int, struct strbuf *, struct strbuf *);
+
+/*
+ * dlinforeq(): issue DL_INFO_REQ and fetch DL_INFO_ACK on stream
+ *
+ * input: int: the stream to do the DL_INFO_REQ on
+ * dl_info_ack_t: a place to store the DL_INFO_ACK
+ * size_t: the size of the dl_info_ack_t
+ * output: int: 0 on success, 1 on failure (errno is set)
+ */
+
+int
+dlinforeq(int fd, dl_info_ack_t *infoackp, size_t infoack_size)
+{
+ struct strbuf ctl;
+
+ infoackp->dl_primitive = DL_INFO_REQ;
+
+ ctl.maxlen = infoack_size;
+ ctl.len = DL_INFO_REQ_SIZE;
+ ctl.buf = (caddr_t)infoackp;
+
+ if (putmsg(fd, &ctl, NULL, 0) == -1)
+ return (1);
+
+ if (strgetmsg(fd, &ctl, NULL) == 1)
+ return (1);
+
+ if (infoackp->dl_primitive != DL_INFO_ACK ||
+ ctl.len < DL_INFO_ACK_SIZE) {
+ errno = EPROTO;
+ return (1);
+ }
+
+ return (0);
+}
+
+/*
+ * dlattachreq(): issue DL_ATTACH_REQ and fetch DL_OK_ACK on stream
+ *
+ * input: int: the stream to do the DL_ATTACH_REQ on
+ * t_uscalar_t: the ppa to do the attach to
+ * output: int: 0 on success, 1 on failure (errno is set)
+ */
+
+int
+dlattachreq(int fd, t_uscalar_t ppa)
+{
+ union DL_primitives *dlp;
+ uint32_t buf[DLPI_BUF_MAX / sizeof (uint32_t)];
+ struct strbuf ctl;
+
+ dlp = (union DL_primitives *)buf;
+ dlp->attach_req.dl_primitive = DL_ATTACH_REQ;
+ dlp->attach_req.dl_ppa = ppa;
+
+ ctl.maxlen = sizeof (buf);
+ ctl.len = DL_ATTACH_REQ_SIZE;
+ ctl.buf = (caddr_t)dlp;
+
+ if (putmsg(fd, &ctl, NULL, 0) == -1)
+ return (1);
+
+ if (strgetmsg(fd, &ctl, NULL) == 1)
+ return (1);
+
+ if (dlp->dl_primitive != DL_OK_ACK) {
+ errno = EPROTO;
+ return (1);
+ }
+
+ return (0);
+}
+
+/*
+ * dlbindreq(): issue DL_BIND_REQ and fetch DL_BIND_ACK on stream
+ *
+ * input: int: the stream to do the DL_BIND_REQ on
+ * t_uscalar_t: the sap to bind to
+ * t_uscalar_t: the max number of outstanding DL_CONNECT_IND messages
+ * uint16_t: the service mode (connectionless/connection-oriented)
+ * uint16_t: whether this is a connection management stream
+ * output: int: 0 on success, 1 on failure (errno is set)
+ */
+
+int
+dlbindreq(int fd, t_uscalar_t sap, t_uscalar_t max_conind,
+ uint16_t service_mode, uint16_t conn_mgmt)
+{
+ union DL_primitives *dlp;
+ uint32_t buf[DLPI_BUF_MAX / sizeof (uint32_t)];
+ struct strbuf ctl;
+
+ dlp = (union DL_primitives *)buf;
+ dlp->bind_req.dl_primitive = DL_BIND_REQ;
+ dlp->bind_req.dl_sap = sap;
+ dlp->bind_req.dl_max_conind = max_conind;
+ dlp->bind_req.dl_service_mode = service_mode;
+ dlp->bind_req.dl_conn_mgmt = conn_mgmt;
+ dlp->bind_req.dl_xidtest_flg = 0;
+
+ ctl.maxlen = sizeof (buf);
+ ctl.len = DL_BIND_REQ_SIZE;
+ ctl.buf = (caddr_t)dlp;
+
+ if (putmsg(fd, &ctl, NULL, 0) == -1)
+ return (1);
+
+ if (strgetmsg(fd, &ctl, NULL) == 1)
+ return (1);
+
+ if (dlp->dl_primitive != DL_BIND_ACK || ctl.len < DL_BIND_ACK_SIZE) {
+ errno = EPROTO;
+ return (1);
+ }
+
+ return (0);
+}
+
+/*
+ * strgetmsg(): timed getmsg(3C)
+ *
+ * input: int: the stream to wait for the message on
+ * struct strbuf *: a buffer to hold the control part of the message
+ * struct strbuf *: a buffer to hold the data part of the message
+ * output: int: 0 on success, 1 on failure (errno is set)
+ */
+
+static int
+strgetmsg(int fd, struct strbuf *ctlp, struct strbuf *datap)
+{
+ struct pollfd fds;
+ int flags = 0;
+ int retval;
+
+ fds.fd = fd;
+ fds.events = POLLIN|POLLPRI;
+
+ switch (poll(&fds, 1, DLPI_TIMEOUT * 1000)) {
+
+ case 0:
+ errno = ETIME;
+ return (1);
+
+ case -1:
+ return (1);
+
+ default:
+
+ retval = getmsg(fd, ctlp, datap, &flags);
+ if (retval == -1)
+ return (1);
+
+ if (retval > 0 || ctlp->len < sizeof (t_uscalar_t)) {
+ errno = EPROTO;
+ return (1);
+ }
+
+ break;
+ }
+
+ return (0);
+}
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/dlprims.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/dlprims.h
new file mode 100644
index 0000000000..6169983c44
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/dlprims.h
@@ -0,0 +1,53 @@
+/*
+ * 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 DLPRIMS_H
+#define DLPRIMS_H
+
+#pragma ident "%W% %E% SMI"
+
+#include <sys/types.h>
+#include <sys/dlpi.h>
+
+/*
+ * dlprims.[ch] provide a "simpler" interface to DLPI. in truth, it's
+ * rather grotesque, but for now it's the best we can do. remove this
+ * file once DLPI routines are provided in a library.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int dlinforeq(int, dl_info_ack_t *, size_t);
+int dlattachreq(int, t_uscalar_t);
+int dlbindreq(int, t_uscalar_t, t_uscalar_t, uint16_t, uint16_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DLPRIMS_H */
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/inc.flg b/usr/src/cmd/cmd-inet/sbin/dhcpagent/inc.flg
new file mode 100644
index 0000000000..a456cbaede
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/inc.flg
@@ -0,0 +1,30 @@
+#!/bin/sh
+#
+# 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
+#
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+# Copyright (c) 1999 by Sun Microsystems, Inc.
+# All rights reserved.
+#
+
+find_files "s.*" usr/src/common/net/dhcp
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/inform.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/inform.c
new file mode 100644
index 0000000000..00a106cef1
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/inform.c
@@ -0,0 +1,148 @@
+/*
+ * 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) 1995-2001 by Sun Microsystems, Inc.
+ * All rights reserved.
+ *
+ * INFORM_SENT state of the client state machine.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/sockio.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/udp.h>
+#include <netinet/ip_var.h>
+#include <netinet/udp_var.h>
+#include <dhcpmsg.h>
+
+#include "util.h"
+#include "packet.h"
+#include "interface.h"
+
+/*
+ * dhcp_inform(): sends an INFORM packet and sets up reception for an ACK
+ *
+ * input: struct ifslist *: the interface to send the inform on, ...
+ * output: void
+ * note: the INFORM cannot be sent successfully if the interface
+ * does not have an IP address
+ */
+
+void
+dhcp_inform(struct ifslist *ifsp)
+{
+ dhcp_pkt_t *dpkt;
+ struct in_addr *our_addr;
+ struct ifreq ifr;
+
+ ifsp->if_state = INIT;
+
+ /*
+ * fetch our IP address -- since we may not manage the
+ * interface, go fetch it with ioctl()
+ */
+
+ (void) memset(&ifr, 0, sizeof (struct ifreq));
+ (void) strlcpy(ifr.ifr_name, ifsp->if_name, IFNAMSIZ);
+ ifr.ifr_addr.sa_family = AF_INET;
+
+ if (ioctl(ifsp->if_sock_fd, SIOCGIFADDR, &ifr) == -1) {
+ ifsp->if_dflags |= DHCP_IF_FAILED;
+ ipc_action_finish(ifsp, DHCP_IPC_E_INT);
+ async_finish(ifsp);
+ return;
+ }
+
+ /*
+ * the error handling here and in the check for IFF_UP below
+ * are handled different from most since it is the user who is
+ * at fault for the problem, not the machine.
+ */
+
+ /* LINTED [ifr_addr is a sockaddr which will be aligned] */
+ our_addr = &((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr;
+ if (our_addr->s_addr == htonl(INADDR_ANY)) {
+ dhcpmsg(MSG_WARNING, "dhcp_inform: INFORM attempted on "
+ "interface with no IP address");
+ ipc_action_finish(ifsp, DHCP_IPC_E_NOIPIF);
+ async_finish(ifsp);
+ remove_ifs(ifsp);
+ return;
+ }
+
+ if (ioctl(ifsp->if_sock_fd, SIOCGIFFLAGS, &ifr) == -1) {
+ ifsp->if_dflags |= DHCP_IF_FAILED;
+ ipc_action_finish(ifsp, DHCP_IPC_E_INT);
+ async_finish(ifsp);
+ return;
+ }
+
+ if ((ifr.ifr_flags & IFF_UP) == 0) {
+ dhcpmsg(MSG_WARNING, "dhcp_inform: INFORM attempted on downed "
+ "interface");
+ ipc_action_finish(ifsp, DHCP_IPC_E_DOWNIF);
+ async_finish(ifsp);
+ remove_ifs(ifsp);
+ return;
+ }
+
+ /*
+ * assemble a DHCPREQUEST packet, without the server id
+ * option. fill in ciaddr, since we know this. if_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(ifsp, INFORM);
+ dpkt->pkt->ciaddr = *our_addr;
+
+ add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, htons(ifsp->if_max -
+ sizeof (struct udpiphdr)));
+ add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len);
+ add_pkt_opt(dpkt, CD_REQUEST_LIST, ifsp->if_prl, ifsp->if_prllen);
+ add_pkt_opt(dpkt, CD_END, NULL, 0);
+
+ if (send_pkt(ifsp, dpkt, ifsp->if_server.s_addr, NULL) == 0) {
+ ifsp->if_dflags |= DHCP_IF_FAILED;
+ dhcpmsg(MSG_ERROR, "dhcp_inform: send_pkt failed");
+ ipc_action_finish(ifsp, DHCP_IPC_E_INT);
+ async_finish(ifsp);
+ return;
+ }
+
+ if (register_acknak(ifsp) == 0) {
+ ifsp->if_dflags |= DHCP_IF_FAILED;
+ ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY);
+ async_finish(ifsp);
+ return;
+ }
+
+ ifsp->if_state = INFORM_SENT;
+}
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/init_reboot.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/init_reboot.c
new file mode 100644
index 0000000000..a66be18865
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/init_reboot.c
@@ -0,0 +1,158 @@
+/*
+ * 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.
+ *
+ * INIT_REBOOT state of the DHCP client state machine.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/dhcp.h>
+#include <netinet/udp.h>
+#include <netinet/ip_var.h>
+#include <netinet/udp_var.h>
+#include <dhcpmsg.h>
+#include <string.h>
+
+#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(): attempts to reuse a cached configuration on an interface
+ *
+ * input: struct ifslist *: the interface to reuse the configuration on
+ * output: void
+ */
+
+void
+dhcp_init_reboot(struct ifslist *ifsp)
+{
+ dhcp_pkt_t *dpkt;
+ const char *reqhost;
+ char hostfile[PATH_MAX + 1];
+
+ dhcpmsg(MSG_VERBOSE, "%s has cached configuration - entering "
+ "INIT_REBOOT", ifsp->if_name);
+
+ ifsp->if_state = INIT_REBOOT;
+
+ if (register_acknak(ifsp) == 0) {
+
+ ifsp->if_state = INIT;
+ ifsp->if_dflags |= DHCP_IF_FAILED;
+ ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY);
+ async_finish(ifsp);
+
+ dhcpmsg(MSG_ERROR, "dhcp_init_reboot: cannot register to "
+ "collect ACK/NAK packets, reverting to INIT on %s",
+ ifsp->if_name);
+ return;
+ }
+
+ /*
+ * 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(ifsp, REQUEST);
+ add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR,
+ ifsp->if_ack->pkt->yiaddr.s_addr);
+
+ add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM));
+ add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, htons(ifsp->if_max -
+ sizeof (struct udpiphdr)));
+
+ add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len);
+ add_pkt_opt(dpkt, CD_REQUEST_LIST, ifsp->if_prl, ifsp->if_prllen);
+
+ /*
+ * Set CD_HOSTNAME option if REQUEST_HOSTNAME is set and a hostname
+ * is found in /etc/hostname.<ifname>
+ */
+ if (df_get_bool(ifsp->if_name, DF_REQUEST_HOSTNAME)) {
+ dhcpmsg(MSG_DEBUG, "dhcp_selecting: DF_REQUEST_HOSTNAME");
+ (void) snprintf(hostfile, sizeof (hostfile), "/etc/hostname.%s",
+ ifsp->if_name);
+
+ if ((reqhost = iffile_to_hostname(hostfile)) != NULL) {
+ dhcpmsg(MSG_DEBUG, "dhcp_selecting: host %s", reqhost);
+ if ((ifsp->if_reqhost = strdup(reqhost)) != NULL)
+ add_pkt_opt(dpkt, CD_HOSTNAME, ifsp->if_reqhost,
+ strlen(ifsp->if_reqhost));
+ else
+ dhcpmsg(MSG_WARNING, "dhcp_selecting: cannot"
+ " allocate memory for host name option");
+ }
+ }
+
+ add_pkt_opt(dpkt, CD_END, NULL, 0);
+
+ (void) send_pkt(ifsp, dpkt, htonl(INADDR_BROADCAST), stop_init_reboot);
+}
+
+/*
+ * stop_init_reboot(): decides when to stop retransmitting REQUESTs
+ *
+ * input: struct ifslist *: the interface REQUESTs are being sent on
+ * unsigned int: the number of REQUESTs sent so far
+ * output: boolean_t: B_TRUE if retransmissions should stop
+ */
+
+static boolean_t
+stop_init_reboot(struct ifslist *ifsp, unsigned int n_requests)
+{
+ if (n_requests >= DHCP_MAX_REQUESTS) {
+
+ (void) unregister_acknak(ifsp);
+
+ dhcpmsg(MSG_INFO, "no ACK/NAK to INIT_REBOOT REQUEST, "
+ "using remainder of existing lease on %s", ifsp->if_name);
+
+ /*
+ * we already stuck our old ack in ifsp->if_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(ifsp, NULL) == 0)
+ dhcp_selecting(ifsp);
+
+ return (B_TRUE);
+ }
+
+ return (B_FALSE);
+}
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/interface.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/interface.c
new file mode 100644
index 0000000000..e79dcb0582
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/interface.c
@@ -0,0 +1,1197 @@
+/*
+ * 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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <sys/dlpi.h>
+#include <stdlib.h>
+#include <sys/sockio.h>
+#include <netinet/in.h>
+#include <netinet/dhcp.h>
+#include <string.h>
+#include <unistd.h>
+#include <netinet/if_ether.h>
+#include <signal.h>
+#include <dhcpmsg.h>
+#include <libdevinfo.h>
+
+#include "interface.h"
+#include "util.h"
+#include "dlpi_io.h"
+#include "packet.h"
+#include "defaults.h"
+#include "states.h"
+#include "script_handler.h"
+
+/*
+ * note to the reader:
+ *
+ * the terminology in here is slightly confusing. in particular, the
+ * term `ifslist' is used to refer both to the `struct ifslist' entry
+ * that makes up a specific interface entry, and the `internal
+ * ifslist' which is a linked list of struct ifslists. to reduce
+ * confusion, in the comments, a `struct ifslist' is referred to as
+ * an `ifs', and `ifslist' refers to the internal ifslist.
+ *
+ */
+
+static struct ifslist *ifsheadp;
+static unsigned int ifscount;
+
+static void init_ifs(struct ifslist *);
+static void free_ifs(struct ifslist *);
+static void cancel_ifs_timer(struct ifslist *, int);
+
+static boolean_t get_prom_prop(const char *, const char *, uchar_t **,
+ unsigned int *);
+
+/*
+ * insert_ifs(): creates a new ifs and chains it on the ifslist. initializes
+ * state which remains consistent across all use of the ifs entry
+ *
+ * input: const char *: the name of the ifs entry (interface name)
+ * boolean_t: if B_TRUE, we're adopting the interface
+ * int *: ignored on input; if insert_ifs fails, set to a DHCP_IPC_E_*
+ * error code with the reason why
+ * output: struct ifslist *: a pointer to the new ifs entry, or NULL on failure
+ */
+
+struct ifslist *
+insert_ifs(const char *if_name, boolean_t is_adopting, int *error)
+{
+ uint32_t buf[DLPI_BUF_MAX / sizeof (uint32_t)];
+ dl_info_ack_t *dlia = (dl_info_ack_t *)buf;
+ caddr_t dl_addr;
+ struct ifreq ifr;
+ unsigned int i, client_id_len = 0;
+ uchar_t *client_id = NULL;
+ const char *prl;
+ struct ifslist *ifsp;
+ long seed;
+
+ ifsp = lookup_ifs(if_name);
+ if (ifsp != NULL) {
+ *error = DHCP_IPC_E_INT; /* should never happen */
+ return (NULL);
+ }
+
+ /*
+ * okay, we've got a request to put a new interface under our
+ * control. it's our job to set everything that doesn't
+ * change for the life of the interface. (state that changes
+ * should be initialized in init_ifs() and reset by reset_ifs())
+ *
+ * 1. verify the interface can support DHCP
+ * 2. get the interface mtu
+ * 3. get the interface hardware type and hardware length
+ * 4. get the interface hardware address
+ * 5. get the interface broadcast address
+ * 6. get the interface flags
+ */
+
+ ifsp = calloc(1, sizeof (struct ifslist));
+ if (ifsp == NULL) {
+ dhcpmsg(MSG_ERR, "insert_ifs: cannot allocate ifs entry for "
+ "%s", if_name);
+ *error = DHCP_IPC_E_MEMORY;
+ return (NULL);
+ }
+
+ (void) strlcpy(ifsp->if_name, if_name, IFNAMSIZ);
+
+ /* step 1 */
+ ifsp->if_dlpi_fd = dlpi_open(if_name, dlia, sizeof (buf), ETHERTYPE_IP);
+ if (ifsp->if_dlpi_fd == -1) {
+ *error = DHCP_IPC_E_INVIF;
+ goto failure;
+ }
+
+ init_ifs(ifsp); /* ifsp->if_dlpi_fd must be valid */
+ ipc_action_init(ifsp);
+
+ /* step 2 */
+ ifsp->if_max = dlia->dl_max_sdu;
+ ifsp->if_opt = ifsp->if_max - BASE_PKT_SIZE;
+ ifsp->if_min = dlia->dl_min_sdu;
+
+ if (ifsp->if_max < DHCP_DEF_MAX_SIZE) {
+ dhcpmsg(MSG_ERROR, "insert_ifs: %s does not have a large "
+ "enough maximum SDU to support DHCP", if_name);
+ *error = DHCP_IPC_E_INVIF;
+ goto failure;
+ }
+
+ /* step 3 */
+ ifsp->if_hwtype = dlpi_to_arp(dlia->dl_mac_type);
+ ifsp->if_hwlen = dlia->dl_addr_length - abs(dlia->dl_sap_length);
+
+ dhcpmsg(MSG_DEBUG, "insert_ifs: %s: sdumax %d, optmax %d, hwtype %d, "
+ "hwlen %d", if_name, ifsp->if_max, ifsp->if_opt, ifsp->if_hwtype,
+ ifsp->if_hwlen);
+
+ /* step 4 */
+ ifsp->if_hwaddr = malloc(ifsp->if_hwlen);
+ if (ifsp->if_hwaddr == NULL) {
+ dhcpmsg(MSG_ERR, "insert_ifs: cannot allocate if_hwaddr "
+ "for %s", if_name);
+ *error = DHCP_IPC_E_MEMORY;
+ goto failure;
+ }
+
+ /*
+ * depending on the DLPI device, the sap and hardware addresses
+ * can be in either order within the dlsap address; find the
+ * location of the hardware address using dl_sap_length. see the
+ * DLPI specification for more on this braindamage.
+ */
+
+ dl_addr = (caddr_t)dlia + dlia->dl_addr_offset;
+ if (dlia->dl_sap_length > 0) {
+ ifsp->if_sap_before++;
+ dl_addr += dlia->dl_sap_length;
+ }
+
+ (void) memcpy(ifsp->if_hwaddr, dl_addr, ifsp->if_hwlen);
+
+ /* step 5 */
+ ifsp->if_saplen = abs(dlia->dl_sap_length);
+ ifsp->if_daddr = build_broadcast_dest(dlia, &ifsp->if_dlen);
+ if (ifsp->if_daddr == NULL) {
+ dhcpmsg(MSG_ERR, "insert_ifs: cannot allocate if_daddr "
+ "for %s", if_name);
+ *error = DHCP_IPC_E_MEMORY;
+ goto failure;
+ }
+
+ /* step 6 */
+ (void) strlcpy(ifr.ifr_name, if_name, IFNAMSIZ);
+
+ if (ioctl(ifsp->if_sock_fd, SIOCGIFFLAGS, &ifr) == -1) {
+ if (errno == ENXIO)
+ *error = DHCP_IPC_E_INVIF;
+ else
+ *error = DHCP_IPC_E_INT;
+ dhcpmsg(MSG_ERR, "insert_ifs: SIOCGIFFLAGS for %s", if_name);
+ goto failure;
+ }
+
+ /*
+ * if DHCPRUNNING is already set on the interface and we're
+ * not adopting it, the agent probably crashed and burned.
+ * note it, but don't let it stop the proceedings. we're
+ * pretty sure we're not already running, since we wouldn't
+ * have been able to bind to our IPC port.
+ */
+
+ if ((is_adopting == B_FALSE) && (ifr.ifr_flags & IFF_DHCPRUNNING))
+ dhcpmsg(MSG_WARNING, "insert_ifs: DHCP flag already set on %s",
+ if_name);
+
+ ifr.ifr_flags |= IFF_DHCPRUNNING;
+ (void) ioctl(ifsp->if_sock_fd, SIOCSIFFLAGS, &ifr);
+
+ ifsp->if_send_pkt.pkt = calloc(ifsp->if_max, 1);
+ if (ifsp->if_send_pkt.pkt == NULL) {
+ dhcpmsg(MSG_ERR, "insert_ifs: cannot allocate if_send_pkt "
+ "for %s", if_name);
+ *error = DHCP_IPC_E_MEMORY;
+ goto failure;
+ }
+
+ if (is_adopting) {
+ /*
+ * if the agent is adopting a lease OBP is initially
+ * searched for a client-id
+ */
+
+ dhcpmsg(MSG_DEBUG, "insert_ifs: getting /chosen:clientid "
+ "property");
+
+ if (!get_prom_prop("chosen", "client-id", &ifsp->if_cid,
+ &client_id_len)) {
+ /*
+ * a failure occurred trying to acquire the client-id
+ */
+
+ dhcpmsg(MSG_DEBUG, "insert_ifs: cannot allocate client "
+ "id for %s", if_name);
+ *error = DHCP_IPC_E_INT;
+ goto failure;
+ } else if (dlia->dl_mac_type == DL_IB && ifsp->if_cid == NULL) {
+ /*
+ * when the interface is infiniband and the agent
+ * is adopting the lease there must be an OBP
+ * client-id.
+ */
+
+ dhcpmsg(MSG_DEBUG, "insert_ifs: no /chosen:clientid"
+ "id for %s", if_name);
+ *error = DHCP_IPC_E_INT;
+ goto failure;
+ }
+
+ ifsp->if_cidlen = client_id_len;
+ } else {
+ /*
+ * look in defaults file for the client-id
+ */
+
+ dhcpmsg(MSG_DEBUG, "insert_ifs: getting defaults client-id "
+ "property");
+
+ client_id = df_get_octet(if_name, DF_CLIENT_ID, &client_id_len);
+
+ /*
+ * at this point, all logical interfaces must be explicitly
+ * configured with a client id by the administrator.
+ */
+
+ if (client_id == NULL && strchr(if_name, ':') != NULL) {
+ dhcpmsg(MSG_ERROR, "no client id configured for "
+ "logical interface %s; cannot manage", if_name);
+ *error = DHCP_IPC_E_NOIFCID;
+ goto failure;
+ }
+
+ if (client_id != NULL) {
+ /*
+ * the defaults client-id value must be copied out to
+ * another buffer
+ */
+
+ ifsp->if_cid = calloc(client_id_len, sizeof (uchar_t));
+
+ if (ifsp->if_cid == NULL) {
+ dhcpmsg(MSG_ERR, "insert_ifs: cannot "
+ "allocate client id for %s", if_name);
+ *error = DHCP_IPC_E_MEMORY;
+ goto failure;
+ }
+
+ (void) memcpy(ifsp->if_cid, client_id, client_id_len);
+
+ ifsp->if_cidlen = client_id_len;
+ } else if (dlia->dl_mac_type == DL_IB) {
+ /*
+ * This comes from DHCP over IPoIB spec. 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)|
+ * +-----+-----+-----+-----+-----+----....----+
+ */
+ ifsp->if_cidlen = 1 + 4 + 16;
+ ifsp->if_cid = client_id = malloc(ifsp->if_cidlen);
+ if (ifsp->if_cid == NULL) {
+ dhcpmsg(MSG_ERR, "insert_ifs: cannot "
+ "allocate client id for %s", if_name);
+ *error = DHCP_IPC_E_MEMORY;
+ goto 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, ifsp->if_hwaddr + 4,
+ ifsp->if_hwlen - 4);
+ (void) memset(client_id, 0, 5);
+ }
+ }
+
+ /*
+ * initialize the parameter request list, if there is one.
+ */
+
+ prl = df_get_string(if_name, DF_PARAM_REQUEST_LIST);
+ if (prl == NULL)
+ ifsp->if_prl = NULL;
+ else {
+ for (ifsp->if_prllen = 1, i = 0; prl[i] != '\0'; i++)
+ if (prl[i] == ',')
+ ifsp->if_prllen++;
+
+ ifsp->if_prl = malloc(ifsp->if_prllen);
+ if (ifsp->if_prl == NULL) {
+ dhcpmsg(MSG_WARNING, "insert_ifs: cannot allocate "
+ "parameter request list for %s (continuing)",
+ if_name);
+ } else {
+ for (i = 0; i < ifsp->if_prllen; prl++, i++) {
+ ifsp->if_prl[i] = strtoul(prl, NULL, 0);
+ while (*prl != ',' && *prl != '\0')
+ prl++;
+ if (*prl == '\0')
+ break;
+ }
+ }
+ }
+
+ ifsp->if_offer_wait = df_get_int(if_name, DF_OFFER_WAIT);
+
+ /*
+ * we're past the point of failure; chain it on.
+ */
+
+ ifsp->next = ifsheadp;
+ ifsp->prev = NULL;
+ ifsheadp = ifsp;
+
+ if (ifsheadp->next != NULL)
+ ifsheadp->next->prev = ifsheadp;
+
+ hold_ifs(ifsp);
+ ifscount++;
+
+ if (inactivity_id != -1) {
+ if (iu_cancel_timer(tq, inactivity_id, NULL) == 1)
+ inactivity_id = -1;
+ }
+
+ /*
+ * seed the random number generator, since we're going to need it
+ * to set transaction id's and for exponential backoff. if an
+ * interface is already initialized, then we just end up harmlessly
+ * reseeding it. note that we try to spread the hardware address
+ * over as many bits of the seed as possible.
+ */
+ seed = gethrtime();
+ for (i = 0; i < ifsp->if_hwlen; i++)
+ seed += ifsp->if_hwaddr[i] << ((i % 7) * 4);
+ seed ^= getpid();
+ srand48(seed);
+
+ dhcpmsg(MSG_DEBUG, "insert_ifs: inserted interface %s", if_name);
+ return (ifsp);
+
+failure:
+ free_ifs(ifsp);
+ return (NULL);
+}
+
+/*
+ * init_ifs(): puts an ifs in its initial state
+ *
+ * input: struct ifslist *: the ifs to initialize
+ * output: void
+ * note: if the interface isn't fresh, use reset_ifs()
+ */
+
+static void
+init_ifs(struct ifslist *ifsp)
+{
+ /*
+ * if_sock_ip_fd is created and bound in configure_if().
+ * if_sock_fd is bound in configure_if(); see comments in
+ * bound.c for more details on why. if creation of if_sock_fd
+ * fails, we'll need more context anyway, so don't check.
+ */
+
+ ifsp->if_sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
+ ifsp->if_sock_ip_fd = -1;
+ ifsp->if_state = INIT;
+ ifsp->if_routers = NULL;
+ ifsp->if_nrouters = 0;
+ ifsp->if_ack = NULL;
+ ifsp->if_orig_ack = NULL;
+ ifsp->if_server.s_addr = htonl(INADDR_BROADCAST);
+ ifsp->if_neg_monosec = monosec();
+ ifsp->if_lease = 0;
+ ifsp->if_t1 = 0;
+ ifsp->if_t2 = 0;
+ ifsp->if_reqhost = NULL;
+
+ ifsp->if_script_helper_pid = -1;
+ ifsp->if_script_callback = NULL;
+ ifsp->if_script_event = NULL;
+ ifsp->if_callback_msg = NULL;
+ ifsp->if_script_event_id = -1;
+ ifsp->if_script_pid = -1;
+ ifsp->if_script_fd = -1;
+
+ ifsp->if_offer_id = -1;
+ ifsp->if_acknak_id = -1;
+ ifsp->if_acknak_bcast_id = -1;
+ ifsp->if_timer[DHCP_T1_TIMER] = -1;
+ ifsp->if_timer[DHCP_T2_TIMER] = -1;
+ ifsp->if_timer[DHCP_LEASE_TIMER] = -1;
+
+ set_packet_filter(ifsp->if_dlpi_fd, dhcp_filter, NULL, "DHCP");
+
+ dhcpmsg(MSG_DEBUG, "init_ifs: initted interface %s", ifsp->if_name);
+}
+
+/*
+ * remove_ifs_default_routes(): removes an ifs's default routes
+ *
+ * input: struct ifslist *: the ifs whose default routes need to be removed
+ * output: void
+ */
+
+static void
+remove_ifs_default_routes(struct ifslist *ifsp)
+{
+ if (ifsp->if_routers != NULL) {
+ while (ifsp->if_nrouters > 0) {
+ (void) del_default_route(ifsp->if_name,
+ &ifsp->if_routers[--ifsp->if_nrouters]);
+ }
+ free(ifsp->if_routers);
+ ifsp->if_routers = NULL;
+ }
+}
+
+/*
+ * reset_ifs(): resets an ifs to its initial state
+ *
+ * input: struct ifslist *: the ifs to reset
+ * output: void
+ */
+
+void
+reset_ifs(struct ifslist *ifsp)
+{
+ ifsp->if_dflags &= ~DHCP_IF_FAILED;
+
+ remove_ifs_default_routes(ifsp);
+
+ if (ifsp->if_sock_fd != -1)
+ (void) close(ifsp->if_sock_fd);
+
+ if (ifsp->if_orig_ack != ifsp->if_ack)
+ free_pkt_list(&ifsp->if_orig_ack);
+
+ free_pkt_list(&ifsp->if_ack);
+
+ if (ifsp->if_sock_ip_fd != -1)
+ (void) close(ifsp->if_sock_ip_fd);
+
+ if (ifsp->if_offer_id != -1) {
+ if (iu_unregister_event(eh, ifsp->if_offer_id, NULL) != 0)
+ (void) release_ifs(ifsp);
+ }
+
+ (void) unregister_acknak(ifsp); /* just in case */
+
+ cancel_ifs_timers(ifsp);
+ init_ifs(ifsp);
+}
+
+/*
+ * lookup_ifs(): looks up an ifs, given its name
+ *
+ * input: const char *: the name of the ifs entry (the interface name)
+ * the name "" searches for the primary interface
+ * output: struct ifslist *: the corresponding ifs, or NULL if not found
+ */
+
+struct ifslist *
+lookup_ifs(const char *if_name)
+{
+ struct ifslist *ifs;
+
+ for (ifs = ifsheadp; ifs != NULL; ifs = ifs->next)
+ if (*if_name != '\0') {
+ if (strcmp(ifs->if_name, if_name) == 0)
+ break;
+ } else if (ifs->if_dflags & DHCP_IF_PRIMARY)
+ break;
+
+ return (ifs);
+}
+
+/*
+ * lookup_ifs_by_xid(): looks up an ifs, given its last used transaction id
+ *
+ * input: int: the transaction id to look up
+ * output: struct ifslist *: the corresponding ifs, or NULL if not found
+ */
+
+struct ifslist *
+lookup_ifs_by_xid(uint32_t xid)
+{
+ struct ifslist *ifs;
+
+ for (ifs = ifsheadp; ifs != NULL; ifs = ifs->next) {
+ if (ifs->if_send_pkt.pkt->xid == xid)
+ break;
+ }
+
+ return (ifs);
+}
+
+/*
+ * remove_ifs(): removes a given ifs from the ifslist. marks the ifs
+ * for being freed (but may not actually free it).
+ *
+ * input: struct ifslist *: the ifs to remove
+ * output: void
+ * note: see interface.h for a discussion of ifs memory management
+ */
+
+void
+remove_ifs(struct ifslist *ifsp)
+{
+ struct ifreq ifr;
+
+ if (ifsp->if_dflags & DHCP_IF_REMOVED)
+ return;
+
+ (void) memset(&ifr, 0, sizeof (struct ifreq));
+ (void) strlcpy(ifr.ifr_name, ifsp->if_name, IFNAMSIZ);
+
+ if (ioctl(ifsp->if_sock_fd, SIOCGIFFLAGS, &ifr) == 0) {
+ ifr.ifr_flags &= ~IFF_DHCPRUNNING;
+ (void) ioctl(ifsp->if_sock_fd, SIOCSIFFLAGS, &ifr);
+ }
+
+ ifsp->if_dflags |= DHCP_IF_REMOVED;
+
+ /*
+ * if we have long term timers, cancel them so that interface
+ * resources can be reclaimed in a reasonable amount of time.
+ */
+
+ cancel_ifs_timers(ifsp);
+
+ if (ifsp->prev != NULL)
+ ifsp->prev->next = ifsp->next;
+ else
+ ifsheadp = ifsp->next;
+
+ if (ifsp->next != NULL)
+ ifsp->next->prev = ifsp->prev;
+
+ ifscount--;
+ (void) release_ifs(ifsp);
+
+ /* no big deal if this fails */
+ if (ifscount == 0) {
+ inactivity_id = iu_schedule_timer(tq, DHCP_INACTIVITY_WAIT,
+ inactivity_shutdown, NULL);
+ }
+}
+
+/*
+ * hold_ifs(): acquires a hold on an ifs
+ *
+ * input: struct ifslist *: the ifs entry to acquire a hold on
+ * output: void
+ */
+
+void
+hold_ifs(struct ifslist *ifsp)
+{
+ ifsp->if_hold_count++;
+
+ dhcpmsg(MSG_DEBUG2, "hold_ifs: hold count on %s: %d",
+ ifsp->if_name, ifsp->if_hold_count);
+}
+
+/*
+ * release_ifs(): releases a hold previously acquired on an ifs. if the
+ * hold count reaches 0, the ifs is freed
+ *
+ * input: struct ifslist *: the ifs entry to release the hold on
+ * output: int: the number of holds outstanding on the ifs
+ */
+
+int
+release_ifs(struct ifslist *ifsp)
+{
+ if (ifsp->if_hold_count == 0) {
+ dhcpmsg(MSG_CRIT, "release_ifs: extraneous release");
+ return (0);
+ }
+
+ if (--ifsp->if_hold_count == 0) {
+ free_ifs(ifsp);
+ return (0);
+ }
+
+ dhcpmsg(MSG_DEBUG2, "release_ifs: hold count on %s: %d",
+ ifsp->if_name, ifsp->if_hold_count);
+
+ return (ifsp->if_hold_count);
+}
+
+/*
+ * free_ifs(): frees the memory occupied by an ifs entry
+ *
+ * input: struct ifslist *: the ifs entry to free
+ * output: void
+ */
+
+static void
+free_ifs(struct ifslist *ifsp)
+{
+ dhcpmsg(MSG_DEBUG, "free_ifs: freeing interface %s", ifsp->if_name);
+
+ free_pkt_list(&ifsp->if_recv_pkt_list);
+ if (ifsp->if_ack != ifsp->if_orig_ack)
+ free_pkt_list(&ifsp->if_orig_ack);
+ free_pkt_list(&ifsp->if_ack);
+ free(ifsp->if_send_pkt.pkt);
+ free(ifsp->if_cid);
+ free(ifsp->if_daddr);
+ free(ifsp->if_hwaddr);
+ free(ifsp->if_prl);
+ free(ifsp->if_reqhost);
+ free(ifsp->if_routers);
+
+ if (ifsp->if_sock_fd != -1)
+ (void) close(ifsp->if_sock_fd);
+
+ if (ifsp->if_sock_ip_fd != -1)
+ (void) close(ifsp->if_sock_ip_fd);
+
+ if (ifsp->if_dlpi_fd != -1)
+ (void) dlpi_close(ifsp->if_dlpi_fd);
+
+ free(ifsp);
+}
+
+/*
+ * checkaddr(): checks if the given address is still set on the given ifs
+ *
+ * input: struct ifslist *: the ifs to check
+ * int: the address to lookup on the interface
+ * struct in_addr *: the address to compare to
+ * output: boolean_t: B_TRUE if the address is still set; B_FALSE if not
+ */
+
+static boolean_t
+checkaddr(struct ifslist *ifsp, int ioccmd, struct in_addr *addr)
+{
+ struct ifreq ifr;
+ struct sockaddr_in *sin;
+
+ /* LINTED [ifr_addr is a sockaddr which will be aligned] */
+ sin = (struct sockaddr_in *)&ifr.ifr_addr;
+
+ (void) memset(&ifr, 0, sizeof (struct ifreq));
+ (void) strlcpy(ifr.ifr_name, ifsp->if_name, IFNAMSIZ);
+ ifr.ifr_addr.sa_family = AF_INET;
+
+ switch (ioctl(ifsp->if_sock_fd, ioccmd, &ifr)) {
+ case 0:
+ if (sin->sin_addr.s_addr != addr->s_addr)
+ return (B_FALSE);
+ break;
+ case -1:
+ if (errno == ENXIO)
+ return (B_FALSE);
+ break;
+ }
+ return (B_TRUE);
+}
+
+/*
+ * verify_ifs(): verifies than an ifs is still valid (i.e., has not been
+ * explicitly or implicitly dropped or released)
+ *
+ * input: struct ifslist *: the ifs to verify
+ * output: int: 1 if the ifs is still valid, 0 if the interface is invalid
+ */
+
+int
+verify_ifs(struct ifslist *ifsp)
+{
+ struct ifreq ifr;
+
+ if (ifsp->if_dflags & DHCP_IF_REMOVED)
+ return (0);
+
+ (void) memset(&ifr, 0, sizeof (struct ifreq));
+ (void) strlcpy(ifr.ifr_name, ifsp->if_name, IFNAMSIZ);
+
+ ifr.ifr_addr.sa_family = AF_INET;
+
+ switch (ifsp->if_state) {
+
+ case BOUND:
+ case RENEWING:
+ case REBINDING:
+
+ /*
+ * if the interface has gone down or been unplumbed, then we
+ * act like there has been an implicit drop.
+ */
+
+ switch (ioctl(ifsp->if_sock_fd, SIOCGIFFLAGS, &ifr)) {
+ case 0:
+ if ((ifr.ifr_flags & (IFF_UP|IFF_DHCPRUNNING)) !=
+ (IFF_UP|IFF_DHCPRUNNING))
+ goto abandon;
+ break;
+ case -1:
+ if (errno == ENXIO)
+ goto abandon;
+ break;
+ }
+ /* FALLTHRU */
+
+ case INIT_REBOOT:
+ case SELECTING:
+ case REQUESTING:
+
+ /*
+ * 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.
+ */
+
+ if (!checkaddr(ifsp, SIOCGIFADDR, &ifsp->if_addr) ||
+ !checkaddr(ifsp, SIOCGIFNETMASK, &ifsp->if_netmask) ||
+ !checkaddr(ifsp, SIOCGIFBRDADDR, &ifsp->if_broadcast))
+ goto abandon;
+ }
+
+ return (1);
+abandon:
+ dhcpmsg(MSG_WARNING, "verify_ifs: %s has changed properties, "
+ "abandoning", ifsp->if_name);
+
+ remove_ifs(ifsp);
+ return (0);
+}
+
+/*
+ * canonize_ifs(): puts the interface in a canonical (zeroed) form
+ *
+ * input: struct ifslist *: the interface to canonize
+ * output: int: 1 on success, 0 on failure
+ */
+
+int
+canonize_ifs(struct ifslist *ifsp)
+{
+ struct sockaddr_in *sin;
+ struct ifreq ifr;
+
+ dhcpmsg(MSG_VERBOSE, "canonizing interface %s", ifsp->if_name);
+
+ /*
+ * note that due to infelicities in the routing code, any default
+ * routes must be removed prior to clearing the UP flag.
+ */
+
+ remove_ifs_default_routes(ifsp);
+
+ /* LINTED [ifr_addr is a sockaddr which will be aligned] */
+ sin = (struct sockaddr_in *)&ifr.ifr_addr;
+
+ (void) memset(&ifr, 0, sizeof (struct ifreq));
+ (void) strlcpy(ifr.ifr_name, ifsp->if_name, IFNAMSIZ);
+
+ if (ioctl(ifsp->if_sock_fd, SIOCGIFFLAGS, &ifr) == -1)
+ return (0);
+
+ /*
+ * clear the UP flag, but don't clear DHCPRUNNING since
+ * that should only be done when the interface is removed
+ * (see remove_ifs())
+ */
+
+ ifr.ifr_flags &= ~IFF_UP;
+
+ if (ioctl(ifsp->if_sock_fd, SIOCSIFFLAGS, &ifr) == -1)
+ return (0);
+
+ /*
+ * since ifr is actually a union, we need to explicitly zero
+ * the flags field before we reuse the structure, or otherwise
+ * cruft may leak over into other members of the union.
+ */
+
+ ifr.ifr_flags = 0;
+ ifr.ifr_addr.sa_family = AF_INET;
+ sin->sin_addr.s_addr = htonl(INADDR_ANY);
+
+ if (ioctl(ifsp->if_sock_fd, SIOCSIFADDR, &ifr) == -1)
+ return (0);
+
+ if (ioctl(ifsp->if_sock_fd, SIOCSIFNETMASK, &ifr) == -1)
+ return (0);
+
+ if (ioctl(ifsp->if_sock_fd, SIOCSIFBRDADDR, &ifr) == -1)
+ return (0);
+
+ /*
+ * any time we change the IP address, netmask, or broadcast we
+ * must be careful to also reset bookkeeping of what these are
+ * set to. this is so we can detect if these characteristics
+ * are changed by another process.
+ */
+
+ ifsp->if_addr.s_addr = htonl(INADDR_ANY);
+ ifsp->if_netmask.s_addr = htonl(INADDR_ANY);
+ ifsp->if_broadcast.s_addr = htonl(INADDR_ANY);
+
+ return (1);
+}
+
+/*
+ * check_ifs(): makes sure an ifs is still valid, and if it is, releases the
+ * ifs. otherwise, it informs the caller the ifs is going away
+ * and expects the caller to perform the release
+ *
+ * input: struct ifslist *: the ifs to check
+ * output: int: 1 if the interface is valid, 0 otherwise
+ */
+
+int
+check_ifs(struct ifslist *ifsp)
+{
+ hold_ifs(ifsp);
+ if (release_ifs(ifsp) == 1 || verify_ifs(ifsp) == 0) {
+
+ /*
+ * this interface is going away. if there's an
+ * uncancelled IPC event roaming around, cancel it
+ * now. we leave the hold on in case anyone else has
+ * any cleanup work that needs to be done before the
+ * interface goes away.
+ */
+
+ ipc_action_finish(ifsp, DHCP_IPC_E_UNKIF);
+ async_finish(ifsp);
+ return (0);
+ }
+
+ (void) release_ifs(ifsp);
+ return (1);
+}
+
+/*
+ * nuke_ifslist(): delete the ifslist (for use when the dhcpagent is exiting)
+ *
+ * input: boolean_t: B_TRUE if the agent is exiting due to SIGTERM
+ * output: void
+ */
+
+void
+nuke_ifslist(boolean_t onterm)
+{
+ int status;
+ struct ifslist *ifsp, *ifsp_next;
+
+ for (ifsp = ifsheadp; ifsp != NULL; ifsp = ifsp_next) {
+ ifsp_next = ifsp->next;
+
+ cancel_ifs_timers(ifsp);
+ if (ifsp->if_script_pid != -1) {
+ /* stop a script if it is not for DROP or RELEASE */
+ if (strcmp(ifsp->if_script_event, EVENT_DROP) == 0 ||
+ strcmp(ifsp->if_script_event, EVENT_RELEASE) == 0) {
+ continue;
+ }
+ script_stop(ifsp);
+ }
+
+ /*
+ * if the script is started by script_start, dhcp_drop and
+ * dhcp_release should and will only be called after the
+ * script exits.
+ */
+ if (onterm &&
+ df_get_bool(ifsp->if_name, DF_RELEASE_ON_SIGTERM)) {
+ if (script_start(ifsp, EVENT_RELEASE, dhcp_release,
+ "DHCP agent is exiting", &status) == 1) {
+ continue;
+ }
+ if (status == 1)
+ continue;
+ }
+ (void) script_start(ifsp, EVENT_DROP, dhcp_drop, NULL, NULL);
+ }
+}
+
+/*
+ * refresh_ifslist(): refreshes all finite leases under DHCP control
+ *
+ * input: iu_eh_t *: unused
+ * int: unused
+ * void *: unused
+ * output: void
+ */
+
+/* ARGSUSED */
+void
+refresh_ifslist(iu_eh_t *eh, int sig, void *arg)
+{
+ struct ifslist *ifsp;
+
+ for (ifsp = ifsheadp; ifsp != NULL; ifsp = ifsp->next) {
+
+ if (ifsp->if_state != BOUND && ifsp->if_state != RENEWING &&
+ ifsp->if_state != REBINDING)
+ continue;
+
+ if (ifsp->if_lease == DHCP_PERM)
+ continue;
+
+ /*
+ * this interface has a finite lease and we do not know
+ * how long the machine's been off for. refresh it.
+ */
+
+ dhcpmsg(MSG_WARNING, "refreshing lease on %s", ifsp->if_name);
+ cancel_ifs_timer(ifsp, DHCP_T1_TIMER);
+ cancel_ifs_timer(ifsp, DHCP_T2_TIMER);
+ (void) iu_adjust_timer(tq, ifsp->if_timer[DHCP_LEASE_TIMER], 0);
+ }
+}
+
+/*
+ * ifs_count(): returns the number of interfaces currently managed
+ *
+ * input: void
+ * output: unsigned int: the number of interfaces currently managed
+ */
+
+unsigned int
+ifs_count(void)
+{
+ return (ifscount);
+}
+
+/*
+ * cancel_ifs_timer(): cancels a lease-related timer on an interface
+ *
+ * input: struct ifslist *: the interface to operate on
+ * int: the timer id of the timer to cancel
+ * output: void
+ */
+
+static void
+cancel_ifs_timer(struct ifslist *ifsp, int timer_id)
+{
+ if (ifsp->if_timer[timer_id] != -1) {
+ if (iu_cancel_timer(tq, ifsp->if_timer[timer_id], NULL) == 1) {
+ (void) release_ifs(ifsp);
+ ifsp->if_timer[timer_id] = -1;
+ } else
+ dhcpmsg(MSG_WARNING, "cancel_ifs_timer: cannot cancel "
+ "if_timer[%d]", timer_id);
+ }
+}
+
+/*
+ * cancel_ifs_timers(): cancels an interface's pending lease-related timers
+ *
+ * input: struct ifslist *: the interface to operate on
+ * output: void
+ */
+
+void
+cancel_ifs_timers(struct ifslist *ifsp)
+{
+ cancel_ifs_timer(ifsp, DHCP_T1_TIMER);
+ cancel_ifs_timer(ifsp, DHCP_T2_TIMER);
+ cancel_ifs_timer(ifsp, DHCP_LEASE_TIMER);
+}
+
+/*
+ * schedule_ifs_timer(): schedules a lease-related timer on an interface
+ *
+ * input: struct ifslist *: the interface to operate on
+ * int: the timer to schedule
+ * uint32_t: the number of seconds in the future it should fire
+ * iu_tq_callback_t *: the callback to call upon firing
+ * output: int: 1 if the timer was scheduled successfully, 0 on failure
+ */
+
+int
+schedule_ifs_timer(struct ifslist *ifsp, int timer_id, uint32_t sec,
+ iu_tq_callback_t *expire)
+{
+ cancel_ifs_timer(ifsp, timer_id); /* just in case */
+
+ ifsp->if_timer[timer_id] = iu_schedule_timer(tq, sec, expire, ifsp);
+ if (ifsp->if_timer[timer_id] == -1) {
+ dhcpmsg(MSG_WARNING, "schedule_ifs_timer: cannot schedule "
+ "if_timer[%d]", timer_id);
+ return (0);
+ }
+
+ hold_ifs(ifsp);
+ return (1);
+}
+
+/*
+ * 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.
+ * unsigned int *: 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. This code is derived from
+ * usr/src/lib/libwanboot/common/bootinfo_aux.c.
+ */
+
+static boolean_t
+get_prom_prop(const char *nodename, const char *propname, uchar_t **propvaluep,
+ unsigned int *lenp)
+{
+ di_node_t root_node = DI_NODE_NIL;
+ 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);
+}
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/interface.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/interface.h
new file mode 100644
index 0000000000..c525143060
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/interface.h
@@ -0,0 +1,385 @@
+/*
+ * 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.
+ */
+
+#ifndef INTERFACE_H
+#define INTERFACE_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * 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 an ifslist -- please read the comments in the ifslist structure
+ * definition below for the rules on accessing various fields.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <net/if.h> /* IFNAMSIZ */
+#include <sys/types.h>
+#include <netinet/dhcp.h>
+#include <dhcpagent_ipc.h>
+#include <libinetutil.h>
+
+#include "async.h"
+#include "agent.h"
+#include "dlpi_io.h"
+#include "ipc_action.h"
+#include "packet.h"
+#include "util.h"
+
+enum { DHCP_T1_TIMER, DHCP_T2_TIMER, DHCP_LEASE_TIMER };
+
+typedef int script_callback_t (struct ifslist *, const char *);
+
+struct ifslist {
+
+ /*
+ * ifslist chain pointers, maintained by insert_ifs() /
+ * remove_ifs().
+ */
+
+ struct ifslist *next;
+ struct ifslist *prev;
+
+ /*
+ * hold count on this ifslist, maintained by hold_ifs() /
+ * release_ifs() -- see below for a discussion of ifs memory
+ * management.
+ */
+
+ uchar_t if_hold_count;
+
+ /*
+ * each interface 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.
+ */
+
+ struct ipc_action if_ia;
+ struct async_action if_async;
+
+ /*
+ * current state of the interface
+ */
+
+ DHCPSTATE if_state;
+
+ /*
+ * flags specific to DHCP (see dhcpagent_ipc.h)
+ */
+
+ uint16_t if_dflags;
+
+ /*
+ * general interface information -- this information is initialized
+ * in insert_ifs() and does not change over the lifetime of the
+ * interface.
+ */
+
+ char if_name[IFNAMSIZ];
+
+ uint16_t if_max; /* largest DHCP packet on this if */
+ uint16_t if_min; /* minimum mtu size on this if */
+ uint16_t if_opt; /* amount of space for options in PKT */
+
+ uchar_t *if_hwaddr; /* our link-layer address */
+ uchar_t if_hwlen; /* our link-layer address len */
+ uchar_t if_hwtype; /* type of link-layer */
+
+ uchar_t *if_cid; /* client id, if set in defaults file */
+ uchar_t if_cidlen; /* client id len */
+
+ uchar_t *if_prl; /* if non-NULL, param request list */
+ uchar_t if_prllen; /* param request list len */
+
+ /*
+ * the destination address is the broadcast address of
+ * the interface, in DLPI terms (which means it
+ * includes both a link-layer broadcast address and a
+ * sap, and the order isn't consistent.) fun, huh?
+ * blame AT&T. we store it as a token like this
+ * because it's generally how we need to use it. we
+ * can pull it apart using the saplen and sap_before
+ * fields below.
+ */
+
+ uchar_t *if_daddr; /* our destination address */
+ uchar_t if_dlen; /* our destination address len */
+
+ uchar_t if_saplen; /* the SAP len */
+ uchar_t if_sap_before; /* does SAP come before address? */
+
+ /*
+ * network descriptors; one is used for the DLPI
+ * traffic before we have our IP address configured;
+ * the other two are used afterwards. there have to
+ * be two socket descriptors since:
+ *
+ * o we need one to be bound to IPPORT_BOOTPC and
+ * and INADDR_BROADCAST, so it can receive all
+ * broadcast traffic. this is if_sock_fd. it
+ * is also used as a general descriptor to perform
+ * socket-related ioctls on, like SIOCGIFFLAGS.
+ *
+ * o we need another to be bound to IPPORT_BOOTPC and
+ * the IP address given to us by the DHCP server,
+ * so we can guarantee the IP address of outgoing
+ * packets when multihomed. (the problem being that
+ * if a packet goes out with the wrong IP address,
+ * then the server's response will come back on the
+ * wrong interface). this is if_sock_ip_fd.
+ *
+ * note that if_sock_fd is created in init_ifs() but
+ * not bound until dhcp_bound(); this is because we
+ * cannot even bind to the broadcast address until we
+ * have an IP address.
+ *
+ * if_sock_ip_fd isn't created until dhcp_bound(),
+ * since we don't need it until then and we can't
+ * bind it until after we have an IP address anyway.
+ *
+ * both socket descriptors are closed in reset_ifs().
+ */
+
+ int if_dlpi_fd;
+ int if_sock_fd;
+ int if_sock_ip_fd;
+
+ /*
+ * 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_ifs().
+ */
+
+ iu_timer_id_t if_timer[3]; /* T1, T2, and LEASE timers */
+
+ lease_t if_t1; /* relative renewal start time, hbo */
+ lease_t if_t2; /* relative rebinding start time, hbo */
+ lease_t if_lease; /* relative expire time, hbo */
+
+ unsigned int if_nrouters; /* the number of default routers */
+ struct in_addr *if_routers; /* an array of default routers */
+ struct in_addr if_server; /* our DHCP server, nbo */
+
+ /*
+ * 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.
+ */
+
+ struct in_addr if_addr; /* our IP address, nbo */
+ struct in_addr if_netmask; /* our netmask, nbo */
+ struct in_addr if_broadcast; /* our broadcast address, nbo */
+
+ PKT_LIST *if_ack; /* ACK from the server */
+
+ /*
+ * We retain the very first ack obtained on the interface 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.
+ */
+
+ PKT_LIST *if_orig_ack;
+
+ /*
+ * other miscellaneous variables set or needed in the process
+ * of acquiring a lease.
+ */
+
+ int if_offer_wait; /* seconds between offers */
+ iu_event_id_t if_offer_id; /* event offer id */
+ iu_event_id_t if_acknak_id; /* event acknak id */
+ iu_event_id_t if_acknak_bcast_id;
+
+ /*
+ * `if_neg_monosec' represents the time since lease
+ * acquisition or renewal began, and is used for
+ * computing the pkt->secs field. `if_newstart_monosec'
+ * represents the time the ACKed REQUEST was sent,
+ * which represents the start time of a new lease.
+ * when the lease actually begins (and thus becomes
+ * current), `if_curstart_monosec' is set to
+ * `if_newstart_monosec'.
+ */
+
+ monosec_t if_neg_monosec;
+ monosec_t if_newstart_monosec;
+ monosec_t if_curstart_monosec;
+
+ /*
+ * time we sent the DISCOVER relative to if_neg_monosec,
+ * so that the REQUEST can have the same pkt->secs.
+ */
+
+ uint16_t if_disc_secs;
+
+ /*
+ * the host name we've been asked to request is remembered
+ * here between the DISCOVER and the REQUEST
+ */
+ char *if_reqhost;
+
+ /*
+ * this is a chain of packets which have been received on this
+ * interface 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 *if_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 if_sent;
+ uint32_t if_received;
+ uint32_t if_bad_offers;
+
+ /*
+ * if_send_pkt.pkt is dynamically allocated to be as big a
+ * packet as we can send out on this interface. the remainder
+ * of this information is needed to make it easy to handle
+ * retransmissions. note that other than if_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 if_send_pkt;
+ uint32_t if_send_timeout;
+ struct sockaddr_in if_send_dest;
+ stop_func_t *if_send_stop_func;
+ uint32_t if_packet_sent;
+ iu_timer_id_t if_retrans_timer;
+
+ int if_script_fd;
+ pid_t if_script_pid;
+ pid_t if_script_helper_pid;
+ const char *if_script_event;
+ iu_event_id_t if_script_event_id;
+ const char *if_callback_msg;
+ script_callback_t *if_script_callback;
+};
+
+/*
+ * a word on memory management and ifslists:
+ *
+ * since ifslists 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, ifslists are reference counted.
+ * here are the rules for managing ifslists:
+ *
+ * an ifslist is created through insert_ifs(). along with
+ * initializing the ifslist, this puts a hold on the ifslist through
+ * hold_ifs().
+ *
+ * whenever an ifslist is released or dropped (implicitly or
+ * explicitly), remove_ifs() is called, which sets the DHCP_IF_REMOVED
+ * flag and removes the interface from the internal list of managed
+ * interfaces. lastly, remove_ifs() calls release_ifs() to remove the
+ * hold acquired in insert_ifs(). if this decrements the hold count
+ * on the interface to zero, then free_ifs() is called. if there are
+ * holds other than the hold acquired in insert_ifs(), the hold count
+ * will still be > 0, and the interface will remain allocated (though
+ * dormant).
+ *
+ * whenever a callback is scheduled against an ifslist, another hold
+ * must be put on the ifslist through hold_ifs().
+ *
+ * whenever a callback is called back against an ifslist,
+ * release_ifs() must be called to decrement the hold count, which may
+ * end up freeing the ifslist if the hold count becomes zero.
+ *
+ * if release_ifs() returns 0, then there are no remaining holds
+ * against this ifslist, and the ifslist in fact no longer exists.
+ *
+ * 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_ifs() if
+ * the cancellation succeeds. this is done in remove_ifs() for the
+ * lease, t1, and t2 callbacks.
+ *
+ * in general, a callback should also call verify_ifs() when it gets
+ * called back in addition to release_ifs(), to make sure that the
+ * interface is still in fact under the dhcpagent's control. to make
+ * coding simpler, there is a third function, check_ifs(), which
+ * performs both the release_ifs() and the verify_ifs(). in addition,
+ * if check_ifs() detects that the callback has the last hold against
+ * a given interface, it informs it instead of performing the final
+ * release, and thus allows it to clean up appropriately before
+ * performing the final release.
+ */
+
+int canonize_ifs(struct ifslist *);
+int check_ifs(struct ifslist *);
+void hold_ifs(struct ifslist *);
+struct ifslist *insert_ifs(const char *, boolean_t, int *);
+struct ifslist *lookup_ifs(const char *);
+struct ifslist *lookup_ifs_by_xid(uint32_t);
+void nuke_ifslist(boolean_t);
+void refresh_ifslist(iu_eh_t *, int, void *);
+int release_ifs(struct ifslist *);
+void remove_ifs(struct ifslist *);
+void reset_ifs(struct ifslist *);
+int verify_ifs(struct ifslist *);
+unsigned int ifs_count(void);
+void cancel_ifs_timers(struct ifslist *);
+int schedule_ifs_timer(struct ifslist *, int, uint32_t,
+ iu_tq_callback_t *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* INTERFACE_H */
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/ipc_action.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/ipc_action.c
new file mode 100644
index 0000000000..f29730d377
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/ipc_action.c
@@ -0,0 +1,187 @@
+/*
+ * 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.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <sys/poll.h>
+#include <dhcpmsg.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: struct ifslist *: the interface to initialize it for
+ * output: void
+ */
+
+void
+ipc_action_init(struct ifslist *ifsp)
+{
+ ifsp->if_ia.ia_tid = -1;
+}
+
+/*
+ * ipc_action_start(): starts an ipc_action request on an interface
+ *
+ * input: struct ifslist *: the interface to start the action on
+ * dhcp_ipc_request_t *: the type of request
+ * int: the descriptor to contact the action requestor
+ * output: int: 1 if the request is started successfully, 0 otherwise
+ */
+
+int
+ipc_action_start(struct ifslist *ifsp, dhcp_ipc_request_t *request, int fd)
+{
+ if (request->timeout == DHCP_IPC_WAIT_DEFAULT)
+ request->timeout = DHCP_IPC_DEFAULT_WAIT;
+
+ ifsp->if_ia.ia_request = request;
+ ifsp->if_ia.ia_fd = fd;
+ ifsp->if_ia.ia_cmd = DHCP_IPC_CMD(request->message_type);
+
+ if (request->timeout == DHCP_IPC_WAIT_FOREVER)
+ ifsp->if_ia.ia_tid = -1;
+ else {
+ ifsp->if_ia.ia_tid = iu_schedule_timer(tq, request->timeout,
+ ipc_action_timeout, ifsp);
+
+ if (ifsp->if_ia.ia_tid == -1)
+ return (0);
+
+ hold_ifs(ifsp);
+ }
+
+ return (1);
+}
+
+/*
+ * ipc_action_pending(): checks if there is a pending ipc_action request on
+ * an interface
+ *
+ * input: struct ifslist *: the interface to check for a pending ipc_action on
+ * output: boolean_t: B_TRUE if there is a pending ipc_action request
+ */
+
+boolean_t
+ipc_action_pending(struct ifslist *ifsp)
+{
+ struct pollfd pollfd;
+
+ if (ifsp->if_ia.ia_fd > 0) {
+
+ pollfd.events = POLLIN;
+ pollfd.fd = ifsp->if_ia.ia_fd;
+
+ if (poll(&pollfd, 1, 0) == 0)
+ return (B_TRUE);
+ }
+
+ return (B_FALSE);
+}
+
+/*
+ * ipc_action_finish(): completes an ipc_action request on an interface
+ *
+ * input: struct ifslist *: the interface to complete the action on
+ * int: the reason why the action finished (nonzero on error)
+ * output: void
+ */
+
+void
+ipc_action_finish(struct ifslist *ifsp, int reason)
+{
+ struct ipc_action *ia = &ifsp->if_ia;
+
+ if (ipc_action_pending(ifsp) == B_FALSE)
+ return;
+
+ if (reason == 0)
+ send_ok_reply(ia->ia_request, &ia->ia_fd);
+ else
+ send_error_reply(ia->ia_request, reason, &ia->ia_fd);
+
+ ipc_action_cancel_timer(ifsp);
+}
+
+/*
+ * ipc_action_timeout(): times out an ipc_action on an interface (the request
+ * continues asynchronously, however)
+ *
+ * input: iu_tq_t *: unused
+ * void *: the struct ifslist * the ipc_action was pending on
+ * output: void
+ */
+
+/* ARGSUSED */
+static void
+ipc_action_timeout(iu_tq_t *tq, void *arg)
+{
+ struct ifslist *ifsp = (struct ifslist *)arg;
+ struct ipc_action *ia = &ifsp->if_ia;
+
+ if (check_ifs(ifsp) == 0) {
+ (void) release_ifs(ifsp);
+ return;
+ }
+
+ dhcpmsg(MSG_VERBOSE, "ipc timeout waiting for agent to complete "
+ "command %d for %s", ia->ia_cmd, ifsp->if_name);
+
+ send_error_reply(ia->ia_request, DHCP_IPC_E_TIMEOUT, &ia->ia_fd);
+ ia->ia_tid = -1;
+}
+
+/*
+ * ipc_action_cancel_timer(): cancels the pending ipc_action timer for this
+ * request
+ *
+ * input: struct ifslist *: the interface with a pending request to cancel
+ * output: void
+ */
+
+void
+ipc_action_cancel_timer(struct ifslist *ifsp)
+{
+ if (ifsp->if_ia.ia_tid == -1)
+ return;
+
+ /*
+ * 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 ifsp, it shouldn't hurt us
+ */
+
+ if (iu_cancel_timer(tq, ifsp->if_ia.ia_tid, NULL) == 1) {
+ ifsp->if_ia.ia_tid = -1;
+ (void) release_ifs(ifsp);
+ }
+}
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/ipc_action.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/ipc_action.h
new file mode 100644
index 0000000000..8ecc62189e
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/ipc_action.h
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+#ifndef IPC_ACTION_H
+#define IPC_ACTION_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <netinet/dhcp.h>
+#include <dhcpagent_ipc.h>
+#include <libinetutil.h>
+
+#include "agent.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
+
+struct ifslist; /* forward declaration */
+
+void ipc_action_init(struct ifslist *);
+int ipc_action_start(struct ifslist *, dhcp_ipc_request_t *, int);
+void ipc_action_finish(struct ifslist *, int);
+boolean_t ipc_action_pending(struct ifslist *);
+void ipc_action_cancel_timer(struct ifslist *);
+
+
+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 */
+ dhcp_ipc_request_t *ia_request; /* ipc request pointer */
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* IPC_ACTION_H */
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/packet.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/packet.c
new file mode 100644
index 0000000000..4beb5524c0
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/packet.c
@@ -0,0 +1,729 @@
+/*
+ * 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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <string.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+#include <dhcpmsg.h>
+#include <stddef.h>
+#include <assert.h>
+
+#include "states.h"
+#include "interface.h"
+#include "agent.h"
+#include "packet.h"
+#include "util.h"
+
+static double fuzzify(uint32_t, double);
+static void retransmit(iu_tq_t *, void *);
+static uint32_t next_retransmission(uint32_t);
+static int send_pkt_internal(struct ifslist *);
+static uchar_t pkt_type(PKT *);
+
+/*
+ * dhcp_type_ptob(): converts the DHCP packet type values in RFC2131 into
+ * values which can be used for recv_pkt()
+ *
+ * input: uchar_t: a DHCP packet type value, as defined in RFC2131
+ * output: dhcp_message_type_t: a packet type value for use with recv_pkt()
+ */
+
+static dhcp_message_type_t
+dhcp_type_ptob(uchar_t 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]);
+
+ return (0);
+}
+
+/*
+ * pkt_type(): returns an integer representing the packet's type; only
+ * for use with outbound packets.
+ *
+ * input: PKT *: the packet to examine
+ * output: uchar_t: the packet type (0 if unknown)
+ */
+
+static uchar_t
+pkt_type(PKT *pkt)
+{
+ uchar_t *option = pkt->options;
+
+ /*
+ * 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.
+ */
+
+ while (*option != CD_DHCP_TYPE) {
+ if (option + 2 - pkt->options >= sizeof (pkt->options))
+ return (0);
+
+ option++;
+ option += *option;
+ }
+
+ return (option[2]);
+}
+
+/*
+ * init_pkt(): initializes and returns a packet of a given type
+ *
+ * input: struct ifslist *: the interface the packet will be going out
+ * uchar_t: the packet type (DHCP message type)
+ * output: dhcp_pkt_t *: a pointer to the initialized packet
+ */
+
+dhcp_pkt_t *
+init_pkt(struct ifslist *ifsp, uchar_t type)
+{
+ uint8_t bootmagic[] = BOOTMAGIC;
+ dhcp_pkt_t *dpkt = &ifsp->if_send_pkt;
+ uint32_t xid;
+
+ dpkt->pkt_max_len = ifsp->if_max;
+ dpkt->pkt_cur_len = offsetof(PKT, options);
+
+ (void) memset(dpkt->pkt, 0, ifsp->if_max);
+ (void) memcpy(dpkt->pkt->cookie, bootmagic, sizeof (bootmagic));
+ if (ifsp->if_hwlen <= sizeof (dpkt->pkt->chaddr)) {
+ dpkt->pkt->hlen = ifsp->if_hwlen;
+ (void) memcpy(dpkt->pkt->chaddr, ifsp->if_hwaddr,
+ ifsp->if_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 BOUND/REBINDING/RENEWING state, do not set
+ * this bit, as it can respond to unicast responses
+ * from server using the 'ciaddr' address.
+ */
+ if ((type == DISCOVER) || ((type == REQUEST) &&
+ (ifsp->if_state != RENEWING) &&
+ (ifsp->if_state != REBINDING) &&
+ (ifsp->if_state != BOUND)))
+ dpkt->pkt->flags = htons(BCAST_MASK);
+ }
+
+ /*
+ * since multiple dhcp leases may be maintained over the same dlpi
+ * device (e.g. "hme0" and "hme0:1"), make sure the xid is unique.
+ */
+
+ do {
+ xid = mrand48();
+ } while (lookup_ifs_by_xid(xid) != NULL);
+
+ dpkt->pkt->xid = xid;
+ dpkt->pkt->op = BOOTREQUEST;
+ dpkt->pkt->htype = ifsp->if_hwtype;
+
+ add_pkt_opt(dpkt, CD_DHCP_TYPE, &type, 1);
+ add_pkt_opt(dpkt, CD_CLIENT_ID, ifsp->if_cid, ifsp->if_cidlen);
+
+ return (dpkt);
+}
+
+/*
+ * add_pkt_opt(): adds an option to a dhcp_pkt_t
+ *
+ * input: dhcp_pkt_t *: the packet to add the option to
+ * uchar_t: the type of option being added
+ * const void *: the value of that option
+ * uchar_t: the length of the value of the option
+ * output: void
+ */
+
+void
+add_pkt_opt(dhcp_pkt_t *dpkt, uchar_t opt_type, const void *opt_val,
+ uchar_t opt_len)
+{
+ caddr_t raw_pkt = (caddr_t)dpkt->pkt;
+ int16_t req_len = opt_len + 2; /* + 2 for code & length bytes */
+
+ /* CD_END and CD_PAD options don't have a length field */
+ if (opt_type == CD_END || opt_type == CD_PAD)
+ req_len--;
+ else if (opt_val == NULL)
+ return;
+
+ if ((dpkt->pkt_cur_len + req_len) > dpkt->pkt_max_len) {
+ dhcpmsg(MSG_WARNING, "add_pkt_opt: not enough room for option "
+ "%d in packet", opt_type);
+ return;
+ }
+
+ raw_pkt[dpkt->pkt_cur_len++] = opt_type;
+
+ if (opt_len > 0) {
+ raw_pkt[dpkt->pkt_cur_len++] = opt_len;
+ (void) memcpy(&raw_pkt[dpkt->pkt_cur_len], opt_val, opt_len);
+ dpkt->pkt_cur_len += opt_len;
+ }
+}
+
+/*
+ * 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
+ * uchar_t: the type of option being added
+ * uint16_t: the value of that option
+ * output: void
+ */
+
+void
+add_pkt_opt16(dhcp_pkt_t *dpkt, uchar_t opt_type, uint16_t opt_value)
+{
+ 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
+ * uchar_t: the type of option being added
+ * uint32_t: the value of that option
+ * output: void
+ */
+
+void
+add_pkt_opt32(dhcp_pkt_t *dpkt, uchar_t opt_type, uint32_t opt_value)
+{
+ add_pkt_opt(dpkt, opt_type, &opt_value, 4);
+}
+
+/*
+ * get_pkt_times(): pulls the lease times out of a packet and stores them as
+ * host-byteorder 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
+ */
+
+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 ||
+ ack->opts[CD_LEASE_TIME] == NULL ||
+ ack->opts[CD_LEASE_TIME]->len != sizeof (lease_t))
+ return;
+
+ (void) memcpy(lease, ack->opts[CD_LEASE_TIME]->value, sizeof (lease_t));
+ *lease = ntohl(*lease);
+
+ if (*lease == DHCP_PERM)
+ 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 ((*t1 == DHCP_PERM) || (*t1 >= *lease))
+ *t1 = (lease_t)fuzzify(*lease, DHCP_T1_FACT);
+
+ 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 ((*t2 == DHCP_PERM) || (*t2 > *lease) || (*t2 <= *t1))
+ *t2 = (lease_t)fuzzify(*lease, DHCP_T2_FACT);
+}
+
+/*
+ * fuzzify(): adds some "fuzz" to a t1/t2 time, in accordance with RFC2131
+ *
+ * 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));
+}
+
+/*
+ * free_pkt_list(): frees a packet list
+ *
+ * input: PKT_LIST **: the packet list to free
+ * output: void
+ */
+
+void
+free_pkt_list(PKT_LIST **plp)
+{
+ PKT_LIST *plp_next;
+
+ for (; *plp != NULL; *plp = plp_next) {
+ plp_next = (*plp)->next;
+ free((*plp)->pkt);
+ free(*plp);
+ }
+}
+
+/*
+ * prepend_to_pkt_list(): prepends a packet to a packet list
+ *
+ * input: PKT_LIST **: the packet list
+ * PKT_LIST *: the packet to prepend
+ * output: void
+ */
+
+static void
+prepend_to_pkt_list(PKT_LIST **list_head, PKT_LIST *new_entry)
+{
+ new_entry->next = *list_head;
+ new_entry->prev = NULL;
+
+ if (*list_head != NULL)
+ (*list_head)->prev = new_entry;
+
+ *list_head = new_entry;
+}
+
+/*
+ * remove_from_pkt_list(): removes a given packet from a packet list
+ *
+ * input: PKT_LIST **: the packet list
+ * PKT_LIST *: the packet to remove
+ * output: void
+ */
+
+void
+remove_from_pkt_list(PKT_LIST **list_head, PKT_LIST *remove)
+{
+ if (*list_head == NULL)
+ return;
+
+ if (*list_head == remove) {
+ *list_head = remove->next;
+ if (*list_head != NULL)
+ (*list_head)->prev = NULL;
+ } else {
+ remove->prev->next = remove->next;
+ if (remove->next != NULL)
+ remove->next->prev = remove->prev;
+ }
+
+ remove->next = NULL;
+ remove->prev = NULL;
+}
+
+/*
+ * send_pkt_internal(): sends a packet out on an interface
+ *
+ * input: struct ifslist *: the interface to send the packet out on
+ * output: int: 1 if the packet is sent, 0 otherwise
+ */
+
+static int
+send_pkt_internal(struct ifslist *ifsp)
+{
+ ssize_t n_bytes;
+ dhcp_pkt_t *dpkt = &ifsp->if_send_pkt;
+ const char *pkt_name = pkt_type_to_string(pkt_type(dpkt->pkt));
+
+ /*
+ * 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 (ifsp->if_send_timeout != 0) {
+ if ((ifsp->if_retrans_timer = iu_schedule_timer_ms(tq,
+ ifsp->if_send_timeout, retransmit, ifsp)) == -1)
+ dhcpmsg(MSG_WARNING, "send_pkt_internal: cannot "
+ "schedule retransmit timer for %s packet",
+ pkt_name);
+ else
+ hold_ifs(ifsp);
+ }
+
+ /*
+ * 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 `if_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 (pkt_type(dpkt->pkt)) {
+
+ case DISCOVER:
+ ifsp->if_newstart_monosec = monosec();
+ ifsp->if_disc_secs = monosec() - ifsp->if_neg_monosec;
+ dpkt->pkt->secs = htons(ifsp->if_disc_secs);
+ break;
+
+ case INFORM:
+ dpkt->pkt->secs = htons(monosec() - ifsp->if_neg_monosec);
+ break;
+
+ case REQUEST:
+ ifsp->if_newstart_monosec = monosec();
+
+ if (ifsp->if_state == REQUESTING) {
+ dpkt->pkt->secs = htons(ifsp->if_disc_secs);
+ break;
+ }
+
+ dpkt->pkt->secs = htons(monosec() - ifsp->if_neg_monosec);
+ break;
+
+ default:
+ dpkt->pkt->secs = htons(0);
+ }
+
+ switch (ifsp->if_state) {
+
+ case BOUND:
+ case RENEWING:
+ case REBINDING:
+ n_bytes = sendto(ifsp->if_sock_ip_fd, dpkt->pkt,
+ dpkt->pkt_cur_len, 0,
+ (struct sockaddr *)&ifsp->if_send_dest,
+ sizeof (struct sockaddr_in));
+ break;
+
+ default:
+ n_bytes = dlpi_sendto(ifsp->if_dlpi_fd, dpkt->pkt,
+ dpkt->pkt_cur_len, &ifsp->if_send_dest,
+ ifsp->if_daddr, ifsp->if_dlen);
+ break;
+ }
+
+ if (n_bytes != dpkt->pkt_cur_len) {
+ if (ifsp->if_retrans_timer == -1)
+ dhcpmsg(MSG_WARNING, "send_pkt_internal: cannot send "
+ "%s packet to server", pkt_name);
+ else
+ dhcpmsg(MSG_WARNING, "send_pkt_internal: cannot send "
+ "%s packet to server (will retry in %u seconds)",
+ pkt_name, ifsp->if_send_timeout / MILLISEC);
+ return (0);
+ }
+
+ dhcpmsg(MSG_VERBOSE, "sent %s packet out %s", pkt_name,
+ ifsp->if_name);
+
+ ifsp->if_packet_sent++;
+ ifsp->if_sent++;
+ return (1);
+}
+
+/*
+ * send_pkt(): sends a packet out on an interface
+ *
+ * input: struct ifslist *: the interface to send the packet out on
+ * 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: int: 1 if the packet was sent, 0 otherwise
+ */
+
+int
+send_pkt(struct ifslist *ifsp, 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));
+
+ ifsp->if_packet_sent = 0;
+
+ (void) memset(&ifsp->if_send_dest, 0, sizeof (ifsp->if_send_dest));
+ ifsp->if_send_dest.sin_addr.s_addr = dest;
+ ifsp->if_send_dest.sin_family = AF_INET;
+ ifsp->if_send_dest.sin_port = htons(IPPORT_BOOTPS);
+ ifsp->if_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 == &ifsp->if_send_pkt);
+
+ /*
+ * clear out any packets which had been previously received
+ * but not pulled off of the recv_packet queue.
+ */
+
+ free_pkt_list(&ifsp->if_recv_pkt_list);
+
+ if (stop == NULL) {
+ ifsp->if_retrans_timer = -1;
+ ifsp->if_send_timeout = 0; /* prevents retransmissions */
+ } else
+ ifsp->if_send_timeout = next_retransmission(0);
+
+ return (send_pkt_internal(ifsp));
+}
+
+/*
+ * retransmit(): retransmits the current packet on an interface
+ *
+ * input: iu_tq_t *: unused
+ * void *: the struct ifslist * to send the packet on
+ * output: void
+ */
+
+/* ARGSUSED */
+static void
+retransmit(iu_tq_t *tqp, void *arg)
+{
+ struct ifslist *ifsp = (struct ifslist *)arg;
+
+ if (check_ifs(ifsp) == 0) {
+ (void) release_ifs(ifsp);
+ return;
+ }
+
+ /*
+ * check the callback to see if we should keep sending retransmissions
+ */
+
+ if (ifsp->if_send_stop_func(ifsp, ifsp->if_packet_sent))
+ return;
+
+ ifsp->if_send_timeout = next_retransmission(ifsp->if_send_timeout);
+ (void) send_pkt_internal(ifsp);
+}
+
+/*
+ * stop_pkt_retransmission(): stops retransmission of last sent packet
+ *
+ * input: struct ifslist *: the interface to stop retransmission on
+ * output: void
+ */
+
+void
+stop_pkt_retransmission(struct ifslist *ifsp)
+{
+ if (ifsp->if_retrans_timer != -1) {
+ if (iu_cancel_timer(tq, ifsp->if_retrans_timer, NULL) == 1) {
+ (void) release_ifs(ifsp);
+ ifsp->if_retrans_timer = -1;
+ }
+ }
+}
+
+/*
+ * recv_pkt(): receives packets on an interface (put on ifsp->if_recv_pkt_list)
+ *
+ * input: struct ifslist *: the interface to receive packets on
+ * int: the file descriptor to receive the packet on
+ * dhcp_message_type_t: the types of packets to receive
+ * boolean_t: if B_TRUE, more than one packet can be received
+ * output: int: 1 if a packet was received successfully, 0 otherwise
+ */
+
+int
+recv_pkt(struct ifslist *ifsp, int fd, dhcp_message_type_t type,
+ boolean_t chain)
+{
+ PKT_LIST *plp;
+ PKT *pkt;
+ ssize_t retval;
+ uchar_t recv_pkt_type;
+ const char *recv_pkt_name;
+
+ /*
+ * collect replies. chain them up if the chain flag is set
+ * and we've already got one, otherwise drop the packet.
+ * calloc the PKT_LIST since dhcp_options_scan() relies on it
+ * being zeroed.
+ */
+
+ pkt = calloc(1, ifsp->if_max);
+ plp = calloc(1, sizeof (PKT_LIST));
+ if (pkt == NULL || plp == NULL) {
+ dhcpmsg(MSG_ERR, "recv_pkt: dropped packet");
+ goto failure;
+ }
+
+ plp->pkt = pkt;
+
+ switch (ifsp->if_state) {
+
+ case BOUND:
+ case RENEWING:
+ case REBINDING:
+ retval = recvfrom(fd, pkt, ifsp->if_max, 0, NULL, 0);
+ break;
+
+ default:
+ retval = dlpi_recvfrom(fd, pkt, ifsp->if_max, 0);
+ break;
+ }
+
+ if (retval == -1) {
+ dhcpmsg(MSG_ERR, "recv_pkt: recvfrom failed, dropped");
+ goto failure;
+ }
+
+ plp->len = retval;
+
+ 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;
+ }
+
+ /*
+ * make sure the packet we got in was one we were expecting --
+ * it needs to have the right type and to have the same xid.
+ */
+
+ if (plp->opts[CD_DHCP_TYPE] != NULL)
+ recv_pkt_type = *plp->opts[CD_DHCP_TYPE]->value;
+ else
+ recv_pkt_type = 0;
+
+ recv_pkt_name = pkt_type_to_string(recv_pkt_type);
+
+ if ((dhcp_type_ptob(recv_pkt_type) & type) == 0) {
+ dhcpmsg(MSG_VERBOSE, "received unexpected %s packet on "
+ "%s, dropped", recv_pkt_name, ifsp->if_name);
+ goto failure;
+ }
+
+ /* the xid is opaque -- no byteorder work */
+ if (plp->pkt->xid != ifsp->if_send_pkt.pkt->xid) {
+ dhcpmsg(MSG_VERBOSE, "received unexpected packet xid (%#x "
+ "instead of %#x) on %s, dropped", plp->pkt->xid,
+ ifsp->if_send_pkt.pkt->xid, ifsp->if_name);
+ goto failure;
+ }
+
+ if (ifsp->if_recv_pkt_list != NULL) {
+ if (chain == B_FALSE) {
+ dhcpmsg(MSG_WARNING, "recv_pkt: unexpected additional "
+ "%s packet, dropped", recv_pkt_name);
+ goto failure;
+ }
+ }
+
+ dhcpmsg(MSG_VERBOSE, "received %s packet on %s", recv_pkt_name,
+ ifsp->if_name);
+
+ prepend_to_pkt_list(&ifsp->if_recv_pkt_list, plp);
+ ifsp->if_received++;
+ return (1);
+
+failure:
+ free(pkt);
+ free(plp);
+ return (0);
+}
+
+/*
+ * next_retransmission(): returns the number of seconds until the next
+ * retransmission, based on the algorithm in RFC2131
+ *
+ * input: uint32_t: the number of milliseconds for the last retransmission
+ * output: uint32_t: the number of milliseconds until the next retransmission
+ */
+
+static uint32_t
+next_retransmission(uint32_t last_timeout_ms)
+{
+ uint32_t timeout_ms;
+
+ /*
+ * start at 4, and increase by a factor of 2 up to 64. at each
+ * iteration, jitter the timeout by some fraction of a second.
+ */
+ if (last_timeout_ms == 0)
+ timeout_ms = 4 * MILLISEC;
+ else
+ timeout_ms = MIN(last_timeout_ms << 1, 64 * MILLISEC);
+
+ return (timeout_ms + ((lrand48() % (2 * MILLISEC)) - MILLISEC));
+}
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/packet.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/packet.h
new file mode 100644
index 0000000000..865bc25bd3
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/packet.h
@@ -0,0 +1,115 @@
+/*
+ * 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-2001 by Sun Microsystems, Inc.
+ * All rights reserved.
+ */
+
+#ifndef _PACKET_H
+#define _PACKET_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <sys/sysmacros.h> /* MIN, MAX, ... */
+#include <netinet/in.h>
+#include <netinet/dhcp.h>
+#include <dhcp_impl.h>
+
+#include "agent.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
+
+struct ifslist; /* forward declaration */
+
+/*
+ * 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.
+ */
+
+typedef enum {
+
+ DHCP_PUNTYPED = 0x001, /* untyped (BOOTP) message */
+ DHCP_PDISCOVER = 0x002,
+ DHCP_POFFER = 0x004,
+ DHCP_PREQUEST = 0x008,
+ DHCP_PDECLINE = 0x010,
+ DHCP_PACK = 0x020,
+ DHCP_PNAK = 0x040,
+ DHCP_PRELEASE = 0x080,
+ DHCP_PINFORM = 0x100
+
+} dhcp_message_type_t;
+
+/*
+ * a dhcp_pkt_t is (right now) what is used by the 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.
+ */
+
+typedef struct dhcp_pkt {
+
+ PKT *pkt; /* the real underlying packet */
+ unsigned int pkt_max_len; /* its maximum length */
+ unsigned int pkt_cur_len; /* its current length */
+
+} 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(struct ifslist *, unsigned int);
+
+dhcp_pkt_t *init_pkt(struct ifslist *, uchar_t);
+void add_pkt_opt(dhcp_pkt_t *, uchar_t, const void *, uchar_t);
+void add_pkt_opt16(dhcp_pkt_t *, uchar_t, uint16_t);
+void add_pkt_opt32(dhcp_pkt_t *, uchar_t, uint32_t);
+void free_pkt_list(PKT_LIST **);
+void remove_from_pkt_list(PKT_LIST **, PKT_LIST *);
+void stop_pkt_retransmission(struct ifslist *);
+int recv_pkt(struct ifslist *, int, dhcp_message_type_t, boolean_t);
+int send_pkt(struct ifslist *, dhcp_pkt_t *, in_addr_t,
+ stop_func_t *);
+void get_pkt_times(PKT_LIST *, uint32_t *, uint32_t *, uint32_t *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PACKET_H */
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/release.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/release.c
new file mode 100644
index 0000000000..3d217c77b8
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/release.c
@@ -0,0 +1,160 @@
+/*
+ * 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.
+ *
+ * DECLINE/RELEASE configuration functionality for the DHCP client.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/dhcp.h>
+#include <dhcpmsg.h>
+#include <dhcp_hostconf.h>
+#include <unistd.h>
+
+#include "packet.h"
+#include "interface.h"
+#include "states.h"
+
+/*
+ * send_decline(): sends a DECLINE message (broadcasted)
+ *
+ * input: struct ifslist *: the interface to send the DECLINE on
+ * char *: an optional text explanation to send with the message
+ * struct in_addr *: the IP address being declined
+ * output: void
+ */
+
+void
+send_decline(struct ifslist *ifsp, char *msg, struct in_addr *declined_ip)
+{
+ dhcp_pkt_t *dpkt;
+
+ dpkt = init_pkt(ifsp, DECLINE);
+ add_pkt_opt32(dpkt, CD_SERVER_ID, ifsp->if_server.s_addr);
+
+ if (msg != NULL)
+ add_pkt_opt(dpkt, CD_MESSAGE, msg, strlen(msg) + 1);
+
+ add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR, declined_ip->s_addr);
+ add_pkt_opt(dpkt, CD_END, NULL, 0);
+
+ (void) send_pkt(ifsp, dpkt, htonl(INADDR_BROADCAST), NULL);
+}
+
+/*
+ * dhcp_release(): sends a RELEASE message to a DHCP server and removes
+ * the interface from DHCP control
+ *
+ * input: struct ifslist *: the interface to send the RELEASE on and remove
+ * const char *: an optional text explanation to send with the message
+ * output: int: 1 on success, 0 on failure
+ */
+
+int
+dhcp_release(struct ifslist *ifsp, const char *msg)
+{
+ int retval = 0;
+ int error = DHCP_IPC_E_INT;
+ dhcp_pkt_t *dpkt;
+
+ if (ifsp->if_dflags & DHCP_IF_BOOTP)
+ goto out;
+
+ if (ifsp->if_state != BOUND && ifsp->if_state != RENEWING &&
+ ifsp->if_state != REBINDING)
+ goto out;
+
+ dhcpmsg(MSG_INFO, "releasing interface %s", ifsp->if_name);
+
+ dpkt = init_pkt(ifsp, RELEASE);
+ dpkt->pkt->ciaddr.s_addr = ifsp->if_addr.s_addr;
+
+ if (msg != NULL)
+ add_pkt_opt(dpkt, CD_MESSAGE, msg, strlen(msg) + 1);
+
+ add_pkt_opt32(dpkt, CD_SERVER_ID, ifsp->if_server.s_addr);
+ add_pkt_opt(dpkt, CD_END, NULL, 0);
+
+ (void) send_pkt(ifsp, dpkt, ifsp->if_server.s_addr, 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 below.
+ */
+
+ (void) usleep(500);
+ (void) canonize_ifs(ifsp);
+
+ remove_ifs(ifsp);
+ error = DHCP_IPC_SUCCESS;
+ retval = 1;
+out:
+ ipc_action_finish(ifsp, error);
+ async_finish(ifsp);
+ return (retval);
+}
+
+/*
+ * dhcp_drop(): drops the interface from DHCP control
+ *
+ * input: struct ifslist *: the interface to drop
+ * const char *: unused
+ * output: int: always 1
+ */
+
+/* ARGSUSED */
+int
+dhcp_drop(struct ifslist *ifsp, const char *msg)
+{
+ PKT_LIST *plp[2];
+
+ dhcpmsg(MSG_INFO, "dropping interface %s", ifsp->if_name);
+
+ if (ifsp->if_state == BOUND || ifsp->if_state == RENEWING ||
+ ifsp->if_state == REBINDING) {
+
+ if ((ifsp->if_dflags & DHCP_IF_BOOTP) == 0) {
+ plp[0] = ifsp->if_ack;
+ plp[1] = ifsp->if_orig_ack;
+ if (write_hostconf(ifsp->if_name, plp, 2,
+ monosec_to_time(ifsp->if_curstart_monosec)) == -1)
+ dhcpmsg(MSG_ERR, "cannot write %s (reboot will "
+ "not use cached configuration)",
+ ifname_to_hostconf(ifsp->if_name));
+ }
+ (void) canonize_ifs(ifsp);
+ }
+ remove_ifs(ifsp);
+ ipc_action_finish(ifsp, DHCP_IPC_SUCCESS);
+ async_finish(ifsp);
+ return (1);
+}
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/renew.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/renew.c
new file mode 100644
index 0000000000..8613a41245
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/renew.c
@@ -0,0 +1,367 @@
+/*
+ * 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.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <netinet/dhcp.h>
+#include <netinet/udp.h>
+#include <netinet/ip_var.h>
+#include <netinet/udp_var.h>
+#include <libinetutil.h>
+#include <dhcpmsg.h>
+#include <string.h>
+
+#include "packet.h"
+#include "agent.h"
+#include "script_handler.h"
+#include "interface.h"
+#include "states.h"
+#include "util.h"
+
+/*
+ * next_extend_time(): returns the next time an EXTEND request should be sent
+ *
+ * input: monosec_t: the absolute time when the next state is entered
+ * output: uint32_t: the number of seconds in the future to send the next
+ * EXTEND request
+ */
+
+static uint32_t
+next_extend_time(monosec_t limit_monosec)
+{
+ monosec_t current_monosec = monosec();
+
+ if (limit_monosec - current_monosec < DHCP_REBIND_MIN)
+ return (0);
+
+ return ((limit_monosec - current_monosec) / 2);
+}
+
+/*
+ * dhcp_renew(): attempts to renew a DHCP lease
+ *
+ * input: iu_tq_t *: unused
+ * void *: the ifslist to renew the lease on
+ * output: void
+ */
+
+/* ARGSUSED */
+void
+dhcp_renew(iu_tq_t *tqp, void *arg)
+{
+ struct ifslist *ifsp = (struct ifslist *)arg;
+ uint32_t next;
+
+
+ ifsp->if_timer[DHCP_T1_TIMER] = -1;
+
+ if (check_ifs(ifsp) == 0) {
+ (void) release_ifs(ifsp);
+ return;
+ }
+
+ /*
+ * sanity check: don't send packets if we're past t2.
+ */
+
+ if (monosec() > (ifsp->if_curstart_monosec + ifsp->if_t2))
+ return;
+
+ next = next_extend_time(ifsp->if_curstart_monosec + ifsp->if_t2);
+
+ /*
+ * if there isn't an async event pending, then try to renew.
+ */
+
+ if (!async_pending(ifsp))
+ if (async_start(ifsp, DHCP_EXTEND, B_FALSE) != 0)
+
+ /*
+ * try to send extend. if we don't succeed,
+ * async_timeout() will clean us up.
+ */
+
+ (void) dhcp_extending(ifsp);
+
+ /*
+ * if we're within DHCP_REBIND_MIN seconds of REBINDING, don't
+ * reschedule ourselves.
+ */
+
+ if (next == 0)
+ return;
+
+ /*
+ * no big deal if we can't reschedule; we still have the REBIND
+ * state to save us.
+ */
+
+ (void) schedule_ifs_timer(ifsp, DHCP_T1_TIMER, next, dhcp_renew);
+}
+
+/*
+ * dhcp_rebind(): attempts to renew a DHCP lease from the REBINDING state
+ *
+ * input: iu_tq_t *: unused
+ * void *: the ifslist to renew the lease on
+ * output: void
+ */
+
+/* ARGSUSED */
+void
+dhcp_rebind(iu_tq_t *tqp, void *arg)
+{
+ struct ifslist *ifsp = (struct ifslist *)arg;
+ uint32_t next;
+
+ ifsp->if_timer[DHCP_T2_TIMER] = -1;
+
+ if (check_ifs(ifsp) == 0) {
+ (void) release_ifs(ifsp);
+ return;
+ }
+
+ /*
+ * sanity check: don't send packets if we've already expired.
+ */
+
+ if (monosec() > (ifsp->if_curstart_monosec + ifsp->if_lease))
+ return;
+
+ next = next_extend_time(ifsp->if_curstart_monosec + ifsp->if_lease);
+
+ /*
+ * if this is our first venture into the REBINDING state, then
+ * reset the server address. we know the renew timer has
+ * already been cancelled (or we wouldn't be here).
+ */
+
+ if (ifsp->if_state == RENEWING) {
+ ifsp->if_state = REBINDING;
+ ifsp->if_server.s_addr = htonl(INADDR_BROADCAST);
+ }
+
+ /*
+ * if there isn't an async event pending, then try to rebind.
+ */
+
+ if (!async_pending(ifsp))
+ if (async_start(ifsp, DHCP_EXTEND, B_FALSE) != 0)
+
+ /*
+ * try to send extend. if we don't succeed,
+ * async_timeout() will clean us up.
+ */
+
+ (void) dhcp_extending(ifsp);
+
+ /*
+ * if we're within DHCP_REBIND_MIN seconds of EXPIRE, don't
+ * reschedule ourselves.
+ */
+
+ if (next == 0) {
+ dhcpmsg(MSG_WARNING, "dhcp_rebind: lease on %s expires in less "
+ "than %i seconds!", ifsp->if_name, DHCP_REBIND_MIN);
+ return;
+ }
+
+ if (schedule_ifs_timer(ifsp, DHCP_T2_TIMER, next, dhcp_rebind) == 0)
+
+ /*
+ * we'll just end up in dhcp_expire(), but it sure sucks.
+ */
+
+ dhcpmsg(MSG_CRIT, "dhcp_rebind: cannot reschedule another "
+ "rebind attempt; lease may expire for %s", ifsp->if_name);
+}
+
+/*
+ * dhcp_restart(): callback function to script_start
+ *
+ * input: struct ifslist *: the interface to be restarted
+ * const char *: unused
+ * output: int: always 1
+ */
+
+/* ARGSUSED */
+static int
+dhcp_restart(struct ifslist *ifsp, const char *msg)
+{
+ dhcpmsg(MSG_INFO, "lease expired on %s -- restarting DHCP",
+ ifsp->if_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 (ifsp->if_state == BOUND) {
+ ifsp->if_bad_offers = 0;
+ ifsp->if_sent = 0;
+ ifsp->if_received = 0;
+ }
+
+ (void) canonize_ifs(ifsp);
+
+ /* reset_ifs() in dhcp_selecting() will clean up any leftover state */
+ dhcp_selecting(ifsp);
+ return (1);
+}
+
+/*
+ * dhcp_expire(): expires a lease on a given interface and restarts DHCP
+ *
+ * input: iu_tq_t *: unused
+ * void *: the ifslist to expire the lease on
+ * output: void
+ */
+
+/* ARGSUSED */
+void
+dhcp_expire(iu_tq_t *tqp, void *arg)
+{
+ struct ifslist *ifsp = (struct ifslist *)arg;
+
+ ifsp->if_timer[DHCP_LEASE_TIMER] = -1;
+
+ if (check_ifs(ifsp) == 0) {
+ (void) release_ifs(ifsp);
+ return;
+ }
+
+ if (async_pending(ifsp))
+
+ if (async_cancel(ifsp) == 0) {
+
+ dhcpmsg(MSG_WARNING, "dhcp_expire: cannot cancel "
+ "current asynchronous command against %s",
+ ifsp->if_name);
+
+ /*
+ * try to schedule ourselves for callback.
+ * we're really situation critical here
+ * there's not much hope for us if this fails.
+ */
+
+ if (iu_schedule_timer(tq, DHCP_EXPIRE_WAIT, dhcp_expire,
+ ifsp) != -1) {
+ hold_ifs(ifsp);
+ return;
+ }
+
+ dhcpmsg(MSG_CRIT, "dhcp_expire: cannot reschedule "
+ "dhcp_expire to get called back, proceeding...");
+ }
+
+ /*
+ * 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.
+ */
+
+ if (async_start(ifsp, DHCP_START, B_FALSE) == 0)
+ dhcpmsg(MSG_WARNING, "dhcp_expire: cannot start asynchronous "
+ "transaction on %s, continuing...", ifsp->if_name);
+
+ (void) script_start(ifsp, EVENT_EXPIRE, dhcp_restart, NULL, NULL);
+}
+
+/*
+ * dhcp_extending(): sends a REQUEST to extend a lease on a given interface
+ * and registers to receive the ACK/NAK server reply
+ *
+ * input: struct ifslist *: the interface to send the REQUEST on
+ * output: int: 1 if the extension request was sent, 0 otherwise
+ */
+
+int
+dhcp_extending(struct ifslist *ifsp)
+{
+ dhcp_pkt_t *dpkt;
+
+ if (ifsp->if_state == BOUND) {
+ ifsp->if_neg_monosec = monosec();
+ ifsp->if_state = RENEWING;
+ ifsp->if_bad_offers = 0;
+ ifsp->if_sent = 0;
+ ifsp->if_received = 0;
+ }
+
+ dhcpmsg(MSG_DEBUG, "dhcp_extending: registering dhcp_acknak on %s",
+ ifsp->if_name);
+
+ if (register_acknak(ifsp) == 0) {
+
+ ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY);
+ async_finish(ifsp);
+
+ dhcpmsg(MSG_WARNING, "dhcp_extending: cannot register "
+ "dhcp_acknak for %s, not sending renew request",
+ ifsp->if_name);
+
+ return (0);
+ }
+
+ /*
+ * 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(ifsp, REQUEST);
+ dpkt->pkt->ciaddr.s_addr = ifsp->if_addr.s_addr;
+
+ add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, htons(ifsp->if_max -
+ sizeof (struct udpiphdr)));
+ add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM));
+
+ add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len);
+ add_pkt_opt(dpkt, CD_REQUEST_LIST, ifsp->if_prl, ifsp->if_prllen);
+ /*
+ * if_reqhost was set for this interface in dhcp_selecting()
+ * if the REQUEST_HOSTNAME option was set and a host name was
+ * found.
+ */
+ if (ifsp->if_reqhost != NULL) {
+ add_pkt_opt(dpkt, CD_HOSTNAME, ifsp->if_reqhost,
+ strlen(ifsp->if_reqhost));
+ }
+ add_pkt_opt(dpkt, CD_END, NULL, 0);
+
+ /*
+ * if we can't send the packet, leave the event handler registered
+ * anyway, since we're not expecting to get any other types of
+ * packets in other than ACKs/NAKs anyway.
+ */
+
+ return (send_pkt(ifsp, dpkt, ifsp->if_server.s_addr, NULL));
+}
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/request.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/request.c
new file mode 100644
index 0000000000..ab6ebb78f5
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/request.c
@@ -0,0 +1,492 @@
+/*
+ * 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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ * REQUESTING state of the client state machine.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <sys/stropts.h> /* FLUSHR/FLUSHW */
+#include <netinet/in.h>
+#include <netinet/dhcp.h>
+#include <netinet/udp.h>
+#include <netinet/ip_var.h>
+#include <netinet/udp_var.h>
+#include <dhcp_hostconf.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <dhcpmsg.h>
+
+#include "states.h"
+#include "util.h"
+#include "packet.h"
+#include "interface.h"
+#include "agent.h"
+#include "defaults.h"
+
+static PKT_LIST *select_best(PKT_LIST **);
+static void restart_dhcp(struct ifslist *);
+static stop_func_t stop_requesting;
+
+/*
+ * 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
+ *
+ * input: iu_tq_t *: unused
+ * void *: the interface receiving OFFER packets
+ * output: void
+ */
+
+/* ARGSUSED */
+void
+dhcp_requesting(iu_tq_t *tqp, void *arg)
+{
+ struct ifslist *ifsp = (struct ifslist *)arg;
+ dhcp_pkt_t *dpkt;
+ PKT_LIST *offer;
+ lease_t lease;
+
+ if (check_ifs(ifsp) == 0) {
+ (void) release_ifs(ifsp);
+ return;
+ }
+
+ /*
+ * select the best OFFER; all others pitched.
+ */
+
+ offer = select_best(&ifsp->if_recv_pkt_list);
+ if (offer == NULL) {
+
+ dhcpmsg(MSG_VERBOSE, "no OFFERs on %s, waiting...",
+ ifsp->if_name);
+
+ /*
+ * no acceptable OFFERs have come in. reschedule
+ * ourselves for callback.
+ */
+
+ if (iu_schedule_timer(tq, ifsp->if_offer_wait,
+ dhcp_requesting, ifsp) == -1) {
+
+ /*
+ * ugh. the best we can do at this point is
+ * revert back to INIT and wait for a user to
+ * restart us.
+ */
+
+ ifsp->if_state = INIT;
+ ifsp->if_dflags |= DHCP_IF_FAILED;
+
+ stop_pkt_retransmission(ifsp);
+ ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY);
+ async_finish(ifsp);
+
+ dhcpmsg(MSG_WARNING, "dhcp_requesting: cannot "
+ "reschedule callback, reverting to INIT state on "
+ "%s", ifsp->if_name);
+ } else
+ hold_ifs(ifsp);
+
+ return;
+ }
+
+ stop_pkt_retransmission(ifsp);
+
+ /*
+ * stop collecting packets. 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 (iu_unregister_event(eh, ifsp->if_offer_id, NULL) != 0) {
+ (void) release_ifs(ifsp);
+ ifsp->if_offer_id = -1;
+ }
+
+ if (offer->opts[CD_DHCP_TYPE] == NULL) {
+
+ ifsp->if_state = REQUESTING;
+
+ if (dhcp_bound(ifsp, offer) == 0) {
+ dhcpmsg(MSG_WARNING, "dhcp_requesting: dhcp_bound "
+ "failed for %s", ifsp->if_name);
+ restart_dhcp(ifsp);
+ return;
+ }
+
+ return;
+ }
+
+ /*
+ * if we got a message from the server, display it.
+ */
+
+ if (offer->opts[CD_MESSAGE] != NULL)
+ print_server_msg(ifsp, offer->opts[CD_MESSAGE]);
+
+ /*
+ * assemble a DHCPREQUEST, with the ciaddr field set to 0,
+ * since we got here from the INIT state.
+ */
+
+ dpkt = init_pkt(ifsp, REQUEST);
+
+ /*
+ * grab the lease out of the OFFER; we know it's valid since
+ * 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));
+
+ add_pkt_opt32(dpkt, CD_LEASE_TIME, lease);
+ add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, htons(ifsp->if_max -
+ sizeof (struct udpiphdr)));
+ add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR, offer->pkt->yiaddr.s_addr);
+ add_pkt_opt(dpkt, CD_SERVER_ID, offer->opts[CD_SERVER_ID]->value,
+ offer->opts[CD_SERVER_ID]->len);
+
+ add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len);
+ add_pkt_opt(dpkt, CD_REQUEST_LIST, ifsp->if_prl, ifsp->if_prllen);
+
+ /*
+ * if_reqhost was set for this interface in dhcp_selecting()
+ * if the DF_REQUEST_HOSTNAME option set and a host name was
+ * found
+ */
+ if (ifsp->if_reqhost != NULL) {
+ add_pkt_opt(dpkt, CD_HOSTNAME, ifsp->if_reqhost,
+ strlen(ifsp->if_reqhost));
+ }
+ add_pkt_opt(dpkt, CD_END, NULL, 0);
+
+ /* all done with the offer */
+ free_pkt_list(&offer);
+
+ /*
+ * send out the REQUEST, trying retransmissions. either a NAK
+ * or too many REQUEST attempts will revert us to SELECTING.
+ */
+
+ ifsp->if_state = REQUESTING;
+ (void) send_pkt(ifsp, dpkt, htonl(INADDR_BROADCAST), stop_requesting);
+
+ /*
+ * wait for an ACK or NAK to come back from the server. if
+ * we can't register this event handler, then we won't be able
+ * to see the server's responses. the best we can really do
+ * in that case is drop back to INIT and hope someone notices.
+ */
+
+ if (register_acknak(ifsp) == 0) {
+
+ ifsp->if_state = INIT;
+ ifsp->if_dflags |= DHCP_IF_FAILED;
+
+ ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY);
+ async_finish(ifsp);
+
+ dhcpmsg(MSG_ERROR, "dhcp_requesting: cannot register to "
+ "collect ACK/NAK packets, reverting to INIT on %s",
+ ifsp->if_name);
+ }
+}
+
+/*
+ * select_best(): selects the best OFFER packet from a list of OFFER packets
+ *
+ * input: PKT_LIST **: a list of packets to select the best from
+ * output: PKT_LIST *: the best packet, or NULL if none are acceptable
+ */
+
+static PKT_LIST *
+select_best(PKT_LIST **pkts)
+{
+ PKT_LIST *current, *best = NULL;
+ uint32_t points, best_points = 0;
+
+ /*
+ * pick out the best offer. point system.
+ * what's important?
+ *
+ * 0) DHCP
+ * 1) no option overload
+ * 2) encapsulated vendor option
+ * 3) non-null sname and siaddr fields
+ * 4) non-null file field
+ * 5) hostname
+ * 6) subnetmask
+ * 7) router
+ */
+
+ for (current = *pkts; current != NULL; current = current->next) {
+
+ points = 0;
+
+ if (current->opts[CD_DHCP_TYPE] == NULL) {
+ dhcpmsg(MSG_VERBOSE, "valid BOOTP reply");
+ goto valid_offer;
+ }
+
+ if (current->opts[CD_LEASE_TIME] == NULL) {
+ dhcpmsg(MSG_WARNING, "select_best: OFFER without "
+ "lease time");
+ continue;
+ }
+
+ if (current->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) {
+ dhcpmsg(MSG_WARNING, "select_best: OFFER with garbled "
+ "lease time");
+ continue;
+ }
+
+ if (current->opts[CD_SERVER_ID] == NULL) {
+ dhcpmsg(MSG_WARNING, "select_best: OFFER without "
+ "server id");
+ continue;
+ }
+
+ if (current->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) {
+ dhcpmsg(MSG_WARNING, "select_best: OFFER with garbled "
+ "server id");
+ continue;
+ }
+
+ /* valid DHCP OFFER. see if we got our parameters. */
+ dhcpmsg(MSG_VERBOSE, "valid OFFER packet");
+ points += 30;
+
+valid_offer:
+ if (current->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 (current->opts[CD_VENDOR_SPEC] != NULL)
+ points += 80;
+
+ if (current->opts[CD_SUBNETMASK] != NULL)
+ points++;
+
+ if (current->opts[CD_ROUTER] != NULL)
+ points++;
+
+ if (current->opts[CD_HOSTNAME] != NULL)
+ points += 5;
+
+ dhcpmsg(MSG_DEBUG, "select_best: OFFER had %d points", points);
+
+ if (points >= best_points) {
+ best_points = points;
+ best = current;
+ }
+ }
+
+ if (best != NULL) {
+ dhcpmsg(MSG_DEBUG, "select_best: most points: %d", best_points);
+ remove_from_pkt_list(pkts, best);
+ } else
+ dhcpmsg(MSG_DEBUG, "select_best: no valid OFFER/BOOTP reply");
+
+ free_pkt_list(pkts);
+ return (best);
+}
+
+/*
+ * dhcp_acknak(): processes reception of an ACK or NAK packet on an interface
+ *
+ * input: iu_eh_t *: unused
+ * int: the file descriptor the ACK/NAK arrived on
+ * short: unused
+ * iu_event_id_t: the id of this event callback with the handler
+ * void *: the interface that received the ACK or NAK
+ * output: void
+ */
+
+/* ARGSUSED */
+void
+dhcp_acknak(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg)
+{
+ struct ifslist *ifsp = (struct ifslist *)arg;
+ PKT_LIST *plp;
+
+ if (check_ifs(ifsp) == 0) {
+ /* unregister_acknak() does our release_ifs() */
+ (void) unregister_acknak(ifsp);
+ (void) ioctl(fd, I_FLUSH, FLUSHR|FLUSHW);
+ return;
+ }
+
+ /*
+ * note that check_ifs() did our release_ifs() but we're not
+ * sure we're done yet; call hold_ifs() to reacquire our hold;
+ * if we're done, unregister_acknak() will release_ifs() below.
+ */
+
+ hold_ifs(ifsp);
+
+ if (recv_pkt(ifsp, fd, DHCP_PACK|DHCP_PNAK, B_FALSE) == 0)
+ return;
+
+ /*
+ * we've got a packet; make sure it's acceptable before
+ * cancelling the REQUEST retransmissions.
+ */
+
+ plp = ifsp->if_recv_pkt_list;
+ remove_from_pkt_list(&ifsp->if_recv_pkt_list, plp);
+
+ if (*plp->opts[CD_DHCP_TYPE]->value == ACK) {
+ if (plp->opts[CD_LEASE_TIME] == NULL ||
+ plp->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) {
+ dhcpmsg(MSG_WARNING, "dhcp_acknak: ACK packet on %s "
+ "missing mandatory lease option, ignored",
+ ifsp->if_name);
+ ifsp->if_bad_offers++;
+ free_pkt_list(&plp);
+ return;
+ }
+ if ((ifsp->if_state == RENEWING ||
+ ifsp->if_state == REBINDING) &&
+ ifsp->if_addr.s_addr != plp->pkt->yiaddr.s_addr) {
+ dhcpmsg(MSG_WARNING, "dhcp_acknak: renewal ACK packet "
+ "has a different IP address (%s), ignored",
+ inet_ntoa(plp->pkt->yiaddr));
+ ifsp->if_bad_offers++;
+ free_pkt_list(&plp);
+ return;
+ }
+ }
+
+ /*
+ * looks good; cancel the retransmission timer and unregister
+ * the acknak handler. ACK to BOUND, NAK back to SELECTING.
+ */
+
+ stop_pkt_retransmission(ifsp);
+ (void) unregister_acknak(ifsp);
+
+ if (*(plp->opts[CD_DHCP_TYPE]->value) == NAK) {
+ dhcpmsg(MSG_WARNING, "dhcp_acknak: NAK on interface %s",
+ ifsp->if_name);
+ ifsp->if_bad_offers++;
+ free_pkt_list(&plp);
+ restart_dhcp(ifsp);
+
+ /*
+ * remove any bogus cached configuration we might have
+ * around (right now would only happen if we got here
+ * from INIT_REBOOT).
+ */
+
+ (void) remove_hostconf(ifsp->if_name);
+ return;
+ }
+
+ if (plp->opts[CD_SERVER_ID] == NULL ||
+ plp->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) {
+ dhcpmsg(MSG_ERROR, "dhcp_acknak: ACK with no valid server id, "
+ "restarting DHCP on %s", ifsp->if_name);
+ ifsp->if_bad_offers++;
+ free_pkt_list(&plp);
+ restart_dhcp(ifsp);
+ return;
+ }
+
+ if (plp->opts[CD_MESSAGE] != NULL)
+ print_server_msg(ifsp, plp->opts[CD_MESSAGE]);
+
+ if (dhcp_bound(ifsp, plp) == 0) {
+ dhcpmsg(MSG_WARNING, "dhcp_acknak: dhcp_bound failed "
+ "for %s", ifsp->if_name);
+ restart_dhcp(ifsp);
+ return;
+ }
+
+ dhcpmsg(MSG_VERBOSE, "ACK on interface %s", ifsp->if_name);
+}
+
+/*
+ * restart_dhcp(): restarts DHCP (from INIT) on a given interface
+ *
+ * input: struct ifslist *: the interface to restart DHCP on
+ * output: void
+ */
+
+static void
+restart_dhcp(struct ifslist *ifsp)
+{
+ if (iu_schedule_timer(tq, DHCP_RESTART_WAIT, dhcp_start, ifsp) == -1) {
+
+ ifsp->if_state = INIT;
+ ifsp->if_dflags |= DHCP_IF_FAILED;
+
+ ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY);
+ async_finish(ifsp);
+
+ dhcpmsg(MSG_ERROR, "restart_dhcp: cannot schedule dhcp_start, "
+ "reverting to INIT state on %s", ifsp->if_name);
+ } else
+ hold_ifs(ifsp);
+}
+
+/*
+ * stop_requesting(): decides when to stop retransmitting REQUESTs
+ *
+ * input: struct ifslist *: the interface REQUESTs are being sent on
+ * unsigned int: the number of REQUESTs sent so far
+ * output: boolean_t: B_TRUE if retransmissions should stop
+ */
+
+static boolean_t
+stop_requesting(struct ifslist *ifsp, unsigned int n_requests)
+{
+ if (n_requests >= DHCP_MAX_REQUESTS) {
+
+ (void) unregister_acknak(ifsp);
+
+ dhcpmsg(MSG_INFO, "no ACK/NAK to REQUESTING REQUEST, "
+ "restarting DHCP on %s", ifsp->if_name);
+
+ dhcp_selecting(ifsp);
+ return (B_TRUE);
+ }
+
+ return (B_FALSE);
+}
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/script_handler.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/script_handler.c
new file mode 100644
index 0000000000..9dd9690748
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/script_handler.c
@@ -0,0 +1,371 @@
+/*
+ * 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.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <time.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <dhcpmsg.h>
+#include "script_handler.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 SIGARLM
+ *
+ * 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: struct ifslist *: the interface
+ * const char *: the event name
+ * int: the pipe end owned by the script helper process
+ * output: void
+ */
+static void
+run_script(struct ifslist *ifsp, const char *event, int fd)
+{
+ int n;
+ char c;
+ char *name;
+ pid_t pid;
+ time_t now;
+ extern int errno;
+
+ if ((pid = fork()) == -1) {
+ return;
+ }
+ if (pid == 0) {
+ name = strrchr(SCRIPT_PATH, '/') + 1;
+
+ /* close all files */
+ closefrom(0);
+
+ /* redirect stdin, stdout and stderr to /dev/null */
+ if ((n = open("/dev/null", O_RDWR)) < 0)
+ exit(-1);
+
+ (void) dup2(n, STDOUT_FILENO);
+ (void) dup2(n, STDERR_FILENO);
+
+ (void) execl(SCRIPT_PATH, name, ifsp->if_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_cleanup(): cleanup helper function
+ *
+ * input: struct ifslist *: the interface
+ * output: void
+ */
+
+static void
+script_cleanup(struct ifslist *ifsp)
+{
+ ifsp->if_script_helper_pid = -1;
+ ifsp->if_script_pid = -1;
+
+ if (ifsp->if_script_fd != -1) {
+ assert(ifsp->if_script_event_id != -1);
+ assert(ifsp->if_script_callback != NULL);
+
+ (void) iu_unregister_event(eh, ifsp->if_script_event_id, NULL);
+ (void) close(ifsp->if_script_fd);
+ ifsp->if_script_event_id = -1;
+ ifsp->if_script_fd = -1;
+ ifsp->if_script_callback(ifsp, ifsp->if_callback_msg);
+ ifsp->if_script_callback = NULL;
+ ifsp->if_script_event = NULL;
+ ifsp->if_callback_msg = NULL;
+
+ (void) release_ifs(ifsp);
+ script_count--;
+ }
+}
+
+/*
+ * script_exit(): does cleanup and invokes callback when script exits
+ *
+ * input: eh_t *: unused
+ * int: the end of pipe owned by dhcpagent
+ * short: unused
+ * eh_event_id_t: unused
+ * void *: the interface
+ * 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 run the script
+ *
+ * input: struct ifslist *: the interface
+ * const char *: the event name
+ * script_callback_t: callback function
+ * void *: data to the callback function
+ * output: int: 1 if script starts successfully
+ * int *: the returned value of the callback function if script
+ * starts unsuccessfully
+ */
+int
+script_start(struct ifslist *ifsp, const char *event,
+ script_callback_t *callback, const char *msg, int *status)
+{
+ int n;
+ int fds[2];
+ pid_t pid;
+ iu_event_id_t event_id;
+
+ assert(callback != NULL);
+
+ if (access(SCRIPT_PATH, X_OK) == -1) {
+ /* script does not exist */
+ goto out;
+ }
+ if (ifsp->if_script_pid != -1) {
+ /* script is running, stop it */
+ dhcpmsg(MSG_ERROR, "script_start: stop script");
+ script_stop(ifsp);
+ }
+
+ /*
+ * 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(ifsp, event, fds[1]);
+ exit(0);
+ }
+
+ (void) close(fds[1]);
+
+ /* get the script's pid */
+ if (read(fds[0], &ifsp->if_script_pid, sizeof (pid_t)) !=
+ sizeof (pid_t)) {
+ (void) kill(pid, SIGKILL);
+ ifsp->if_script_pid = -1;
+ (void) close(fds[0]);
+ goto out;
+ }
+
+ ifsp->if_script_helper_pid = pid;
+ event_id = iu_register_event(eh, fds[0], POLLIN, script_exit, ifsp);
+ if (event_id == -1) {
+ (void) close(fds[0]);
+ script_stop(ifsp);
+ goto out;
+ }
+
+ script_count++;
+ ifsp->if_script_event_id = event_id;
+ ifsp->if_script_callback = callback;
+ ifsp->if_script_event = event;
+ ifsp->if_callback_msg = msg;
+ ifsp->if_script_fd = fds[0];
+ hold_ifs(ifsp);
+ return (1);
+
+out:
+ /* callback won't be called in script_exit, so call it here */
+ n = callback(ifsp, msg);
+ if (status != NULL)
+ *status = n;
+
+ return (0);
+}
+
+/*
+ * script_stop(): stops the script if it is running
+ *
+ * input: struct ifslist *: the interface
+ * output: void
+ */
+void
+script_stop(struct ifslist *ifsp)
+{
+ if (ifsp->if_script_pid != -1) {
+ assert(ifsp->if_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(ifsp->if_script_pid, SIGTERM);
+ (void) kill(ifsp->if_script_helper_pid, SIGTERM);
+ }
+
+ script_cleanup(ifsp);
+}
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/script_handler.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/script_handler.h
new file mode 100644
index 0000000000..caf334a10d
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/script_handler.h
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+#ifndef SCRIPT_HANDLER_H
+#define SCRIPT_HANDLER_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include "interface.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_RELEASE "RELEASE"
+
+/*
+ * script location.
+ */
+#define SCRIPT_PATH "/etc/dhcp/eventhook"
+
+/*
+ * the number of running scripts.
+ */
+extern unsigned int script_count;
+
+int script_start(struct ifslist *, const char *,
+ script_callback_t *, const char *, int *);
+void script_stop(struct ifslist *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SCRIPT_HANDLER_H */
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/select.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/select.c
new file mode 100644
index 0000000000..abd4b2def1
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/select.c
@@ -0,0 +1,226 @@
+/*
+ * 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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ * SELECTING state of the client state machine.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <strings.h>
+#include <time.h>
+#include <limits.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <net/route.h>
+#include <net/if.h>
+#include <netinet/dhcp.h>
+#include <netinet/udp.h>
+#include <netinet/ip_var.h>
+#include <netinet/udp_var.h>
+#include <stropts.h> /* FLUSHR/FLUSHW */
+#include <dhcpmsg.h>
+
+#include "states.h"
+#include "agent.h"
+#include "util.h"
+#include "interface.h"
+#include "packet.h"
+#include "defaults.h"
+
+static iu_eh_callback_t dhcp_collect_offers;
+static stop_func_t stop_selecting;
+
+/*
+ * dhcp_start(): starts DHCP on an interface
+ *
+ * input: iu_tq_t *: unused
+ * void *: the interface to start DHCP on
+ * output: void
+ */
+
+/* ARGSUSED */
+void
+dhcp_start(iu_tq_t *tqp, void *arg)
+{
+ struct ifslist *ifsp = (struct ifslist *)arg;
+
+ if (check_ifs(ifsp) == 0) {
+ (void) release_ifs(ifsp);
+ return;
+ }
+
+ dhcpmsg(MSG_VERBOSE, "starting DHCP on %s", ifsp->if_name);
+ dhcp_selecting(ifsp);
+}
+
+/*
+ * dhcp_selecting(): sends a DISCOVER and sets up reception for an OFFER
+ *
+ * input: struct ifslist *: the interface to send the DISCOVER on, ...
+ * output: void
+ */
+
+void
+dhcp_selecting(struct ifslist *ifsp)
+{
+ dhcp_pkt_t *dpkt;
+ const char *reqhost;
+ char hostfile[PATH_MAX + 1];
+
+ /*
+ * we first set up to collect OFFER packets as they arrive.
+ * we then send out DISCOVER probes. then we wait at a
+ * user-tunable number of seconds before seeing if OFFERs have
+ * come in response to our DISCOVER. if none have come in, we
+ * continue to wait, sending out our DISCOVER probes with
+ * exponential backoff. if an OFFER is never received, we
+ * will wait forever (note that since we're event-driven
+ * though, we're still able to service other interfaces.)
+ *
+ * note that we do an reset_ifs() here because we may be
+ * landing in dhcp_selecting() as a result of restarting DHCP,
+ * so the ifs may not be fresh.
+ */
+
+ reset_ifs(ifsp);
+ ifsp->if_state = SELECTING;
+
+ if ((ifsp->if_offer_id = iu_register_event(eh, ifsp->if_dlpi_fd, POLLIN,
+ dhcp_collect_offers, ifsp)) == -1) {
+
+ dhcpmsg(MSG_ERROR, "dhcp_selecting: cannot register to collect "
+ "OFFER packets, reverting to INIT on %s",
+ ifsp->if_name);
+
+ ifsp->if_state = INIT;
+ ifsp->if_dflags |= DHCP_IF_FAILED;
+ ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY);
+ async_finish(ifsp);
+ return;
+ } else
+ hold_ifs(ifsp);
+
+
+ if (iu_schedule_timer(tq, ifsp->if_offer_wait, dhcp_requesting,
+ ifsp) == -1) {
+
+ dhcpmsg(MSG_ERROR, "dhcp_selecting: cannot schedule to read "
+ "OFFER packets");
+
+ if (iu_unregister_event(eh, ifsp->if_offer_id, NULL) != 0) {
+ ifsp->if_offer_id = -1;
+ (void) release_ifs(ifsp);
+ }
+
+ ifsp->if_state = INIT;
+ ifsp->if_dflags |= DHCP_IF_FAILED;
+ ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY);
+ async_finish(ifsp);
+ return;
+ } else
+ hold_ifs(ifsp);
+
+ /*
+ * Assemble DHCPDISCOVER 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(ifsp, DISCOVER);
+
+ add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, htons(ifsp->if_max -
+ sizeof (struct udpiphdr)));
+ add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM));
+
+ add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len);
+ add_pkt_opt(dpkt, CD_REQUEST_LIST, ifsp->if_prl, ifsp->if_prllen);
+
+ if (df_get_bool(ifsp->if_name, DF_REQUEST_HOSTNAME)) {
+ dhcpmsg(MSG_DEBUG, "dhcp_selecting: DF_REQUEST_HOSTNAME");
+ (void) snprintf(hostfile, sizeof (hostfile), "/etc/hostname.%s",
+ ifsp->if_name);
+
+ if ((reqhost = iffile_to_hostname(hostfile)) != NULL) {
+ dhcpmsg(MSG_DEBUG, "dhcp_selecting: host %s", reqhost);
+ if ((ifsp->if_reqhost = strdup(reqhost)) != NULL)
+ add_pkt_opt(dpkt, CD_HOSTNAME, ifsp->if_reqhost,
+ strlen(ifsp->if_reqhost));
+ else
+ dhcpmsg(MSG_WARNING, "dhcp_selecting: cannot"
+ " allocate memory for host name option");
+ }
+ }
+ add_pkt_opt(dpkt, CD_END, NULL, 0);
+
+ (void) send_pkt(ifsp, dpkt, htonl(INADDR_BROADCAST), stop_selecting);
+}
+
+/*
+ * dhcp_collect_offers(): collects incoming OFFERs to a DISCOVER
+ *
+ * input: iu_eh_t *: unused
+ * int: the file descriptor the OFFER arrived on
+ * short: unused
+ * iu_event_id_t: the id of this event callback with the handler
+ * void *: the interface that received the OFFER
+ * output: void
+ */
+
+/* ARGSUSED */
+static void
+dhcp_collect_offers(iu_eh_t *eh, int fd, short events, iu_event_id_t id,
+ void *arg)
+{
+ struct ifslist *ifsp = (struct ifslist *)arg;
+
+ if (verify_ifs(ifsp) == 0) {
+ (void) ioctl(fd, I_FLUSH, FLUSHR|FLUSHW);
+ return;
+ }
+
+ /*
+ * DHCP_PUNTYPED messages are BOOTP server responses.
+ */
+
+ (void) recv_pkt(ifsp, fd, DHCP_POFFER|DHCP_PUNTYPED, B_TRUE);
+}
+
+/*
+ * stop_selecting(): decides when to stop retransmitting DISCOVERs (never)
+ *
+ * input: struct ifslist *: the interface DISCOVERs are being sent on
+ * unsigned int: the number of DISCOVERs sent so far
+ * output: boolean_t: B_TRUE if retransmissions should stop
+ */
+
+/* ARGSUSED */
+static boolean_t
+stop_selecting(struct ifslist *ifsp, unsigned int n_discovers)
+{
+ return (B_FALSE);
+}
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/states.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/states.h
new file mode 100644
index 0000000000..74190cdd13
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/states.h
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+#ifndef STATES_H
+#define STATES_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <netinet/dhcp.h>
+#include <libinetutil.h>
+
+#include "interface.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
+
+void dhcp_acknak(iu_eh_t *, int, short, iu_event_id_t, void *);
+int dhcp_adopt(void);
+int dhcp_bound(struct ifslist *, PKT_LIST *);
+int dhcp_drop(struct ifslist *, const char *);
+void dhcp_expire(iu_tq_t *, void *);
+int dhcp_extending(struct ifslist *);
+void dhcp_inform(struct ifslist *);
+void dhcp_init_reboot(struct ifslist *);
+void dhcp_rebind(iu_tq_t *, void *);
+int dhcp_release(struct ifslist *, const char *);
+void dhcp_renew(iu_tq_t *, void *);
+void dhcp_requesting(iu_tq_t *, void *);
+void dhcp_selecting(struct ifslist *);
+void dhcp_start(iu_tq_t *, void *);
+void send_decline(struct ifslist *, char *, struct in_addr *);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* STATES_H */
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.c
new file mode 100644
index 0000000000..5db77d8be3
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.c
@@ -0,0 +1,757 @@
+/*
+ * 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.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <netinet/in.h> /* struct in_addr */
+#include <netinet/dhcp.h>
+#include <signal.h>
+#include <sys/dlpi.h>
+#include <sys/sockio.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <net/route.h>
+#include <net/if_arp.h>
+#include <string.h>
+#include <dhcpmsg.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <stdio.h>
+
+#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 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 acknak handler functions
+ *
+ * o true miscellany -- anything else
+ */
+
+/*
+ * pkt_type_to_string(): stringifies a packet type
+ *
+ * input: uchar_t: a DHCP packet type value, as defined in RFC2131
+ * output: const char *: the stringified packet type
+ */
+
+const char *
+pkt_type_to_string(uchar_t type)
+{
+ /*
+ * note: the ordering here allows direct indexing of the table
+ * based on the RFC2131 packet type value passed in.
+ */
+
+ static const char *types[] = {
+ "BOOTP", "DISCOVER", "OFFER", "REQUEST", "DECLINE",
+ "ACK", "NAK", "RELEASE", "INFORM"
+ };
+
+ if (type >= (sizeof (types) / sizeof (*types)) || types[type] == NULL)
+ return ("<unknown>");
+
+ return (types[type]);
+}
+
+/*
+ * dlpi_to_arp(): converts DLPI datalink types into ARP datalink types
+ *
+ * input: uchar_t: the DLPI datalink type
+ * output: uchar_t: the ARP datalink type (0 if no corresponding code)
+ */
+
+uchar_t
+dlpi_to_arp(uchar_t dlpi_type)
+{
+ switch (dlpi_type) {
+
+ case DL_ETHER:
+ return (1);
+
+ case DL_FRAME:
+ return (15);
+
+ case DL_ATM:
+ return (16);
+
+ case DL_HDLC:
+ return (17);
+
+ case DL_FC:
+ return (18);
+
+ case DL_CSMACD: /* ieee 802 networks */
+ case DL_TPB:
+ case DL_TPR:
+ case DL_METRO:
+ case DL_FDDI:
+ return (6);
+ case DL_IB:
+ return (ARPHRD_IB);
+ }
+
+ return (0);
+}
+
+/*
+ * 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);
+}
+
+/*
+ * send_ok_reply(): sends an "ok" reply to a request and closes the ipc
+ * connection
+ *
+ * input: dhcp_ipc_request_t *: the request to reply to
+ * int *: the ipc connection file descriptor (set to -1 on return)
+ * output: void
+ * note: the request is freed (thus the request must be on the heap).
+ */
+
+void
+send_ok_reply(dhcp_ipc_request_t *request, int *control_fd)
+{
+ send_error_reply(request, 0, control_fd);
+}
+
+/*
+ * send_error_reply(): sends an "error" reply to a request and closes the ipc
+ * connection
+ *
+ * input: dhcp_ipc_request_t *: the request to reply to
+ * int: the error to send back on the ipc connection
+ * int *: the ipc connection file descriptor (set to -1 on return)
+ * output: void
+ * note: the request is freed (thus the request must be on the heap).
+ */
+
+void
+send_error_reply(dhcp_ipc_request_t *request, int error, int *control_fd)
+{
+ send_data_reply(request, control_fd, error, DHCP_TYPE_NONE, NULL, NULL);
+}
+
+/*
+ * send_data_reply(): sends a reply to a request and closes the ipc connection
+ *
+ * input: dhcp_ipc_request_t *: the request to reply to
+ * int *: the ipc connection file descriptor (set to -1 on return)
+ * 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
+ * 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(dhcp_ipc_request_t *request, int *control_fd,
+ int error, dhcp_data_type_t type, void *buffer, size_t size)
+{
+ dhcp_ipc_reply_t *reply;
+
+ if (*control_fd == -1)
+ return;
+
+ reply = dhcp_ipc_alloc_reply(request, error, buffer, size, type);
+ if (reply == NULL)
+ dhcpmsg(MSG_ERR, "send_data_reply: cannot allocate reply");
+
+ else if (dhcp_ipc_send_reply(*control_fd, reply) != 0)
+ dhcpmsg(MSG_ERR, "send_data_reply: dhcp_ipc_send_reply");
+
+ /*
+ * 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(request);
+ (void) dhcp_ipc_close(*control_fd);
+ *control_fd = -1;
+}
+
+/*
+ * print_server_msg(): prints a message from a DHCP server
+ *
+ * input: struct ifslist *: the interface the message came in on
+ * DHCP_OPT *: the option containing the string to display
+ * output: void
+ */
+
+void
+print_server_msg(struct ifslist *ifsp, DHCP_OPT *p)
+{
+ dhcpmsg(MSG_INFO, "%s: message from server: %.*s", ifsp->if_name,
+ p->len, p->value);
+}
+
+/*
+ * 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);
+ dhcpmsg(MSG_DEBUG, "dhcpagent: daemonize: "
+ "waiting for adoption to complete.");
+ if (sleep(DHCP_ADOPT_SLEEP) == 0) {
+ dhcpmsg(MSG_WARNING, "dhcpagent: daemonize: "
+ "timed out awaiting adoption.");
+ }
+ }
+ _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: int: 1 on success, 0 on failure
+ */
+
+static int
+update_default_route(const char *ifname, int type, struct in_addr *gateway_nbo,
+ int flags)
+{
+ static int rtsock_fd = -1;
+ 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;
+
+ if (rtsock_fd == -1) {
+ rtsock_fd = socket(PF_ROUTE, SOCK_RAW, 0);
+ if (rtsock_fd == -1) {
+ dhcpmsg(MSG_ERR, "update_default_route: "
+ "cannot create routing socket");
+ return (0);
+ }
+ }
+
+ (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 = if_nametoindex(ifname);
+
+ 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: int: 1 on success, 0 on failure
+ */
+
+int
+add_default_route(const char *ifname, struct in_addr *gateway_nbo)
+{
+ if (strchr(ifname, ':') != NULL) /* see README */
+ return (1);
+
+ return (update_default_route(ifname, 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: int: 1 on success, 0 on failure
+ */
+
+int
+del_default_route(const char *ifname, struct in_addr *gateway_nbo)
+{
+ if (strchr(ifname, ':') != NULL)
+ return (1);
+
+ if (gateway_nbo->s_addr == htonl(INADDR_ANY)) /* no router */
+ return (1);
+
+ return (update_default_route(ifname, RTM_DELETE, gateway_nbo, 0));
+}
+
+/*
+ * inactivity_shutdown(): shuts down agent if there are no interfaces to manage
+ *
+ * input: iu_tq_t *: unused
+ * void *: unused
+ * output: void
+ */
+
+/* ARGSUSED */
+void
+inactivity_shutdown(iu_tq_t *tqp, void *arg)
+{
+ if (ifs_count() > 0) /* shouldn't happen, but... */
+ return;
+
+ 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, drain_script, NULL);
+}
+
+/*
+ * register_acknak(): registers dhcp_acknak() to be called back when ACK or
+ * NAK packets are received on a given interface
+ *
+ * input: struct ifslist *: the interface to register for
+ * output: int: 1 on success, 0 on failure
+ */
+
+int
+register_acknak(struct ifslist *ifsp)
+{
+ iu_event_id_t ack_id, ack_bcast_id = -1;
+
+ /*
+ * having an acknak id already registered isn't impossible;
+ * handle the situation as gracefully as possible.
+ */
+
+ if (ifsp->if_acknak_id != -1) {
+ dhcpmsg(MSG_DEBUG, "register_acknak: acknak id pending, "
+ "attempting to cancel");
+ if (unregister_acknak(ifsp) == 0)
+ return (0);
+ }
+
+ switch (ifsp->if_state) {
+
+ case BOUND:
+ case REBINDING:
+ case RENEWING:
+
+ ack_bcast_id = iu_register_event(eh, ifsp->if_sock_fd, POLLIN,
+ dhcp_acknak, ifsp);
+
+ if (ack_bcast_id == -1) {
+ dhcpmsg(MSG_WARNING, "register_acknak: cannot "
+ "register to receive socket broadcasts");
+ return (0);
+ }
+
+ ack_id = iu_register_event(eh, ifsp->if_sock_ip_fd, POLLIN,
+ dhcp_acknak, ifsp);
+ break;
+
+ default:
+ ack_id = iu_register_event(eh, ifsp->if_dlpi_fd, POLLIN,
+ dhcp_acknak, ifsp);
+ break;
+ }
+
+ if (ack_id == -1) {
+ dhcpmsg(MSG_WARNING, "register_acknak: cannot register event");
+ (void) iu_unregister_event(eh, ack_bcast_id, NULL);
+ return (0);
+ }
+
+ ifsp->if_acknak_id = ack_id;
+ hold_ifs(ifsp);
+
+ ifsp->if_acknak_bcast_id = ack_bcast_id;
+ if (ifsp->if_acknak_bcast_id != -1) {
+ hold_ifs(ifsp);
+ dhcpmsg(MSG_DEBUG, "register_acknak: registered broadcast id "
+ "%d", ack_bcast_id);
+ }
+
+ dhcpmsg(MSG_DEBUG, "register_acknak: registered acknak id %d", ack_id);
+ return (1);
+}
+
+/*
+ * unregister_acknak(): unregisters dhcp_acknak() to be called back
+ *
+ * input: struct ifslist *: the interface to unregister for
+ * output: int: 1 on success, 0 on failure
+ */
+
+int
+unregister_acknak(struct ifslist *ifsp)
+{
+ if (ifsp->if_acknak_id != -1) {
+
+ if (iu_unregister_event(eh, ifsp->if_acknak_id, NULL) == 0) {
+ dhcpmsg(MSG_DEBUG, "unregister_acknak: cannot "
+ "unregister acknak id %d on %s",
+ ifsp->if_acknak_id, ifsp->if_name);
+ return (0);
+ }
+
+ dhcpmsg(MSG_DEBUG, "unregister_acknak: unregistered acknak id "
+ "%d", ifsp->if_acknak_id);
+
+ ifsp->if_acknak_id = -1;
+ (void) release_ifs(ifsp);
+ }
+
+ if (ifsp->if_acknak_bcast_id != -1) {
+
+ if (iu_unregister_event(eh, ifsp->if_acknak_bcast_id, NULL)
+ == 0) {
+ dhcpmsg(MSG_DEBUG, "unregister_acknak: cannot "
+ "unregister broadcast id %d on %s",
+ ifsp->if_acknak_id, ifsp->if_name);
+ return (0);
+ }
+
+ dhcpmsg(MSG_DEBUG, "unregister_acknak: unregistered "
+ "broadcast id %d", ifsp->if_acknak_bcast_id);
+
+ ifsp->if_acknak_bcast_id = -1;
+ (void) release_ifs(ifsp);
+ }
+
+ return (1);
+}
+
+/*
+ * 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: int: 1 on success, 0 on failure
+ */
+
+int
+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);
+}
+
+/*
+ * valid_hostname(): check whether a string is a valid hostname
+ *
+ * input: const char *: the string to verify as a hostname
+ * output: boolean_t: B_TRUE if the string is a valid hostname
+ *
+ * Note that we accept both host names beginning with a digit and
+ * those containing hyphens. Neither is strictly legal according
+ * to the RFCs, but both are in common practice, so we endeavour
+ * to not break what customers are using.
+ */
+
+static boolean_t
+valid_hostname(const char *hostname)
+{
+ unsigned int i;
+
+ for (i = 0; hostname[i] != '\0'; i++) {
+
+ if (isalpha(hostname[i]) || isdigit(hostname[i]) ||
+ (((hostname[i] == '-') || (hostname[i] == '.')) && (i > 0)))
+ continue;
+
+ return (B_FALSE);
+ }
+
+ return (i > 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.<if> 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.<if> 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 (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);
+}
diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.h
new file mode 100644
index 0000000000..51070ab658
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.h
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+#ifndef UTIL_H
+#define UTIL_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <netinet/dhcp.h>
+#include <libinetutil.h>
+#include <dhcpagent_ipc.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 ifslist; /* forward declaration */
+
+typedef int64_t monosec_t; /* see README for details */
+
+/* conversion functions */
+const char *pkt_type_to_string(uchar_t);
+const char *monosec_to_string(monosec_t);
+time_t monosec_to_time(monosec_t);
+uchar_t dlpi_to_arp(uchar_t);
+
+/* shutdown handlers */
+void graceful_shutdown(int);
+void inactivity_shutdown(iu_tq_t *, void *);
+
+/* acknak handlers */
+int register_acknak(struct ifslist *);
+int unregister_acknak(struct ifslist *);
+
+/* ipc functions */
+void send_error_reply(dhcp_ipc_request_t *, int, int *);
+void send_ok_reply(dhcp_ipc_request_t *, int *);
+void send_data_reply(dhcp_ipc_request_t *, int *, int,
+ dhcp_data_type_t, void *, size_t);
+
+/* miscellaneous */
+int add_default_route(const char *, struct in_addr *);
+int del_default_route(const char *, struct in_addr *);
+int daemonize(void);
+monosec_t monosec(void);
+void print_server_msg(struct ifslist *, DHCP_OPT *);
+int bind_sock(int, in_port_t, in_addr_t);
+const char *iffile_to_hostname(const char *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UTIL_H */