diff options
author | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
---|---|---|
committer | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
commit | 7c478bd95313f5f23a4c958a745db2134aa03244 (patch) | |
tree | c871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/cmd/cmd-inet/sbin/dhcpagent | |
download | illumos-gate-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz |
OpenSolaris Launch
Diffstat (limited to 'usr/src/cmd/cmd-inet/sbin/dhcpagent')
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 */ |