diff options
author | carlsonj <none@none> | 2007-01-17 08:41:37 -0800 |
---|---|---|
committer | carlsonj <none@none> | 2007-01-17 08:41:37 -0800 |
commit | d04ccbb3f3163ae5962a8b7465d9796bff6ca434 (patch) | |
tree | ee9becc340fed8326cfa6bac8d30f7d4b18313ce /usr/src | |
parent | 1c25cdbd0f6ba3ec11a8ab1653c801027a1ffc61 (diff) | |
download | illumos-gate-d04ccbb3f3163ae5962a8b7465d9796bff6ca434.tar.gz |
PSARC 2006/597 DHCPv6 Client
4863327 dhcp client causes drag
6369116 dhcpagent doesn't notice when logical interfaces disappear
6386331 dhcpagent should implement RFC 4361 client identifier
6450744 dhcpagent prevents aggrs from being deleted
6462355 impact of RELEASE_ON_SIGTERM should be clearer
6464136 Solaris should support the client side of DHCPv6
6482163 libxnet lint library lacks __EXTENSIONS__
6485164 dead rule_zone_specific() rule in ipif_select_source_v6()
6487534 snoop "dhcp" filtering keyword doesn't actually work
6487958 async internal timeout can trample on renew/rebind timer
Diffstat (limited to 'usr/src')
94 files changed, 14729 insertions, 4802 deletions
diff --git a/usr/src/Targetdirs b/usr/src/Targetdirs index 1b7d92e374..bba9d08d16 100644 --- a/usr/src/Targetdirs +++ b/usr/src/Targetdirs @@ -19,7 +19,7 @@ # CDDL HEADER END # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" @@ -102,6 +102,7 @@ ROOT.SYS= \ /etc/security \ /etc/gss \ /etc/init.d \ + /etc/dhcp \ /etc/lib \ /etc/opt \ /etc/rc0.d \ diff --git a/usr/src/cmd/cmd-inet/etc/dhcp/Makefile b/usr/src/cmd/cmd-inet/etc/dhcp/Makefile index 39cc258b69..dd540b79c3 100644 --- a/usr/src/cmd/cmd-inet/etc/dhcp/Makefile +++ b/usr/src/cmd/cmd-inet/etc/dhcp/Makefile @@ -2,9 +2,8 @@ # 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. +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. @@ -20,38 +19,28 @@ # CDDL HEADER END # # -#ident "%W% %E% SMI" +# ident "%Z%%M% %I% %E% SMI" # -# Copyright (c) 1999 by Sun Microsystems, Inc. -# All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. # # cmd/cmd-inet/etc/dhcp/Makefile # -DHCPDIR= dhcp -ETCPROG= inittab +DHCPFILES= inittab inittab6 include ../../../Makefile.cmd -ETCDHCPDIR= $(ROOTETC)/$(DHCPDIR) -ETCDHCPPROG= $(ETCPROG:%=$(ETCDHCPDIR)/%) +ETCDHCPDIR= $(ROOTETC)/dhcp +ETCDHCPFILES= $(DHCPFILES:%=$(ETCDHCPDIR)/%) FILEMODE= 0644 -OWNER= root GROUP= sys .KEEP_STATE: -all: $(ETCPROG) +all: +install: $(ETCDHCPFILES) -install: all $(ETCDHCPDIR) $(ETCDHCPPROG) - -$(ETCDHCPDIR)/% : % +$(ETCDHCPDIR)/%: % $(INS.file) - -$(ETCDHCPDIR): - $(INS.dir) - -FRC: - -clean clobber lint: diff --git a/usr/src/cmd/cmd-inet/etc/dhcp/inittab6 b/usr/src/cmd/cmd-inet/etc/dhcp/inittab6 new file mode 100644 index 0000000000..5e8e083a04 --- /dev/null +++ b/usr/src/cmd/cmd-inet/etc/dhcp/inittab6 @@ -0,0 +1,68 @@ +# +# Copyright 2007 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 (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" +# +# This file provides information about all supported DHCPv6 options, for +# use by DHCPv6-related programs. +# +# Please consult dhcp_inittab(4) for further information. Note that +# this interface is "Uncommitted" as defined by attributes(5). +# +# Note: options 10 and 35 are not assigned. +# +ClientID STANDARD, 1, DUID, 1, 1, sdi +ServerID STANDARD, 2, DUID, 1, 1, sdi +Preference STANDARD, 7, UNUMBER8, 1, 1, sdmi +Unicast STANDARD, 12, IPV6, 1, 1, sdmi +Status STANDARD, 13, UNUMBER16, 1, 1, si +RapidCommit STANDARD, 14, BOOL, 0, 0, sdi +UserClass STANDARD, 15, OCTET, 1, 0, sdi +VendorClass STANDARD, 16, OCTET, 1, 0, sdi +Reconfigure STANDARD, 19, UNUMBER8, 1, 1, si +SIPNames STANDARD, 21, DOMAIN, 1, 0, sdmi +SIPAddresses STANDARD, 22, IPV6, 1, 0, sdmi +DNSAddresses STANDARD, 23, IPV6, 1, 0, sdmi +DNSSearch STANDARD, 24, DOMAIN, 1, 0, sdmi +NISServers STANDARD, 27, IPV6, 1, 0, sdmi +NIS+Servers STANDARD, 28, IPV6, 1, 0, sdmi +NISDomain STANDARD, 29, DOMAIN, 1, 0, sdmi +NIS+Domain STANDARD, 30, DOMAIN, 1, 0, sdmi +SNTPServers STANDARD, 31, IPV6, 1, 0, sdmi +InfoRefresh STANDARD, 32, UNUMBER32, 1, 1, sdmi +BCMCDomain STANDARD, 33, DOMAIN, 1, 0, sdmi +BCMCAddresses STANDARD, 34, IPV6, 1, 0, sdmi +Geoconf STANDARD, 36, OCTET, 1, 3, sdmi +RemoteID STANDARD, 37, OCTET, 1, 4, si +Subscriber STANDARD, 38, OCTET, 1, 0, sdmi +ClientFQDN STANDARD, 39, OCTET, 1, 0, sdmi + +# +# DHCPv6 packet fields. Code field is byte offset into DHCPv6 packet. +# + +MsgType FIELD, 0, UNUMBER8, 1, 1, id +TransId FIELD, 1, UNUMBER24, 1, 1, id +HopCount FIELD, 1, UNUMBER8, 1, 1, id +LinkAddr FIELD, 2, IPV6, 1, 1, id +PeerAddr FIELD, 18, IPV6, 1, 1, id diff --git a/usr/src/cmd/cmd-inet/etc/services b/usr/src/cmd/cmd-inet/etc/services index 3c864f2f66..c5a9e591ba 100644 --- a/usr/src/cmd/cmd-inet/etc/services +++ b/usr/src/cmd/cmd-inet/etc/services @@ -1,15 +1,14 @@ #ident "%Z%%M% %I% %E% SMI" # # -# Copyright 2005 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 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. +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. @@ -60,6 +59,8 @@ sunrpc 111/tcp rpcbind imap 143/tcp imap2 # Internet Mail Access Protocol v2 ldap 389/tcp # Lightweight Directory Access Protocol ldap 389/udp # Lightweight Directory Access Protocol +dhcpv6-client 546/udp dhcpv6c # DHCPv6 Client (RFC 3315) +dhcpv6-server 547/udp dhcpv6s # DHCPv6 Server (RFC 3315) submission 587/tcp # Mail Message Submission submission 587/udp # see RFC 2476 ldaps 636/tcp # LDAP protocol over TLS/SSL (was sldap) diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/Makefile b/usr/src/cmd/cmd-inet/sbin/dhcpagent/Makefile index af6d01ccc7..c4e23f1016 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/Makefile +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/Makefile @@ -19,7 +19,7 @@ # CDDL HEADER END # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" @@ -31,7 +31,8 @@ DEFAULTFILES = dhcpagent.dfl LOCOBJS = adopt.o agent.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 + packet.o release.o renew.o request.o script_handler.o select.o \ + states.o util.o COMDIR = $(SRC)/common/net/dhcp COMOBJS = ipv4_sum.o udp_sum.o @@ -47,8 +48,11 @@ XGETFLAGS += -a -x dhcpagent.xcl # to compile a debug version, do a `make COPTFLAG="-g -XO0"' # -CPPFLAGS += -I$(COMDIR) -LDLIBS += -lsocket -lnvpair -lnsl -ldhcpagent -ldhcputil -linetutil -ldevinfo +CPPFLAGS += -I$(COMDIR) -D_XOPEN_SOURCE=500 -D__EXTENSIONS__ +LDLIBS += -lxnet -lnvpair -ldhcpagent -ldhcputil -linetutil -ldevinfo + +# Disable warnings that affect all XPG applications. +LINTFLAGS += -erroff=E_INCONS_ARG_DECL2 -erroff=E_INCONS_VAL_TYPE_DECL2 .KEEP_STATE: diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/README b/usr/src/cmd/cmd-inet/sbin/dhcpagent/README index b331b57470..92794d0ca3 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/README +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/README @@ -1,9 +1,8 @@ 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. +Common Development and Distribution License (the "License"). +You may not use this file except in compliance with the License. You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing. @@ -18,7 +17,7 @@ information: Portions Copyright [yyyy] [name of copyright owner] CDDL HEADER END -Copyright 2004 Sun Microsystems, Inc. All rights reserved. +Copyright 2007 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms. Architectural Overview for the DHCP agent @@ -28,45 +27,48 @@ 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: +The Solaris DHCP agent (dhcpagent) is a DHCP client implementation +compliant with RFCs 2131, 3315, and others. The major forces shaping +its design were: * Must be capable of managing multiple network interfaces. * Must consume little CPU, since it will always be running. * Must have a small memory footprint, since it will always be running. - * Must not rely on any shared libraries, since it must run - before all filesystems have been mounted. + * Must not rely on any shared libraries outside of /lib, since + it must run before all filesystems have been mounted. When a DHCP agent implementation is only required to control a single interface on a machine, the problem is expressed well as a simple state-machine, as shown in RFC2131. However, when a DHCP agent is responsible for managing more than one interface at a time, the -problem becomes much more complicated, 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. +problem becomes much more complicated. + +This can be resolved using threads or with an event-driven model. +Given that DHCP's behavior can be expressed concisely as a state +machine, the event-driven model is the closest match. + +While tried-and-true, that model is subtle and easy to get wrong. +Indeed, much of the agent's code is there to manage the complexity of +programming in an asynchronous event-driven paradigm. THE BASICS ========== -The DHCP agent consists of roughly 20 source files, most with a -companion header file. While the largest source file is around 700 +The DHCP agent consists of roughly 30 source files, most with a +companion header file. While the largest source file is around 1700 lines, most are much shorter. The source files can largely be broken up into three groups: - * Source files, which along with their companion header files, + * Source files that, 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. + the system. Examples include "packet.c", which along with + "packet.h" provide a Packet object for use by the rest of + the agent; and "async.c", which along with "async.h" defines + an interface for managing asynchronous transactions within + the agent. - * Source files which implement a given state of the agent; for + * Source files that implement a given state of the agent; for instance, there is a "request.c" which comprises all of the procedural "work" which must be done while in the REQUESTING state of the agent. By encapsulating states in @@ -90,6 +92,10 @@ DHCP agent implementation. Note that there is of course much more that is not discussed here, but after this overview you should be able to fend for yourself in the source code. +For details on the DHCPv6 aspects of the design, and how this relates +to the implementation present in previous releases of Solaris, see the +README.v6 file. + Event Handlers and Timer Queues ------------------------------- @@ -142,16 +148,31 @@ 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. +the maximum MTU) and its connections to DHCP-related state (the +protocol state machines). This state is stored in a pair of +structures called `dhcp_pif_t' (the IP physical interface layer or +PIF) and `dhcp_lif_t' (the IP logical interface layer or LIF). Each +dhcp_pif_t represents a single physical interface, such as "hme0," for +a given IP protocol version (4 or 6), and has a list of dhcp_lif_t +structures representing the logical interfaces (such as "hme0:1") in +use by the agent. + +This split is important because of differences between IPv4 and IPv6. +For IPv4, each DHCP state machine manages a single IP address and +associated configuration data. This corresponds to a single logical +interface, which must be specified by the user. For IPv6, however, +each DHCP state machine manages a group of addresses, and is +associated with DUID value rather than with just an interface. + +Thus, DHCPv6 behaves more like in.ndpd in its creation of "ADDRCONF" +interfaces. The agent automatically plumbs logical interfaces when +needed and removes them when the addresses expire. + +The state for a given session is stored separately in `dhcp_smach_t'. +This state machine then points to the main LIF used for I/O, and to a +list of `dhcp_lease_t' structures representing individual leases, and +each of those points to a list of LIFs corresponding to the individual +addresses being managed. One point that was brushed over in the preceding discussion of event handlers and timer queues was context. Recall that the event-driven @@ -159,27 +180,26 @@ 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. +response. This is done by sending a REQUEST and then returning to the +event handler that waits for an ACK or NAK packet to arrive on the +file descriptor associated with the interface. Note however, that +when the ACK or NAK does arrive, and the callback function called +back, it must know which state machine this packet is for (it must get +back its context). This could be handled through an ad-hoc mapping of +file descriptors to state machines, but a cleaner approach is to have +the event handler's register function (iu_register_event()) take in an +opaque context pointer, which will then be passed back to the +callback. In the agent, the context pointer used depends on the +nature of the event: events on LIFs use the dhcp_lif_t pointer, events +on the state machine use dhcp_smach_t, and so on. Note that there is nothing that guarantees the pointer passed into iu_register_event() or iu_schedule_timer() will still be valid when the callback is called back (for instance, the memory may have been -freed in the meantime). To solve this problem, 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. +freed in the meantime). To solve this problem, all of the data +structures used in this way are reference counted. For more details +on how the reference count scheme is implemented, see the closing +comments in interface.h regarding memory management. Transactions ------------ @@ -192,17 +212,17 @@ 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.) +on another state machine), eventually checks to see if an acceptable +OFFER has come in, and so forth. To some degree, the notion of the +state machine's current state (SELECTING, REQUESTING, etc) helps +control the potential chaos of the event-driven model (for instance, +if while the agent is waiting for an OFFER on a given state machine, +an IPC event comes in requesting that the leases be RELEASED, the +agent knows to send back an error since the state machine must be in +at least the BOUND state before a RELEASE can be performed.) However, states are not enough -- for instance, suppose that the agent -begins trying to renew a lease -- this is done by sending a REQUEST +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, @@ -213,79 +233,56 @@ 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. +asynchronous action), of which each state machine can have at most +one. The `async_action' structure is embedded in the `dhcp_smach_t' +structure, which is fine since there can be at most one pending +transaction per state machine. Typical "asynchronous transactions" +are START, EXTEND, and INFORM, since each consists of a sequence of +packets that must be done without interruption. Note that not all +DHCP operations are "asynchronous" -- for instance, a DHCPv4 RELEASE +operation is synchronous (not asynchronous) since after the RELEASE is +sent no reply is expected from the DHCP server, but DHCPv6 Release is +asynchronous, as all DHCPv6 messages are transactional. Some +operations, such as status query, are synchronous and do not affect +the system state, and thus do not require sequencing. When the agent realizes it must perform an asynchronous transaction, -it 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. +it calls async_async() to open the transaction. If one is already +pending, then the new transaction must fail (the details of failure +depend on how the transaction was initiated, which is described in +more detail later when the `ipc_action' object is discussed). If +there is no pending asynchronous transaction, the operation succeeds. + +When the transaction is complete, either async_finish() or +async_cancel() must be called to complete or cancel the asynchronous +action on that state machine. If the transaction is unable to +complete within a certain amount of time (more on this later), a timer +should be used to cancel the operation. The notion of asynchronous transactions is complicated by the fact that they may originate from both inside and outside of the agent. For instance, a user initiates an asynchronous START transaction when he performs an `ifconfig hme0 dhcp start', but the agent will internally need to perform asynchronous EXTEND transactions to extend -the lease before it expires. 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 +the lease before it expires. Note that user-initiated actions always +have priority over internal actions: the former will cancel the +latter, if necessary. + +This leads us into the `ipc_action' object. An `ipc_action' +represents the IPC-related pieces of an asynchronous transaction that +was started as a result of a user request, as well as the `BUSY' state +of the administrative interface. Only IPC-generated asynchronous +transactions have a valid `ipc_action' object. Note that since there +can be at most one asynchronous action per state machine, there can +also be at most one `ipc_action' per state machine (this means it can +also conveniently be embedded inside the `dhcp_smach_t' structure). + +One of the main purposes of the `ipc_action' object is to timeout user +events. When the user specifies a timeout value as an argument to ifconfig, he is specifying an `ipc_action' timeout; in other words, -how long he is willing to wait for the command to complete. 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). +how long he is willing to wait for the command to complete. When this +time expires, the ipc_action is terminated, as well as the +asynchronous operation. The API provided for the `ipc_action' object is quite similar to the one for the `async_action' object: when an IPC request comes in for an @@ -304,31 +301,48 @@ 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 +the `dhcp_smach_t', but this may change in the future.. After calling init_pkt(), the add_pkt_opt*() functions are used to add options to -the DHCP packet. Finally, send_pkt() can be used to transmit the -packet to a given IP address. +the DHCP packet. Finally, send_pkt() and send_pkt_v6() can be used to +transmit the packet to a given IP address. The send_pkt() function 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 +must internally use either DLPI or sockets depending on the machine +state; for another, 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 +"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. +retransmission. The callback may alter dsm_send_timeout if necessary +to place a cap on the next timeout; this is done for DHCPv6 in +stop_init_reboot() in order to implement the CNF_MAX_RD constraint. + +The return value from this function indicates whether to continue +retransmission or not, which allows the send_pkt() caller to control +the retransmission policy without making it have to deal with the +retransmission mechanism. See request.c for an example of this in +action. The recv_pkt() function is simpler but still complicated by the fact that one may want to receive several different types of packets at -once; 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. +once and in different ways (DLPI or sockets). The caller registers an +event handler on the file descriptor, and then calls recv_pkt() to +read in the packet along with meta information about the message (the +sender and interface identifier). + +For IPv6, packet reception is done with a single socket, using +IPV6_PKTINFO to determine the actual destination address and receiving +interface. Packets are then matched against the state machines on the +given interface through the transaction ID. + +The same facility exists for inbound IPv4 packets, but because there's +no IP_PKTINFO processing on output yet in Solaris, and because IPv4 +still relies on DLPI, DHCP packets are handled on a per-LIF (when +bound) and per-PIF (when unbound) basis. Eventually, when IP_PKTINFO +is available for IPv4, the per-LIF sockets can go away. If it ever +becomes possible to send and receive IP packets without having an IP +address configured on an interface, then the DLPI streams can go as +well. Time ---- @@ -394,13 +408,13 @@ 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(). +the leases, and is handled by refresh_smachs(). Note that it appears like a more intelligent approach would be to record the time(2) when the system is suspended, compare that against the time(2) when the system is resumed, and use the delta between them to decide which leases have expired. Sadly, this cannot be done since -through at least Solaris 8, it is not possible for userland programs +through at least Solaris 10, it is not possible for userland programs to be notified of system suspend events. Configuration @@ -413,25 +427,34 @@ 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. +However, unless used in "inform" mode, the DHCP client *does* +configure each IP interface enough to allow it to communicate with +other hosts. Specifically, the DHCP client configures the interface's +IP address, netmask, and broadcast address using the information +provided by the server. Further, for IPv4 logical interface 0 +("hme0"), any provided default routes are also configured. + +For IPv6, only the IP addresses are set. The netmask (prefix) is then +set automatically by in.ndpd, and routes are discovered in the usual +way by router discovery or routing protocols. DHCPv6 doesn't set +routes. + +Since logical interfaces cannot be specified as output interfaces in +the kernel forwarding table, and in most cases, logical interfaces +share a default route with their associated physical interface, the +DHCP client does not automatically add or remove default routes when +IPv4 leases are acquired or expired on logical interfaces. Event Scripting --------------- The DHCP client supports user program invocations on DHCP events. The -supported events are BOUND, EXTEND, EXPIRE, DROP 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). +supported events are BOUND, EXTEND, EXPIRE, DROP, RELEASE, and INFORM +for DHCPv4, and BUILD6, EXTEND6, EXPIRE6, DROP6, LOSS6, RELEASE6, and +INFORM6 for DHCPv6. The user program runs asynchronous to the DHCP +client so that the main event loop stays active to process other +events, including events triggered by the user program (for example, +when it invokes dhcpinfo). The user program execution is part of the transaction of a DHCP command. For example, if the user program is not enabled, the transaction of the @@ -442,7 +465,7 @@ 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 +An upper bound of 58 seconds is imposed on how long the user program can run. If the user program does not exit after 55 seconds, the signal SIGTERM is sent to it. If it still does not exit after additional 3 seconds, the signal SIGKILL is sent to it. Since the event handler is @@ -450,7 +473,7 @@ 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 +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, diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/README.v6 b/usr/src/cmd/cmd-inet/sbin/dhcpagent/README.v6 new file mode 100644 index 0000000000..72c3490bd9 --- /dev/null +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/README.v6 @@ -0,0 +1,1225 @@ +CDDL HEADER START + +The contents of this file are subject to the terms of the +Common Development and Distribution License (the "License"). +You may not use this file except in compliance with the License. + +You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +or http://www.opensolaris.org/os/licensing. +See the License for the specific language governing permissions +and limitations under the License. + +When distributing Covered Code, include this CDDL HEADER in each +file and include the License file at usr/src/OPENSOLARIS.LICENSE. +If applicable, add the following below this CDDL HEADER, with the +fields enclosed by brackets "[]" replaced with your own identifying +information: Portions Copyright [yyyy] [name of copyright owner] + +CDDL HEADER END + +Copyright 2007 Sun Microsystems, Inc. All rights reserved. +Use is subject to license terms. + +ident "%Z%%M% %I% %E% SMI" + +DHCPv6 Client Low-Level Design + +Introduction + + This project adds DHCPv6 client-side (not server) support to + Solaris. Future projects may add server-side support as well as + enhance the basic capabilities added here. These future projects + are not discussed in detail in this document. + + This document assumes that the reader is familiar with the following + other documents: + + - RFC 3315: the primary description of DHCPv6 + - RFCs 2131 and 2132: IPv4 DHCP + - RFCs 2461 and 2462: IPv6 NDP and stateless autoconfiguration + - RFC 3484: IPv6 default address selection + - ifconfig(1M): Solaris IP interface configuration + - in.ndpd(1M): Solaris IPv6 Neighbor and Router Discovery daemon + - dhcpagent(1M): Solaris DHCP client + - dhcpinfo(1): Solaris DHCP parameter utility + - ndpd.conf(4): in.ndpd configuration file + - netstat(1M): Solaris network status utility + - snoop(1M): Solaris network packet capture and inspection + - "DHCPv6 Client High-Level Design" + + Several terms from those documents (such as the DHCPv6 IA_NA and + IAADDR options) are used without further explanation in this + document; see the reference documents above for details. + + The overall plan is to enhance the existing Solaris dhcpagent so + that it is able to process DHCPv6. It would also have been possible + to create a new, separate daemon process for this, or to integrate + the feature into in.ndpd. These alternatives, and the reason for + the chosen design, are discussed in Appendix A. + + This document discusses the internal design issues involved in the + protocol implementation, and with the associated components (such as + in.ndpd, snoop, and the kernel's source address selection + algorithm). It does not discuss the details of the protocol itself, + which are more than adequately described in the RFC, nor the + individual lines of code, which will be in the code review. + + As a cross-reference, Appendix B has a summary of the components + involved and the changes to each. + + +Background + + In order to discuss the design changes for DHCPv6, it's necessary + first to talk about the current IPv4-only design, and the + assumptions built into that design. + + The main data structure used in dhcpagent is the 'struct ifslist'. + Each instance of this structure represents a Solaris logical IP + interface under DHCP's control. It also represents the shared state + with the DHCP server that granted the address, the address itself, + and copies of the negotiated options. + + There is one list in dhcpagent containing all of the IP interfaces + that are under DHCP control. IP interfaces not under DHCP control + (for example, those that are statically addressed) are not included + in this list, even when plumbed on the system. These ifslist + entries are chained like this: + + ifsheadp -> ifslist -> ifslist -> ifslist -> NULL + net0 net0:1 net1 + + Each ifslist entry contains the address, mask, lease information, + interface name, hardware information, packets, protocol state, and + timers. The name of the logical IP interface under DHCP's control + is also the name used in the administrative interfaces (dhcpinfo, + ifconfig) and when logging events. + + Each entry holds open a DLPI stream and two sockets. The DLPI + stream is nulled-out with a filter when not in use, but still + consumes system resources. (Most significantly, it causes data + copies in the driver layer that end up sapping performance.) + + The entry storage is managed by a insert/hold/release/remove model + and reference counts. In this model, insert_ifs() allocates a new + ifslist entry and inserts it into the global list, with the global + list holding a reference. remove_ifs() removes it from the global + list and drops that reference. hold_ifs() and release_ifs() are + used by data structures that refer to ifslist entries, such as timer + entries, to make sure that the ifslist entry isn't freed until the + timer has been dispatched or deleted. + + The design is single-threaded, so code that walks the global list + needn't bother taking holds on the ifslist structure. Only + references that may be used at a different time (i.e., pointers + stored in other data structures) need to be recorded. + + Packets are handled using PKT (struct dhcp; <netinet/dhcp.h>), + PKT_LIST (struct dhcp_list; <dhcp_impl.h>), and dhcp_pkt_t (struct + dhcp_pkt; "packet.h"). PKT is just the RFC 2131 DHCP packet + structure, and has no additional information, such as packet length. + PKT_LIST contains a PKT pointer, length, decoded option arrays, and + linkage for putting the packet in a list. Finally, dhcp_pkt_t has a + PKT pointer and length values suitable for modifying the packet. + + Essentially, PKT_LIST is a wrapper for received packets, and + dhcp_pkt_t is a wrapper for packets to be sent. + + The basic PKT structure is used in dhcpagent, inetboot, in.dhcpd, + libdhcpagent, libwanboot, libdhcputil, and others. PKT_LIST is used + in a similar set of places, including the kernel NFS modules. + dhcp_pkt_t is (as the header file implies) limited to dhcpagent. + + In addition to these structures, dhcpagent maintains a set of + internal supporting abstractions. Two key ones involved in this + project are the "async operation" and the "IPC action." An async + operation encapsulates the actions needed for a given operation, so + that if cancellation is needed, there's a single point where the + associated resources can be freed. An IPC action represents the + user state related to the private interface used by ifconfig. + + +DHCPv6 Inherent Differences + + DHCPv6 naturally has some commonality with IPv4 DHCP, but also has + some significant differences. + + Unlike IPv4 DHCP, DHCPv6 relies on link-local IP addresses to do its + work. This means that, on Solaris, the client doesn't need DLPI to + perform any of the I/O; regular IP sockets will do the job. It also + means that, unlike IPv4 DHCP, DHCPv6 does not need to obtain a lease + for the address used in its messages to the server. The system + provides the address automatically. + + IPv4 DHCP expects some messages from the server to be broadcast. + DHCPv6 has no such mechanism; all messages from the server to the + client are unicast. In the case where the client and server aren't + on the same subnet, a relay agent is used to get the unicast replies + back to the client's link-local address. + + With IPv4 DHCP, a single address plus configuration options is + leased with a given client ID and a single state machine instance, + and the implementation binds that to a single IP logical interface + specified by the user. The lease has a "Lease Time," a required + option, as well as two timers, called T1 (renew) and T2 (rebind), + which are controlled by regular options. + + DHCPv6 uses a single client/server session to control the + acquisition of configuration options and "identity associations" + (IAs). The identity associations, in turn, contain lists of + addresses for the client to use and the T1/T2 timer values. Each + individual address has its own preferred and valid lifetime, with + the address being marked "deprecated" at the end of the preferred + interval, and removed at the end of the valid interval. + + IPv4 DHCP leaves many of the retransmit decisions up to the client, + and some things (such as RELEASE and DECLINE) are sent just once. + Others (such as the REQUEST message used for renew and rebind) are + dealt with by heuristics. DHCPv6 treats each message to the server + as a separate transaction, and resends each message using a common + retransmission mechanism. DHCPv6 also has separate messages for + Renew, Rebind, and Confirm rather than reusing the Request + mechanism. + + The set of options (which are used to convey configuration + information) for each protocol are distinct. Notably, two of the + mistakes from IPv4 DHCP have been fixed: DHCPv6 doesn't carry a + client name, and doesn't attempt to impersonate a routing protocol + by setting a "default route." + + Another welcome change is the lack of a netmask/prefix length with + DHCPv6. Instead, the client uses the Router Advertisement prefixes + to set the correct interface netmask. This reduces the number of + databases that need to be kept in sync. (The equivalent mechanism + in IPv4 would have been the use of ICMP Address Mask Request / + Reply, but the BOOTP designers chose to embed it in the address + assignment protocol itself.) + + Otherwise, DHCPv6 is similar to IPv4 DHCP. The same overall + renew/rebind and lease expiry strategy is used, although the state + machine events must now take into account multiple IAs and the fact + that each can cause RENEWING or REBINDING state independently. + + +DHCPv6 And Solaris + + The protocol distinctions above have several important implications. + For the logical interfaces: + + - Because Solaris uses IP logical interfaces to configure + addresses, we must have multiple IP logical interfaces per IA + with IPv6. + + - Because we need to support multiple addresses (and thus multiple + IP logical interfaces) per IA and multiple IAs per client/server + session, the IP logical interface name isn't a unique name for + the lease. + + As a result, IP logical interfaces will come and go with DHCPv6, + just as happens with the existing stateless address + autoconfiguration support in in.ndpd. The logical interface names + (visible in ifconfig) have no administrative significance. + + Fortunately, DHCPv6 does end up with one fixed name that can be used + to identify a session. Because DHCPv6 uses link local addresses for + communication with the server, the name of the IP logical interface + that has this link local address (normally the same as the IP + physical interface) can be used as an identifier for dhcpinfo and + logging purposes. + + +Dhcpagent Redesign Overview + + The redesign starts by refactoring the IP interface representation. + Because we need to have multiple IP logical interfaces (LIFs) for a + single identity association (IA), we should not store all of the + DHCP state information along with the LIF information. + + For DHCPv6, we will need to keep LIFs on a single IP physical + interface (PIF) together, so this is probably also a good time to + reconsider the way dhcpagent represents physical interfaces. The + current design simply replicates the state (notably the DLPI stream, + but also the hardware address and other bits) among all of the + ifslist entries on the same physical interface. + + The new design creates two lists of dhcp_pif_t entries, one list for + IPv4 and the other for IPv6. Each dhcp_pif_t represents a PIF, with + a list of dhcp_lif_t entries attached, each of which represents a + LIF used by dhcpagent. This structure mirrors the kernel's ill_t + and ipif_t interface representations. + + Next, the lease-tracking needs to be refactored. DHCPv6 is the + functional superset in this case, as it has two lifetimes per + address (LIF) and IA groupings with shared T1/T2 timers. To + represent these groupings, we will use a new dhcp_lease_t structure. + IPv4 DHCP will have one such structure per state machine, while + DHCPv6 will have a list. (Note: the initial implementation will + have only one lease per DHCPv6 state machine, because each state + machine uses a single link-local address, a single DUID+IAID pair, + and supports only Non-temporary Addresses [IA_NA option]. Future + enhancements may use multiple leases per DHCPv6 state machine or + support other IA types.) + + For all of these new structures, we will use the same insert/hold/ + release/remove model as with the original ifslist. + + Finally, the remaining items (and the bulk of the original ifslist + members) are kept on a per-state-machine basis. As this is no + longer just an "interface," a new dhcp_smach_t structure will hold + these, and the ifslist structure is gone. + + +Lease Representation + + For DHCPv6, we need to track multiple LIFs per lease (IA), but we + also need multiple LIFs per PIF. Rather than having two sets of + list linkage for each LIF, we can observe that a LIF is on exactly + one PIF and is a member of at most one lease, and then simplify: the + lease structure will use a base pointer for the first LIF in the + lease, and a count for the number of consecutive LIFs in the PIF's + list of LIFs that belong to the lease. + + When removing a LIF from the system, we need to decrement the count + of LIFs in the lease, and advance the base pointer if the LIF being + removed is the first one. Inserting a LIF means just moving it into + this list and bumping the counter. + + When removing a lease from a state machine, we need to dispose of + the LIFs referenced. If the LIF being disposed is the main LIF for + a state machine, then all that we can do is canonize the LIF + (returning it to a default state); this represents the normal IPv4 + DHCP operation on lease expiry. Otherwise, the lease is the owner + of that LIF (it was created because of a DHCPv6 IA), and disposal + means unplumbing the LIF from the actual system and removing the LIF + entry from the PIF. + + +Main Structure Linkage + + For IPv4 DHCP, the new linkage is straightforward. Using the same + system configuration example as in the initial design discussion: + + +- lease +- lease +- lease + | ^ | ^ | ^ + | | | | | | + \ smach \ smach \ smach + \ ^| \ ^| \ ^| + v|v v|v v|v + lif ----> lif -> NULL lif -> NULL + net0 net0:1 net1 + ^ ^ + | | + v4root -> pif --------------------> pif -> NULL + net0 net1 + + This diagram shows three separate state machines running (with + backpointers omitted for clarity). Each state machine has a single + "main" LIF with which it's associated (and named). Each also has a + single lease structure that points back to the same LIF (count of + 1), because IPv4 DHCP controls a single address allocation per state + machine. + + DHCPv6 is a bit more complex. This shows DHCPv6 running on two + interfaces (more or fewer interfaces are of course possible) and + with multiple leases on the first interface, and each lease with + multiple addresses (one with two addresses, the second with one). + + lease ----------------> lease -> NULL lease -> NULL + ^ \(2) |(1) ^ \ (1) + | \ | | \ + smach \ | smach \ + ^ | \ | ^ | \ + | v v v | v v + lif --> lif --> lif --> lif --> NULL lif --> lif -> NULL + net0 net0:1 net0:4 net0:2 net1 net1:5 + ^ ^ + | | + v6root -> pif ----------------------------------> pif -> NULL + net0 net1 + + Note that there's intentionally no ordering based on name in the + list of LIFs. Instead, the contiguous LIF structures in that list + represent the addresses in each lease. The logical interfaces + themselves are allocated and numbered by the system kernel, so they + may not be sequential, and there may be gaps in the list if other + entities (such as in.ndpd) are also configuring interfaces. + + Note also that with IPv4 DHCP, the lease points to the LIF that's + also the main LIF for the state machine, because that's the IP + interface that dhcpagent controls. With DHCPv6, the lease (one per + IA structure) points to a separate set of LIFs that are created just + for the leased addresses (one per IA address in an IAADDR option). + The state machine alone points to the main LIF. + + +Packet Structure Extensions + + Obviously, we need some DHCPv6 packet data structures and + definitions. A new <netinet/dhcp6.h> file will be introduced with + the necessary #defines and structures. The key structure there will + be: + + struct dhcpv6_message { + uint8_t d6m_msg_type; + uint8_t d6m_transid_ho; + uint16_t d6m_transid_lo; + }; + typedef struct dhcpv6_message dhcpv6_message_t; + + This defines the usual (non-relay) DHCPv6 packet header, and is + roughly equivalent to PKT for IPv4. + + Extending dhcp_pkt_t for DHCPv6 is straightforward, as it's used + only within dhcpagent. This structure will be amended to use a + union for v4/v6 and include a boolean to flag which version is in + use. + + For the PKT_LIST structure, things are more complex. This defines + both a queuing mechanism for received packets (typically OFFERs) and + a set of packet decoding structures. The decoding structures are + highly specific to IPv4 DHCP -- they have no means to handle nested + or repeated options (as used heavily in DHCPv6) and make use of the + DHCP_OPT structure which is specific to IPv4 DHCP -- and are + somewhat expensive in storage, due to the use of arrays indexed by + option code number. + + Worse, this structure is used throughout the system, so changes to + it need to be made carefully. (For example, the existing 'pkt' + member can't just be turned into a union.) + + For an initial prototype, since discarded, I created a new + dhcp_plist_t structure to represent packet lists as used inside + dhcpagent and made dhcp_pkt_t valid for use on input and output. + The result is unsatisfying, though, as it results in code that + manipulates far too many data structures in common cases; it's a sea + of pointers to pointers. + + The better answer is to use PKT_LIST for both IPv4 and IPv6, adding + the few new bits of metadata required to the end (receiving ifIndex, + packet source/destination addresses), and staying within the overall + existing design. + + For option parsing, dhcpv6_find_option() and dhcpv6_pkt_option() + functions will be added to libdhcputil. The former function will + walk a DHCPv6 option list, and provide safe (bounds-checked) access + to the options inside. The function can be called recursively, so + that option nesting can be handled fairly simply by nested loops, + and can be called repeatedly to return each instance of a given + option code number. The latter function is just a convenience + wrapper on dhcpv6_find_option() that starts with a PKT_LIST pointer + and iterates over the top-level options with a given code number. + + There are two special considerations for the use of these library + interfaces: there's no "pad" option for DHCPv6 or alignment + requirements on option headers or contents, and nested options + always follow a structure that has type-dependent length. This + means that code that handles options must all be written to deal + with unaligned data, and suboption code must index the pointer past + the type-dependent part. + + +Packet Construction + + Unlike DHCPv4, DHCPv6 places the transaction timer value in an + option. The existing code sets the current time value in + send_pkt_internal(), which allows it to be updated in a + straightforward way when doing retransmits. + + To make this work in a simple manner for DHCPv6, I added a + remove_pkt_opt() function. The update logic just does a remove and + re-adds the option. We could also just assume the presence of the + option, find it, and modify in place, but the remove feature seems + more general. + + DHCPv6 uses nesting options. To make this work, two new utility + functions are needed. First, an add_pkt_subopt() function will take + a pointer to an existing option and add an embedded option within + it. The packet length and existing option length are updated. If + that existing option isn't a top-level option, though, this means + that the caller must update the lengths of all of the enclosing + options up to the top level. To do this, update_v6opt_len() will be + added. This is used in the special case of adding a Status Code + option to an IAADDR option within an IA_NA top-level option. + + +Sockets and I/O Handling + + DHCPv6 doesn't need or use either a DLPI or a broadcast IP socket. + Instead, a single unicast-bound IP socket on a link-local address + would be the most that is needed. This is roughly equivalent to + if_sock_ip_fd in the existing design, but that existing socket is + bound only after DHCP reaches BOUND state -- that is, when it + switches away from DLPI. We need something different. + + This, along with the excess of open file descriptors in an otherwise + idle daemon and the potentially serious performance problems in + leaving DLPI open at all times, argues for a larger redesign of the + I/O logic in dhcpagent. + + The first thing that we can do is eliminate the need for the + per-ifslist if_sock_fd. This is used primarily for issuing ioctls + to configure interfaces -- a task that would work as well with any + open socket -- and is also registered to receive any ACK/NAK packets + that may arrive via broadcast. Both of these can be eliminated by + creating a pair of global sockets (IPv4 and IPv6), bound and + configured for ACK/NAK reception. The only functional difference is + that the list of running state machines must be scanned on reception + to find the correct transaction ID, but the existing design + effectively already goes to this effort because the kernel + replicates received datagrams among all matching sockets, and each + ifslist entry has a socket open. + + (The existing code for if_sock_fd makes oblique reference to unknown + problems in the system that may prevent binding from working in some + cases. The reference dates back some seven years to the original + DHCP implementation. I've observed no such problems in extensive + testing and if any do show up, they will be dealt with by fixing the + underlying bugs.) + + This leads to an important simplification: it's no longer necessary + to register, unregister, and re-register for packet reception while + changing state -- register_acknak() and unregister_acknak() are + gone. Instead, we always receive, and we dispatch the packets as + they arrive. As a result, when receiving a DHCPv4 ACK or DHCPv6 + Reply when in BOUND state, we know it's a duplicate, and we can + discard. + + The next part is in minimizing DLPI usage. A DLPI stream is needed + at most for each IPv4 PIF, and it's not needed when all of the + DHCP instances on that PIF are bound. In fact, the current + implementation deals with this in configure_bound() by setting a + "blackhole" packet filter. The stream is left open. + + To simplify this, we will open at most one DLPI stream on a PIF, and + use reference counts from the state machines to determine when the + stream must be open and when it can be closed. This mechanism will + be centralized in a set_smach_state() function that changes the + state and opens/closes the DLPI stream when needed. + + This leads to another simplification. The I/O logic in the existing + dhcpagent makes use of the protocol state to select between DLPI and + sockets. Now that we keep track of this in a simpler manner, we no + longer need to switch out on state in when sending a packet; just + test the dsm_using_dlpi flag instead. + + Still another simplification is in the handling of DHCPv4 INFORM. + The current code has separate logic in it for getting the interface + state and address information. This is no longer necessary, as the + LIF mechanism keeps track of the interface state. And since we have + separate lease structures, and INFORM doesn't acquire a lease, we no + longer have to be careful about canonizing the interface on + shutdown. + + Although the default is to send all client messages to a well-known + multicast address for servers and relays, DHCPv6 also has a + mechanism that allows the client to send unicast messages to the + server. The operation of this mechanism is slightly complex. + First, the server sends the client a unicast address via an option. + We may use this address as the destination (rather than the + well-known multicast address for local DHCPv6 servers and relays) + only if we have a viable local source address. This means using + SIOCGDSTINFO each time we try to send unicast. Next, the server may + send back a special status code: UseMulticast. If this is received, + and if we were actually using unicast in our messages to the server, + then we need to forget the unicast address, switch back to + multicast, and resend our last message. + + Note that it's important to avoid the temptation to resend the last + message every time UseMulticast is seen, and do it only once on + switching back to multicast: otherwise, a potential feedback loop is + created. + + Because IP_PKTINFO (PSARC 2006/466) has integrated, we could go a + step further by removing the need for any per-LIF sockets and just + use the global sockets for all but DLPI. However, in order to + facilitate a Solaris 10 backport, this will be done separately as CR + 6509317. + + In the case of DHCPv6, we already have IPV6_PKTINFO, so we will pave + the way for IPv4 by beginning to using this now, and thus have just + a single socket (bound to "::") for all of DHCPv6. Doing this + requires switching from the old BSD4.2 -lsocket -lnsl to the + standards-compliant -lxnet in order to use ancillary data. + + It may also be possible to remove the need for DLPI for IPv4, and + incidentally simplify the code a fair amount, by adding a kernel + option to allow transmission and reception of UDP packets over + interfaces that are plumbed but not marked IFF_UP. This is left for + future work. + + +The State Machine + + Several parts of the existing state machine need additions to handle + DHCPv6, which is a superset of DHCPv4. + + First, there are the RENEWING and REBINDING states. For IPv4 DHCP, + these states map one-to-one with a single address and single lease + that's undergoing renewal. It's a simple progression (on timeout) + from BOUND, to RENEWING, to REBINDING and finally back to SELECTING + to start over. Each retransmit is done by simply rescheduling the + T1 or T2 timer. + + For DHCPv6, things are somewhat more complex. At any one time, + there may be multiple IAs (leases) that are effectively in renewing + or rebinding state, based on the T1/T2 timers for each IA, and many + addresses that have expired. + + However, because all of the leases are related to a single server, + and that server either responds to our requests or doesn't, we can + simplify the states to be nearly identical to IPv4 DHCP. + + The revised definition for use with DHCPv6 is: + + - Transition from BOUND to RENEWING state when the first T1 timer + (of any lease on the state machine) expires. At this point, as + an optimization, we should begin attempting to renew any IAs + that are within REN_TIMEOUT (10 seconds) of reaching T1 as well. + We may as well avoid sending an excess of packets. + + - When a T1 lease timer expires and we're in RENEWING or REBINDING + state, just ignore it, because the transaction is already in + progress. + + - At each retransmit timeout, we should check to see if there are + more IAs that need to join in because they've passed point T1 as + well, and, if so, add them. This check isn't necessary at this + time, because only a single IA_NA is possible with the initial + design. + + - When we reach T2 on any IA and we're in BOUND or RENEWING state, + enter REBINDING state. At this point, we have a choice. For + those other IAs that are past T1 but not yet at T2, we could + ignore them (sending only those that have passed point T2), + continue to send separate Renew messages for them, or just + include them in the Rebind message. This isn't an issue that + must be dealt with for this project, but the plan is to include + them in the Rebind message. + + - When a T2 lease timer expires and we're in REBINDING state, just + ignore it, as with the corresponding T1 timer. + + - As addresses reach the end of their preferred lifetimes, set the + IFF_DEPRECATED flag. As they reach the end of the valid + lifetime, remove them from the system. When an IA (lease) + becomes empty, just remove it. When there are no more leases + left, return to SELECTING state to start over. + + Note that the RFC treats the IAs as separate entities when + discussing the renew/rebind T1/T2 timers, but treats them as a unit + when doing the initial negotiation. This is, to say the least, + confusing, especially so given that there's no reason to expect that + after having failed to elicit any responses at all from the server + on one IA, the server will suddenly start responding when we attempt + to renew some other IA. We rationalize this behavior by using a + single renew/rebind state for the entire state machine (and thus + client/server pair). + + There's a subtle timing difference here between DHCPv4 and DHCPv6. + For DHCPv4, the client just sends packets more and more frequently + (shorter timeouts) as the next state gets nearer. DHCPv6 treats + each as a transaction, using the same retransmit logic as for other + messages. The DHCPv6 method is a cleaner design, so we will change + the DHCPv4 implementation to do the same, and compute the new timer + values as part of stop_extending(). + + Note that it would be possible to start the SELECTING state earlier + than waiting for the last lease to expire, and thus avoid a loss of + connectivity. However, it this point, there are other servers on + the network that have seen us attempting to Rebind for quite some + time, and they have not responded. The likelihood that there's a + server that will ignore Rebind but then suddenly spring into action + on a Solicit message seems low enough that the optimization won't be + done now. (Starting SELECTING state earlier may be done in the + future, if it's found to be useful.) + + +Persistent State + + IPv4 DHCP has only minimal need for persistent state, beyond the + configuration parameters. The state is stored when "ifconfig dhcp + drop" is run or the daemon receives SIGTERM, which is typically done + only well after the system is booted and running. + + The daemon stores this state in /etc/dhcp, because it needs to be + available when only the root file system has been mounted. + + Moreover, dhcpagent starts very early in the boot process. It runs + as part of svc:/network/physical:default, which runs well before + root is mounted read/write: + + svc:/system/filesystem/root:default -> + svc:/system/metainit:default -> + svc:/system/identity:node -> + svc:/network/physical:default + svc:/network/iscsi_initiator:default -> + svc:/network/physical:default + + and, of course, well before either /var or /usr is mounted. This + means that any persistent state must be kept in the root file + system, and that if we write before shutdown, we have to cope + gracefully with the root file system returning EROFS on write + attempts. + + For DHCPv6, we need to try to keep our stable DUID and IAID values + stable across reboots to fulfill the demands of RFC 3315. + + The DUID is either configured or automatically generated. When + configured, it comes from the /etc/default/dhcpagent file, and thus + does not need to be saved by the daemon. If automatically + generated, there's exactly one of these created, and it will + eventually be needed before /usr is mounted, if /usr is mounted over + IPv6. This means a new file in the root file system, + /etc/dhcp/duid, will be used to hold the automatically generated + DUID. + + The determination of whether to use a configured DUID or one saved + in a file is made in get_smach_cid(). This function will + encapsulate all of the DUID parsing and generation machinery for the + rest of dhcpagent. + + If root is not writable at the point when dhcpagent starts, and our + attempt fails with EROFS, we will set a timer for 60 second + intervals to retry the operation periodically. In the unlikely case + that it just never succeeds or that we're rebooted before root + becomes writable, then the impact will be that the daemon will wake + up once a minute and, ultimately, we'll choose a different DUID on + next start-up, and we'll thus lose our leases across a reboot. + + The IAID similarly must be kept stable if at all possible, but + cannot be configured by the user. To do make these values stable, + we will use two strategies. First the IAID value for a given + interface (if not known) will just default to the IP ifIndex value, + provided that there's no known saved IAID using that value. Second, + we will save off the IAID we choose in a single /etc/dhcp/iaid file, + containing an array of entries indexed by logical interface name. + Keeping it in a single file allows us to scan for used and unused + IAID values when necessary. + + This mechanism depends on the interface name, and thus will need to + be revisited when Clearview vanity naming and NWAM are available. + + Currently, the boot system (GRUB, OBP, the miniroot) does not + support installing over IPv6. This could change in the future, so + one of the goals of the above stability plan is to support that + event. + + When running in the miniroot on an x86 system, /etc/dhcp (and the + rest of the root) is mounted on a read-only ramdisk. In this case, + writing to /etc/dhcp will just never work. A possible solution + would be to add a new privileged command in ifconfig that forces + dhcpagent to write to an alternate location. The initial install + process could then do "ifconfig <x> dhcp write /a" to get the needed + state written out to the newly-constructed system root. + + This part (the new write option) won't be implemented as part of + this project, because it's not needed yet. + + +Router Advertisements + + IPv6 Router Advertisements perform two functions related to DHCPv6: + + - they specify whether and how to run DHCPv6 on a given interface. + - they provide a list of the valid prefixes on an interface. + + For the first function, in.ndpd needs to use the same DHCP control + interfaces that ifconfig uses, so that it can launch dhcpagent and + trigger DHCPv6 when necessary. Note that it never needs to shut + down DHCPv6, as router advertisements can't do that. + + However, launching dhcpagent presents new problems. As a part of + the "Quagga SMF Modifications" project (PSARC 2006/552), in.ndpd in + Nevada is now privilege-aware and runs with limited privileges, + courtesy of SMF. Dhcpagent, on the other hand, must run with all + privileges. + + A simple work-around for this issue is to rip out the "privileges=" + clause from the method_credential for in.ndpd. I've taken this + direction initially, but the right longer-term answer seems to be + converting dhcpagent into an SMF service. This is quite a bit more + complex, as it means turning the /sbin/dhcpagent command line + interface into a utility that manipulates the service and passes the + command line options via IPC extensions. + + Such a design also begs the question of whether dhcpagent itself + ought to run with reduced privileges. It could, but it still needs + the ability to grant "all" (traditional UNIX root) privileges to the + eventhook script, if present. There seem to be few ways to do this, + though it's a good area for research. + + The second function, prefix handling, is also subtle. Unlike IPv4 + DHCP, DHCPv6 does not give the netmask or prefix length along with + the leased address. The client is on its own to determine the right + netmask to use. This is where the advertised prefixes come in: + these must be used to finish the interface configuration. + + We will have the DHCPv6 client configure each interface with an + all-ones (/128) netmask by default. In.ndpd will be modified so + that when it detects a new IFF_DHCPRUNNING IP logical interface, it + checks for a known matching prefix, and sets the netmask as + necessary. If no matching prefix is known, it will send a new + Router Solicitation message to try to find one. + + When in.ndpd learns of a new prefix from a Router Advertisement, it + will scan all of the IFF_DHCPRUNNING IP logical interfaces on the + same physical interface and set the netmasks when necessary. + Dhcpagent, for its part, will ignore the netmask on IPv6 interfaces + when checking for changes that would require it to "abandon" the + interface. + + Given the way that DHCPv6 and in.ndpd control both the horizontal + and the vertical in plumbing and removing logical interfaces, and + users do not, it might be worthwhile to consider roping off any + direct user changes to IPv6 logical interfaces under control of + in.ndpd or dhcpagent, and instead force users through a higher-level + interface. This won't be done as part of this project, however. + + +ARP Hardware Types + + There are multiple places within the DHCPv6 client where the mapping + of DLPI MAC type to ARP Hardware Type is required: + + - When we are constructing an automatic, stable DUID for our own + identity, we prefer to use a DUID-LLT if possible. This is done + by finding a link-layer interface, opening it, reading the MAC + address and type, and translating in the make_stable_duid() + function in libdhcpagent. + + - When we translate a user-configured DUID from + /etc/default/dhcpagent into a binary representation, we may have + to deal with a physical interface name. In this case, we must + open that interface and read the MAC address and type. + + - As part of the PIF data structure initialization, we need to read + out the MAC type so that it can be used in the BOOTP/DHCPv4 + 'htype' field. + + Ideally, these would all be provided by a single libdlpi + implementation. However, that project is on-going at this time and + has not yet integrated. For the time being, a dlpi_to_arp() + translation function (taking dl_mac_type and returning an ARP + Hardware Type number) will be placed in libdhcputil. + + This temporary function should be removed and this section of the + code updated when the new libdlpi from Clearview integrates. + + +Field Mappings + + Old (all in ifslist) New + next dhcp_smach_t.dsm_next + prev dhcp_smach_t.dsm_prev + if_hold_count dhcp_smach_t.dsm_hold_count + if_ia dhcp_smach_t.dsm_ia + if_async dhcp_smach_t.dsm_async + if_state dhcp_smach_t.dsm_state + if_dflags dhcp_smach_t.dsm_dflags + if_name dhcp_smach_t.dsm_name (see text) + if_index dhcp_pif_t.pif_index + if_max dhcp_lif_t.lif_max and dhcp_pif_t.pif_max + if_min (was unused; removed) + if_opt (was unused; removed) + if_hwaddr dhcp_pif_t.pif_hwaddr + if_hwlen dhcp_pif_t.pif_hwlen + if_hwtype dhcp_pif_t.pif_hwtype + if_cid dhcp_smach_t.dsm_cid + if_cidlen dhcp_smach_t.dsm_cidlen + if_prl dhcp_smach_t.dsm_prl + if_prllen dhcp_smach_t.dsm_prllen + if_daddr dhcp_pif_t.pif_daddr + if_dlen dhcp_pif_t.pif_dlen + if_saplen dhcp_pif_t.pif_saplen + if_sap_before dhcp_pif_t.pif_sap_before + if_dlpi_fd dhcp_pif_t.pif_dlpi_fd + if_sock_fd v4_sock_fd and v6_sock_fd (globals) + if_sock_ip_fd dhcp_lif_t.lif_sock_ip_fd + if_timer (see text) + if_t1 dhcp_lease_t.dl_t1 + if_t2 dhcp_lease_t.dl_t2 + if_lease dhcp_lif_t.lif_expire + if_nrouters dhcp_smach_t.dsm_nrouters + if_routers dhcp_smach_t.dsm_routers + if_server dhcp_smach_t.dsm_server + if_addr dhcp_lif_t.lif_v6addr + if_netmask dhcp_lif_t.lif_v6mask + if_broadcast dhcp_lif_t.lif_v6peer + if_ack dhcp_smach_t.dsm_ack + if_orig_ack dhcp_smach_t.dsm_orig_ack + if_offer_wait dhcp_smach_t.dsm_offer_wait + if_offer_timer dhcp_smach_t.dsm_offer_timer + if_offer_id dhcp_pif_t.pif_dlpi_id + if_acknak_id dhcp_lif_t.lif_acknak_id + if_acknak_bcast_id v4_acknak_bcast_id (global) + if_neg_monosec dhcp_smach_t.dsm_neg_monosec + if_newstart_monosec dhcp_smach_t.dsm_newstart_monosec + if_curstart_monosec dhcp_smach_t.dsm_curstart_monosec + if_disc_secs dhcp_smach_t.dsm_disc_secs + if_reqhost dhcp_smach_t.dsm_reqhost + if_recv_pkt_list dhcp_smach_t.dsm_recv_pkt_list + if_sent dhcp_smach_t.dsm_sent + if_received dhcp_smach_t.dsm_received + if_bad_offers dhcp_smach_t.dsm_bad_offers + if_send_pkt dhcp_smach_t.dsm_send_pkt + if_send_timeout dhcp_smach_t.dsm_send_timeout + if_send_dest dhcp_smach_t.dsm_send_dest + if_send_stop_func dhcp_smach_t.dsm_send_stop_func + if_packet_sent dhcp_smach_t.dsm_packet_sent + if_retrans_timer dhcp_smach_t.dsm_retrans_timer + if_script_fd dhcp_smach_t.dsm_script_fd + if_script_pid dhcp_smach_t.dsm_script_pid + if_script_helper_pid dhcp_smach_t.dsm_script_helper_pid + if_script_event dhcp_smach_t.dsm_script_event + if_script_event_id dhcp_smach_t.dsm_script_event_id + if_callback_msg dhcp_smach_t.dsm_callback_msg + if_script_callback dhcp_smach_t.dsm_script_callback + + Notes: + + - The dsm_name field currently just points to the lif_name on the + controlling LIF. This may need to be named differently in the + future; perhaps when Zones are supported. + + - The timer mechanism will be refactored. Rather than using the + separate if_timer[] array to hold the timer IDs and + if_{t1,t2,lease} to hold the relative timer values, we will + gather this information into a dhcp_timer_t structure: + + dt_id timer ID value + dt_start relative start time + + New fields not accounted for above: + + dhcp_pif_t.pif_next linkage in global list of PIFs + dhcp_pif_t.pif_prev linkage in global list of PIFs + dhcp_pif_t.pif_lifs pointer to list of LIFs on this PIF + dhcp_pif_t.pif_isv6 IPv6 flag + dhcp_pif_t.pif_dlpi_count number of state machines using DLPI + dhcp_pif_t.pif_hold_count reference count + dhcp_pif_t.pif_name name of physical interface + dhcp_lif_t.lif_next linkage in per-PIF list of LIFs + dhcp_lif_t.lif_prev linkage in per-PIF list of LIFs + dhcp_lif_t.lif_pif backpointer to parent PIF + dhcp_lif_t.lif_smachs pointer to list of state machines + dhcp_lif_t.lif_lease backpointer to lease holding LIF + dhcp_lif_t.lif_flags interface flags (IFF_*) + dhcp_lif_t.lif_hold_count reference count + dhcp_lif_t.lif_dad_wait waiting for DAD resolution flag + dhcp_lif_t.lif_removed removed from list flag + dhcp_lif_t.lif_plumbed plumbed by dhcpagent flag + dhcp_lif_t.lif_expired lease has expired flag + dhcp_lif_t.lif_declined reason to refuse this address (string) + dhcp_lif_t.lif_iaid unique and stable 32-bit identifier + dhcp_lif_t.lif_iaid_id timer for delayed /etc writes + dhcp_lif_t.lif_preferred preferred timer for v6; deprecate after + dhcp_lif_t.lif_name name of logical interface + dhcp_smach_t.dsm_lif controlling (main) LIF + dhcp_smach_t.dsm_leases pointer to list of leases + dhcp_smach_t.dsm_lif_wait number of LIFs waiting on DAD + dhcp_smach_t.dsm_lif_down number of LIFs that have failed + dhcp_smach_t.dsm_using_dlpi currently using DLPI flag + dhcp_smach_t.dsm_send_tcenter v4 central timer value; v6 MRT + dhcp_lease_t.dl_next linkage in per-state-machine list of leases + dhcp_lease_t.dl_prev linkage in per-state-machine list of leases + dhcp_lease_t.dl_smach back pointer to state machine + dhcp_lease_t.dl_lifs pointer to first LIF configured by lease + dhcp_lease_t.dl_nlifs number of configured consecutive LIFs + dhcp_lease_t.dl_hold_count reference counter + dhcp_lease_t.dl_removed removed from list flag + dhcp_lease_t.dl_stale lease was not updated by Renew/Rebind + + +Snoop + + The snoop changes are fairly straightforward. As snoop just decodes + the messages, and the message format is quite different between + DHCPv4 and DHCPv6, a new module will be created to handle DHCPv6 + decoding, and will export a interpret_dhcpv6() function. + + The one bit of commonality between the two protocols is the use of + ARP Hardware Type numbers, which are found in the underlying BOOTP + message format for DHCPv4 and in the DUID-LL and DUID-LLT + construction for DHCPv6. To simplify this, the existing static + show_htype() function in snoop_dhcp.c will be renamed to arp_htype() + (to better reflect its functionality), updated with more modern + hardware types, moved to snoop_arp.c (where it belongs), and made a + public symbol within snoop. + + While I'm there, I'll update snoop_arp.c so that when it prints an + ARP message in verbose mode, it uses arp_htype() to translate the + ar_hrd value. + + The snoop updates also involve the addition of a new "dhcp6" keyword + for filtering. As a part of this, CR 6487534 will be fixed. + + +IPv6 Source Address Selection + + One of the customer requests for DHCPv6 is to be able to predict the + address selection behavior in the presence of both stateful and + stateless addresses on the same network. + + Solaris implements RFC 3484 address selection behavior. In this + scheme, the first seven rules implement some basic preferences for + addresses, with Rule 8 being a deterministic tie breaker. + + Rule 8 relies on a special function, CommonPrefixLen, defined in the + RFC, that compares leading bits of the address without regard to + configured prefix length. As Rule 1 eliminates equal addresses, + this always picks a single address. + + This rule, though, allows for additional checks: + + Rule 8 may be superseded if the implementation has other means of + choosing among source addresses. For example, if the implementation + somehow knows which source address will result in the "best" + communications performance. + + We will thus split Rule 8 into three separate rules: + + - First, compare on configured prefix. The interface with the + longest configured prefix length that also matches the candidate + address will be preferred. + + - Next, check the type of address. Prefer statically configured + addresses above all others. Next, those from DHCPv6. Next, + stateless autoconfigured addresses. Finally, temporary addresses. + (Note that Rule 7 will take care of temporary address preferences, + so that this rule doesn't actually need to look at them.) + + - Finally, run the check-all-bits (CommonPrefixLen) tie breaker. + + The result of this is that if there's a local address in the same + configured prefix, then we'll prefer that over other addresses. If + there are multiple to choose from, then will pick static first, then + DHCPv6, then dynamic. Finally, if there are still multiples, we'll + use the "closest" address, bitwise. + + Also, this basic implementation scheme also addresses CR 6485164, so + a fix for that will be included with this project. + + +Minor Improvements + + Various small problems with the system encountered during + development will be fixed along with this project. Some of these + are: + + - List of ARPHRD_* types is a bit short; add some new ones. + + - List of IPPORT_* values is similarly sparse; add others in use by + snoop. + + - dhcpmsg.h lacks PRINTFLIKE for dhcpmsg(); add it. + + - CR 6482163 causes excessive lint errors with libxnet; will fix. + + - libdhcpagent uses gettimeofday() for I/O timing, and this can + drift on systems with NTP. It should use a stable time source + (gethrtime()) instead, and should return better error values. + + - Controlling debug mode in the daemon shouldn't require changing + the command line arguments or jumping through special hoops. I've + added undocumented ".DEBUG_LEVEL=[0-3]" and ".VERBOSE=[01]" + features to /etc/default/dhcpagent. + + - The various attributes of the IPC commands (requires privileges, + creates a new session, valid with BOOTP, immediate reply) should + be gathered together into one look-up table rather than scattered + as hard-coded tests. + + - Remove the event unregistration from the command dispatch loop and + get rid of the ipc_action_pending() botch. We'll get a + zero-length read any time the client goes away, and that will be + enough to trigger termination. This fix removes async_pending() + and async_timeout() as well, and fixes CR 6487958 as a + side-effect. + + - Throughout the dhcpagent code, there are private implementations + of doubly-linked and singly-linked lists for each data type. + These will all be removed and replaced with insque(3C) and + remque(3C). + + +Testing + + The implementation was tested using the TAHI test suite for DHCPv6 + (www.tahi.org). There are some peculiar aspects to this test suite, + and these issues directed some of the design. In particular: + + - If Renew/Rebind doesn't mention one of our leases, then we need to + allow the message to be retransmitted. Real servers are unlikely + to do this. + + - We must look for a status code within IAADDR and within IA_NA, and + handle the paradoxical case of "NoAddrAvail." That doesn't make + sense, as a server with no addresses wouldn't use those options. + That option makes more sense at the top level of the message. + + - If we get "UseMulticast" when we were already using multicast, + then ignore the error code. Sending another request would cause a + loop. + + - TAHI uses "NoBinding" at the top level of the message. This + status code only makes sense within an IA, as it refers to the + GUID:IAID binding, which doesn't exist outside an IA. We must + ignore such errors -- treat them as success. + + +Interactions With Other Projects + + Clearview UV (vanity naming) will cause link names, and thus IP + interface names, to become changeable over time. This will break + the IAID stability mechanism if UV is used for arbitrary renaming, + rather than as just a DR enhancement. + + When this portion of Clearview integrates, this part of the DHCPv6 + design may need to be revisited. (The solution will likely be + handled at some higher layer, such as within Network Automagic.) + + Clearview is also contributing a new libdlpi that will work for + dhcpagent, and is thus removing the private dlpi_io.[ch] functions + from this daemon. When that Clearview project integrates, the + DHCPv6 project will need to adjust to the new interfaces, and remove + or relocate the dlpi_to_arp() function. + + +Futures + + Zones currently cannot address any IP interfaces by way of DHCP. + This project will not fix that problem, but the DUID/IAID could be + used to help fix it in the future. + + In particular, the DUID allows the client to obtain separate sets of + addresses and configuration parameters on a single interface, just + like an IPv4 Client ID, but it includes a clean mechanism for vendor + extensions. If we associate the DUID with the zone identifier or + name through an extension, then we have a really simple way of + allocating per-zone addresses. + + Moreover, RFC 4361 describes a handy way of using DHCPv6 DUID/IAID + values with IPv4 DHCP, which would quickly solve the problem of + using DHCP for IPv4 address assignment in non-global zones as well. + + (One potential risk with this plan is that there may be server + implementations that either do not implement the RFC correctly or + otherwise mishandle the DUID. This has apparently bitten some early + adopters.) + + Implementing the FQDN option for DHCPv6 would, given the current + libdhcputil design, require a new 'type' of entry for the inittab6 + file. This is because the design does not allow for any simple + means to ``compose'' a sequence of basic types together. Thus, + every type of option must either be a basic type, or an array of + multiple instances of the same basic type. + + If we implement FQDN in the future, it may be useful to explore some + means of allowing a given option instance to be a sequence of basic + types. + + This project does not make the DNS resolver or any other subsystem + use the data gathered by DHCPv6. It just makes the data available + through dhcpinfo(1). Future projects should modify those services + to use configuration data learned via DHCPv6. (One of the reasons + this is not being done now is that Network Automagic [NWAM] will + likely be changing this area substantially in the very near future, + and thus the effort would be largely wasted.) + + +Appendix A - Choice of Venue + + There are three logical places to implement DHCPv6: + + - in dhcpagent + - in in.ndpd + - in a new daemon (say, 'dhcp6agent') + + We need to access parameters via dhcpinfo, and should provide the + same set of status and control features via ifconfig as are present + for IPv4. (For the latter, if we fail to do that, it will likely + confuse users. The expense for doing it is comparatively small, and + it will be useful for testing, even though it should not be needed + in normal operation.) + + If we implement somewhere other than dhcpagent, then we need to give + that new daemon (in.ndpd or dhcp6agent) the same basic IPC features + as dhcpagent already has. This means either extracting those bits + (async.c and ipc_action.c) into a shared library or just copying + them. Obviously, the former would be preferred, but as those bits + depend on the rest of the dhcpagent infrastructure for timers and + state handling, this means that the new process would have to look a + lot like dhcpagent. + + Implementing DHCPv6 as part of in.ndpd is attractive, as it + eliminates the confusion that the router discovery process for + determining interface netmasks can cause, along with the need to do + any signaling at all to bring DHCPv6 up. However, the need to make + in.ndpd more like dhcpagent is unattractive. + + Having a new dhcp6agent daemon seems to have little to recommend it, + other than leaving the existing dhcpagent code untouched. If we do + that, then we end up with two implementations that do many similar + things, and must be maintained in parallel. + + Thus, although it leads to some complexity in reworking the data + structures to fit both protocols, on balance the simplest solution + is to extend dhcpagent. + + +Appendix B - Cross-Reference + + in.ndpd + + - Start dhcpagent and issue "dhcp start" command via libdhcpagent + - Parse StatefulAddrConf interface option from ndpd.conf + - Watch for M and O bits to trigger DHCPv6 + - Handle "no routers found" case and start DHCPv6 + - Track prefixes and set prefix length on IFF_DHCPRUNNING aliases + - Send new Router Solicitation when prefix unknown + - Change privileges so that dhcpagent can be launched successfully + + libdhcputil + + - Parse new /etc/dhcp/inittab6 file + - Handle new UNUMBER24, SNUMBER64, IPV6, DUID and DOMAIN types + - Add DHCPv6 option iterators (dhcpv6_find_option and + dhcpv6_pkt_option) + - Add dlpi_to_arp function (temporary) + + libdhcpagent + + - Add stable DUID and IAID creation and storage support + functions and add new dhcp_stable.h include file + - Support new DECLINING and RELEASING states introduced by DHCPv6. + - Update implementation so that it doesn't rely on gettimeofday() + for I/O timeouts + - Extend the hostconf functions to support DHCPv6, using a new + ".dh6" file + + snoop + + - Add support for DHCPv6 packet decoding (all types) + - Add "dhcp6" filter keyword + - Fix known bugs in DHCP filtering + + ifconfig + + - Remove inet-only restriction on "dhcp" keyword + + netstat + + - Remove strange "-I list" feature. + - Add support for DHCPv6 and iterating over IPv6 interfaces. + + ip + + - Add extensions to IPv6 source address selection to prefer DHCPv6 + addresses when all else is equal + - Fix known bugs in source address selection (remaining from TX + integration) + + other + + - Add ifindex and source/destination address into PKT_LIST. + - Add more ARPHDR_* and IPPORT_* values. diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/adopt.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/adopt.c index 72009cdedc..de4ab6f164 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/adopt.c +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/adopt.c @@ -19,10 +19,11 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * - * ADOPTING state of the client state machine. + * ADOPTING state of the client state machine. This is used only during + * diskless boot with IPv4. */ #pragma ident "%Z%%M% %I% %E% SMI" @@ -32,14 +33,16 @@ #include <unistd.h> #include <stdlib.h> #include <signal.h> -#include <sys/sockio.h> #include <sys/socket.h> +#include <net/if_arp.h> #include <netinet/in.h> #include <sys/systeminfo.h> #include <netinet/inetutil.h> #include <netinet/dhcp.h> #include <dhcpmsg.h> +#include <libdevinfo.h> +#include "agent.h" #include "async.h" #include "util.h" #include "packet.h" @@ -54,21 +57,26 @@ typedef struct { static int get_dhcp_kcache(dhcp_kcache_t **, size_t *); +static boolean_t get_prom_prop(const char *, const char *, uchar_t **, + uint_t *); + /* * dhcp_adopt(): adopts the interface managed by the kernel for diskless boot * * input: void - * output: int: nonzero on success, zero on failure + * output: boolean_t: B_TRUE success, B_FALSE on failure */ -int +boolean_t dhcp_adopt(void) { int retval; dhcp_kcache_t *kcache = NULL; size_t kcache_size; PKT_LIST *plp = NULL; - struct ifslist *ifsp; + dhcp_lif_t *lif; + dhcp_smach_t *dsmp = NULL; + uint_t client_id_len; retval = get_dhcp_kcache(&kcache, &kcache_size); if (retval == 0 || kcache_size < sizeof (dhcp_kcache_t)) { @@ -82,19 +90,14 @@ dhcp_adopt(void) * convert the kernel's ACK into binary */ - plp = calloc(1, sizeof (PKT_LIST)); + plp = alloc_pkt_entry(strlen(kcache->dk_ack) / 2, B_FALSE); 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) { + 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; } @@ -120,60 +123,105 @@ dhcp_adopt(void) * totally hosed and can only bail out. */ - ifsp = insert_ifs(kcache->dk_if_name, B_TRUE, &retval); - if (ifsp == NULL) + if ((lif = attach_lif(kcache->dk_if_name, B_FALSE, &retval)) == NULL) { + dhcpmsg(MSG_ERROR, "dhcp_adopt: unable to attach %s: %d", + kcache->dk_if_name, retval); goto failure; + } - ifsp->if_state = ADOPTING; - ifsp->if_dflags |= DHCP_IF_PRIMARY; + if ((dsmp = insert_smach(lif, &retval)) == NULL) { + dhcpmsg(MSG_ERROR, "dhcp_adopt: unable to create state " + "machine for %s: %d", kcache->dk_if_name, retval); + goto failure; + } + + /* + * If the agent is adopting a lease, then OBP is initially + * searched for a client-id. + */ + + dhcpmsg(MSG_DEBUG, "dhcp_adopt: getting /chosen:clientid property"); + + client_id_len = 0; + if (!get_prom_prop("chosen", "client-id", &dsmp->dsm_cid, + &client_id_len)) { + /* + * a failure occurred trying to acquire the client-id + */ + + dhcpmsg(MSG_DEBUG, + "dhcp_adopt: cannot allocate client id for %s", + dsmp->dsm_name); + goto failure; + } else if (dsmp->dsm_hwtype == ARPHRD_IB && dsmp->dsm_cid == NULL) { + /* + * when the interface is infiniband and the agent + * is adopting the lease there must be an OBP + * client-id. + */ + + dhcpmsg(MSG_DEBUG, "dhcp_adopt: no /chosen:clientid id for %s", + dsmp->dsm_name); + goto failure; + } + + dsmp->dsm_cidlen = client_id_len; + + if (set_lif_dhcp(lif, B_TRUE) != DHCP_IPC_SUCCESS) + goto failure; + + if (!set_smach_state(dsmp, ADOPTING)) + goto failure; + dsmp->dsm_dflags = DHCP_IF_PRIMARY; /* * move to BOUND and use the information in our ACK packet. * adoption will continue after DAD via dhcp_adopt_complete. */ - if (dhcp_bound(ifsp, plp) == 0) { + if (!dhcp_bound(dsmp, plp)) { dhcpmsg(MSG_CRIT, "dhcp_adopt: cannot use cached packet"); goto failure; } free(kcache); - return (1); + return (B_TRUE); failure: + /* Note: no need to free lif; dsmp holds reference */ + if (dsmp != NULL) + release_smach(dsmp); free(kcache); - if (plp != NULL) - free(plp->pkt); - free(plp); - return (0); + free_pkt_entry(plp); + return (B_FALSE); } /* * dhcp_adopt_complete(): completes interface adoption process after kernel * duplicate address detection (DAD) is done. * - * input: struct ifslist *: the interface on which a lease is being adopted + * input: dhcp_smach_t *: the state machine on which a lease is being adopted * output: none */ void -dhcp_adopt_complete(struct ifslist *ifsp) +dhcp_adopt_complete(dhcp_smach_t *dsmp) { dhcpmsg(MSG_DEBUG, "dhcp_adopt_complete: completing adoption"); - if (async_start(ifsp, DHCP_EXTEND, B_FALSE) == 0) { + if (async_start(dsmp, DHCP_EXTEND, B_FALSE) == 0) { dhcpmsg(MSG_CRIT, "dhcp_adopt_complete: async_start failed"); return; } - if (dhcp_extending(ifsp) == 0) { + if (dhcp_extending(dsmp) == 0) { dhcpmsg(MSG_CRIT, "dhcp_adopt_complete: cannot send renew request"); return; } if (grandparent != (pid_t)0) { - dhcpmsg(MSG_DEBUG, "adoption complete, signalling parent (%i)" + dhcpmsg(MSG_DEBUG, "adoption complete, signalling parent (%ld)" " to exit.", grandparent); (void) kill(grandparent, SIGALRM); } @@ -205,3 +253,146 @@ get_dhcp_kcache(dhcp_kcache_t **kernel_cachep, size_t *kcache_size) (void) sysinfo(SI_DHCP_CACHE, (caddr_t)*kernel_cachep, size); return (1); } + +/* + * get_prom_prop(): get the value of the named property on the named node in + * devinfo root. + * + * input: const char *: The name of the node containing the property. + * const char *: The name of the property. + * uchar_t **: The property value, modified iff B_TRUE is returned. + * If no value is found the value is set to NULL. + * uint_t *: The length of the property value + * output: boolean_t: Returns B_TRUE if successful (no problems), + * otherwise B_FALSE. + * note: The memory allocated by this function must be freed by + * the caller. 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, + uint_t *lenp) +{ + di_node_t root_node; + di_node_t node; + di_prom_handle_t phdl = DI_PROM_HANDLE_NIL; + di_prom_prop_t pp; + uchar_t *value = NULL; + unsigned int len = 0; + boolean_t success = B_TRUE; + + /* + * locate root node + */ + + if ((root_node = di_init("/", DINFOCPYALL)) == DI_NODE_NIL || + (phdl = di_prom_init()) == DI_PROM_HANDLE_NIL) { + dhcpmsg(MSG_DEBUG, "get_prom_prop: property root node " + "not found"); + goto get_prom_prop_cleanup; + } + + /* + * locate nodename within '/' + */ + + for (node = di_child_node(root_node); + node != DI_NODE_NIL; + node = di_sibling_node(node)) { + if (strcmp(di_node_name(node), nodename) == 0) { + break; + } + } + + if (node == DI_NODE_NIL) { + dhcpmsg(MSG_DEBUG, "get_prom_prop: node not found"); + goto get_prom_prop_cleanup; + } + + /* + * scan all properties of /nodename for the 'propname' property + */ + + for (pp = di_prom_prop_next(phdl, node, DI_PROM_PROP_NIL); + pp != DI_PROM_PROP_NIL; + pp = di_prom_prop_next(phdl, node, pp)) { + + dhcpmsg(MSG_DEBUG, "get_prom_prop: property = %s", + di_prom_prop_name(pp)); + + if (strcmp(propname, di_prom_prop_name(pp)) == 0) { + break; + } + } + + if (pp == DI_PROM_PROP_NIL) { + dhcpmsg(MSG_DEBUG, "get_prom_prop: property not found"); + goto get_prom_prop_cleanup; + } + + /* + * get the property; allocate some memory copy it out + */ + + len = di_prom_prop_data(pp, (uchar_t **)&value); + + if (value == NULL) { + /* + * property data read problems + */ + + success = B_FALSE; + dhcpmsg(MSG_ERR, "get_prom_prop: cannot read property data"); + goto get_prom_prop_cleanup; + } + + if (propvaluep != NULL) { + /* + * allocate somewhere to copy the property value to + */ + + *propvaluep = calloc(len, sizeof (uchar_t)); + + if (*propvaluep == NULL) { + /* + * allocation problems + */ + + success = B_FALSE; + dhcpmsg(MSG_ERR, "get_prom_prop: cannot allocate " + "memory for property value"); + goto get_prom_prop_cleanup; + } + + /* + * copy data out + */ + + (void) memcpy(*propvaluep, value, len); + + /* + * copy out the length if a suitable pointer has + * been supplied + */ + + if (lenp != NULL) { + *lenp = len; + } + + dhcpmsg(MSG_DEBUG, "get_prom_prop: property value " + "length = %d", len); + } + +get_prom_prop_cleanup: + + if (phdl != DI_PROM_HANDLE_NIL) { + di_prom_fini(phdl); + } + + if (root_node != DI_NODE_NIL) { + di_fini(root_node); + } + + return (success); +} diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/agent.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/agent.c index 8d3538237b..14a5e2aed8 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/agent.c +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/agent.c @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -36,6 +36,7 @@ #include <stdio_ext.h> #include <dhcp_hostconf.h> #include <dhcpagent_ipc.h> +#include <dhcpagent_util.h> #include <dhcpmsg.h> #include <netinet/dhcp.h> #include <net/route.h> @@ -48,6 +49,8 @@ #include "class_id.h" #include "states.h" #include "packet.h" +#include "interface.h" +#include "defaults.h" #ifndef TEXT_DOMAIN #define TEXT_DOMAIN "SYS_TEST" @@ -70,14 +73,14 @@ static iu_eh_callback_t accept_event, ipc_event, rtsock_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. + * START is permitted if the state machine is fresh, or if we are in the + * process of trying to obtain a lease (as a convenience to save the + * administrator from having to do an explicit DROP). EXTEND, RELEASE, and + * GET_TAG require a lease to be obtained in order to make sense. INFORM is + * permitted if the interface is fresh or has an INFORM in progress or + * previously done on it -- otherwise a DROP or RELEASE is first required. + * PING and STATUS always make sense and thus are always permitted, as is DROP + * in order to permit the administrator to always bail out. */ static int ipc_cmd_allowed[DHCP_NSTATES][DHCP_NIPC] = { /* D E P R S S I G */ @@ -94,21 +97,42 @@ static int ipc_cmd_allowed[DHCP_NSTATES][DHCP_NIPC] = { /* 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 } + /* INFORMATION */ { 1, 0, 1, 0, 1, 1, 1, 1 }, + /* INIT_REBOOT */ { 1, 0, 1, 1, 1, 1, 0, 0 }, + /* ADOPTING */ { 1, 0, 1, 1, 0, 1, 0, 0 }, + /* INFORM_SENT */ { 1, 0, 1, 0, 1, 1, 1, 0 }, + /* DECLINING */ { 1, 1, 1, 1, 0, 1, 0, 1 }, + /* RELEASING */ { 1, 0, 1, 0, 0, 1, 0, 1 }, +}; + +#define CMD_ISPRIV 0x1 /* Command requires privileges */ +#define CMD_CREATE 0x2 /* Command creates an interface */ +#define CMD_BOOTP 0x4 /* Command is valid with BOOTP */ +#define CMD_IMMED 0x8 /* Reply is immediate (no BUSY state) */ + +static uint_t ipc_cmd_flags[DHCP_NIPC] = { + /* DHCP_DROP */ CMD_ISPRIV|CMD_BOOTP, + /* DHCP_EXTEND */ CMD_ISPRIV, + /* DHCP_PING */ CMD_BOOTP|CMD_IMMED, + /* DHCP_RELEASE */ CMD_ISPRIV, + /* DHCP_START */ CMD_CREATE|CMD_ISPRIV|CMD_BOOTP, + /* DHCP_STATUS */ CMD_BOOTP|CMD_IMMED, + /* DHCP_INFORM */ CMD_CREATE|CMD_ISPRIV, + /* DHCP_GET_TAG */ CMD_BOOTP|CMD_IMMED }; int main(int argc, char **argv) { boolean_t is_daemon = B_TRUE; - boolean_t is_verbose = B_FALSE; + boolean_t is_verbose; int ipc_fd; int c; struct rlimit rl; + debug_level = df_get_int("", B_FALSE, DF_DEBUG_LEVEL); + is_verbose = df_get_bool("", B_FALSE, DF_VERBOSE); + /* * -l is ignored for compatibility with old agent. */ @@ -161,6 +185,12 @@ main(int argc, char **argv) return (EXIT_FAILURE); } + /* + * Seed the random number generator, since we're going to need it + * to set transaction id's and for exponential backoff. + */ + srand48(gethrtime() ^ gethostid() ^ getpid()); + dhcpmsg_init(argv[0], is_daemon, is_verbose, debug_level); (void) atexit(dhcpmsg_fini); @@ -190,7 +220,7 @@ main(int argc, char **argv) * upon SIGTHAW we need to refresh any non-infinite leases. */ - (void) iu_eh_register_signal(eh, SIGTHAW, refresh_ifslist, NULL); + (void) iu_eh_register_signal(eh, SIGTHAW, refresh_smachs, NULL); class_id = get_class_id(); if (class_id != NULL) @@ -221,6 +251,14 @@ main(int argc, char **argv) (void) enable_extended_FILE_stdio(-1, -1); /* + * Create and bind default IP sockets used to control interfaces and to + * catch stray packets. + */ + + if (!dhcp_ip_default()) + return (EXIT_FAILURE); + + /* * create the ipc channel that the agent will listen for * requests on, and register it with the event handler so that * `accept_event' will be called back. @@ -250,9 +288,10 @@ main(int argc, char **argv) * Create the global routing socket. This is used for monitoring * interface transitions, so that we learn about the kernel's Duplicate * Address Detection status, and for inserting and removing default - * routes as learned from DHCP servers. + * routes as learned from DHCP servers. Both v4 and v6 are handed + * with this one socket. */ - rtsock_fd = socket(PF_ROUTE, SOCK_RAW, AF_INET); + rtsock_fd = socket(PF_ROUTE, SOCK_RAW, 0); if (rtsock_fd == -1) { dhcpmsg(MSG_ERR, "cannot open routing socket"); return (EXIT_FAILURE); @@ -267,10 +306,20 @@ main(int argc, char **argv) * kernel-managed interface before we start. */ - if (do_adopt && dhcp_adopt() == 0) + if (do_adopt && !dhcp_adopt()) return (EXIT_FAILURE); /* + * For DHCPv6, we own all of the interfaces marked DHCPRUNNING. As + * we're starting operation here, if there are any of those interfaces + * lingering around, they're strays, and need to be removed. + * + * It might be nice to save these addresses off somewhere -- for both + * v4 and v6 -- and use them as hints for later negotiation. + */ + remove_v6_strays(); + + /* * enter the main event loop; this is where all the real work * takes place (through registering events and scheduling timers). * this function only returns when the agent is shutting down. @@ -319,7 +368,7 @@ 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); + nuke_smach_list(); } return (script_count == 0); } @@ -359,7 +408,7 @@ accept_event(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) * 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 + * iu_event_id_t: event ID * void *: indicates whether the request is from a privileged client * output: void */ @@ -368,214 +417,225 @@ accept_event(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) 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; + ipc_action_t ia, *iap; + dhcp_smach_t *dsmp; 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); + const char *ifname; + boolean_t isv6; + + ipc_action_init(&ia); + error = dhcp_ipc_recv_request(fd, &ia.ia_request, + DHCP_IPC_REQUEST_WAIT); + if (error != DHCP_IPC_SUCCESS) { + if (error != DHCP_IPC_E_EOF) { + dhcpmsg(MSG_ERROR, + "ipc_event: dhcp_ipc_recv_request failed: %s", + dhcp_ipc_strerror(error)); + } else { + dhcpmsg(MSG_DEBUG, "ipc_event: connection closed"); + } + if ((dsmp = lookup_smach_by_event(id)) != NULL) { + ipc_action_finish(dsmp, error); + } else { + (void) iu_unregister_event(eh, id, NULL); + (void) dhcp_ipc_close(fd); + } return; } - cmd = DHCP_IPC_CMD(request->message_type); - if (cmd >= DHCP_NIPC) { - send_error_reply(request, DHCP_IPC_E_CMD_UNKNOWN, &fd); + /* Fill in temporary ipc_action structure for utility functions */ + ia.ia_cmd = DHCP_IPC_CMD(ia.ia_request->message_type); + ia.ia_fd = fd; + ia.ia_eid = id; + + if (ia.ia_cmd >= DHCP_NIPC) { + dhcpmsg(MSG_ERROR, + "ipc_event: invalid command (%s) attempted on %s", + dhcp_ipc_type_to_string(ia.ia_cmd), ia.ia_request->ifname); + send_error_reply(&ia, DHCP_IPC_E_CMD_UNKNOWN); return; } /* return EPERM for any of the privileged actions */ - if (!is_priv) { - 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; - } + if (!is_priv && (ipc_cmd_flags[ia.ia_cmd] & CMD_ISPRIV)) { + dhcpmsg(MSG_WARNING, + "ipc_event: privileged ipc command (%s) attempted on %s", + dhcp_ipc_type_to_string(ia.ia_cmd), ia.ia_request->ifname); + send_error_reply(&ia, DHCP_IPC_E_PERM); + return; } /* - * try to locate the 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. + * Try to locate the state machine associated with this command. If + * the command is DHCP_START or DHCP_INFORM and there isn't a state + * machine already, make one (there may already be one from a previous + * failed attempt to START or INFORM). Otherwise, verify the reference + * is still valid. + * + * The interface name may be blank. In that case, we look up the + * primary interface, and the requested type (v4 or v6) doesn't matter. */ - ifsp = lookup_ifs(request->ifname); + isv6 = (ia.ia_request->message_type & DHCP_V6) != 0; + ifname = ia.ia_request->ifname; + if (*ifname == '\0') + dsmp = primary_smach(isv6); + else + dsmp = lookup_smach(ifname, isv6); - switch (cmd) { + if (dsmp != NULL) { + /* Note that verify_smach drops a reference */ + hold_smach(dsmp); + if (!verify_smach(dsmp)) + dsmp = NULL; + } - case DHCP_START: /* FALLTHRU */ - case DHCP_INFORM: + if (dsmp == NULL) { /* - * 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 the user asked for the primary DHCP interface, but there + * is none, then report failure. */ - - if (ifsp != NULL && verify_ifs(ifsp) == 0) - ifsp = NULL; + if (ifname[0] == '\0') { + error = DHCP_IPC_E_NOPRIMARY; /* - * as part of initializing the ifs, insert_ifs() - * creates a DLPI stream at ifsp->if_dlpi_fd. + * If there's no interface, and we're starting up, then create + * it now, along with a state machine for it. Note that if + * insert_smach fails, it discards the LIF reference. */ + } else if (ipc_cmd_flags[ia.ia_cmd] & CMD_CREATE) { + dhcp_lif_t *lif; - if (ifsp == NULL) { - ifsp = insert_ifs(request->ifname, B_FALSE, &error); - if (ifsp == NULL) { - send_error_reply(request, error, &fd); - return; + lif = attach_lif(ifname, isv6, &error); + if (lif != NULL && + (dsmp = insert_smach(lif, &error)) != NULL) { + /* + * Get client ID and set "DHCPRUNNING" flag on + * logical interface. (V4 only, because V6 + * plumbs its own interfaces.) + */ + error = get_smach_cid(dsmp); + if (error == DHCP_IPC_SUCCESS) + error = set_lif_dhcp(lif, B_FALSE); + if (error != DHCP_IPC_SUCCESS) { + release_smach(dsmp); + dsmp = NULL; + } } - } - 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); + /* + * Otherwise, this is an operation on an unknown interface. + */ + } else { + error = DHCP_IPC_E_UNKIF; + } + if (dsmp == NULL) { + send_error_reply(&ia, error); return; } - break; } - if (verify_ifs(ifsp) == 0) { - send_error_reply(request, DHCP_IPC_E_UNKIF, &fd); + if ((dsmp->dsm_dflags & DHCP_IF_BOOTP) && + !(ipc_cmd_flags[ia.ia_cmd] & CMD_BOOTP)) { + dhcpmsg(MSG_ERROR, "command %s not valid for BOOTP on %s", + dhcp_ipc_type_to_string(ia.ia_cmd), dsmp->dsm_name); + send_error_reply(&ia, DHCP_IPC_E_BOOTP); return; } - 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 + * verify that the state machine is in a state which will allow the * command. we do this up front so that we can return an error * *before* needlessly cancelling an in-progress transaction. */ - if (!ipc_cmd_allowed[ifsp->if_state][cmd]) { - send_error_reply(request, DHCP_IPC_E_OUTSTATE, &fd); + if (!check_cmd_allowed(dsmp->dsm_state, ia.ia_cmd)) { + dhcpmsg(MSG_DEBUG, + "in state %s; not allowing %s command on %s", + dhcp_state_to_string(dsmp->dsm_state), + dhcp_ipc_type_to_string(ia.ia_cmd), dsmp->dsm_name); + send_error_reply(&ia, + ia.ia_cmd == DHCP_START && dsmp->dsm_state != INIT ? + DHCP_IPC_E_RUNNING : DHCP_IPC_E_OUTSTATE); return; } - 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; - } + dhcpmsg(MSG_DEBUG, "in state %s; allowing %s command on %s", + dhcp_state_to_string(dsmp->dsm_state), + dhcp_ipc_type_to_string(ia.ia_cmd), dsmp->dsm_name); + + if ((ia.ia_request->message_type & DHCP_PRIMARY) && is_priv) + make_primary(dsmp); /* - * 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): + * The current design dictates that there can be only one outstanding + * transaction per state machine -- this simplifies the code + * considerably and also fits well with RFCs 2131 and 3315. It is + * worth classifying the different DHCP commands into synchronous + * (those which we will handle now and reply to immediately) and + * asynchronous (those which require transactions and will be completed + * at an indeterminate time in the future): * - * DROP: removes the agent's management of an interface. + * DROP: removes the agent's management of a state machine. * asynchronous as the script program may be invoked. * - * PING: checks to see if the agent controls an interface. + * PING: checks to see if the agent has a named state machine. * synchronous, since no packets need to be sent * to the DHCP server. * - * STATUS: returns information about the an interface. + * STATUS: returns information about a state machine. * synchronous, since no packets need to be sent * to the DHCP server. * - * RELEASE: releases the agent's management of an interface - * and brings the interface down. asynchronous as - * the script program may be invoked. + * RELEASE: releases the agent's management of a state machine + * and brings the associated interfaces down. asynchronous + * as the script program may be invoked. * * EXTEND: renews a lease. asynchronous, since the agent * needs to wait for an ACK, etc. * - * START: starts DHCP on an interface. asynchronous since + * START: starts DHCP on a named state machine. asynchronous since * the agent needs to wait for OFFERs, ACKs, etc. * - * INFORM: obtains configuration parameters for an externally - * configured interface. asynchronous, since the + * INFORM: obtains configuration parameters for the system using + * externally configured interface. asynchronous, since the * agent needs to wait for an ACK. * - * notice that EXTEND, INFORM, START, DROP and RELEASE are - * asynchronous. notice also that asynchronous commands may - * occur from within the agent -- for instance, the agent - * will need to do implicit EXTENDs to extend the lease. in - * order to make the code simpler, the following rules apply - * for asynchronous commands: + * 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. + * There can only be one asynchronous command at a time per state + * machine. The current asynchronous command is managed by the async_* + * api: async_start(), async_finish(), and async_cancel(). + * async_start() starts management of a new asynchronous command on an + * state machine, which should only be done after async_cancel() to + * terminate a previous command. When the command is completed, + * async_finish() should be called. * - * asynchronous commands started by a user command have an - * associated ipc_action which provides the agent with - * information for how to get in touch with the user command - * when the action completes. these ipc_action records also - * have an associated timeout which may be infinite. - * ipc_action_start() should be called when starting an - * asynchronous command requested by a user, which sets up the - * timer and keeps track of the ipc information (file - * descriptor, request type). when the asynchronous command - * completes, ipc_action_finish() should be called to return a - * command status code to the user and close the ipc - * connection). if the command does not complete before the - * timer fires, ipc_action_timeout() is called which closes - * the ipc connection and returns DHCP_IPC_E_TIMEOUT to the - * user. note that independent of ipc_action_timeout(), - * ipc_action_finish() should be called. + * 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): + * on a case-by-case basis, here is what happens (per state machine): * - * 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, + * o When an asynchronous command is requested, then + * async_cancel() is called to terminate any non-user + * action in progress. If there's a user action running, * the user command is sent DHCP_IPC_E_PEND. * * o otherwise, the the transaction is started with @@ -590,7 +650,7 @@ ipc_event(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) * 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 + * async_finish() is called, and the state machine is * is returned to a known state based on the command. * if there was an associated ipc_action, * ipc_action_finish() is called to complete it. @@ -599,122 +659,188 @@ ipc_event(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) * 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 (ipc_cmd_flags[ia.ia_cmd] & CMD_IMMED) { + /* + * Only immediate commands (ping, status, get_tag) need to + * worry about freeing ia through one of the reply functions + * before returning. + */ + iap = &ia; + } else { /* * if shutdown request has been received, send back an error. */ if (shutdown_started) { - send_error_reply(request, DHCP_IPC_E_OUTSTATE, &fd); + send_error_reply(&ia, DHCP_IPC_E_OUTSTATE); return; } - if (async_pending(ifsp)) { - send_error_reply(request, DHCP_IPC_E_PEND, &fd); + if (dsmp->dsm_dflags & DHCP_IF_BUSY) { + send_error_reply(&ia, DHCP_IPC_E_PEND); return; } - if (ipc_action_start(ifsp, request, fd) == 0) { + if (!ipc_action_start(dsmp, &ia)) { 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); + "failed for %s", dsmp->dsm_name); + send_error_reply(&ia, DHCP_IPC_E_MEMORY); return; } - break; - default: - break; + /* Action structure consumed by above function */ + iap = &dsmp->dsm_ia; } - switch (cmd) { + switch (iap->ia_cmd) { case DHCP_DROP: - (void) script_start(ifsp, EVENT_DROP, dhcp_drop, NULL, NULL); - return; + (void) script_start(dsmp, isv6 ? EVENT_DROP6 : EVENT_DROP, + dhcp_drop, NULL, NULL); + break; /* not an immediate function */ case DHCP_EXTEND: - (void) dhcp_extending(ifsp); + (void) dhcp_extending(dsmp); break; case DHCP_GET_TAG: { dhcp_optnum_t optnum; - DHCP_OPT *opt = NULL; + void *opt = NULL; + uint_t optlen; boolean_t did_alloc = B_FALSE; - PKT_LIST *ack = ifsp->if_ack; + PKT_LIST *ack = dsmp->dsm_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; + if (iap->ia_request->data_type != DHCP_TYPE_OPTNUM || + iap->ia_request->data_length != sizeof (dhcp_optnum_t)) { + send_error_reply(iap, DHCP_IPC_E_PROTO); + break; } - (void) memcpy(&optnum, request->buffer, sizeof (dhcp_optnum_t)); + (void) memcpy(&optnum, iap->ia_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]; + if (isv6) { + opt = dhcpv6_pkt_option(ack, NULL, optnum.code, + NULL); + } else { + 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 (isv6) { + dhcpv6_option_t *d6o; + uint32_t ent; - if ((optnum.code > VS_OPTION_START || - optnum.code == VS_OPTION_START) && - optnum.code <= VS_OPTION_END) - opt = ack->vs[optnum.code]; + /* + * Look through vendor options to find our + * enterprise number. + */ + d6o = NULL; + for (;;) { + d6o = dhcpv6_pkt_option(ack, d6o, + DHCPV6_OPT_VENDOR_OPT, &optlen); + if (d6o == NULL) + break; + optlen -= sizeof (*d6o); + if (optlen < sizeof (ent)) + continue; + (void) memcpy(&ent, d6o + 1, + sizeof (ent)); + if (ntohl(ent) != DHCPV6_SUN_ENT) + continue; + break; + } + if (d6o != NULL) { + /* + * Now find the requested vendor option + * within the vendor options block. + */ + opt = dhcpv6_find_option( + (char *)(d6o + 1) + sizeof (ent), + optlen - sizeof (ent), NULL, + optnum.code, NULL); + } + } else { + /* + * the test against VS_OPTION_START is broken + * up into two tests to avoid compiler warnings + * under intel. + */ + if ((optnum.code > VS_OPTION_START || + optnum.code == VS_OPTION_START) && + optnum.code <= VS_OPTION_END) + opt = ack->vs[optnum.code]; + } break; case DSYM_FIELD: - if (optnum.code + optnum.size > sizeof (PKT)) - break; + if (isv6) { + dhcpv6_message_t *d6m = + (dhcpv6_message_t *)ack->pkt; + dhcpv6_option_t *d6o; + + /* Validate the packet field the user wants */ + optlen = optnum.code + optnum.size; + if (d6m->d6m_msg_type == + DHCPV6_MSG_RELAY_FORW || + d6m->d6m_msg_type == + DHCPV6_MSG_RELAY_REPL) { + if (optlen > sizeof (dhcpv6_relay_t)) + break; + } else { + if (optlen > sizeof (*d6m)) + break; + } + + opt = malloc(sizeof (*d6o) + optnum.size); + if (opt != NULL) { + d6o = opt; + d6o->d6o_code = htons(optnum.code); + d6o->d6o_len = htons(optnum.size); + (void) memcpy(d6o + 1, (caddr_t)d6m + + optnum.code, optnum.size); + } + } else { + if (optnum.code + optnum.size > sizeof (PKT)) + break; + + /* + * + 2 to account for option code and length + * byte + */ + opt = malloc(optnum.size + 2); + if (opt != NULL) { + DHCP_OPT *v4opt = opt; + + v4opt->len = optnum.size; + v4opt->code = optnum.code; + (void) memcpy(v4opt->value, + (caddr_t)ack->pkt + optnum.code, + optnum.size); + } + } - /* + 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); + send_error_reply(iap, DHCP_IPC_E_MEMORY); 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); + send_error_reply(iap, DHCP_IPC_E_PROTO); return; } @@ -724,19 +850,26 @@ load_option: */ if (opt != NULL) { - send_data_reply(request, &fd, 0, DHCP_TYPE_OPTION, opt, - opt->len + 2); + if (isv6) { + dhcpv6_option_t d6ov; + + (void) memcpy(&d6ov, opt, sizeof (d6ov)); + optlen = ntohs(d6ov.d6o_len) + sizeof (d6ov); + } else { + optlen = ((DHCP_OPT *)opt)->len + 2; + } + send_data_reply(iap, 0, DHCP_TYPE_OPTION, opt, optlen); if (did_alloc) free(opt); - return; - } else if (ack != ifsp->if_orig_ack) { + break; + } else if (ack != dsmp->dsm_orig_ack) { /* * There wasn't any definition for the option in the * current ack, so now retry with the original ack if * the original ack is not the current ack. */ - ack = ifsp->if_orig_ack; + ack = dsmp->dsm_orig_ack; goto load_option; } @@ -748,29 +881,32 @@ load_option: * ever performing ipc with the agent. */ - send_ok_reply(request, &fd); - return; + send_ok_reply(iap); + break; } case DHCP_INFORM: - dhcp_inform(ifsp); + dhcp_inform(dsmp); /* next destination: dhcp_acknak() */ - return; + break; /* not an immediate function */ case DHCP_PING: - if (ifsp->if_dflags & DHCP_IF_FAILED) - send_error_reply(request, DHCP_IPC_E_FAILEDIF, &fd); + if (dsmp->dsm_dflags & DHCP_IF_FAILED) + send_error_reply(iap, DHCP_IPC_E_FAILEDIF); else - send_ok_reply(request, &fd); - return; + send_ok_reply(iap); + break; case DHCP_RELEASE: - (void) script_start(ifsp, EVENT_RELEASE, dhcp_release, - "Finished with lease.", NULL); - return; + (void) script_start(dsmp, isv6 ? EVENT_RELEASE6 : + EVENT_RELEASE, dhcp_release, "Finished with lease.", NULL); + break; /* not an immediate function */ + + case DHCP_START: { + PKT_LIST *ack, *oack; + PKT_LIST *plp[2]; - case DHCP_START: - (void) canonize_ifs(ifsp); + deprecate_leases(dsmp); /* * if we have a valid hostconf lying around, then jump @@ -778,22 +914,26 @@ load_option: * 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); + error = read_hostconf(dsmp->dsm_name, plp, 2, dsmp->dsm_isv6); + ack = error > 0 ? plp[0] : NULL; + oack = error > 1 ? plp[1] : NULL; + + /* + * If the allocation of the old ack fails, that's fine; + * continue without it. + */ + if (oack == NULL) + oack = ack; + + /* + * As long as we've allocated something, start using it. + */ + if (ack != NULL) { + dsmp->dsm_orig_ack = oack; + dsmp->dsm_ack = ack; + dhcp_init_reboot(dsmp); /* next destination: dhcp_acknak() */ - return; + break; } /* @@ -801,51 +941,59 @@ load_option: * 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; - } + if (debug_level == 0 && + iu_schedule_timer_ms(tq, lrand48() % DHCP_SELECT_WAIT, + dhcp_start, dsmp) != -1) { + hold_smach(dsmp); + /* next destination: dhcp_start() */ + break; } - dhcp_selecting(ifsp); + dhcp_selecting(dsmp); /* next destination: dhcp_requesting() */ - return; + break; + } case DHCP_STATUS: { dhcp_status_t status; + dhcp_lease_t *dlp; + + status.if_began = monosec_to_time(dsmp->dsm_curstart_monosec); - status.if_began = monosec_to_time(ifsp->if_curstart_monosec); + /* + * We return information on just the first lease as being + * representative of the lot. A better status mechanism is + * needed. + */ + dlp = dsmp->dsm_leases; - if (ifsp->if_lease == DHCP_PERM) { + if (dlp == NULL || + dlp->dl_lifs->lif_expire.dt_start == DHCP_PERM) { status.if_t1 = DHCP_PERM; status.if_t2 = DHCP_PERM; status.if_lease = DHCP_PERM; } else { - status.if_t1 = status.if_began + ifsp->if_t1; - status.if_t2 = status.if_began + ifsp->if_t2; - status.if_lease = status.if_began + ifsp->if_lease; + status.if_t1 = status.if_began + + dlp->dl_t1.dt_start; + status.if_t2 = status.if_began + + dlp->dl_t2.dt_start; + status.if_lease = status.if_began + + dlp->dl_lifs->lif_expire.dt_start; } status.version = DHCP_STATUS_VER; - status.if_state = 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; + status.if_state = dsmp->dsm_state; + status.if_dflags = dsmp->dsm_dflags; + status.if_sent = dsmp->dsm_sent; + status.if_recv = dsmp->dsm_received; + status.if_bad_offers = dsmp->dsm_bad_offers; - (void) strlcpy(status.if_name, ifsp->if_name, IFNAMSIZ); + (void) strlcpy(status.if_name, dsmp->dsm_name, IFNAMSIZ); - send_data_reply(request, &fd, 0, DHCP_TYPE_STATUS, &status, + send_data_reply(iap, 0, DHCP_TYPE_STATUS, &status, sizeof (dhcp_status_t)); - return; + break; } - - default: - return; } } @@ -853,33 +1001,33 @@ load_option: * check_rtm_addr(): determine if routing socket message matches interface * address * - * input: struct if_msghdr *: pointer to routing socket message - * struct in_addr: IP address - * output: boolean_t + * input: const struct if_msghdr *: pointer to routing socket message + * int: routing socket message length + * boolean_t: set to B_TRUE if IPv6 + * const in6_addr_t *: pointer to IP address + * output: boolean_t: B_TRUE if address is a match */ + static boolean_t -check_rtm_addr(struct ifa_msghdr *ifam, int msglen, struct in_addr addr) +check_rtm_addr(const struct ifa_msghdr *ifam, int msglen, boolean_t isv6, + const in6_addr_t *addr) { - char *cp, *lim; + const char *cp, *lim; uint_t flag; - struct sockaddr *sa; - struct sockaddr_in *sinp; + const struct sockaddr *sa; if (!(ifam->ifam_addrs & RTA_IFA)) return (B_FALSE); - cp = (char *)(ifam + 1); - lim = (char *)ifam + msglen; + cp = (const char *)(ifam + 1); + lim = (const char *)ifam + msglen; for (flag = 1; flag < RTA_IFA; flag <<= 1) { if (ifam->ifam_addrs & flag) { /* LINTED: alignment */ - sa = (struct sockaddr *)cp; - if ((char *)(sa + 1) > lim) + sa = (const struct sockaddr *)cp; + if ((const char *)(sa + 1) > lim) return (B_FALSE); switch (sa->sa_family) { - case AF_UNIX: - cp += sizeof (struct sockaddr_un); - break; case AF_INET: cp += sizeof (struct sockaddr_in); break; @@ -895,11 +1043,243 @@ check_rtm_addr(struct ifa_msghdr *ifam, int msglen, struct in_addr addr) } } } - /* LINTED: alignment */ - sinp = (struct sockaddr_in *)cp; - if ((char *)(sinp + 1) > lim) + if (isv6) { + const struct sockaddr_in6 *sin6; + + /* LINTED: alignment */ + sin6 = (const struct sockaddr_in6 *)cp; + if ((const char *)(sin6 + 1) > lim) + return (B_FALSE); + if (sin6->sin6_family != AF_INET6) + return (B_FALSE); + return (IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, addr)); + } else { + const struct sockaddr_in *sinp; + ipaddr_t v4addr; + + /* LINTED: alignment */ + sinp = (const struct sockaddr_in *)cp; + if ((const char *)(sinp + 1) > lim) + return (B_FALSE); + if (sinp->sin_family != AF_INET) + return (B_FALSE); + IN6_V4MAPPED_TO_IPADDR(addr, v4addr); + return (sinp->sin_addr.s_addr == v4addr); + } +} + +/* + * is_rtm_v6(): determine if routing socket message is IPv6 + * + * input: struct ifa_msghdr *: pointer to routing socket message + * int: message length + * output: boolean_t + */ + +static boolean_t +is_rtm_v6(const struct ifa_msghdr *ifam, int msglen) +{ + const char *cp, *lim; + uint_t flag; + const struct sockaddr *sa; + + cp = (const char *)(ifam + 1); + lim = (const char *)ifam + msglen; + for (flag = ifam->ifam_addrs; flag != 0; flag &= flag - 1) { + /* LINTED: alignment */ + sa = (const struct sockaddr *)cp; + if ((const char *)(sa + 1) > lim) + return (B_FALSE); + switch (sa->sa_family) { + case AF_INET: + return (B_FALSE); + case AF_LINK: + cp += sizeof (struct sockaddr_dl); + break; + case AF_INET6: + return (B_TRUE); + default: + cp += sizeof (struct sockaddr); + break; + } + } + return (B_FALSE); +} + +/* + * check_lif(): check the state of a given logical interface and its DHCP + * lease. We've been told by the routing socket that the + * corresponding ifIndex has changed. This may mean that DAD has + * completed or failed. + * + * input: dhcp_lif_t *: pointer to the LIF + * const struct ifa_msghdr *: routing socket message + * int: size of routing socket message + * output: boolean_t: B_TRUE if DAD has completed on this interface + */ + +static boolean_t +check_lif(dhcp_lif_t *lif, const struct ifa_msghdr *ifam, int msglen) +{ + boolean_t isv6, dad_wait, unplumb; + int fd; + struct lifreq lifr; + + isv6 = lif->lif_pif->pif_isv6; + fd = isv6 ? v6_sock_fd : v4_sock_fd; + + /* + * Get the real (64 bit) logical interface flags. Note that the + * routing socket message has flags, but these are just the lower 32 + * bits. + */ + unplumb = B_FALSE; + (void) memset(&lifr, 0, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, lif->lif_name, sizeof (lifr.lifr_name)); + if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) { + /* + * Failing to retrieve flags means that the interface is gone. + * It hasn't failed to verify with DAD, but we still have to + * give up on it. + */ + lifr.lifr_flags = 0; + if (errno == ENXIO) { + lif->lif_plumbed = B_FALSE; + dhcpmsg(MSG_INFO, "%s has been removed; abandoning", + lif->lif_name); + } else { + dhcpmsg(MSG_ERR, + "unable to retrieve interface flags on %s", + lif->lif_name); + } + unplumb = B_TRUE; + } else if (!check_rtm_addr(ifam, msglen, isv6, &lif->lif_v6addr)) { + /* + * If the message is not about this logical interface, + * then just ignore it. + */ return (B_FALSE); - return (sinp->sin_addr.s_addr == addr.s_addr); + } else if (lifr.lifr_flags & IFF_DUPLICATE) { + dhcpmsg(MSG_ERROR, "interface %s has duplicate address", + lif->lif_name); + lif_mark_decline(lif, "duplicate address"); + close_ip_lif(lif); + } + + dad_wait = lif->lif_dad_wait; + if (dad_wait) { + dhcpmsg(MSG_VERBOSE, "check_lif: %s has finished DAD", + lif->lif_name); + lif->lif_dad_wait = B_FALSE; + } + + if (unplumb) + unplumb_lif(lif); + + return (dad_wait); +} + +/* + * check_main_lif(): check the state of a main logical interface for a state + * machine. This is used only for DHCPv6. + * + * input: dhcp_smach_t *: pointer to the state machine + * const struct ifa_msghdr *: routing socket message + * int: size of routing socket message + * output: boolean_t: B_TRUE if LIF is ok. + */ + +static boolean_t +check_main_lif(dhcp_smach_t *dsmp, const struct ifa_msghdr *ifam, int msglen) +{ + dhcp_lif_t *lif = dsmp->dsm_lif; + struct lifreq lifr; + + /* + * Get the real (64 bit) logical interface flags. Note that the + * routing socket message has flags, but these are just the lower 32 + * bits. + */ + (void) memset(&lifr, 0, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, lif->lif_name, sizeof (lifr.lifr_name)); + if (ioctl(v6_sock_fd, SIOCGLIFFLAGS, &lifr) == -1) { + /* + * Failing to retrieve flags means that the interface is gone. + * Our state machine is now trash. + */ + if (errno == ENXIO) { + dhcpmsg(MSG_INFO, "%s has been removed; abandoning", + lif->lif_name); + } else { + dhcpmsg(MSG_ERR, + "unable to retrieve interface flags on %s", + lif->lif_name); + } + return (B_FALSE); + } else if (!check_rtm_addr(ifam, msglen, B_TRUE, &lif->lif_v6addr)) { + /* + * If the message is not about this logical interface, + * then just ignore it. + */ + return (B_TRUE); + } else if (lifr.lifr_flags & IFF_DUPLICATE) { + dhcpmsg(MSG_ERROR, "interface %s has duplicate address", + lif->lif_name); + return (B_FALSE); + } else { + return (B_TRUE); + } +} + +/* + * process_link_up_down(): check the state of a physical interface for up/down + * transitions; must go through INIT_REBOOT state if + * the link flaps. + * + * input: dhcp_pif_t *: pointer to the physical interface to check + * const struct if_msghdr *: routing socket message + * output: none + */ + +static void +process_link_up_down(dhcp_pif_t *pif, const struct if_msghdr *ifm) +{ + struct lifreq lifr; + boolean_t isv6; + int fd; + + /* + * If the message implies no change of flags, then we're done; no need + * to check further. Note that if we have multiple state machines on a + * single physical interface, this test keeps us from issuing an ioctl + * for each one. + */ + if ((ifm->ifm_flags & IFF_RUNNING) && pif->pif_running || + !(ifm->ifm_flags & IFF_RUNNING) && !pif->pif_running) + return; + + /* + * We don't know what the real interface flags are, because the + * if_index number is only 16 bits; we must go ask. + */ + isv6 = pif->pif_isv6; + fd = isv6 ? v6_sock_fd : v4_sock_fd; + (void) memset(&lifr, 0, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, pif->pif_name, sizeof (lifr.lifr_name)); + + if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1 || + !(lifr.lifr_flags & IFF_RUNNING)) { + /* + * If we've lost the interface or it has gone down, then + * nothing special to do; just turn off the running flag. + */ + pif_status(pif, B_FALSE); + } else { + /* + * Interface has come back up: go through verification process. + */ + pif_status(pif, B_TRUE); + } } /* @@ -916,48 +1296,53 @@ check_rtm_addr(struct ifa_msghdr *ifam, int msglen, struct in_addr addr) static void rtsock_event(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) { - struct ifslist *ifs; + dhcp_smach_t *dsmp, *dsmnext; union { struct ifa_msghdr ifam; + struct if_msghdr ifm; char buf[1024]; } msg; uint16_t ifindex; - struct lifreq lifr; - char *fail; int msglen; - DHCPSTATE oldstate; + boolean_t isv6; if ((msglen = read(fd, &msg, sizeof (msg))) <= 0) return; - /* - * These are the messages that can identify a particular logical - * interface by local IP address. - */ - if (msg.ifam.ifam_type != RTM_DELADDR && - msg.ifam.ifam_type != RTM_NEWADDR) + /* Note that the routing socket interface index is just 16 bits */ + if (msg.ifm.ifm_type == RTM_IFINFO) { + ifindex = msg.ifm.ifm_index; + isv6 = (msg.ifm.ifm_flags & IFF_IPV6) ? B_TRUE : B_FALSE; + } else if (msg.ifam.ifam_type == RTM_DELADDR || + msg.ifam.ifam_type == RTM_NEWADDR) { + ifindex = msg.ifam.ifam_index; + isv6 = is_rtm_v6(&msg.ifam, msglen); + } else { return; + } - /* Note that ifam_index is just 16 bits */ - ifindex = msg.ifam.ifam_index; + for (dsmp = lookup_smach_by_uindex(ifindex, NULL, isv6); + dsmp != NULL; dsmp = dsmnext) { + DHCPSTATE oldstate; + boolean_t lif_finished; + boolean_t lease_removed; + dhcp_lease_t *dlp, *dlnext; - for (ifs = lookup_ifs_by_uindex(ifindex, NULL); - ifs != NULL; - ifs = lookup_ifs_by_uindex(ifindex, ifs)) { + /* + * Note that script_start can call dhcp_drop directly, and + * that will do release_smach. + */ + dsmnext = lookup_smach_by_uindex(ifindex, dsmp, isv6); + oldstate = dsmp->dsm_state; /* - * The if_sock_ip_fd is set to a non-negative integer by - * configure_bound(). If it's negative, then DHCP doesn't - * think we're bound. - * - * For pre-bound interfaces, we want to check to see if the - * IFF_UP bit has been reported. This means that DAD is - * complete. + * Look for link up/down notifications. These occur on a + * physical interface basis. */ - oldstate = ifs->if_state; - if (ifs->if_sock_ip_fd == -1 && - (oldstate != PRE_BOUND && oldstate != ADOPTING)) + if (msg.ifm.ifm_type == RTM_IFINFO) { + process_link_up_down(dsmp->dsm_lif->lif_pif, &msg.ifm); continue; + } /* * Since we cannot trust the flags reported by the routing @@ -966,57 +1351,118 @@ rtsock_event(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) * bits and also doesn't reflect the alias in use), we get * flags on all matching interfaces, and go by that. */ - (void) strlcpy(lifr.lifr_name, ifs->if_name, - sizeof (lifr.lifr_name)); - if (ioctl(ifs->if_sock_fd, SIOCGLIFFLAGS, &lifr) == -1) { - fail = "unable to retrieve interface flags on %s"; - lifr.lifr_flags = 0; - } else if (!check_rtm_addr(&msg.ifam, msglen, ifs->if_addr)) { - /* - * If the message is not about this logical interface, - * then just ignore it. - */ - continue; - } else if (lifr.lifr_flags & IFF_DUPLICATE) { - fail = "interface %s has duplicate address"; - } else { - /* - * If we're now up and we were waiting for that, then - * kick off this interface. DAD is done. - */ - if (lifr.lifr_flags & IFF_UP) { - if (oldstate == PRE_BOUND || - oldstate == ADOPTING) - dhcp_bound_complete(ifs); - if (oldstate == ADOPTING) - dhcp_adopt_complete(ifs); + lif_finished = B_FALSE; + lease_removed = B_FALSE; + for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlnext) { + dhcp_lif_t *lif, *lifnext; + uint_t nlifs = dlp->dl_nlifs; + + dlnext = dlp->dl_next; + for (lif = dlp->dl_lifs; lif != NULL && nlifs > 0; + lif = lifnext, nlifs--) { + lifnext = lif->lif_next; + if (check_lif(lif, &msg.ifam, msglen)) { + dsmp->dsm_lif_wait--; + lif_finished = B_TRUE; + } + } + if (dlp->dl_nlifs == 0) { + remove_lease(dlp); + lease_removed = B_TRUE; } - continue; } - if (ifs->if_sock_ip_fd != -1) { - (void) close(ifs->if_sock_ip_fd); - ifs->if_sock_ip_fd = -1; + if ((isv6 && !check_main_lif(dsmp, &msg.ifam, msglen)) || + (!isv6 && !verify_lif(dsmp->dsm_lif))) { + if (dsmp->dsm_script_pid != -1) + script_stop(dsmp); + (void) script_start(dsmp, EVENT_DROP6, dhcp_drop, NULL, + NULL); + continue; } - dhcpmsg(MSG_ERROR, fail, ifs->if_name); /* - * The binding has evidently failed, so it's as though it never - * happened. We need to do switch back to PRE_BOUND state so - * that send_pkt_internal() uses DLPI instead of sockets. Our - * logical interface has already been torn down by the kernel, - * and thus we can't send DHCPDECLINE by way of regular IP. - * (Unless we're adopting -- allow the grandparent to be - * handled as expected.) + * Ignore this state machine if nothing interesting has + * happened. + */ + if (!lif_finished && dsmp->dsm_lif_down == 0 && + (dsmp->dsm_leases != NULL || !lease_removed)) + continue; + + /* + * If we're still waiting for DAD to complete on some of the + * configured LIFs, then don't send a response. */ - if (oldstate != ADOPTING) - ifs->if_state = PRE_BOUND; + if (dsmp->dsm_lif_wait != 0) { + dhcpmsg(MSG_VERBOSE, "rtsock_event: %s still has %d " + "LIFs waiting on DAD", dsmp->dsm_name, + dsmp->dsm_lif_wait); + continue; + } - if (ifs->if_ack->opts[CD_DHCP_TYPE] != NULL && - (lifr.lifr_flags & IFF_DUPLICATE)) - send_decline(ifs, fail, &ifs->if_addr); + /* + * If we have some failed LIFs, then handle them now. We'll + * remove them from the list. Any leases that become empty are + * also removed as part of the decline-generation process. + */ + if (dsmp->dsm_lif_down != 0) { + /* + * We need to switch back to PRE_BOUND state so that + * send_pkt_internal() uses DLPI instead of sockets. + * Our logical interface has already been torn down by + * the kernel, and thus we can't send DHCPDECLINE by + * way of regular IP. (Unless we're adopting -- allow + * the grandparent to be handled as expected.) + */ + if (oldstate != ADOPTING) { + (void) set_smach_state(dsmp, PRE_BOUND); + } + send_declines(dsmp); + } - ifs->if_bad_offers++; - dhcp_restart(ifs); + if (dsmp->dsm_leases == NULL) { + dsmp->dsm_bad_offers++; + /* + * For DHCPv6, we'll process the restart once we're + * done sending Decline messages, because these are + * supposed to be acknowledged. With DHCPv4, there's + * no acknowledgment for a DECLINE, so after sending + * it, we just restart right away. + */ + if (!dsmp->dsm_isv6) { + dhcpmsg(MSG_VERBOSE, "rtsock_event: %s has no " + "LIFs left; restarting", dsmp->dsm_name); + dhcp_restart(dsmp); + } + } else { + /* + * If we're now up on at least some of the leases and + * we were waiting for that, then kick off the rest of + * configuration. Lease validation and DAD are done. + */ + dhcpmsg(MSG_VERBOSE, "rtsock_event: all LIFs verified " + "on %s in %s state", dsmp->dsm_name, + dhcp_state_to_string(oldstate)); + if (oldstate == PRE_BOUND || + oldstate == ADOPTING) + dhcp_bound_complete(dsmp); + if (oldstate == ADOPTING) + dhcp_adopt_complete(dsmp); + } } } + +/* + * check_cmd_allowed(): check whether the requested command is allowed in the + * state specified. + * + * input: DHCPSTATE: current state + * dhcp_ipc_type_t: requested command + * output: boolean_t: B_TRUE if command is allowed in this state + */ + +boolean_t +check_cmd_allowed(DHCPSTATE state, dhcp_ipc_type_t cmd) +{ + return (ipc_cmd_allowed[state][cmd] != 0); +} diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/agent.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/agent.h index b9bafe59bb..89558bed01 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/agent.h +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/agent.h @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -30,6 +30,7 @@ #include <sys/types.h> #include <libinetutil.h> +#include <dhcpagent_ipc.h> /* * agent.h contains general symbols that should be available to all @@ -48,7 +49,7 @@ extern "C" { * 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 + * if there are no state machines to manage for DHCP_INACTIVITY_WAIT * seconds. `grandparent' is the pid of the original process when in * adopt mode. `rtsock_fd' is the global routing socket file descriptor. */ @@ -62,6 +63,7 @@ extern pid_t grandparent; extern int rtsock_fd; boolean_t drain_script(iu_eh_t *, void *); +boolean_t check_cmd_allowed(DHCPSTATE, dhcp_ipc_type_t); /* * global tunable parameters. an `I' in the preceding comment indicates @@ -113,6 +115,31 @@ boolean_t drain_script(iu_eh_t *, void *); #define DHCP_IPC_REQUEST_WAIT (3*1000) /* three seconds */ /* + * DHCPv6 timer and retransmit values from RFC 3315. + */ +#define DHCPV6_SOL_MAX_DELAY 1000 /* Max delay of first Solicit; 1s */ +#define DHCPV6_CNF_MAX_DELAY 1000 /* Max delay of first Confirm; 1s */ +#define DHCPV6_INF_MAX_DELAY 1000 /* Max delay of first Info-req; 1s */ +#define DHCPV6_SOL_TIMEOUT 1000 /* Initial Solicit timeout; 1s */ +#define DHCPV6_REQ_TIMEOUT 1000 /* Initial Request timeout; 1s */ +#define DHCPV6_CNF_TIMEOUT 1000 /* Initial Confirm timeout; 1s */ +#define DHCPV6_REN_TIMEOUT 10000 /* Initial Renew timeout; 10s */ +#define DHCPV6_REB_TIMEOUT 10000 /* Initial Rebind timeout; 10s */ +#define DHCPV6_INF_TIMEOUT 1000 /* Initial Info-req timeout; 1s */ +#define DHCPV6_REL_TIMEOUT 1000 /* Initial Release timeout; 1s */ +#define DHCPV6_DEC_TIMEOUT 1000 /* Initial Decline timeout; 1s */ +#define DHCPV6_SOL_MAX_RT 120000 /* Max Solicit timeout; 2m */ +#define DHCPV6_REQ_MAX_RT 30000 /* Max Request timeout; 30s */ +#define DHCPV6_CNF_MAX_RT 4000 /* Max Confirm timeout; 4s */ +#define DHCPV6_REN_MAX_RT 600000 /* Max Renew timeout; 5m */ +#define DHCPV6_REB_MAX_RT 600000 /* Max Rebind timeout; 5m */ +#define DHCPV6_INF_MAX_RT 120000 /* Max Info-req timeout; 2m */ +#define DHCPV6_CNF_MAX_RD 10000 /* Max Confirm duration; 10s */ +#define DHCPV6_REQ_MAX_RC 10 /* Max Request attempts */ +#define DHCPV6_REL_MAX_RC 5 /* Max Release attempts */ +#define DHCPV6_DEC_MAX_RC 5 /* Max Decline attempts */ + +/* * reasons for why iu_handle_events() returned */ enum { DHCP_REASON_INACTIVITY, DHCP_REASON_SIGNAL, DHCP_REASON_TERMINATE }; diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/async.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/async.c index c9e3052c1d..a794cf8ce1 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/async.c +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/async.c @@ -19,119 +19,56 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 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 <dhcpagent_util.h> #include "async.h" #include "util.h" -#include "agent.h" #include "interface.h" #include "script_handler.h" #include "states.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. + * async_start(): starts an asynchronous command on a state machine * - * 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 + * input: dhcp_smach_t *: the state machine to start the async command on + * dhcp_ipc_type_t: the command to start + * boolean_t: B_TRUE if the command was started by a user + * output: boolean: B_TRUE on success, B_FALSE on failure */ boolean_t -async_pending(struct ifslist *ifsp) +async_start(dhcp_smach_t *dsmp, dhcp_ipc_type_t cmd, boolean_t user) { - if (!(ifsp->if_dflags & DHCP_IF_BUSY)) + if (dsmp->dsm_async.as_present) { 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)) + } else { + dsmp->dsm_async.as_cmd = cmd; + dsmp->dsm_async.as_user = user; + dsmp->dsm_async.as_present = B_TRUE; 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 + * input: dhcp_smach_t *: the state machine with the pending async command * output: void * note: should only be used when the command has no residual state to * clean up */ void -async_finish(struct ifslist *ifsp) +async_finish(dhcp_smach_t *dsmp) { /* * be defensive here. the script may still be running if @@ -139,158 +76,32 @@ async_finish(struct ifslist *ifsp) * 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"); + if (dsmp->dsm_script_pid != -1) + script_stop(dsmp); + dsmp->dsm_async.as_present = B_FALSE; } /* * 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_restart = 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. - * jump to SELECTING and start from there -- but not until - * after we've finished the asynchronous command! - */ - - do_restart = 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_restart) - dhcp_selecting(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 + * input: dhcp_smach_t *: the state machine with the pending async command + * output: boolean: B_TRUE if cancellation was successful, B_FALSE on failure */ -static void -async_timeout(iu_tq_t *tq, void *arg) +boolean_t +async_cancel(dhcp_smach_t *dsmp) { - 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); + if (!dsmp->dsm_async.as_present) + return (B_TRUE); + if (dsmp->dsm_async.as_user) { + dhcpmsg(MSG_DEBUG, + "async_cancel: cannot abort command %d from user", + (int)dsmp->dsm_async.as_cmd); + return (B_FALSE); + } else { + async_finish(dsmp); + dhcpmsg(MSG_DEBUG, "async_cancel: command %d aborted", + (int)dsmp->dsm_async.as_cmd); + return (B_TRUE); } } diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/async.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/async.h index 05c3d7d21a..f0b250fcd6 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/async.h +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/async.h @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2005 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -33,6 +32,8 @@ #include <libinetutil.h> #include <dhcpagent_ipc.h> +#include "common.h" + /* * async.[ch] comprise the interface used to handle asynchronous DHCP * commands. see ipc_event() in agent.c for more documentation on @@ -44,21 +45,15 @@ 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 */ +typedef struct async_action { + dhcp_ipc_type_t as_cmd; /* command/action in progress */ + boolean_t as_user; /* user-generated async cmd */ + boolean_t as_present; /* async operation present */ +} async_action_t; -boolean_t async_pending(struct ifslist *); -int async_start(struct ifslist *, dhcp_ipc_type_t, boolean_t); -void async_finish(struct ifslist *); -int async_cancel(struct ifslist *); +boolean_t async_start(dhcp_smach_t *, dhcp_ipc_type_t, boolean_t); +void async_finish(dhcp_smach_t *); +boolean_t async_cancel(dhcp_smach_t *); #ifdef __cplusplus } diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/bound.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/bound.c index 9b00244a40..2b5d9e0bef 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/bound.c +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/bound.c @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * BOUND state of the DHCP client state machine. @@ -36,8 +36,10 @@ #include <time.h> #include <arpa/inet.h> #include <stdlib.h> +#include <search.h> #include <sys/sysmacros.h> #include <dhcp_hostconf.h> +#include <dhcpagent_util.h> #include <dhcpmsg.h> #include "states.h" @@ -47,228 +49,488 @@ #include "interface.h" #include "script_handler.h" -#define IS_DHCP(plp) ((plp)->opts[CD_DHCP_TYPE] != NULL) +/* + * Possible outcomes for IPv6 binding attempt. + */ +enum v6_bind_result { + v6Restart, /* report failure and restart state machine */ + v6Resent, /* new Request message has been sent */ + v6Done /* successful binding */ +}; -static int configure_if(struct ifslist *); -static int configure_bound(struct ifslist *); -static int configure_timers(struct ifslist *); +static enum v6_bind_result configure_v6_leases(dhcp_smach_t *); +static boolean_t configure_v4_lease(dhcp_smach_t *); +static boolean_t configure_v4_timers(dhcp_smach_t *); /* * bound_event_cb(): callback for script_start on the event EVENT_BOUND * - * input: struct ifslist *: the interface configured - * const char *: unused + * input: dhcp_smach_t *: the state machine configured + * void *: unused * output: int: always 1 */ -/* ARGSUSED */ +/* ARGSUSED1 */ static int -bound_event_cb(struct ifslist *ifsp, const char *msg) +bound_event_cb(dhcp_smach_t *dsmp, void *arg) { - ipc_action_finish(ifsp, DHCP_IPC_SUCCESS); - async_finish(ifsp); + if (dsmp->dsm_ia.ia_fd != -1) + ipc_action_finish(dsmp, DHCP_IPC_SUCCESS); + else + async_finish(dsmp); 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. + * dhcp_bound(): configures an state machine and interfaces using information + * contained in the ACK/Reply packet and sets up lease timers. + * Before starting, the requested address is verified by + * Duplicate Address Detection to make sure it's not in use. * - * input: 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 + * input: dhcp_smach_t *: the state machine to move to bound + * PKT_LIST *: the ACK/Reply packet, or NULL to use dsmp->dsm_ack + * output: boolean_t: B_TRUE on success, B_FALSE on failure */ -int -dhcp_bound(struct ifslist *ifsp, PKT_LIST *ack) +boolean_t +dhcp_bound(dhcp_smach_t *dsmp, PKT_LIST *ack) { - lease_t cur_lease, new_lease; - int msg_level; - const char *noext = "lease renewed but time not extended"; - uint_t minleft; + DHCPSTATE oldstate; + lease_t new_lease; + dhcp_lif_t *lif; + dhcp_lease_t *dlp; + enum v6_bind_result v6b; if (ack != NULL) { /* If ack we're replacing is not the original, then free it */ - if (ifsp->if_ack != ifsp->if_orig_ack) - free_pkt_list(&ifsp->if_ack); - ifsp->if_ack = ack; + if (dsmp->dsm_ack != dsmp->dsm_orig_ack) + free_pkt_entry(dsmp->dsm_ack); + dsmp->dsm_ack = ack; /* Save the first ack as the original */ - if (ifsp->if_orig_ack == NULL) - ifsp->if_orig_ack = ack; + if (dsmp->dsm_orig_ack == NULL) + dsmp->dsm_orig_ack = ack; } - switch (ifsp->if_state) { + oldstate = dsmp->dsm_state; + switch (oldstate) { case ADOPTING: + /* Note that adoption occurs only for IPv4 DHCP. */ + + /* Ignore BOOTP */ + if (ack->opts[CD_DHCP_TYPE] == NULL) + return (B_FALSE); /* - * if we're adopting an interface, the lease timers + * if we're adopting a lease, the lease timers * only provide an upper bound since we don't know * from what time they are relative to. assume we * have a lease time of at most DHCP_ADOPT_LEASE_MAX. */ - - if (!IS_DHCP(ifsp->if_ack)) - return (0); - - (void) memcpy(&new_lease, - ifsp->if_ack->opts[CD_LEASE_TIME]->value, sizeof (lease_t)); + (void) memcpy(&new_lease, ack->opts[CD_LEASE_TIME]->value, + sizeof (lease_t)); new_lease = htonl(MIN(ntohl(new_lease), DHCP_ADOPT_LEASE_MAX)); - (void) memcpy(ifsp->if_ack->opts[CD_LEASE_TIME]->value, - &new_lease, sizeof (lease_t)); + (void) memcpy(ack->opts[CD_LEASE_TIME]->value, &new_lease, + sizeof (lease_t)); /* * we have no idea when the REQUEST that generated * this ACK was sent, but for diagnostic purposes * we'll assume its close to the current time. */ - ifsp->if_newstart_monosec = monosec(); + dsmp->dsm_newstart_monosec = monosec(); - if (configure_if(ifsp) == 0) - return (0); + if (dsmp->dsm_isv6) { + if ((v6b = configure_v6_leases(dsmp)) != v6Done) + return (v6b == v6Resent); + } else { + if (!configure_v4_lease(dsmp)) + return (B_FALSE); - if (configure_timers(ifsp) == 0) - return (0); + if (!configure_v4_timers(dsmp)) + return (B_FALSE); + } - ifsp->if_curstart_monosec = ifsp->if_newstart_monosec; + dsmp->dsm_curstart_monosec = dsmp->dsm_newstart_monosec; break; + case SELECTING: case REQUESTING: case INIT_REBOOT: - if (configure_if(ifsp) == 0) - return (0); + if (dsmp->dsm_isv6) { + if ((v6b = configure_v6_leases(dsmp)) != v6Done) + return (v6b == v6Resent); + } else { + if (!configure_v4_lease(dsmp)) + return (B_FALSE); + + if (!configure_v4_timers(dsmp)) + return (B_FALSE); + + if (!clear_lif_deprecated(dsmp->dsm_lif)) + return (B_FALSE); + } - if (configure_timers(ifsp) == 0) - return (0); + /* Stop sending requests now */ + stop_pkt_retransmission(dsmp); + + /* + * If we didn't end up with any usable leases, then we have a + * problem. + */ + if (dsmp->dsm_leases == NULL) { + dhcpmsg(MSG_WARNING, + "dhcp_bound: no address lease established"); + return (B_FALSE); + } + + /* + * If this is a Rapid-Commit (selecting state) or if we're + * dealing with a reboot (init-reboot), then we will have a new + * server ID to save. + */ + if (ack != NULL && + (oldstate == SELECTING || oldstate == INIT_REBOOT) && + dsmp->dsm_isv6 && !save_server_id(dsmp, ack)) { + dhcpmsg(MSG_ERROR, + "dhcp_bound: unable to save server ID on %s", + dsmp->dsm_name); + return (B_FALSE); + } /* - * We will continue configuring this interface via - * dhcp_bound_complete, once kernel DAD completes. + * We will continue configuring the interfaces via + * dhcp_bound_complete, once kernel DAD completes. If no new + * leases were created (which can happen on an init-reboot used + * for link-up confirmation), then go straight to bound state. */ - ifsp->if_state = PRE_BOUND; + if (!set_smach_state(dsmp, PRE_BOUND)) + return (B_FALSE); + if (dsmp->dsm_lif_wait == 0) + dhcp_bound_complete(dsmp); break; case PRE_BOUND: + case BOUND: /* This is just a duplicate ack; silently ignore it */ - return (1); + return (B_TRUE); case RENEWING: case REBINDING: - case BOUND: - cur_lease = ifsp->if_lease; - if (configure_timers(ifsp) == 0) - return (0); + + if (dsmp->dsm_isv6) { + if ((v6b = configure_v6_leases(dsmp)) != v6Done) + return (v6b == v6Resent); + } else { + if (!configure_v4_timers(dsmp)) + return (B_FALSE); + if (!clear_lif_deprecated(dsmp->dsm_lif)) + return (B_FALSE); + } /* - * if the current lease is mysteriously close to the new - * lease, warn the user. unless there's less than a minute - * left, round to the closest minute. + * If some or all of the leases were torn down by the server, + * then handle that as an expiry. When the script is done + * running for the LOSS6 event, we'll end up back here. */ + if ((lif = find_expired_lif(dsmp)) != NULL) { + hold_lif(lif); + dhcp_expire(NULL, lif); + while ((lif = find_expired_lif(dsmp)) != NULL) { + dlp = lif->lif_lease; + unplumb_lif(lif); + if (dlp->dl_nlifs == 0) + remove_lease(dlp); + } + if (dsmp->dsm_leases == NULL) + return (B_FALSE); + } - if (abs((ifsp->if_newstart_monosec + ifsp->if_lease) - - (ifsp->if_curstart_monosec + cur_lease)) < DHCP_LEASE_EPS) { + if (oldstate == REBINDING && dsmp->dsm_isv6 && + !save_server_id(dsmp, ack)) { + return (B_FALSE); + } - if (ifsp->if_lease < DHCP_LEASE_ERROR_THRESH) - msg_level = MSG_ERROR; - else - msg_level = MSG_VERBOSE; + /* + * Handle Renew/Rebind that fails to address one of our leases. + * (Should just never happen, but RFC 3315 section 18.1.8 + * requires it, and TAHI tests for it.) + */ + for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) { + if (dlp->dl_stale && dlp->dl_nlifs > 0) + break; + } + if (dlp != NULL) { + dhcpmsg(MSG_DEBUG, "dhcp_bound: lease not updated; " + "allow retransmit"); + return (B_TRUE); + } - minleft = (ifsp->if_lease + 30) / 60; + if (!set_smach_state(dsmp, BOUND)) + return (B_FALSE); - if (ifsp->if_lease < 60) { - dhcpmsg(msg_level, "%s; expires in %d seconds", - noext, ifsp->if_lease); - } else if (minleft == 1) { - dhcpmsg(msg_level, "%s; expires in 1 minute", - noext); - } else { - dhcpmsg(msg_level, "%s; expires in %d minutes", - noext, minleft); - } - } + (void) script_start(dsmp, dsmp->dsm_isv6 ? EVENT_EXTEND6 : + EVENT_EXTEND, bound_event_cb, NULL, NULL); - (void) script_start(ifsp, EVENT_EXTEND, bound_event_cb, - NULL, NULL); + dsmp->dsm_curstart_monosec = dsmp->dsm_newstart_monosec; - ifsp->if_state = BOUND; - ifsp->if_curstart_monosec = ifsp->if_newstart_monosec; + /* Stop sending requests now */ + stop_pkt_retransmission(dsmp); break; case INFORM_SENT: - (void) bound_event_cb(ifsp, NULL); - ifsp->if_state = INFORMATION; + if (dsmp->dsm_isv6 && !save_server_id(dsmp, ack)) { + return (B_FALSE); + } + + (void) bound_event_cb(dsmp, NULL); + if (!set_smach_state(dsmp, INFORMATION)) + return (B_FALSE); + + /* Stop sending requests now */ + stop_pkt_retransmission(dsmp); break; default: /* something is really bizarre... */ - dhcpmsg(MSG_DEBUG, "dhcp_bound: called in unexpected state"); - return (0); + dhcpmsg(MSG_DEBUG, + "dhcp_bound: called in unexpected state: %s", + dhcp_state_to_string(dsmp->dsm_state)); + return (B_FALSE); } /* * remove any stale hostconf file that might be lying around for - * this interface. (in general, it's harmless, since we'll write a + * this state machine. (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); + (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); + return (B_TRUE); } /* * dhcp_bound_complete(): complete interface configuration after DAD * - * input: struct ifslist *: the interface to configure + * input: dhcp_smach_t *: the state machine now ready * output: none */ void -dhcp_bound_complete(struct ifslist *ifsp) +dhcp_bound_complete(dhcp_smach_t *dsmp) { - if (configure_bound(ifsp) == 0) + PKT_LIST *ack; + DHCP_OPT *router_list; + int i; + DHCPSTATE oldstate; + + /* + * Do bound state entry processing only if running IPv4. There's no + * need for this with DHCPv6 because link-locals are used for I/O and + * because DHCPv6 isn't entangled with routing. + */ + if (dsmp->dsm_isv6) { + (void) set_smach_state(dsmp, BOUND); + dhcpmsg(MSG_DEBUG, "configure_bound: bound %s", + dsmp->dsm_name); + (void) script_start(dsmp, EVENT_BOUND6, bound_event_cb, NULL, + NULL); + dsmp->dsm_curstart_monosec = dsmp->dsm_newstart_monosec; return; + } /* - * if the state is ADOPTING, event loop has not been started + * add each provided router; we'll clean them up when the + * state machine goes away or when our lease expires. + */ + + ack = dsmp->dsm_ack; + router_list = ack->opts[CD_ROUTER]; + if (router_list && (router_list->len % sizeof (ipaddr_t)) == 0) { + + dsmp->dsm_nrouters = router_list->len / sizeof (ipaddr_t); + dsmp->dsm_routers = malloc(router_list->len); + if (dsmp->dsm_routers == NULL) { + dhcpmsg(MSG_ERR, "configure_bound: cannot allocate " + "default router list, ignoring default routers"); + dsmp->dsm_nrouters = 0; + } + + for (i = 0; i < dsmp->dsm_nrouters; i++) { + + (void) memcpy(&dsmp->dsm_routers[i].s_addr, + router_list->value + (i * sizeof (ipaddr_t)), + sizeof (ipaddr_t)); + + if (!add_default_route(dsmp->dsm_name, + &dsmp->dsm_routers[i])) { + dhcpmsg(MSG_ERR, "configure_bound: cannot add " + "default router %s on %s", inet_ntoa( + dsmp->dsm_routers[i]), dsmp->dsm_name); + dsmp->dsm_routers[i].s_addr = htonl(INADDR_ANY); + continue; + } + + dhcpmsg(MSG_INFO, "added default router %s on %s", + inet_ntoa(dsmp->dsm_routers[i]), dsmp->dsm_name); + } + } + + oldstate = dsmp->dsm_state; + if (!set_smach_state(dsmp, BOUND)) { + dhcpmsg(MSG_ERR, + "configure_bound: cannot set bound state on %s", + dsmp->dsm_name); + return; + } + + dhcpmsg(MSG_DEBUG, "configure_bound: bound %s", dsmp->dsm_name); + + /* + * We're now committed to this binding, so if it came from BOOTP, set + * the flag. + */ + + if (ack->opts[CD_DHCP_TYPE] == NULL) + dsmp->dsm_dflags |= DHCP_IF_BOOTP; + + /* + * If the previous state was ADOPTING, event loop has not been started * at this time; so don't run the EVENT_BOUND script. */ - if (ifsp->if_state != ADOPTING) { - (void) script_start(ifsp, EVENT_BOUND, bound_event_cb, NULL, + if (oldstate != ADOPTING) { + (void) script_start(dsmp, EVENT_BOUND, bound_event_cb, NULL, NULL); } - ifsp->if_curstart_monosec = ifsp->if_newstart_monosec; - ifsp->if_state = BOUND; + dsmp->dsm_curstart_monosec = dsmp->dsm_newstart_monosec; } /* - * configure_timers(): configures the lease timers on an interface + * fuzzify(): adds some "fuzz" to a t1/t2 time, in accordance with RFC2131. + * We use up to plus or minus 2% jitter in the time. This is a + * small value, but the timers involved are typically long. A + * common T1 value is one day, and the fuzz is up to 28.8 minutes; + * plenty of time to make sure that individual clients don't renew + * all at the same time. * - * input: struct ifslist *: the interface to configure (with a valid if_ack) - * output: int: 1 on success, 0 on failure + * 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 int -configure_timers(struct ifslist *ifsp) +static double +fuzzify(uint32_t sec, double pct) +{ + return (sec * (pct + (drand48() - 0.5) / 25.0)); +} + +/* + * get_pkt_times(): pulls the lease times out of a v4 DHCP packet and stores + * them as host byte-order relative times in the passed in + * parameters. + * + * input: PKT_LIST *: the packet to pull the packet times from + * lease_t *: where to store the relative lease time in hbo + * lease_t *: where to store the relative t1 time in hbo + * lease_t *: where to store the relative t2 time in hbo + * output: void + */ + +static void +get_pkt_times(PKT_LIST *ack, lease_t *lease, lease_t *t1, lease_t *t2) +{ + *lease = DHCP_PERM; + *t1 = DHCP_PERM; + *t2 = DHCP_PERM; + + if (ack->opts[CD_DHCP_TYPE] == NULL) { + dhcpmsg(MSG_VERBOSE, + "get_pkt_times: BOOTP response; infinite lease"); + return; + } + if (ack->opts[CD_LEASE_TIME] == NULL) { + dhcpmsg(MSG_VERBOSE, + "get_pkt_times: no lease option provided"); + return; + } + if (ack->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) { + dhcpmsg(MSG_VERBOSE, "get_pkt_times: invalid lease option"); + } + + (void) memcpy(lease, ack->opts[CD_LEASE_TIME]->value, sizeof (lease_t)); + *lease = ntohl(*lease); + + if (*lease == DHCP_PERM) { + dhcpmsg(MSG_VERBOSE, "get_pkt_times: infinite lease granted"); + return; + } + + if (ack->opts[CD_T1_TIME] != NULL && + ack->opts[CD_T1_TIME]->len == sizeof (lease_t)) { + (void) memcpy(t1, ack->opts[CD_T1_TIME]->value, sizeof (*t1)); + *t1 = ntohl(*t1); + } + + if (ack->opts[CD_T2_TIME] != NULL && + ack->opts[CD_T2_TIME]->len == sizeof (lease_t)) { + (void) memcpy(t2, ack->opts[CD_T2_TIME]->value, sizeof (*t2)); + *t2 = ntohl(*t2); + } + + if ((*t1 == DHCP_PERM) || (*t1 >= *lease)) + *t1 = (lease_t)fuzzify(*lease, DHCP_T1_FACT); + + if ((*t2 == DHCP_PERM) || (*t2 > *lease) || (*t2 <= *t1)) + *t2 = (lease_t)fuzzify(*lease, DHCP_T2_FACT); + + dhcpmsg(MSG_VERBOSE, "get_pkt_times: lease %u t1 %u t2 %u", + *lease, *t1, *t2); +} + +/* + * configure_v4_timers(): configures the lease timers on a v4 state machine + * + * input: dhcp_smach_t *: the state machine to configure + * output: boolean_t: B_TRUE on success, B_FALSE on failure + */ + +static boolean_t +configure_v4_timers(dhcp_smach_t *dsmp) { + PKT_LIST *ack = dsmp->dsm_ack; lease_t lease, t1, t2; + dhcp_lease_t *dlp; + dhcp_lif_t *lif; - 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); + /* v4 has just one lease per state machine, and one LIF */ + dlp = dsmp->dsm_leases; + lif = dlp->dl_lifs; + + /* + * If it's DHCP, but there's no valid lease time, then complain, + * decline the lease and return error. + */ + if (ack->opts[CD_DHCP_TYPE] != NULL && + (ack->opts[CD_LEASE_TIME] == NULL || + ack->opts[CD_LEASE_TIME]->len != sizeof (lease_t))) { + lif_mark_decline(lif, "Missing or corrupted lease time"); + send_declines(dsmp); + dhcpmsg(MSG_WARNING, "configure_v4_timers: %s lease time in " + "ACK on %s", ack->opts[CD_LEASE_TIME] == NULL ? "missing" : + "corrupt", dsmp->dsm_name); + return (B_FALSE); } - cancel_ifs_timers(ifsp); + /* Stop the T1 and T2 timers */ + cancel_lease_timers(dlp); + + /* Stop the LEASE timer */ + cancel_lif_timers(lif); /* * type has already been verified as ACK. if type is not set, @@ -277,89 +539,471 @@ configure_timers(struct ifslist *ifsp) * returned as relative host-byte-ordered times. */ - get_pkt_times(ifsp->if_ack, &lease, &t1, &t2); + get_pkt_times(ack, &lease, &t1, &t2); + + /* + * if the current lease is mysteriously close to the new + * lease, warn the user. unless there's less than a minute + * left, round to the closest minute. + */ + + if (lif->lif_expire.dt_start != 0 && + abs((dsmp->dsm_newstart_monosec + lease) - + (dsmp->dsm_curstart_monosec + lif->lif_expire.dt_start)) < + DHCP_LEASE_EPS) { + const char *noext = "configure_v4_timers: lease renewed but " + "time not extended"; + int msg_level; + uint_t minleft; + + if (lif->lif_expire.dt_start < DHCP_LEASE_ERROR_THRESH) + msg_level = MSG_ERROR; + else + msg_level = MSG_VERBOSE; + + minleft = (lif->lif_expire.dt_start + 30) / 60; + + if (lif->lif_expire.dt_start < 60) { + dhcpmsg(msg_level, "%s; expires in %d seconds", + noext, lif->lif_expire.dt_start); + } else if (minleft == 1) { + dhcpmsg(msg_level, "%s; expires in 1 minute", noext); + } else if (minleft > 120) { + dhcpmsg(msg_level, "%s; expires in %d hours", + noext, (minleft + 30) / 60); + } else { + dhcpmsg(msg_level, "%s; expires in %d minutes", + noext, minleft); + } + } - ifsp->if_t1 = t1; - ifsp->if_t2 = t2; - ifsp->if_lease = lease; + init_timer(&dlp->dl_t1, t1); + init_timer(&dlp->dl_t2, t2); + init_timer(&lif->lif_expire, lease); - if (ifsp->if_lease == DHCP_PERM) { - dhcpmsg(MSG_INFO, "%s acquired permanent lease", ifsp->if_name); - return (1); + if (lease == DHCP_PERM) { + dhcpmsg(MSG_INFO, + "configure_v4_timers: %s acquired permanent lease", + dsmp->dsm_name); + return (B_TRUE); } - dhcpmsg(MSG_INFO, "%s acquired lease, expires %s", ifsp->if_name, - monosec_to_string(ifsp->if_newstart_monosec + ifsp->if_lease)); + dhcpmsg(MSG_INFO, "configure_v4_timers: %s acquired lease, expires %s", + dsmp->dsm_name, + monosec_to_string(dsmp->dsm_newstart_monosec + lease)); - dhcpmsg(MSG_INFO, "%s begins renewal at %s", ifsp->if_name, - monosec_to_string(ifsp->if_newstart_monosec + ifsp->if_t1)); + dhcpmsg(MSG_INFO, "configure_v4_timers: %s begins renewal at %s", + dsmp->dsm_name, monosec_to_string(dsmp->dsm_newstart_monosec + + dlp->dl_t1.dt_start)); - dhcpmsg(MSG_INFO, "%s begins rebinding at %s", ifsp->if_name, - monosec_to_string(ifsp->if_newstart_monosec + ifsp->if_t2)); + dhcpmsg(MSG_INFO, "configure_v4_timers: %s begins rebinding at %s", + dsmp->dsm_name, monosec_to_string(dsmp->dsm_newstart_monosec + + dlp->dl_t2.dt_start)); /* * according to RFC2131, there is no minimum lease time, but don't * set up renew/rebind timers if lease is shorter than DHCP_REBIND_MIN. */ - if (schedule_ifs_timer(ifsp, DHCP_LEASE_TIMER, lease, dhcp_expire) == 0) + if (!schedule_lif_timer(lif, &lif->lif_expire, dhcp_expire)) 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); + dhcpmsg(MSG_WARNING, "configure_v4_timers: lease on %s is for " + "less than %d seconds!", dsmp->dsm_name, DHCP_REBIND_MIN); + return (B_TRUE); } - if (schedule_ifs_timer(ifsp, DHCP_T1_TIMER, t1, dhcp_renew) == 0) + if (!schedule_lease_timer(dlp, &dlp->dl_t1, dhcp_renew)) goto failure; - if (schedule_ifs_timer(ifsp, DHCP_T2_TIMER, t2, dhcp_rebind) == 0) + if (!schedule_lease_timer(dlp, &dlp->dl_t2, dhcp_rebind)) goto failure; - return (1); + return (B_TRUE); failure: - cancel_ifs_timers(ifsp); - dhcpmsg(MSG_WARNING, "dhcp_bound: cannot schedule lease timers"); - return (0); + cancel_lease_timers(dlp); + cancel_lif_timers(lif); + dhcpmsg(MSG_WARNING, + "configure_v4_timers: cannot schedule lease timers"); + return (B_FALSE); } /* - * configure_if(): configures an interface with DHCP parameters from an ACK + * configure_v6_leases(): configures the IPv6 leases on a state machine from + * the current DHCPv6 ACK. We need to scan the ACK, + * create a lease for each IA_NA, and a new LIF for each + * IAADDR. * - * input: struct ifslist *: the interface to configure (with a valid if_ack) - * output: int: 1 on success, 0 on failure + * input: dhcp_smach_t *: the machine to configure (with a valid dsm_ack) + * output: enum v6_bind_result: restart, resend, or done */ -static int -configure_if(struct ifslist *ifsp) +static enum v6_bind_result +configure_v6_leases(dhcp_smach_t *dsmp) { - struct ifreq ifr; + const dhcpv6_option_t *d6o, *d6so, *d6sso; + const char *optbase, *estr, *msg; + uint_t olen, solen, ssolen, msglen; + dhcpv6_ia_na_t d6in; + dhcpv6_iaaddr_t d6ia; + dhcp_lease_t *dlp; + uint32_t shortest; + dhcp_lif_t *lif; + uint_t nlifs; + boolean_t got_iana = B_FALSE; + uint_t scode; + + for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) + dlp->dl_stale = B_TRUE; + + d6o = NULL; + while ((d6o = dhcpv6_pkt_option(dsmp->dsm_ack, d6o, DHCPV6_OPT_IA_NA, + &olen)) != NULL) { + if (olen < sizeof (d6in)) { + dhcpmsg(MSG_WARNING, + "configure_v6_leases: garbled IA_NA"); + continue; + } + + /* + * Check the IAID. It should be for our controlling LIF. If a + * single state machine needs to use multiple IAIDs, then this + * will need to change. + */ + (void) memcpy(&d6in, d6o, sizeof (d6in)); + d6in.d6in_iaid = ntohl(d6in.d6in_iaid); + if (d6in.d6in_iaid != dsmp->dsm_lif->lif_iaid) { + dhcpmsg(MSG_WARNING, "configure_v6_leases: ignored " + "IA_NA for IAID %x (not %x)", d6in.d6in_iaid, + dsmp->dsm_lif->lif_iaid); + continue; + } + + /* + * See notes below; there's only one IA_NA and a single IAID + * for now. + */ + if ((dlp = dsmp->dsm_leases) != NULL) + dlp->dl_stale = B_FALSE; + + /* + * Note that some bug-ridden servers will try to give us + * multiple IA_NA options for a single IAID. We ignore + * duplicates. + */ + if (got_iana) { + dhcpmsg(MSG_WARNING, "configure_v6_leases: unexpected " + "extra IA_NA ignored"); + continue; + } + + d6in.d6in_t1 = ntohl(d6in.d6in_t1); + d6in.d6in_t2 = ntohl(d6in.d6in_t2); + + /* RFC 3315 required check for invalid T1/T2 combinations */ + if (d6in.d6in_t1 > d6in.d6in_t2 && d6in.d6in_t2 != 0) { + dhcpmsg(MSG_WARNING, "configure_v6_leases: ignored " + "IA_NA with invalid T1 %u > T2 %u", d6in.d6in_t1, + d6in.d6in_t2); + continue; + } + + /* + * There may be a status code here. Process if present. + */ + optbase = (const char *)d6o + sizeof (d6in); + olen -= sizeof (d6in); + d6so = dhcpv6_find_option(optbase, olen, NULL, + DHCPV6_OPT_STATUS_CODE, &solen); + scode = dhcpv6_status_code(d6so, solen, &estr, &msg, &msglen); + if (scode != DHCPV6_STAT_SUCCESS) { + dhcpmsg(MSG_WARNING, + "configure_v6_leases: IA_NA: %s: %.*s", + estr, msglen, msg); + } + print_server_msg(dsmp, msg, msglen); + + /* + * Other errors are possible here. According to RFC 3315 + * section 18.1.8, we ignore the entire IA if it gives the "no + * addresses" status code. We may try another server if we + * like -- we instead opt to allow the addresses to expire and + * then try a new server. + * + * If the status code is "no binding," then we must go back and + * redo the Request. Surprisingly, it doesn't matter if it's + * any other code. + */ + if (scode == DHCPV6_STAT_NOADDRS) { + dhcpmsg(MSG_DEBUG, "configure_v6_leases: ignoring " + "no-addrs status in IA_NA"); + continue; + } + + if (scode == DHCPV6_STAT_NOBINDING) { + send_v6_request(dsmp); + return (v6Resent); + } + + /* + * Find or create the lease structure. This part is simple, + * because we support only IA_NA and a single IAID. This means + * there's only one lease structure. The design supports + * multiple lease structures so that IA_TA and IA_PD can be + * added later. + */ + if ((dlp = dsmp->dsm_leases) == NULL && + (dlp = insert_lease(dsmp)) == NULL) { + dhcpmsg(MSG_ERROR, "configure_v6_leases: unable to " + "allocate memory for lease"); + return (v6Restart); + } + + /* + * Iterate over the IAADDR options contained within this IA_NA. + */ + shortest = DHCPV6_INFTIME; + d6so = NULL; + while ((d6so = dhcpv6_find_option(optbase, olen, d6so, + DHCPV6_OPT_IAADDR, &solen)) != NULL) { + if (solen < sizeof (d6ia)) { + dhcpmsg(MSG_WARNING, + "configure_v6_leases: garbled IAADDR"); + continue; + } + (void) memcpy(&d6ia, d6so, sizeof (d6ia)); + + d6ia.d6ia_preflife = ntohl(d6ia.d6ia_preflife); + d6ia.d6ia_vallife = ntohl(d6ia.d6ia_vallife); + + /* RFC 3315 required validity check */ + if (d6ia.d6ia_preflife > d6ia.d6ia_vallife) { + dhcpmsg(MSG_WARNING, + "configure_v6_leases: ignored IAADDR with " + "preferred lifetime %u > valid %u", + d6ia.d6ia_preflife, d6ia.d6ia_vallife); + continue; + } + + /* + * RFC 3315 allows a status code to be buried inside + * the IAADDR option. Look for it, and process if + * present. Process in a manner similar to that for + * the IA itself; TAHI checks for this. Real servers + * likely won't do this. + */ + d6sso = dhcpv6_find_option((const char *)d6so + + sizeof (d6ia), solen - sizeof (d6ia), NULL, + DHCPV6_OPT_STATUS_CODE, &ssolen); + scode = dhcpv6_status_code(d6sso, ssolen, &estr, &msg, + &msglen); + print_server_msg(dsmp, msg, msglen); + if (scode == DHCPV6_STAT_NOADDRS) { + dhcpmsg(MSG_DEBUG, "configure_v6_leases: " + "ignoring no-addrs status in IAADDR"); + continue; + } + if (scode == DHCPV6_STAT_NOBINDING) { + send_v6_request(dsmp); + return (v6Resent); + } + if (scode != DHCPV6_STAT_SUCCESS) { + dhcpmsg(MSG_WARNING, + "configure_v6_leases: IAADDR: %s", estr); + } + + /* + * Locate the existing LIF within the lease associated + * with this address, if any. + */ + lif = dlp->dl_lifs; + for (nlifs = dlp->dl_nlifs; nlifs > 0; + nlifs--, lif = lif->lif_next) { + if (IN6_ARE_ADDR_EQUAL(&d6ia.d6ia_addr, + &lif->lif_v6addr)) + break; + } + + /* + * If the server has set the lifetime to zero, then + * delete the LIF. Otherwise, set the new LIF expiry + * time, adding the LIF if necessary. + */ + if (d6ia.d6ia_vallife == 0) { + /* If it was found, then it's expired */ + if (nlifs != 0) { + dhcpmsg(MSG_DEBUG, + "configure_v6_leases: lif %s has " + "expired", lif->lif_name); + lif->lif_expired = B_TRUE; + } + continue; + } + + /* If it wasn't found, then create it now. */ + if (nlifs == 0) { + lif = plumb_lif(dsmp->dsm_lif->lif_pif, + &d6ia.d6ia_addr); + if (lif == NULL) + continue; + if (++dlp->dl_nlifs == 1) { + dlp->dl_lifs = lif; + } else { + remque(lif); + insque(lif, dlp->dl_lifs); + } + lif->lif_lease = dlp; + lif->lif_dad_wait = _B_TRUE; + dsmp->dsm_lif_wait++; + } else { + /* If it was found, cancel timer */ + cancel_lif_timers(lif); + if (d6ia.d6ia_preflife != 0 && + !clear_lif_deprecated(lif)) { + unplumb_lif(lif); + continue; + } + } + + /* Set the new expiry timers */ + init_timer(&lif->lif_preferred, d6ia.d6ia_preflife); + init_timer(&lif->lif_expire, d6ia.d6ia_vallife); + + /* + * If the preferred lifetime is over now, then the LIF + * is deprecated. If it's the same as the expiry time, + * then we don't need a separate timer for it. + */ + if (d6ia.d6ia_preflife == 0) { + set_lif_deprecated(lif); + } else if (d6ia.d6ia_preflife != DHCPV6_INFTIME && + d6ia.d6ia_preflife != d6ia.d6ia_vallife && + !schedule_lif_timer(lif, &lif->lif_preferred, + dhcp_deprecate)) { + unplumb_lif(lif); + continue; + } + + if (d6ia.d6ia_vallife != DHCPV6_INFTIME && + !schedule_lif_timer(lif, &lif->lif_expire, + dhcp_expire)) { + unplumb_lif(lif); + continue; + } + + if (d6ia.d6ia_preflife < shortest) + shortest = d6ia.d6ia_preflife; + } + + if (dlp->dl_nlifs == 0) { + dhcpmsg(MSG_WARNING, + "configure_v6_leases: no IAADDRs found in IA_NA"); + remove_lease(dlp); + continue; + } + + if (d6in.d6in_t1 == 0 && d6in.d6in_t2 == 0) { + /* Default values from RFC 3315: 0.5 and 0.8 */ + if ((d6in.d6in_t1 = shortest / 2) == 0) + d6in.d6in_t1 = 1; + d6in.d6in_t2 = shortest - shortest / 5; + } + + cancel_lease_timers(dlp); + init_timer(&dlp->dl_t1, d6in.d6in_t1); + init_timer(&dlp->dl_t2, d6in.d6in_t2); + + if ((d6in.d6in_t1 != DHCPV6_INFTIME && + !schedule_lease_timer(dlp, &dlp->dl_t1, dhcp_renew)) || + (d6in.d6in_t2 != DHCPV6_INFTIME && + !schedule_lease_timer(dlp, &dlp->dl_t2, dhcp_rebind))) { + dhcpmsg(MSG_WARNING, "configure_v6_leases: unable to " + "set renew/rebind timers"); + } else { + got_iana = B_TRUE; + } + } + + if (!got_iana) { + dhcpmsg(MSG_WARNING, + "configure_v6_leases: no usable IA_NA option found"); + } + + return (v6Done); +} + +/* + * configure_v4_lease(): configures the IPv4 lease on a state machine from + * the current DHCP ACK. There's only one lease and LIF + * per state machine in IPv4. + * + * input: dhcp_smach_t *: the machine to configure (with a valid dsm_ack) + * output: boolean_t: B_TRUE on success, B_FALSE on failure + */ + +static boolean_t +configure_v4_lease(dhcp_smach_t *dsmp) +{ + struct lifreq lifr; struct sockaddr_in *sin; - PKT_LIST *ack = ifsp->if_ack; + PKT_LIST *ack = dsmp->dsm_ack; + dhcp_lease_t *dlp; + dhcp_lif_t *lif; + uint32_t addrhbo; + struct in_addr inaddr; /* * if we're using DHCP, then we'll have a valid CD_SERVER_ID * (we checked in dhcp_acknak()); set it now so that - * ifsp->if_server is valid in case we need to send_decline(). + * dsmp->dsm_server is valid in case we need to send_decline(). * note that we use comparisons against opts[CD_DHCP_TYPE] * since we haven't set DHCP_IF_BOOTP yet (we don't do that * until we're sure we want the offered address.) */ - if (ifsp->if_ack->opts[CD_DHCP_TYPE] != NULL) - (void) memcpy(&ifsp->if_server.s_addr, - ack->opts[CD_SERVER_ID]->value, sizeof (ipaddr_t)); + if (ack->opts[CD_DHCP_TYPE] != NULL) { + (void) memcpy(&inaddr, ack->opts[CD_SERVER_ID]->value, + sizeof (inaddr)); + IN6_INADDR_TO_V4MAPPED(&inaddr, &dsmp->dsm_server); + } + + /* + * There needs to be exactly one lease for IPv4, and that lease + * controls the main LIF for the state machine. If it doesn't exist + * yet, then create it now. + */ + if ((dlp = dsmp->dsm_leases) == NULL && + (dlp = insert_lease(dsmp)) == NULL) { + dhcpmsg(MSG_ERROR, "configure_v4_lease: unable to allocate " + "memory for lease"); + return (B_FALSE); + } + if (dlp->dl_nlifs == 0) { + dlp->dl_lifs = dsmp->dsm_lif; + dlp->dl_nlifs = 1; + + /* The lease holds a reference on the LIF */ + hold_lif(dlp->dl_lifs); + dlp->dl_lifs->lif_lease = dlp; + } - 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); + lif = dlp->dl_lifs; + + IN6_INADDR_TO_V4MAPPED(&ack->pkt->yiaddr, &lif->lif_v6addr); + addrhbo = ntohl(ack->pkt->yiaddr.s_addr); + if ((addrhbo & IN_CLASSA_NET) == 0 || + (addrhbo >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET || + IN_CLASSD(addrhbo)) { + dhcpmsg(MSG_ERROR, + "configure_v4_lease: got invalid IP address %s for %s", + inet_ntoa(ack->pkt->yiaddr), lif->lif_name); + return (B_FALSE); } - (void) memset(&ifr, 0, sizeof (struct ifreq)); - (void) strlcpy(ifr.ifr_name, ifsp->if_name, IFNAMSIZ); + (void) memset(&lifr, 0, sizeof (struct lifreq)); + (void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ); /* * bring the interface online. note that there is no optimal @@ -373,104 +1017,111 @@ configure_if(struct ifslist *ifsp) * fixes the problem. */ - /* LINTED [ifr_addr is a sockaddr which will be aligned] */ - sin = (struct sockaddr_in *)&ifr.ifr_addr; + sin = (struct sockaddr_in *)&lifr.lifr_addr; sin->sin_family = AF_INET; + (void) memset(&lif->lif_v6mask, 0xff, sizeof (lif->lif_v6mask)); if (ack->opts[CD_SUBNETMASK] != NULL && - ack->opts[CD_SUBNETMASK]->len == sizeof (ipaddr_t)) { + ack->opts[CD_SUBNETMASK]->len == sizeof (inaddr)) { - (void) memcpy(&ifsp->if_netmask.s_addr, - ack->opts[CD_SUBNETMASK]->value, sizeof (ipaddr_t)); + (void) memcpy(&inaddr, ack->opts[CD_SUBNETMASK]->value, + sizeof (inaddr)); } 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 (inaddr)) { + dhcpmsg(MSG_WARNING, "configure_v4_lease: specified " + "subnet mask length is %d instead of %d, ignoring", ack->opts[CD_SUBNETMASK]->len, sizeof (ipaddr_t)); + } else { + dhcpmsg(MSG_WARNING, "configure_v4_lease: no IP " + "netmask specified for %s, making best guess", + lif->lif_name); + } /* * no legitimate IP subnet mask specified.. use best - * guess. recall that if_addr is in network order, so + * guess. recall that lif_addr is in network order, so * imagine it's 0x11223344: then when it is read into * a register on x86, it becomes 0x44332211, so we * must ntohl() it to convert it to 0x11223344 in * order to use the macros in <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); + if (IN_CLASSA(addrhbo)) + inaddr.s_addr = htonl(IN_CLASSA_NET); + else if (IN_CLASSB(addrhbo)) + inaddr.s_addr = htonl(IN_CLASSB_NET); + else /* must be class c */ + inaddr.s_addr = htonl(IN_CLASSC_NET); } + lif->lif_v6mask._S6_un._S6_u32[3] = inaddr.s_addr; - dhcpmsg(MSG_INFO, "setting IP netmask to %s on %s", - inet_ntoa(ifsp->if_netmask), ifsp->if_name); + sin->sin_addr = inaddr; + dhcpmsg(MSG_INFO, "configure_v4_lease: setting IP netmask to %s on %s", + inet_ntoa(sin->sin_addr), lif->lif_name); - 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); + if (ioctl(v4_sock_fd, SIOCSLIFNETMASK, &lifr) == -1) { + dhcpmsg(MSG_ERR, "configure_v4_lease: cannot set IP netmask " + "on %s", lif->lif_name); + return (B_FALSE); } - dhcpmsg(MSG_INFO, "setting IP address to %s on %s", - inet_ntoa(ifsp->if_addr), ifsp->if_name); + IN6_V4MAPPED_TO_INADDR(&lif->lif_v6addr, &sin->sin_addr); + dhcpmsg(MSG_INFO, "configure_v4_lease: setting IP address to %s on %s", + inet_ntoa(sin->sin_addr), lif->lif_name); - 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 (ioctl(v4_sock_fd, SIOCSLIFADDR, &lifr) == -1) { + dhcpmsg(MSG_ERR, "configure_v4_lease: cannot set IP address " + "on %s", lif->lif_name); + return (B_FALSE); } + if (ioctl(v4_sock_fd, SIOCGLIFFLAGS, &lifr) == -1) { + dhcpmsg(MSG_ERR, "configure_v4_lease: cannot get interface " + "flags for %s", lif->lif_name); + return (B_FALSE); + } + + lifr.lifr_flags |= IFF_UP; + if (ioctl(v4_sock_fd, SIOCSLIFFLAGS, &lifr) == -1) { + dhcpmsg(MSG_ERR, "configure_v4_lease: cannot set interface " + "flags for %s", lif->lif_name); + return (B_FALSE); + } + lif->lif_flags = lifr.lifr_flags; + + lif->lif_dad_wait = B_TRUE; + dsmp->dsm_lif_wait++; + if (ack->opts[CD_BROADCASTADDR] != NULL && - ack->opts[CD_BROADCASTADDR]->len == sizeof (ipaddr_t)) { + ack->opts[CD_BROADCASTADDR]->len == sizeof (inaddr)) { - (void) memcpy(&ifsp->if_broadcast.s_addr, - ack->opts[CD_BROADCASTADDR]->value, sizeof (ipaddr_t)); + (void) memcpy(&inaddr, ack->opts[CD_BROADCASTADDR]->value, + sizeof (inaddr)); } else { if (ack->opts[CD_BROADCASTADDR] != NULL && - ack->opts[CD_BROADCASTADDR]->len != sizeof (ipaddr_t)) - dhcpmsg(MSG_WARNING, "configure_if: specified " + ack->opts[CD_BROADCASTADDR]->len != sizeof (inaddr)) { + dhcpmsg(MSG_WARNING, "configure_v4_lease: specified " "broadcast address length is %d instead of %d, " "ignoring", ack->opts[CD_BROADCASTADDR]->len, - sizeof (ipaddr_t)); + sizeof (inaddr)); + } else { + dhcpmsg(MSG_WARNING, "configure_v4_lease: no IP " + "broadcast specified for %s, making best guess", + lif->lif_name); + } /* * no legitimate IP broadcast specified. compute it * from the IP address and netmask. */ - 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); + IN6_V4MAPPED_TO_INADDR(&lif->lif_v6addr, &inaddr); + inaddr.s_addr |= ~lif->lif_v6mask._S6_un._S6_u32[3]; } /* @@ -483,114 +1134,49 @@ configure_if(struct ifslist *ifsp) * 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 (ioctl(v4_sock_fd, SIOCGLIFBRDADDR, &lifr) == -1) { + dhcpmsg(MSG_ERR, "configure_v4_lease: cannot get broadcast " + "address for %s", lif->lif_name); + return (B_FALSE); } - if (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); + if (inaddr.s_addr != sin->sin_addr.s_addr) { + dhcpmsg(MSG_WARNING, "configure_v4_lease: incorrect broadcast " + "address %s specified for %s; ignoring", inet_ntoa(inaddr), + lif->lif_name); } - ifsp->if_broadcast = sin->sin_addr; - dhcpmsg(MSG_INFO, "using broadcast address %s on %s", - inet_ntoa(ifsp->if_broadcast), ifsp->if_name); - return (1); + lif->lif_broadcast = inaddr.s_addr; + dhcpmsg(MSG_INFO, + "configure_v4_lease: using broadcast address %s on %s", + inet_ntoa(inaddr), lif->lif_name); + return (B_TRUE); } /* - * configure_bound(): configures routing with DHCP parameters from an ACK, - * and sets up the if_sock_ip_fd socket used for lease - * renewal. + * save_server_id(): save off the new DHCPv6 Server ID * - * input: struct ifslist *: the interface to configure (with a valid if_ack) - * output: int: 1 on success, 0 on failure + * input: dhcp_smach_t *: the state machine to use + * PKT_LIST *: the packet with the Reply message + * output: boolean_t: B_TRUE on success, B_FALSE on failure */ -static int -configure_bound(struct ifslist *ifsp) +boolean_t +save_server_id(dhcp_smach_t *dsmp, PKT_LIST *msg) { - PKT_LIST *ack = ifsp->if_ack; - DHCP_OPT *router_list; - int i; - - /* - * 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_bound: 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_bound: 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_bound: 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_bound: 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_bound: cannot bind broadcast " - "socket on %s", ifsp->if_name); - return (0); + const dhcpv6_option_t *d6o; + uint_t olen; + + d6o = dhcpv6_pkt_option(msg, NULL, DHCPV6_OPT_SERVERID, &olen); + if (d6o == NULL) + return (B_FALSE); + olen -= sizeof (*d6o); + free(dsmp->dsm_serverid); + if ((dsmp->dsm_serverid = malloc(olen)) == NULL) { + return (B_FALSE); + } else { + dsmp->dsm_serveridlen = olen; + (void) memcpy(dsmp->dsm_serverid, d6o + 1, olen); + return (B_TRUE); } - - /* - * 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_bound: bound ifsp->if_sock_ip_fd"); - return (1); } diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/common.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/common.h new file mode 100644 index 0000000000..1c83b8e03c --- /dev/null +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/common.h @@ -0,0 +1,69 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _COMMON_H +#define _COMMON_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/types.h> + +/* + * Common opaque structure definitions and values used throughout the dhcpagent + * implementation. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Things (unfortunately) required because we're in an XPG environment. + */ +#define B_TRUE _B_TRUE +#define B_FALSE _B_FALSE + +struct dhcp_smach_s; +typedef struct dhcp_smach_s dhcp_smach_t; +struct dhcp_lease_s; +typedef struct dhcp_lease_s dhcp_lease_t; +struct dhcp_lif_s; +typedef struct dhcp_lif_s dhcp_lif_t; +struct dhcp_pif_s; +typedef struct dhcp_pif_s dhcp_pif_t; +typedef int script_callback_t(dhcp_smach_t *, void *); +struct dhcp_timer_s; +typedef struct dhcp_timer_s dhcp_timer_t; +struct dhcp_ipc_s; +typedef struct dhcp_ipc_s dhcp_ipc_t; + +typedef int64_t monosec_t; /* see README for details */ + +#ifdef __cplusplus +} +#endif + +#endif /* _COMMON_H */ diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/defaults.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/defaults.c index 7800163061..3cd4243c2f 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/defaults.c +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/defaults.c @@ -19,16 +19,13 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 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> @@ -37,6 +34,7 @@ #include <sys/stat.h> #include <libnvpair.h> +#include "common.h" #include "defaults.h" struct dhcp_default { @@ -58,8 +56,10 @@ static struct dhcp_default defaults[] = { { "OFFER_WAIT", "3", 1, 20 }, { "ARP_WAIT", "1000", 0, -1 }, { "CLIENT_ID", NULL, 0, 0 }, - { "PARAM_REQUEST_LIST", NULL, 0, 0 }, - { "REQUEST_HOSTNAME", "1", 0, 0 } + { "PARAM_REQUEST_LIST", NULL, 0, 0 }, + { "REQUEST_HOSTNAME", "1", 0, 0 }, + { "DEBUG_LEVEL", "0", 0, 3 }, + { "VERBOSE", "0", 0, 0 } }; /* @@ -74,7 +74,7 @@ df_build_cache(void) { char entry[1024]; int i; - char *param, *value, *end; + char *param, *pastv6, *value, *end; FILE *fp; nvlist_t *nvlist; struct dhcp_default *defp; @@ -107,15 +107,18 @@ df_build_cache(void) * leading interface name) in upper case. */ - if ((param = strchr(entry, '.')) == NULL) - param = entry; - else - param++; + if ((param = strchr(entry, '.')) == NULL) { + pastv6 = param = entry; + } else { + pastv6 = ++param; + if (strncasecmp(param, "v6.", 3) == 0) + pastv6 += 3; + } for (defp = defaults; (char *)defp < (char *)defaults + sizeof (defaults); defp++) { - if (strcasecmp(param, defp->df_name) == 0) { + if (strcasecmp(pastv6, defp->df_name) == 0) { if (defp->df_max == -1) { dhcpmsg(MSG_WARNING, "parameter %s is " "obsolete; ignored", defp->df_name); @@ -144,7 +147,8 @@ df_build_cache(void) * 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 + * boolean_t: B_TRUE for DHCPv6, B_FALSE for IPv4 DHCP + * uint_t: the parameter number to look up * output: const char *: the parameter's value, or default if not set * (must be copied by caller to be kept) * NOTE: df_get_string() is both used by functions outside this source @@ -153,16 +157,17 @@ df_build_cache(void) */ const char * -df_get_string(const char *if_name, unsigned int p) +df_get_string(const char *if_name, boolean_t isv6, uint_t param) { char *value; - char param[256]; + char paramstr[256]; + char name[256]; struct stat statbuf; static struct stat df_statbuf; static boolean_t df_unavail_msg = B_FALSE; static nvlist_t *df_nvlist = NULL; - if (p >= (sizeof (defaults) / sizeof (*defaults))) + if (param >= (sizeof (defaults) / sizeof (*defaults))) return (NULL); if (stat(DHCP_AGENT_DEFAULTS, &statbuf) != 0) { @@ -171,7 +176,7 @@ df_get_string(const char *if_name, unsigned int p) "built-in defaults", DHCP_AGENT_DEFAULTS); df_unavail_msg = B_TRUE; } - return (defaults[p].df_default); + return (defaults[param].df_default); } /* @@ -186,120 +191,84 @@ df_get_string(const char *if_name, unsigned int p) df_nvlist = df_build_cache(); } - (void) snprintf(param, sizeof (param), "%s.%s", if_name, - defaults[p].df_name); + if (isv6) { + (void) snprintf(name, sizeof (name), ".V6.%s", + defaults[param].df_name); + (void) snprintf(paramstr, sizeof (paramstr), "%s%s", if_name, + name); + } else { + (void) strlcpy(name, defaults[param].df_name, sizeof (name)); + (void) snprintf(paramstr, sizeof (paramstr), "%s.%s", if_name, + name); + } /* - * first look for `if_name.param', then `param'. if neither + * first look for `if_name.[v6.]param', then `[v6.]param'. if neither * has been set, use the built-in default. */ - if (nvlist_lookup_string(df_nvlist, param, &value) == 0 || - nvlist_lookup_string(df_nvlist, defaults[p].df_name, &value) == 0) + if (nvlist_lookup_string(df_nvlist, paramstr, &value) == 0 || + nvlist_lookup_string(df_nvlist, 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); + return (defaults[param].df_default); } /* * df_get_int(): gets the integer value of a given user-tunable parameter * * input: const char *: the interface the parameter applies to - * unsigned int: the parameter number to look up + * boolean_t: B_TRUE for DHCPv6, B_FALSE for IPv4 DHCP + * uint_t: the parameter number to look up * output: int: the parameter's value, or default if not set */ int -df_get_int(const char *if_name, unsigned int p) +df_get_int(const char *if_name, boolean_t isv6, uint_t param) { const char *value; int value_int; - if (p >= (sizeof (defaults) / sizeof (*defaults))) + if (param >= (sizeof (defaults) / sizeof (*defaults))) return (0); - value = df_get_string(if_name, p); + value = df_get_string(if_name, isv6, param); if (value == NULL || !isdigit(*value)) goto failure; value_int = atoi(value); - if (value_int > defaults[p].df_max || value_int < defaults[p].df_min) + if (value_int > defaults[param].df_max || + value_int < defaults[param].df_min) goto failure; return (value_int); failure: dhcpmsg(MSG_WARNING, "df_get_int: parameter `%s' is not between %d and " - "%d, defaulting to `%s'", defaults[p].df_name, defaults[p].df_min, - defaults[p].df_max, defaults[p].df_default); - return (atoi(defaults[p].df_default)); + "%d, defaulting to `%s'", defaults[param].df_name, + defaults[param].df_min, defaults[param].df_max, + defaults[param].df_default); + return (atoi(defaults[param].df_default)); } /* * df_get_bool(): gets the boolean value of a given user-tunable parameter * * input: const char *: the interface the parameter applies to - * unsigned int: the parameter number to look up + * boolean_t: B_TRUE for DHCPv6, B_FALSE for IPv4 DHCP + * uint_t: the parameter number to look up * output: boolean_t: B_TRUE if true, B_FALSE if false, default if not set */ boolean_t -df_get_bool(const char *if_name, unsigned int p) +df_get_bool(const char *if_name, boolean_t isv6, uint_t param) { const char *value; - if (p >= (sizeof (defaults) / sizeof (*defaults))) + if (param >= (sizeof (defaults) / sizeof (*defaults))) return (0); - value = df_get_string(if_name, p); + value = df_get_string(if_name, isv6, param); if (value != NULL) { if (strcasecmp(value, "true") == 0 || @@ -312,8 +281,8 @@ df_get_bool(const char *if_name, unsigned int p) } 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); + "`%s', defaulting to `%s'", defaults[param].df_name, + value != NULL ? value : "NULL", defaults[param].df_default); - return ((atoi(defaults[p].df_default) == 0) ? B_FALSE : B_TRUE); + return ((atoi(defaults[param].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 index 95e14cfa34..94cf7204e1 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/defaults.h +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/defaults.h @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -52,15 +52,16 @@ enum { _UNUSED_DF_ARP_WAIT, DF_CLIENT_ID, /* our client id */ DF_PARAM_REQUEST_LIST, /* our parameter request list */ - DF_REQUEST_HOSTNAME /* request hostname associated with interface */ + DF_REQUEST_HOSTNAME, /* request hostname associated with interface */ + DF_DEBUG_LEVEL, /* set debug level (undocumented) */ + DF_VERBOSE /* set verbose mode (undocumented) */ }; #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 *); +boolean_t df_get_bool(const char *, boolean_t, uint_t); +int df_get_int(const char *, boolean_t, uint_t); +const char *df_get_string(const char *, boolean_t, uint_t); #ifdef __cplusplus } diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/dhcpagent.dfl b/usr/src/cmd/cmd-inet/sbin/dhcpagent/dhcpagent.dfl index 406882c77f..79054a3615 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/dhcpagent.dfl +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/dhcpagent.dfl @@ -20,7 +20,7 @@ # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" @@ -36,12 +36,35 @@ # # hme0.RELEASE_ON_SIGTERM=no # RELEASE_ON_SIGTERM=yes +# +# An interface name alone specifies IPv4 DHCP. For DHCPv6, append ".v6". +# Some examples: +# +# hme0.RELEASE_ON_SIGTERM=no specify hme0 v4 behavior +# hme0.v6.RELEASE_ON_SIGTERM=no specify hme0 v6 behavior +# RELEASE_ON_SIGTERM=no match all v4 interfaces +# .v6.RELEASE_ON_SIGTERM=no match all v6 interfaces -# 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. +# By default, when the DHCP agent is sent a SIGTERM (typically when +# the system is shut down), all managed addresses are dropped rather +# than released. Dropping an address does not notify the DHCP server +# that the address is no longer in use, leaving it possibly available +# for subsequent use by the same client. If DHCP is later restarted +# on the interface, the client will ask the server if it can continue +# to use the address. If the server either grants the request, or +# does not answer (and the lease has not yet expired), then the client +# will use the original address. +# +# By uncommenting the following parameter-value pairs, all managed +# interfaces are released on SIGTERM instead. In that case, the DHCP +# server is notified that the address is available for use. Further, +# if DHCP is later restarted on the interface, the client will not +# request its previous address from the server, nor will it attempt to +# reuse the previous lease. This behavior is often preferred for +# roaming systems. # # RELEASE_ON_SIGTERM=yes +# .v6.RELEASE_ON_SIGTERM=yes # By default, the DHCP agent waits 3 seconds to collect OFFER # responses to a DISCOVER. If it receives no OFFERs in this time, it @@ -82,6 +105,14 @@ # 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. +# numbers correspond to the values defined in the IANA bootp-dhcp-parameters +# registry at the time of this writing. # PARAM_REQUEST_LIST=1,3,6,12,15,28,43 + +# The default DHCPv6 parameter request list has preference (7), unicast (12), +# DNS addresses (23), DNS search list (24), NIS addresses (27), and +# NIS domain (29). This may be changed by altering the following parameter- +# value pair. The numbers correspond to the values defined in the IANA +# dhcpv6-parameters registry at the time of this writing. +.v6.PARAM_REQUEST_LIST=7,12,23,24,27,29 diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/dlpi_io.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/dlpi_io.c index 2c74b6bc88..3d53b78575 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/dlpi_io.c +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/dlpi_io.c @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -31,13 +30,11 @@ #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> @@ -47,10 +44,8 @@ #include <dhcpmsg.h> #include <libinetutil.h> -#include "agent.h" #include "dlprims.h" #include "dlpi_io.h" -#include "interface.h" #include "v4_sum_impl.h" /* @@ -138,22 +133,24 @@ dlpi_open(const char *if_name, dl_info_ack_t *dlia, size_t dlia_size, } if (dlia->dl_version != DL_VERSION_2) { - dhcpmsg(MSG_ERROR, "dlpi_open: %s is DLPI version %d, not 2", + dhcpmsg(MSG_ERROR, "dlpi_open: %s is DLPI version %ld, 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", + dhcpmsg(MSG_ERROR, + "dlpi_open: %s is DL_STYLE %lx, 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); + dhcpmsg(MSG_ERROR, "dlpi_open: %s is %#lx, not DL_CLDLS, " + "which is not supported", device_name, + dlia->dl_service_mode); (void) close(fd); return (-1); } @@ -214,11 +211,13 @@ dlpi_close(int fd) * 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 + * struct sockaddr_in *: if non-NULL, recipient's IP address * 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) +dlpi_recvfrom(int fd, void *buffer, size_t buf_len, struct sockaddr_in *from, + struct sockaddr_in *to) { struct ip *ip; struct udphdr *udphdr; @@ -274,10 +273,17 @@ dlpi_recvfrom(int fd, void *buffer, size_t buf_len, struct sockaddr_in *from) (void) memcpy(buffer, &udphdr[1], data_length); if (from != NULL) { - from->sin_addr = ip->ip_dst; + from->sin_family = AF_INET; + from->sin_addr = ip->ip_src; from->sin_port = udphdr->uh_sport; } + if (to != NULL) { + to->sin_family = AF_INET; + to->sin_addr = ip->ip_dst; + to->sin_port = udphdr->uh_dport; + } + free(data_buffer); return (data_length); } @@ -506,8 +512,8 @@ set_packet_filter(int fd, filter_func_t *filter, void *arg, 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); + dhcpmsg(MSG_DEBUG, "set_packet_filter: set filter %p " + "(%s filter)", (void *)filter, filter_name); /* * clean out any potential cruft on the descriptor that @@ -575,22 +581,6 @@ dhcp_filter(ushort_t *pfp, void *arg) } /* - * 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 * diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/dlpi_io.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/dlpi_io.h index fdfe57be48..9d868a2b3b 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/dlpi_io.h +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/dlpi_io.h @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,17 +19,16 @@ * CDDL HEADER END */ /* - * Copyright (c) 1999 by Sun Microsystems, Inc. - * All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. */ #ifndef DLPI_IO_H #define DLPI_IO_H -#pragma ident "%W% %E% SMI" +#pragma ident "%Z%%M% %I% %E% SMI" #include <netinet/in.h> -#include <sys/socket.h> #include <sys/types.h> #include <sys/dlpi.h> @@ -62,12 +60,13 @@ extern "C" { typedef ushort_t *filter_func_t(ushort_t *, void *); -filter_func_t dhcp_filter, blackhole_filter; +filter_func_t dhcp_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_recvfrom(int, void *, size_t, struct sockaddr_in *, + 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 *, diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/inform.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/inform.c index 00a106cef1..f4ba297828 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/inform.c +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/inform.c @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,8 +19,8 @@ * CDDL HEADER END */ /* - * Copyright (c) 1995-2001 by Sun Microsystems, Inc. - * All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. * * INFORM_SENT state of the client state machine. */ @@ -29,10 +28,7 @@ #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> @@ -40,109 +36,89 @@ #include <netinet/udp_var.h> #include <dhcpmsg.h> -#include "util.h" -#include "packet.h" +#include "agent.h" +#include "states.h" #include "interface.h" +#include "packet.h" + +static boolean_t stop_informing(dhcp_smach_t *, unsigned int); /* * dhcp_inform(): sends an INFORM packet and sets up reception for an ACK * - * input: struct ifslist *: the interface to send the inform on, ... + * input: dhcp_smach_t *: the state machine to use * output: void * note: the INFORM cannot be sent successfully if the interface - * does not have an IP address + * does not have an IP address (this is mostly an issue for IPv4). */ void -dhcp_inform(struct ifslist *ifsp) +dhcp_inform(dhcp_smach_t *dsmp) { 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; + if (!set_smach_state(dsmp, INIT)) + goto failed; + + if (dsmp->dsm_isv6) { + dpkt = init_pkt(dsmp, DHCPV6_MSG_INFO_REQ); + + /* Add required Option Request option */ + (void) add_pkt_prl(dpkt, dsmp); + dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers; + (void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server, + stop_informing, DHCPV6_INF_TIMEOUT, DHCPV6_INF_MAX_RT); + } else { + ipaddr_t server; + + /* + * Assemble a DHCPREQUEST packet, without the Server ID option. + * Fill in ciaddr, since we know this. dsm_server will be set + * to the server's IP address, which will be the broadcast + * address if we don't know it. The max DHCP message size + * option is set to the interface max, minus the size of the + * UDP and IP headers. + */ + + dpkt = init_pkt(dsmp, INFORM); + IN6_V4MAPPED_TO_INADDR(&dsmp->dsm_lif->lif_v6addr, + &dpkt->pkt->ciaddr); + + (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, + htons(dsmp->dsm_lif->lif_pif->pif_max - + sizeof (struct udpiphdr))); + (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len); + (void) add_pkt_prl(dpkt, dsmp); + (void) add_pkt_opt(dpkt, CD_END, NULL, 0); + + IN6_V4MAPPED_TO_IPADDR(&dsmp->dsm_server, server); + if (!send_pkt(dsmp, dpkt, server, NULL)) { + dhcpmsg(MSG_ERROR, "dhcp_inform: send_pkt failed"); + goto failed; + } } - /* - * 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 (!set_smach_state(dsmp, INFORM_SENT)) + goto failed; - 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; - } + 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; - } +failed: + dsmp->dsm_dflags |= DHCP_IF_FAILED; + ipc_action_finish(dsmp, DHCP_IPC_E_INT); +} - if (register_acknak(ifsp) == 0) { - ifsp->if_dflags |= DHCP_IF_FAILED; - ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY); - async_finish(ifsp); - return; - } +/* + * stop_informing(): decides when to stop retransmitting Information-Requests + * + * input: dhcp_smach_t *: the state machine Info-Reqs are being sent from + * unsigned int: the number of requests sent so far + * output: boolean_t: B_TRUE if retransmissions should stop + */ - ifsp->if_state = INFORM_SENT; +/* ARGSUSED */ +static boolean_t +stop_informing(dhcp_smach_t *dsmp, unsigned int n_requests) +{ + return (B_FALSE); } diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/init_reboot.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/init_reboot.c index a66be18865..16f7dbb5f4 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/init_reboot.c +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/init_reboot.c @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * INIT_REBOOT state of the DHCP client state machine. @@ -30,6 +29,7 @@ #include <sys/types.h> #include <stdio.h> +#include <stdlib.h> #include <limits.h> #include <sys/socket.h> #include <netinet/in.h> @@ -50,109 +50,234 @@ static stop_func_t stop_init_reboot; /* - * dhcp_init_reboot(): attempts to reuse a cached configuration on an interface + * dhcp_init_reboot_v4(): attempts to reuse a cached configuration for a state + * machine. * - * input: struct ifslist *: the interface to reuse the configuration on + * input: dhcp_smach_t *: the state machine to examine for reuse * output: void */ -void -dhcp_init_reboot(struct ifslist *ifsp) +static void +dhcp_init_reboot_v4(dhcp_smach_t *dsmp) { 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); + dpkt = init_pkt(dsmp, REQUEST); + (void) add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR, + dsmp->dsm_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))); + (void) add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM)); + (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, + htons(dsmp->dsm_lif->lif_pif->pif_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); + (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len); + (void) add_pkt_prl(dpkt, dsmp); /* * 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"); + if (df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, DF_REQUEST_HOSTNAME)) { (void) snprintf(hostfile, sizeof (hostfile), "/etc/hostname.%s", - ifsp->if_name); + dsmp->dsm_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)); + if ((dsmp->dsm_reqhost = strdup(reqhost)) != NULL) + (void) add_pkt_opt(dpkt, CD_HOSTNAME, + dsmp->dsm_reqhost, + strlen(dsmp->dsm_reqhost)); else dhcpmsg(MSG_WARNING, "dhcp_selecting: cannot" " allocate memory for host name option"); + } else { + dhcpmsg(MSG_DEBUG, + "dhcp_selecting: no hostname for %s", + dsmp->dsm_name); } } - add_pkt_opt(dpkt, CD_END, NULL, 0); + (void) add_pkt_opt(dpkt, CD_END, NULL, 0); - (void) send_pkt(ifsp, dpkt, htonl(INADDR_BROADCAST), stop_init_reboot); + (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), stop_init_reboot); +} + + +/* + * dhcp_init_reboot_v6(): attempts to reuse a cached configuration for a state + * machine. Create a Confirm message and multicast it + * out. + * + * input: dhcp_smach_t *: the state machine to examine for reuse + * output: void + */ + +static void +dhcp_init_reboot_v6(dhcp_smach_t *dsmp) +{ + dhcp_pkt_t *dpkt; + dhcpv6_option_t *d6o, *d6so, *popt; + uint_t olen, solen; + dhcpv6_ia_na_t d6in; + dhcpv6_iaaddr_t d6ia; + char *obase; + + /* + * Assemble a Confirm message based on the current ack. + */ + + dpkt = init_pkt(dsmp, DHCPV6_MSG_CONFIRM); + + /* + * Loop over and copy IA_NAs and IAADDRs we have in our last ack. This + * is what we'll be requesting. + */ + d6o = NULL; + while ((d6o = dhcpv6_pkt_option(dsmp->dsm_ack, d6o, DHCPV6_OPT_IA_NA, + &olen)) != NULL) { + + /* + * Copy in IA_NA option from the ack. Note that we use zero + * for all timers in accordance with RFC 3315. (It would make + * some sense to say what we think the current timers are as + * a hint to the server, but the RFC doesn't agree.) + */ + if (olen < sizeof (dhcpv6_ia_na_t)) + continue; + (void) memcpy(&d6in, d6o, sizeof (d6in)); + d6in.d6in_t1 = 0; + d6in.d6in_t2 = 0; + popt = add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA, + (char *)&d6in + sizeof (*d6o), + sizeof (d6in) - sizeof (*d6o)); + if (popt == NULL) + goto failure; + + /* + * Now loop over the IAADDR suboptions and add those. + */ + obase = (char *)d6o + sizeof (dhcpv6_ia_na_t); + olen -= sizeof (dhcpv6_ia_na_t); + d6so = NULL; + while ((d6so = dhcpv6_find_option(obase, olen, d6so, + DHCPV6_OPT_IAADDR, &solen)) != NULL) { + if (solen < sizeof (dhcpv6_iaaddr_t)) + continue; + (void) memcpy(&d6ia, d6so, sizeof (d6ia)); + d6ia.d6ia_preflife = 0; + d6ia.d6ia_vallife = 0; + if (add_pkt_subopt(dpkt, popt, DHCPV6_OPT_IAADDR, + (char *)&d6ia + sizeof (*d6so), + sizeof (d6ia) - sizeof (*d6so)) == NULL) + goto failure; + } + } + + /* Add required Option Request option */ + (void) add_pkt_prl(dpkt, dsmp); + + (void) send_pkt_v6(dsmp, dpkt, ipv6_all_dhcp_relay_and_servers, + stop_init_reboot, DHCPV6_CNF_TIMEOUT, DHCPV6_CNF_MAX_RT); + + return; + +failure: + if (iu_schedule_timer_ms(tq, lrand48() % DHCP_SELECT_WAIT, dhcp_start, + dsmp) != -1) + hold_smach(dsmp); + else + dhcp_selecting(dsmp); +} + +/* + * dhcp_init_reboot(): attempts to reuse a cached configuration for a state + * machine. + * + * input: dhcp_smach_t *: the state machine to examine for reuse + * output: void + */ + +void +dhcp_init_reboot(dhcp_smach_t *dsmp) +{ + dhcpmsg(MSG_VERBOSE, "%s has cached configuration - entering " + "INIT_REBOOT", dsmp->dsm_name); + + if (!set_smach_state(dsmp, INIT_REBOOT)) { + dhcpmsg(MSG_ERROR, "dhcp_init_reboot: cannot register to " + "collect ACK/NAK packets, reverting to INIT on %s", + dsmp->dsm_name); + + dsmp->dsm_dflags |= DHCP_IF_FAILED; + (void) set_smach_state(dsmp, INIT); + ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); + return; + } + + if (dsmp->dsm_isv6) + dhcp_init_reboot_v6(dsmp); + else + dhcp_init_reboot_v4(dsmp); } /* * stop_init_reboot(): decides when to stop retransmitting REQUESTs * - * input: struct ifslist *: the interface REQUESTs are being sent on + * input: dhcp_smach_t *: the state machine sending the REQUESTs * unsigned int: the number of REQUESTs sent so far * output: boolean_t: B_TRUE if retransmissions should stop */ static boolean_t -stop_init_reboot(struct ifslist *ifsp, unsigned int n_requests) +stop_init_reboot(dhcp_smach_t *dsmp, unsigned int n_requests) { - if (n_requests >= DHCP_MAX_REQUESTS) { - - (void) unregister_acknak(ifsp); + if (dsmp->dsm_isv6) { + uint_t nowabs, maxabs; + nowabs = gethrtime() / (NANOSEC / MILLISEC); + maxabs = dsmp->dsm_neg_hrtime / (NANOSEC / MILLISEC) + + DHCPV6_CNF_MAX_RD; + if (nowabs < maxabs) { + /* Cap the timer based on the maximum */ + if (nowabs + dsmp->dsm_send_timeout > maxabs) + dsmp->dsm_send_timeout = maxabs - nowabs; + return (B_FALSE); + } + dhcpmsg(MSG_INFO, "no Reply to Confirm, using remainder of " + "existing lease on %s", dsmp->dsm_name); + } else { + if (n_requests < DHCP_MAX_REQUESTS) + return (B_FALSE); 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. - */ + "using remainder of existing lease on %s", dsmp->dsm_name); + } - if (dhcp_bound(ifsp, NULL) == 0) - dhcp_selecting(ifsp); + /* + * We already stuck our old ack in dsmp->dsm_ack and relativized the + * packet times, so we can just pretend that the server sent it to us + * and move to bound. If that fails, fall back to selecting. + */ - return (B_TRUE); + if (dhcp_bound(dsmp, NULL)) { + if (dsmp->dsm_isv6) { + if (!save_server_id(dsmp, dsmp->dsm_ack)) + goto failure; + server_unicast_option(dsmp, dsmp->dsm_ack); + } + } else { +failure: + dhcpmsg(MSG_INFO, "unable to use saved lease on %s; restarting", + dsmp->dsm_name); + dhcp_selecting(dsmp); } - return (B_FALSE); + return (B_TRUE); } diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/interface.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/interface.c index d88f52f38b..39d4e80a0c 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/interface.c +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/interface.c @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -35,1221 +35,1642 @@ #include <netinet/dhcp.h> #include <string.h> #include <unistd.h> +#include <search.h> +#include <libdevinfo.h> #include <netinet/if_ether.h> -#include <signal.h> +#include <arpa/inet.h> #include <dhcpmsg.h> -#include <libdevinfo.h> +#include <dhcp_inittab.h> +#include "agent.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. - * - */ +dhcp_pif_t *v4root; +dhcp_pif_t *v6root; -static struct ifslist *ifsheadp; -static unsigned int ifscount; +static uint_t cached_v4_max_mtu, cached_v6_max_mtu; -static void init_ifs(struct ifslist *); -static void free_ifs(struct ifslist *); -static void cancel_ifs_timer(struct ifslist *, int); +/* + * Interface flags to watch: things that should be under our direct control. + */ +#define DHCP_IFF_WATCH (IFF_DHCPRUNNING | IFF_DEPRECATED | \ + IFF_ADDRCONF | IFF_MIPRUNNING | IFF_TEMPORARY) -static boolean_t get_prom_prop(const char *, const char *, uchar_t **, - unsigned int *); +static void clear_lif_dhcp(dhcp_lif_t *); /* - * insert_ifs(): creates a new ifs and chains it on the ifslist. initializes - * state which remains consistent across all use of the ifs entry + * insert_pif(): creates a new physical interface structure and chains it on + * the list. Initializes state that remains consistent across + * all use of the physical interface entry. * - * input: const char *: the name of the 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_* + * input: const char *: the name of the physical interface + * boolean_t: if B_TRUE, this is DHCPv6 + * int *: ignored on input; if insert_pif fails, set to a DHCP_IPC_E_* * error code with the reason why - * output: struct ifslist *: a pointer to the new ifs entry, or NULL on failure + * output: dhcp_pif_t *: a pointer to the new entry, or NULL on failure */ -struct ifslist * -insert_ifs(const char *if_name, boolean_t is_adopting, int *error) +dhcp_pif_t * +insert_pif(const char *pname, boolean_t isv6, 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 - */ + dhcp_pif_t *pif; + struct lifreq lifr; - ifsp = calloc(1, sizeof (struct ifslist)); - if (ifsp == NULL) { - dhcpmsg(MSG_ERR, "insert_ifs: cannot allocate ifs entry for " - "%s", if_name); + if ((pif = calloc(1, sizeof (*pif))) == NULL) { + dhcpmsg(MSG_ERR, "insert_pif: cannot allocate pif entry for " + "%s", pname); *error = DHCP_IPC_E_MEMORY; return (NULL); } - (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); + pif->pif_isv6 = isv6; + pif->pif_dlpi_fd = -1; + pif->pif_dlpi_id = -1; + pif->pif_hold_count = 1; + pif->pif_running = B_TRUE; - /* 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); + if (strlcpy(pif->pif_name, pname, LIFNAMSIZ) >= LIFNAMSIZ) { + dhcpmsg(MSG_ERROR, "insert_pif: interface name %s is too long", + pname); *error = DHCP_IPC_E_INVIF; goto failure; } - /* 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, SIOCGIFINDEX, &ifr) == -1) { - if (errno == ENXIO) - *error = DHCP_IPC_E_INVIF; - else - *error = DHCP_IPC_E_INT; - dhcpmsg(MSG_ERR, "insert_ifs: SIOCGIFINDEX for %s", if_name); - goto failure; - } - ifsp->if_index = ifr.ifr_index; - - 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; - } + /* We do not use DLPI with DHCPv6 */ + if (!isv6) { + uint32_t buf[DLPI_BUF_MAX / sizeof (uint32_t)]; + dl_info_ack_t *dlia = (dl_info_ack_t *)buf; + caddr_t dl_addr; - if (is_adopting) { /* - * if the agent is adopting a lease OBP is initially - * searched for a client-id + * Do the allocations necessary for IPv4 DHCP. + * + * 1. open the interface using DLPI + * 2. get the interface max SDU + * 3. get the interface hardware type and hardware length + * 4. get the interface hardware address + * 5. get the interface hardware broadcast address */ - 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; + /* step 1 */ + pif->pif_dlpi_fd = dlpi_open(pname, dlia, sizeof (buf), + ETHERTYPE_IP); + if (pif->pif_dlpi_fd == -1) { + *error = DHCP_IPC_E_INVIF; 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; + /* step 2 */ + pif->pif_max = dlia->dl_max_sdu; + if (pif->pif_max < DHCP_DEF_MAX_SIZE) { + dhcpmsg(MSG_ERROR, "insert_pif: %s does not have a " + "large enough maximum SDU to support DHCP " + "(%u < %u)", pname, pif->pif_max, + DHCP_DEF_MAX_SIZE); + *error = DHCP_IPC_E_INVIF; 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); + /* step 3 */ + pif->pif_hwtype = dlpi_to_arp(dlia->dl_mac_type); + pif->pif_hwlen = dlia->dl_addr_length - + abs(dlia->dl_sap_length); + + dhcpmsg(MSG_DEBUG, "insert_pif: %s: sdumax %u, hwtype %d, " + "hwlen %d", pname, pif->pif_max, pif->pif_hwtype, + pif->pif_hwlen); + + /* step 4 */ + if (pif->pif_hwlen > 0) { + pif->pif_hwaddr = malloc(pif->pif_hwlen); + if (pif->pif_hwaddr == NULL) { + dhcpmsg(MSG_ERR, "insert_pif: cannot allocate " + "pif_hwaddr for %s", pname); *error = DHCP_IPC_E_MEMORY; goto failure; } + } - (void) memcpy(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; - } + /* + * 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. + */ - /* - * 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); + dl_addr = (caddr_t)dlia + dlia->dl_addr_offset; + if (dlia->dl_sap_length > 0) { + pif->pif_sap_before = B_TRUE; + dl_addr += dlia->dl_sap_length; } - } - /* - * initialize the parameter request list, if there is one. - */ + (void) memcpy(pif->pif_hwaddr, dl_addr, pif->pif_hwlen); - 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; - } + /* step 5 */ + pif->pif_saplen = abs(dlia->dl_sap_length); + pif->pif_daddr = build_broadcast_dest(dlia, &pif->pif_dlen); + if (pif->pif_daddr == NULL) { + dhcpmsg(MSG_ERR, "insert_pif: cannot allocate " + "pif_daddr for %s", pname); + *error = DHCP_IPC_E_MEMORY; + goto failure; } - } - ifsp->if_offer_wait = df_get_int(if_name, DF_OFFER_WAIT); + /* Close the DLPI stream until actually needed */ + close_dlpi_pif(pif); + } /* - * we're past the point of failure; chain it on. + * This is a bit gross, but IP has a confused interface. We must + * assume that the zeroth LIF is plumbed, and must query there to get + * the interface index number. */ + (void) strlcpy(lifr.lifr_name, pname, LIFNAMSIZ); - 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; + if (ioctl(isv6 ? v6_sock_fd : v4_sock_fd, SIOCGLIFINDEX, &lifr) == -1) { + if (errno == ENXIO) + *error = DHCP_IPC_E_INVIF; + else + *error = DHCP_IPC_E_INT; + dhcpmsg(MSG_ERR, "insert_pif: SIOCGLIFINDEX for %s", pname); + goto failure; } + pif->pif_index = lifr.lifr_index; - /* - * 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); + insque(pif, isv6 ? &v6root : &v4root); - dhcpmsg(MSG_DEBUG, "insert_ifs: inserted interface %s", if_name); - return (ifsp); + return (pif); failure: - free_ifs(ifsp); + release_pif(pif); return (NULL); } /* - * init_ifs(): puts an ifs in its initial state + * hold_pif(): acquire a hold on a physical interface structure. * - * input: struct ifslist *: the ifs to initialize - * output: void - * note: if the interface isn't fresh, use reset_ifs() + * input: dhcp_pif_t *: a pointer to the PIF structure + * output: none */ -static void -init_ifs(struct ifslist *ifsp) +void +hold_pif(dhcp_pif_t *pif) { - /* - * 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; - ifsp->if_offer_timer = -1; - - set_packet_filter(ifsp->if_dlpi_fd, dhcp_filter, NULL, "DHCP"); - - dhcpmsg(MSG_DEBUG, "init_ifs: initted interface %s", ifsp->if_name); + pif->pif_hold_count++; + dhcpmsg(MSG_DEBUG2, "hold_pif: hold count on %s: %u", pif->pif_name, + pif->pif_hold_count); } /* - * remove_ifs_default_routes(): removes an ifs's default routes + * release_pif(): release a hold on a physical interface structure; will + * destroy the structure on the last hold removed. * - * input: struct ifslist *: the ifs whose default routes need to be removed - * output: void + * input: dhcp_pif_t *: a pointer to the PIF structure + * output: none */ -static void -remove_ifs_default_routes(struct ifslist *ifsp) +void +release_pif(dhcp_pif_t *pif) { - 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; + if (pif->pif_hold_count == 0) { + dhcpmsg(MSG_CRIT, "release_pif: extraneous release"); + return; + } + + if (--pif->pif_hold_count == 0) { + dhcpmsg(MSG_DEBUG, "release_pif: freeing PIF %s", + pif->pif_name); + + remque(pif); + pif->pif_dlpi_count = 1; + close_dlpi_pif(pif); + free(pif->pif_hwaddr); + free(pif->pif_daddr); + free(pif); + } else { + dhcpmsg(MSG_DEBUG2, "release_pif: hold count on %s: %u", + pif->pif_name, pif->pif_hold_count); } } /* - * reset_ifs(): resets an ifs to its initial state + * lookup_pif_by_index(): Looks up PIF entries given regular ifIndex. * - * input: struct ifslist *: the ifs to reset - * output: void + * input: uint_t: the interface index + * boolean_t: B_TRUE if using DHCPv6, B_FALSE otherwise + * output: dhcp_pif_t *: the matching PIF, or NULL if not found */ -void -reset_ifs(struct ifslist *ifsp) +dhcp_pif_t * +lookup_pif_by_index(uint_t ifindex, boolean_t isv6) { - 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 */ + dhcp_pif_t *pif; - cancel_ifs_timers(ifsp); - - if (ifsp->if_offer_timer != -1) { - if (iu_cancel_timer(tq, ifsp->if_offer_timer, NULL)) - (void) release_ifs(ifsp); + for (pif = isv6 ? v6root : v4root; pif != NULL; pif = pif->pif_next) { + if (pif->pif_index == ifindex) + break; } - stop_pkt_retransmission(ifsp); - - init_ifs(ifsp); + return (pif); } /* - * lookup_ifs(): looks up an ifs, given its name + * lookup_pif_by_uindex(): Looks up PIF entries given truncated index and + * previous PIF pointer (or NULL for list start). + * Caller is expected to iterate through all + * potential matches to find interface of interest. * - * input: 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 + * input: uint16_t: the interface index (truncated) + * dhcp_pif_t *: the previous PIF, or NULL for list start + * boolean_t: B_TRUE if using DHCPv6, B_FALSE otherwise + * output: dhcp_pif_t *: the next matching PIF, or NULL if not found + * note: This operates using the 'truncated' (16-bit) ifindex as seen by + * routing socket clients. The value stored in pif_index is the + * 32-bit ifindex from the ioctl interface. */ -struct ifslist * -lookup_ifs(const char *if_name) +dhcp_pif_t * +lookup_pif_by_uindex(uint16_t ifindex, dhcp_pif_t *pif, boolean_t isv6) { - struct ifslist *ifs; + if (pif == NULL) + pif = isv6 ? v6root : v4root; + else + pif = pif->pif_next; - 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) + for (; pif != NULL; pif = pif->pif_next) { + if ((pif->pif_index & 0xffff) == ifindex) break; + } - return (ifs); + return (pif); } /* - * lookup_ifs_by_xid(): looks up an ifs, given its last used transaction id + * lookup_pif_by_name(): Looks up a physical interface entry given a name. * - * input: int: the transaction id to look up - * output: struct ifslist *: the corresponding ifs, or NULL if not found + * input: const char *: the physical interface name + * boolean_t: B_TRUE if using DHCPv6, B_FALSE otherwise + * output: dhcp_pif_t *: the matching PIF, or NULL if not found */ -struct ifslist * -lookup_ifs_by_xid(uint32_t xid) +dhcp_pif_t * +lookup_pif_by_name(const char *pname, boolean_t isv6) { - struct ifslist *ifs; + dhcp_pif_t *pif; + + pif = isv6 ? v6root : v4root; - for (ifs = ifsheadp; ifs != NULL; ifs = ifs->next) { - if (ifs->if_send_pkt.pkt->xid == xid) + for (; pif != NULL; pif = pif->pif_next) { + if (strcmp(pif->pif_name, pname) == 0) break; } - return (ifs); + return (pif); } /* - * lookup_ifs_by_uindex(): Looks up ifs entries given truncated index and - * previous ifs pointer (or NULL for list start). - * Caller is expected to iterate through all - * potential matches to find interface of interest. + * open_dlpi_pif(): register the use of DLPI I/O by a LIF on a PIF, opening + * the connection if necessary. * - * input: int: the interface index - * struct ifslist *: the previous ifs, or NULL for list start - * output: struct ifslist *: the next matching ifs, or NULL if not found - * note: This operates using the 'truncated' (16-bit) ifindex as seen by - * routing socket clients. The value stored in if_index is the - * 32-bit ifindex from the ioctl interface. + * input: dhcp_pif_t *: the physical interface on which to use DLPI + * output: boolean_t: B_TRUE on success, B_FALSE on failure. */ -struct ifslist * -lookup_ifs_by_uindex(uint16_t ifindex, struct ifslist *ifs) +boolean_t +open_dlpi_pif(dhcp_pif_t *pif) { - if (ifs == NULL) - ifs = ifsheadp; - else - ifs = ifs->next; + if (pif->pif_dlpi_fd == -1) { + uint32_t buf[DLPI_BUF_MAX / sizeof (uint32_t)]; + dl_info_ack_t *dlia = (dl_info_ack_t *)buf; - for (; ifs != NULL; ifs = ifs->next) { - if ((ifs->if_index & 0xffff) == ifindex) - break; + pif->pif_dlpi_fd = dlpi_open(pif->pif_name, dlia, sizeof (buf), + ETHERTYPE_IP); + if (pif->pif_dlpi_fd == -1) + return (B_FALSE); + set_packet_filter(pif->pif_dlpi_fd, dhcp_filter, NULL, "DHCP"); + pif->pif_dlpi_id = iu_register_event(eh, pif->pif_dlpi_fd, + POLLIN, dhcp_collect_dlpi, pif); + if (pif->pif_dlpi_id == -1) { + (void) dlpi_close(pif->pif_dlpi_fd); + pif->pif_dlpi_fd = -1; + return (B_FALSE); + } } + pif->pif_dlpi_count++; + return (B_TRUE); +} + +/* + * close_dlpi_pif(): unregister the use of DLPI I/O by a LIF on a PIF, closing + * the connection if this was the last user. + * + * input: dhcp_pif_t *: the physical interface on which we're using DLPI + * output: none + */ - return (ifs); +void +close_dlpi_pif(dhcp_pif_t *pif) +{ + if (pif->pif_dlpi_count > 1) { + pif->pif_dlpi_count--; + return; + } + pif->pif_dlpi_count = 0; + if (pif->pif_dlpi_id != -1) { + (void) iu_unregister_event(eh, pif->pif_dlpi_id, NULL); + pif->pif_dlpi_id = -1; + } + if (pif->pif_dlpi_fd != -1) { + (void) dlpi_close(pif->pif_dlpi_fd); + pif->pif_dlpi_fd = -1; + } } /* - * remove_ifs(): removes a given ifs from the ifslist. marks the ifs - * for being freed (but may not actually free it). + * pif_status(): update the physical interface up/down status. * - * input: struct ifslist *: the ifs to remove - * output: void - * note: see interface.h for a discussion of ifs memory management + * input: dhcp_pif_t *: the physical interface on which we're using DLPI + * boolean_t: B_TRUE if the interface is going up + * output: none */ void -remove_ifs(struct ifslist *ifsp) +pif_status(dhcp_pif_t *pif, boolean_t isup) { - struct ifreq ifr; + dhcp_lif_t *lif; + dhcp_smach_t *dsmp; + + pif->pif_running = isup; + dhcpmsg(LOG_DEBUG, "interface %s has %s", pif->pif_name, + isup ? "come back up" : "gone down"); + for (lif = pif->pif_lifs; lif != NULL; lif = lif->lif_next) { + for (dsmp = lif->lif_smachs; dsmp != NULL; + dsmp = dsmp->dsm_next) { + if (isup) + refresh_smach(dsmp); + else + remove_default_routes(dsmp); + } + } +} - if (ifsp->if_dflags & DHCP_IF_REMOVED) - return; +/* Helper for insert_lif: extract addresses as defined */ +#define ASSIGN_ADDR(v4, v6, lf) \ + if (pif->pif_isv6) { \ + lif->v6 = ((struct sockaddr_in6 *)&lifr.lf)->sin6_addr; \ + } else { \ + lif->v4 = ((struct sockaddr_in *)&lifr.lf)->sin_addr.s_addr; \ + } + +/* + * insert_lif(): Creates a new logical interface structure and chains it on + * the list for a given physical interface. Initializes state + * that remains consistent across all use of the logical + * interface entry. Caller's PIF hold is transferred to the + * LIF on success, and is dropped on failure. + * + * input: dhcp_pif_t *: pointer to the physical interface for this LIF + * const char *: the name of the logical interface + * int *: ignored on input; if insert_pif fails, set to a DHCP_IPC_E_* + * error code with the reason why + * output: dhcp_lif_t *: a pointer to the new entry, or NULL on failure + */ + +dhcp_lif_t * +insert_lif(dhcp_pif_t *pif, const char *lname, int *error) +{ + dhcp_lif_t *lif; + int fd; + struct lifreq lifr; - (void) memset(&ifr, 0, sizeof (struct ifreq)); - (void) strlcpy(ifr.ifr_name, ifsp->if_name, IFNAMSIZ); + if ((lif = calloc(1, sizeof (*lif))) == NULL) { + dhcpmsg(MSG_ERR, "insert_lif: cannot allocate lif entry for " + "%s", lname); + *error = DHCP_IPC_E_MEMORY; + return (NULL); + } - if (ioctl(ifsp->if_sock_fd, SIOCGIFFLAGS, &ifr) == 0) { - ifr.ifr_flags &= ~IFF_DHCPRUNNING; - (void) ioctl(ifsp->if_sock_fd, SIOCSIFFLAGS, &ifr); + lif->lif_sock_ip_fd = -1; + lif->lif_acknak_id = -1; + lif->lif_iaid_id = -1; + lif->lif_hold_count = 1; + lif->lif_pif = pif; + lif->lif_removed = B_TRUE; + init_timer(&lif->lif_preferred, 0); + init_timer(&lif->lif_expire, 0); + + if (strlcpy(lif->lif_name, lname, LIFNAMSIZ) >= LIFNAMSIZ) { + dhcpmsg(MSG_ERROR, "insert_lif: interface name %s is too long", + lname); + *error = DHCP_IPC_E_INVIF; + goto failure; + } + + (void) strlcpy(lifr.lifr_name, lname, LIFNAMSIZ); + + fd = pif->pif_isv6 ? v6_sock_fd : v4_sock_fd; + + if (ioctl(fd, SIOCGLIFMTU, &lifr) == -1) + lif->lif_max = 1024; + else + lif->lif_max = lifr.lifr_mtu; + + if (ioctl(fd, SIOCGLIFADDR, &lifr) == -1) { + if (errno == ENXIO) + *error = DHCP_IPC_E_INVIF; + else + *error = DHCP_IPC_E_INT; + dhcpmsg(MSG_ERR, "insert_lif: SIOCGLIFADDR for %s", lname); + goto failure; + } + ASSIGN_ADDR(lif_addr, lif_v6addr, lifr_addr); + + if (ioctl(fd, SIOCGLIFNETMASK, &lifr) == -1) { + if (errno == ENXIO) + *error = DHCP_IPC_E_INVIF; + else + *error = DHCP_IPC_E_INT; + dhcpmsg(MSG_ERR, "insert_lif: SIOCGLIFNETMASK for %s", lname); + goto failure; } + ASSIGN_ADDR(lif_netmask, lif_v6mask, lifr_addr); - ifsp->if_dflags |= DHCP_IF_REMOVED; + if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) { + *error = DHCP_IPC_E_INT; + dhcpmsg(MSG_ERR, "insert_lif: SIOCGLIFFLAGS for %s", lname); + goto failure; + } + lif->lif_flags = lifr.lifr_flags; /* - * if we have long term timers, cancel them so that interface - * resources can be reclaimed in a reasonable amount of time. + * If we've just detected the interface going up or down, then signal + * an appropriate action. There may be other state machines here. */ + if ((lifr.lifr_flags & IFF_RUNNING) && !pif->pif_running) { + pif_status(pif, B_TRUE); + } else if (!(lifr.lifr_flags & IFF_RUNNING) && pif->pif_running) { + pif_status(pif, B_FALSE); + } - cancel_ifs_timers(ifsp); + if (lifr.lifr_flags & IFF_POINTOPOINT) { + if (ioctl(fd, SIOCGLIFDSTADDR, &lifr) == -1) { + *error = DHCP_IPC_E_INT; + dhcpmsg(MSG_ERR, "insert_lif: SIOCGLIFDSTADDR for %s", + lname); + goto failure; + } + ASSIGN_ADDR(lif_peer, lif_v6peer, lifr_dstaddr); + } else if (!pif->pif_isv6 && (lifr.lifr_flags & IFF_BROADCAST)) { + if (ioctl(fd, SIOCGLIFBRDADDR, &lifr) == -1) { + *error = DHCP_IPC_E_INT; + dhcpmsg(MSG_ERR, "insert_lif: SIOCGLIFBRDADDR for %s", + lname); + goto failure; + } + lif->lif_broadcast = + ((struct sockaddr_in *)&lifr.lifr_broadaddr)->sin_addr. + s_addr; + } - if (ifsp->prev != NULL) - ifsp->prev->next = ifsp->next; + if (pif->pif_isv6) + cached_v6_max_mtu = 0; else - ifsheadp = ifsp->next; + cached_v4_max_mtu = 0; - if (ifsp->next != NULL) - ifsp->next->prev = ifsp->prev; + lif->lif_removed = B_FALSE; + insque(lif, &pif->pif_lifs); - ifscount--; - (void) release_ifs(ifsp); + return (lif); - /* no big deal if this fails */ - if (ifscount == 0) { - inactivity_id = iu_schedule_timer(tq, DHCP_INACTIVITY_WAIT, - inactivity_shutdown, NULL); - } +failure: + release_lif(lif); + return (NULL); } /* - * hold_ifs(): acquires a hold on an ifs + * hold_lif(): acquire a hold on a logical interface structure. * - * input: struct ifslist *: the ifs entry to acquire a hold on - * output: void + * input: dhcp_lif_t *: a pointer to the LIF structure + * output: none */ void -hold_ifs(struct ifslist *ifsp) +hold_lif(dhcp_lif_t *lif) { - ifsp->if_hold_count++; - - dhcpmsg(MSG_DEBUG2, "hold_ifs: hold count on %s: %d", - ifsp->if_name, ifsp->if_hold_count); + lif->lif_hold_count++; + dhcpmsg(MSG_DEBUG2, "hold_lif: hold count on %s: %u", lif->lif_name, + lif->lif_hold_count); } /* - * release_ifs(): releases a hold previously acquired on an ifs. if the - * hold count reaches 0, the ifs is freed + * release_lif(): release a hold on a logical interface structure; will + * destroy the structure on the last hold removed. * - * input: struct ifslist *: the ifs entry to release the hold on - * output: int: the number of holds outstanding on the ifs + * input: dhcp_lif_t *: a pointer to the LIF structure + * output: none */ -int -release_ifs(struct ifslist *ifsp) +void +release_lif(dhcp_lif_t *lif) { - if (ifsp->if_hold_count == 0) { - dhcpmsg(MSG_CRIT, "release_ifs: extraneous release"); - return (0); + if (lif->lif_hold_count == 0) { + dhcpmsg(MSG_CRIT, "release_lif: extraneous release on %s", + lif->lif_name); + return; } - if (--ifsp->if_hold_count == 0) { - free_ifs(ifsp); - return (0); + if (lif->lif_hold_count == 1 && !lif->lif_removed) { + unplumb_lif(lif); + return; } - dhcpmsg(MSG_DEBUG2, "release_ifs: hold count on %s: %d", - ifsp->if_name, ifsp->if_hold_count); + if (--lif->lif_hold_count == 0) { + dhcp_pif_t *pif; + + dhcpmsg(MSG_DEBUG, "release_lif: freeing LIF %s", + lif->lif_name); - return (ifsp->if_hold_count); + if (lif->lif_lease != NULL) + dhcpmsg(MSG_CRIT, + "release_lif: still holding lease at last hold!"); + close_ip_lif(lif); + pif = lif->lif_pif; + if (pif->pif_isv6) + cached_v6_max_mtu = 0; + else + cached_v4_max_mtu = 0; + release_pif(pif); + free(lif); + } else { + dhcpmsg(MSG_DEBUG2, "release_lif: hold count on %s: %u", + lif->lif_name, lif->lif_hold_count); + } } /* - * free_ifs(): frees the memory occupied by an ifs entry + * remove_lif(): remove a logical interface from its PIF and lease (if any) and + * the lease's hold on the LIF. Assumes that we did not plumb + * the interface. * - * input: struct ifslist *: the ifs entry to free - * output: void + * input: dhcp_lif_t *: a pointer to the LIF structure + * output: none */ -static void -free_ifs(struct ifslist *ifsp) +void +remove_lif(dhcp_lif_t *lif) { - 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); + if (lif->lif_plumbed) { + dhcpmsg(MSG_CRIT, "remove_lif: attempted invalid removal of %s", + lif->lif_name); + return; + } + if (lif->lif_removed) { + dhcpmsg(MSG_CRIT, "remove_lif: extraneous removal of %s", + lif->lif_name); + } else { + dhcp_lif_t *lifnext; + dhcp_lease_t *dlp; + + dhcpmsg(MSG_DEBUG2, "remove_lif: removing %s", lif->lif_name); + lif->lif_removed = B_TRUE; + lifnext = lif->lif_next; + clear_lif_dhcp(lif); + cancel_lif_timers(lif); + if (lif->lif_iaid_id != -1 && + iu_cancel_timer(tq, lif->lif_iaid_id, NULL) == 1) { + lif->lif_iaid_id = -1; + release_lif(lif); + } + + /* Remove from PIF list */ + remque(lif); + + /* If we were part of a lease, then remove ourselves */ + if ((dlp = lif->lif_lease) != NULL) { + if (--dlp->dl_nlifs == 0) + dlp->dl_lifs = NULL; + else if (dlp->dl_lifs == lif) + dlp->dl_lifs = lifnext; + if (lif->lif_flags & IFF_DHCPRUNNING) + clear_lif_dhcp(lif); + if (lif->lif_declined != NULL) { + dlp->dl_smach->dsm_lif_down--; + lif->lif_declined = NULL; + } + lif->lif_lease = NULL; + release_lif(lif); + } + } +} + +/* + * lookup_lif_by_name(): Looks up a logical interface entry given a name and + * a physical interface. + * + * input: const char *: the logical interface name + * const dhcp_pif_t *: the physical interface + * output: dhcp_lif_t *: the matching LIF, or NULL if not found + */ + +dhcp_lif_t * +lookup_lif_by_name(const char *lname, const dhcp_pif_t *pif) +{ + dhcp_lif_t *lif; + + for (lif = pif->pif_lifs; lif != NULL; lif = lif->lif_next) { + if (strcmp(lif->lif_name, lname) == 0) + break; + } + + return (lif); } /* - * checkaddr(): checks if the given address is still set on the given ifs + * checkaddr(): checks if the given address is still set on the given LIF * - * input: struct ifslist *: the ifs to check - * int: the address to lookup on the interface - * struct in_addr *: the address to compare to + * input: const dhcp_lif_t *: the LIF to check + * int: the address to look up on the interface (ioctl) + * const in6_addr_t *: the address to compare to + * const char *: name of the address for logging purposes * output: boolean_t: B_TRUE if the address is still set; B_FALSE if not */ static boolean_t -checkaddr(struct ifslist *ifsp, int ioccmd, struct in_addr *addr) +checkaddr(const dhcp_lif_t *lif, int ioccmd, const in6_addr_t *addr, + const char *aname) { - struct ifreq ifr; - struct sockaddr_in *sin; + boolean_t isv6; + int fd; + struct lifreq lifr; - /* LINTED [ifr_addr is a sockaddr which will be aligned] */ - sin = (struct sockaddr_in *)&ifr.ifr_addr; + (void) memset(&lifr, 0, sizeof (struct lifreq)); + (void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ); - (void) memset(&ifr, 0, sizeof (struct ifreq)); - (void) strlcpy(ifr.ifr_name, ifsp->if_name, IFNAMSIZ); - ifr.ifr_addr.sa_family = AF_INET; + isv6 = lif->lif_pif->pif_isv6; + fd = isv6 ? v6_sock_fd : v4_sock_fd; - switch (ioctl(ifsp->if_sock_fd, ioccmd, &ifr)) { - case 0: - if (sin->sin_addr.s_addr != addr->s_addr) + if (ioctl(fd, ioccmd, &lifr) == -1) { + if (errno == ENXIO) { + dhcpmsg(MSG_WARNING, "checkaddr: interface %s is gone", + lif->lif_name); return (B_FALSE); - break; - case -1: - if (errno == ENXIO) + } + dhcpmsg(MSG_DEBUG, + "checkaddr: ignoring ioctl error on %s %x: %s", + lif->lif_name, ioccmd, strerror(errno)); + } else if (isv6) { + struct sockaddr_in6 *sin6 = + (struct sockaddr_in6 *)&lifr.lifr_addr; + char abuf1[INET6_ADDRSTRLEN]; + char abuf2[INET6_ADDRSTRLEN]; + + if (!IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, addr)) { + dhcpmsg(MSG_WARNING, + "checkaddr: expected %s %s on %s, have %s", + aname, inet_ntop(AF_INET6, &sin6->sin6_addr, abuf1, + sizeof (abuf1)), lif->lif_name, + inet_ntop(AF_INET6, addr, abuf2, sizeof (abuf2))); return (B_FALSE); - break; + } + } else { + struct sockaddr_in *sinp = + (struct sockaddr_in *)&lifr.lifr_addr; + ipaddr_t v4addr; + char abuf1[INET_ADDRSTRLEN]; + char abuf2[INET_ADDRSTRLEN]; + + IN6_V4MAPPED_TO_IPADDR(addr, v4addr); + if (sinp->sin_addr.s_addr != v4addr) { + dhcpmsg(MSG_WARNING, + "checkaddr: expected %s %s on %s, have %s", + aname, inet_ntop(AF_INET, &sinp->sin_addr, abuf1, + sizeof (abuf1)), lif->lif_name, + inet_ntop(AF_INET, &v4addr, abuf2, + sizeof (abuf2))); + return (B_FALSE); + } } return (B_TRUE); } /* - * verify_ifs(): verifies than an ifs is still valid (i.e., has not been + * verify_lif(): verifies than a LIF 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 + * input: const dhcp_lif_t *: the LIF to verify + * output: boolean_t: B_TRUE if the LIF is still valid, B_FALSE otherwise */ -int -verify_ifs(struct ifslist *ifsp) +boolean_t +verify_lif(const dhcp_lif_t *lif) { - struct ifreq ifr; + boolean_t isv6; + int fd; + struct lifreq lifr; - if (ifsp->if_dflags & DHCP_IF_REMOVED) - return (0); + (void) memset(&lifr, 0, sizeof (struct lifreq)); + (void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ); - (void) memset(&ifr, 0, sizeof (struct ifreq)); - (void) strlcpy(ifr.ifr_name, ifsp->if_name, IFNAMSIZ); + isv6 = lif->lif_pif->pif_isv6; + fd = isv6 ? v6_sock_fd : v4_sock_fd; - ifr.ifr_addr.sa_family = AF_INET; + if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) { + dhcpmsg(MSG_ERR, "verify_lif: SIOCGLIFFLAGS failed on %s", + lif->lif_name); + return (B_FALSE); + } - switch (ifsp->if_state) { + /* + * If important flags have changed, then abandon the interface. + */ + if ((lif->lif_flags ^ lifr.lifr_flags) & DHCP_IFF_WATCH) { + dhcpmsg(MSG_DEBUG, "verify_lif: unexpected flag change on %s: " + "%llx to %llx (%llx)", lif->lif_name, lif->lif_flags, + lifr.lifr_flags, (lif->lif_flags ^ lifr.lifr_flags) & + DHCP_IFF_WATCH); + return (B_FALSE); + } - case BOUND: - case RENEWING: - case REBINDING: + /* + * Special case: if the interface has gone down as a duplicate, then + * this alone does _not_ mean that we're abandoning it just yet. Allow + * the state machine to handle this normally by trying to get a new + * lease. + */ + if ((lifr.lifr_flags & (IFF_UP|IFF_DUPLICATE)) == IFF_DUPLICATE) { + dhcpmsg(MSG_DEBUG, "verify_lif: duplicate address on %s", + lif->lif_name); + return (B_TRUE); + } - /* - * if the interface has gone down or been unplumbed, then we - * act like there has been an implicit drop. - */ + /* + * If the user has torn down or started up the interface manually, then + * abandon the lease. + */ + if ((lif->lif_flags ^ lifr.lifr_flags) & IFF_UP) { + dhcpmsg(MSG_DEBUG, "verify_lif: user has %s %s", + lifr.lifr_flags & IFF_UP ? "started up" : "shut down", + lif->lif_name); + return (B_FALSE); + } - 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; - } - switch (ioctl(ifsp->if_sock_fd, SIOCGIFINDEX, &ifr)) { - case 0: - if (ifr.ifr_index != ifsp->if_index) - goto abandon; - break; - case -1: - if (errno == ENXIO) - goto abandon; - break; - } - /* FALLTHRU */ + /* + * Check for delete and recreate. + */ + if (ioctl(fd, SIOCGLIFINDEX, &lifr) == -1) { + dhcpmsg(MSG_ERR, "verify_lif: SIOCGLIFINDEX failed on %s", + lif->lif_name); + return (B_FALSE); + } + if (lifr.lifr_index != lif->lif_pif->pif_index) { + dhcpmsg(MSG_DEBUG, + "verify_lif: ifindex on %s changed: %u to %u", + lif->lif_name, lif->lif_pif->pif_index, lifr.lifr_index); + return (B_FALSE); + } - 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. (Note that the netmask is under DHCP control for + * IPv4, but not for DHCPv6, and that IPv6 doesn't have broadcast + * addresses.) + */ + if (!checkaddr(lif, SIOCGLIFADDR, &lif->lif_v6addr, "local address")) + return (B_FALSE); + + if (isv6) { /* - * if 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 it's not point-to-point, we're done. If it is, then + * check the peer's address as well. */ + return (!(lif->lif_flags & IFF_POINTOPOINT) || + checkaddr(lif, SIOCGLIFDSTADDR, &lif->lif_v6peer, + "peer address")); + } else { + if (!checkaddr(lif, SIOCGLIFNETMASK, &lif->lif_v6mask, + "netmask")) + return (B_FALSE); - if (!checkaddr(ifsp, SIOCGIFADDR, &ifsp->if_addr) || - !checkaddr(ifsp, SIOCGIFNETMASK, &ifsp->if_netmask) || - !checkaddr(ifsp, SIOCGIFBRDADDR, &ifsp->if_broadcast)) - goto abandon; + return (checkaddr(lif, + (lif->lif_flags & IFF_POINTOPOINT) ? SIOCGLIFDSTADDR : + SIOCGLIFBRDADDR, &lif->lif_v6peer, "peer address")); } - - 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 + * canonize_lif(): puts the interface in a canonical (zeroed) form. This is + * used only on the "main" LIF for IPv4. All other interfaces + * are under dhcpagent control and are removed using + * unplumb_lif(). * - * input: struct ifslist *: the interface to canonize - * output: int: 1 on success, 0 on failure + * input: dhcp_lif_t *: the interface to canonize + * output: none */ -int -canonize_ifs(struct ifslist *ifsp) +static void +canonize_lif(dhcp_lif_t *lif) { - struct sockaddr_in *sin; - struct ifreq ifr; - - dhcpmsg(MSG_VERBOSE, "canonizing interface %s", ifsp->if_name); + boolean_t isv6; + int fd; + struct lifreq lifr; /* - * note that due to infelicities in the routing code, any default - * routes must be removed prior to clearing the UP flag. + * If there's nothing here, then don't touch the interface. This can + * happen when an already-canonized LIF is recanonized. */ + if (IN6_IS_ADDR_UNSPECIFIED(&lif->lif_v6addr)) + return; + + isv6 = lif->lif_pif->pif_isv6; + dhcpmsg(MSG_VERBOSE, "canonizing IPv%d interface %s", + isv6 ? 6 : 4, lif->lif_name); - remove_ifs_default_routes(ifsp); + lif->lif_v6addr = my_in6addr_any; + lif->lif_v6mask = my_in6addr_any; + lif->lif_v6peer = my_in6addr_any; - /* LINTED [ifr_addr is a sockaddr which will be aligned] */ - sin = (struct sockaddr_in *)&ifr.ifr_addr; + (void) memset(&lifr, 0, sizeof (struct lifreq)); + (void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ); - (void) memset(&ifr, 0, sizeof (struct ifreq)); - (void) strlcpy(ifr.ifr_name, ifsp->if_name, IFNAMSIZ); + fd = isv6 ? v6_sock_fd : v4_sock_fd; - if (ioctl(ifsp->if_sock_fd, SIOCGIFFLAGS, &ifr) == -1) - return (0); + if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) { + dhcpmsg(MSG_ERR, "canonize_lif: can't get flags for %s", + lif->lif_name); + return; + } + + /* Should not happen */ + if (!(lifr.lifr_flags & IFF_DHCPRUNNING)) { + dhcpmsg(MSG_INFO, + "canonize_lif: cannot clear %s; flags are %llx", + lif->lif_name, lifr.lifr_flags); + return; + } /* * clear the UP flag, but don't clear DHCPRUNNING since * that should only be done when the interface is removed - * (see remove_ifs()) + * (see clear_lif_dhcp() and remove_lif()) */ - 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. - */ + lif->lif_flags = lifr.lifr_flags &= ~IFF_UP; - ifr.ifr_flags = 0; - ifr.ifr_addr.sa_family = AF_INET; - sin->sin_addr.s_addr = htonl(INADDR_ANY); + if (ioctl(fd, SIOCSLIFFLAGS, &lifr) == -1) { + dhcpmsg(MSG_ERR, "canonize_lif: can't set flags for %s", + lif->lif_name); + return; + } - if (ioctl(ifsp->if_sock_fd, SIOCSIFADDR, &ifr) == -1) - return (0); + (void) memset(&lifr.lifr_addr, 0, sizeof (lifr.lifr_addr)); + if (isv6) { + struct sockaddr_in6 *sin6 = + (struct sockaddr_in6 *)&lifr.lifr_addr; - if (ioctl(ifsp->if_sock_fd, SIOCSIFNETMASK, &ifr) == -1) - return (0); + sin6->sin6_family = AF_INET6; + sin6->sin6_addr = my_in6addr_any; + } else { + struct sockaddr_in *sinv = + (struct sockaddr_in *)&lifr.lifr_addr; - if (ioctl(ifsp->if_sock_fd, SIOCSIFBRDADDR, &ifr) == -1) - return (0); + sinv->sin_family = AF_INET; + sinv->sin_addr.s_addr = htonl(INADDR_ANY); + } - /* - * 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. - */ + if (ioctl(fd, SIOCSLIFADDR, &lifr) == -1) { + dhcpmsg(MSG_ERR, + "canonize_lif: can't clear local address on %s", + lif->lif_name); + } - ifsp->if_addr.s_addr = htonl(INADDR_ANY); - ifsp->if_netmask.s_addr = htonl(INADDR_ANY); - ifsp->if_broadcast.s_addr = htonl(INADDR_ANY); + /* Netmask is under in.ndpd control with IPv6 */ + if (!isv6 && ioctl(fd, SIOCSLIFNETMASK, &lifr) == -1) { + dhcpmsg(MSG_ERR, "canonize_lif: can't clear netmask on %s", + lif->lif_name); + } - return (1); + if (lif->lif_flags & IFF_POINTOPOINT) { + if (ioctl(fd, SIOCSLIFDSTADDR, &lifr) == -1) { + dhcpmsg(MSG_ERR, + "canonize_lif: can't clear remote address on %s", + lif->lif_name); + } + } else if (!isv6) { + if (ioctl(fd, SIOCSLIFBRDADDR, &lifr) == -1) { + dhcpmsg(MSG_ERR, + "canonize_lif: can't clear broadcast address on %s", + lif->lif_name); + } + } } /* - * 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 + * plumb_lif(): Adds the LIF to the system. This is used for all + * DHCPv6-derived interfaces. The returned LIF has a hold + * on it. * - * input: struct ifslist *: the ifs to check - * output: int: 1 if the interface is valid, 0 otherwise + * input: dhcp_lif_t *: the interface to unplumb + * output: none */ -int -check_ifs(struct ifslist *ifsp) +dhcp_lif_t * +plumb_lif(dhcp_pif_t *pif, const in6_addr_t *addr) { - hold_ifs(ifsp); - if (release_ifs(ifsp) == 1 || verify_ifs(ifsp) == 0) { + dhcp_lif_t *lif; + char abuf[INET6_ADDRSTRLEN]; + struct lifreq lifr; + struct sockaddr_in6 *sin6; + int error; + + (void) inet_ntop(AF_INET6, addr, abuf, sizeof (abuf)); + + for (lif = pif->pif_lifs; lif != NULL; lif = lif->lif_next) { + if (IN6_ARE_ADDR_EQUAL(&lif->lif_v6addr, addr)) { + dhcpmsg(MSG_ERR, + "plumb_lif: entry for %s already exists!", abuf); + return (NULL); + } + } - /* - * 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. - */ + /* First, create a new zero-address logical interface */ + (void) memset(&lifr, 0, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, pif->pif_name, sizeof (lifr.lifr_name)); + if (ioctl(v6_sock_fd, SIOCLIFADDIF, &lifr) == -1) { + dhcpmsg(MSG_ERR, "plumb_lif: SIOCLIFADDIF %s", pif->pif_name); + return (NULL); + } + + /* Next, set the netmask to all ones */ + sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; + sin6->sin6_family = AF_INET6; + (void) memset(&sin6->sin6_addr, 0xff, sizeof (sin6->sin6_addr)); + if (ioctl(v6_sock_fd, SIOCSLIFNETMASK, &lifr) == -1) { + dhcpmsg(MSG_ERR, "plumb_lif: SIOCSLIFNETMASK %s", + lifr.lifr_name); + goto failure; + } + + /* Now set the interface address */ + sin6->sin6_addr = *addr; + if (ioctl(v6_sock_fd, SIOCSLIFADDR, &lifr) == -1) { + dhcpmsg(MSG_ERR, "plumb_lif: SIOCSLIFADDR %s %s", + lifr.lifr_name, abuf); + goto failure; + } - ipc_action_finish(ifsp, DHCP_IPC_E_UNKIF); - async_finish(ifsp); - return (0); + /* Mark the interface up */ + if (ioctl(v6_sock_fd, SIOCGLIFFLAGS, &lifr) == -1) { + dhcpmsg(MSG_ERR, "plumb_lif: SIOCGLIFFLAGS %s", + lifr.lifr_name); + goto failure; + } + lifr.lifr_flags |= IFF_UP | IFF_DHCPRUNNING; + if (ioctl(v6_sock_fd, SIOCSLIFFLAGS, &lifr) == -1) { + dhcpmsg(MSG_ERR, "plumb_lif: SIOCSLIFFLAGS %s", + lifr.lifr_name); + goto failure; } - (void) release_ifs(ifsp); - return (1); + /* Now we can create the internal LIF structure */ + hold_pif(pif); + if ((lif = insert_lif(pif, lifr.lifr_name, &error)) == NULL) + goto failure; + + dhcpmsg(MSG_DEBUG, "plumb_lif: plumbed up %s on %s", abuf, + lif->lif_name); + lif->lif_plumbed = B_TRUE; + + return (lif); + +failure: + if (ioctl(v6_sock_fd, SIOCLIFREMOVEIF, &lifr) == -1 && + errno != ENXIO) { + dhcpmsg(MSG_ERR, "plumb_lif: SIOCLIFREMOVEIF %s", + lifr.lifr_name); + } + return (NULL); } /* - * nuke_ifslist(): delete the ifslist (for use when the dhcpagent is exiting) + * unplumb_lif(): Removes the LIF from dhcpagent and the system. This is used + * for all interfaces configured by DHCP (those in leases). * - * input: boolean_t: B_TRUE if the agent is exiting due to SIGTERM - * output: void + * input: dhcp_lif_t *: the interface to unplumb + * output: none */ void -nuke_ifslist(boolean_t onterm) +unplumb_lif(dhcp_lif_t *lif) { - 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); + dhcp_lease_t *dlp; + + if (lif->lif_plumbed) { + struct lifreq lifr; + + (void) memset(&lifr, 0, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, lif->lif_name, + sizeof (lifr.lifr_name)); + if (ioctl(v6_sock_fd, SIOCLIFREMOVEIF, &lifr) == -1 && + errno != ENXIO) { + dhcpmsg(MSG_ERR, "unplumb_lif: SIOCLIFREMOVEIF %s", + lif->lif_name); } - - /* - * 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; + lif->lif_plumbed = B_FALSE; + } + lif->lif_flags = 0; + /* + * Special case: if we're "unplumbing" the main LIF for DHCPv4, then + * just canonize it and remove it from the lease. + */ + if ((dlp = lif->lif_lease) != NULL && dlp->dl_smach->dsm_lif == lif) { + canonize_lif(lif); + cancel_lif_timers(lif); + if (lif->lif_declined != NULL) { + dlp->dl_smach->dsm_lif_down--; + lif->lif_declined = NULL; } - (void) script_start(ifsp, EVENT_DROP, dhcp_drop, NULL, NULL); + dlp->dl_nlifs = 0; + dlp->dl_lifs = NULL; + lif->lif_lease = NULL; + release_lif(lif); + } else { + remove_lif(lif); } } /* - * refresh_ifslist(): refreshes all finite leases under DHCP control + * attach_lif(): create a new logical interface, creating the physical + * interface as necessary. * - * input: iu_eh_t *: unused - * int: unused - * void *: unused - * output: void + * input: const char *: the logical interface name + * boolean_t: B_TRUE for IPv6 + * int *: set to DHCP_IPC_E_* if creation fails + * output: dhcp_lif_t *: pointer to new entry, or NULL on failure */ -/* ARGSUSED */ -void -refresh_ifslist(iu_eh_t *eh, int sig, void *arg) +dhcp_lif_t * +attach_lif(const char *lname, boolean_t isv6, int *error) { - 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; + dhcp_pif_t *pif; + char pname[LIFNAMSIZ], *cp; - if (ifsp->if_lease == DHCP_PERM) - continue; + (void) strlcpy(pname, lname, sizeof (pname)); + if ((cp = strchr(pname, ':')) != NULL) + *cp = '\0'; - /* - * this interface has a finite lease and we do not know - * how long the machine's been off for. refresh it. - */ + if ((pif = lookup_pif_by_name(pname, isv6)) != NULL) + hold_pif(pif); + else if ((pif = insert_pif(pname, isv6, error)) == NULL) + return (NULL); - 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); + if (lookup_lif_by_name(lname, pif) != NULL) { + dhcpmsg(MSG_ERROR, "attach_lif: entry for %s already exists!", + lname); + release_pif(pif); + *error = DHCP_IPC_E_INVIF; + return (NULL); } + + /* If LIF creation fails, then insert_lif discards our PIF hold */ + return (insert_lif(pif, lname, error)); } /* - * ifs_count(): returns the number of interfaces currently managed + * set_lif_dhcp(): Set logical interface flags to show that it's managed + * by DHCP. * - * input: void - * output: unsigned int: the number of interfaces currently managed + * input: dhcp_lif_t *: the logical interface + * boolean_t: B_TRUE if adopting + * output: int: set to DHCP_IPC_E_* if operation fails */ -unsigned int -ifs_count(void) +int +set_lif_dhcp(dhcp_lif_t *lif, boolean_t is_adopting) { - return (ifscount); + int fd; + int err; + struct lifreq lifr; + + fd = lif->lif_pif->pif_isv6 ? v6_sock_fd : v4_sock_fd; + + (void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ); + + if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) { + err = errno; + dhcpmsg(MSG_ERR, "set_lif_dhcp: SIOCGLIFFLAGS for %s", + lif->lif_name); + return (err == ENXIO ? DHCP_IPC_E_INVIF : DHCP_IPC_E_INT); + } + lif->lif_flags = lifr.lifr_flags; + + /* + * Check for conflicting sources of address control, and other + * unacceptable configurations. + */ + if (lifr.lifr_flags & (IFF_LOOPBACK|IFF_ADDRCONF|IFF_MIPRUNNING| + IFF_TEMPORARY|IFF_VIRTUAL)) { + dhcpmsg(MSG_ERR, "set_lif_dhcp: cannot use %s: flags are %llx", + lif->lif_name, lifr.lifr_flags); + return (DHCP_IPC_E_INVIF); + } + + /* + * if 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 (lifr.lifr_flags & IFF_DHCPRUNNING) { + if (!is_adopting) { + dhcpmsg(MSG_WARNING, "set_lif_dhcp: DHCP flag already " + "set on %s", lif->lif_name); + } + } else { + lifr.lifr_flags |= IFF_DHCPRUNNING; + if (ioctl(fd, SIOCSLIFFLAGS, &lifr) == -1) { + dhcpmsg(MSG_ERR, "set_lif_dhcp: SIOCSLIFFLAGS for %s", + lif->lif_name); + return (DHCP_IPC_E_INT); + } + lif->lif_flags = lifr.lifr_flags; + } + return (DHCP_IPC_SUCCESS); } /* - * cancel_ifs_timer(): cancels a lease-related timer on an interface + * clear_lif_dhcp(): Clear logical interface flags to show that it's no longer + * managed by DHCP. * - * input: struct ifslist *: the interface to operate on - * int: the timer id of the timer to cancel - * output: void + * input: dhcp_lif_t *: the logical interface + * output: none */ static void -cancel_ifs_timer(struct ifslist *ifsp, int timer_id) +clear_lif_dhcp(dhcp_lif_t *lif) { - 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); - } + int fd; + struct lifreq lifr; + + fd = lif->lif_pif->pif_isv6 ? v6_sock_fd : v4_sock_fd; + + (void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ); + + if (!(lif->lif_flags & IFF_DHCPRUNNING)) + return; + + if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) + return; + + if (!(lifr.lifr_flags & IFF_DHCPRUNNING)) + return; + + lif->lif_flags = lifr.lifr_flags &= ~IFF_DHCPRUNNING; + (void) ioctl(fd, SIOCSLIFFLAGS, &lifr); } /* - * cancel_ifs_timers(): cancels an interface's pending lease-related timers + * set_lif_deprecated(): Set the "deprecated" flag to tell users that this + * address will be going away. As the interface is + * going away, we don't care if there are errors. * - * input: struct ifslist *: the interface to operate on - * output: void + * input: dhcp_lif_t *: the logical interface + * output: none */ void -cancel_ifs_timers(struct ifslist *ifsp) +set_lif_deprecated(dhcp_lif_t *lif) { - cancel_ifs_timer(ifsp, DHCP_T1_TIMER); - cancel_ifs_timer(ifsp, DHCP_T2_TIMER); - cancel_ifs_timer(ifsp, DHCP_LEASE_TIMER); + int fd; + struct lifreq lifr; + + if (lif->lif_flags & IFF_DEPRECATED) + return; + + fd = lif->lif_pif->pif_isv6 ? v6_sock_fd : v4_sock_fd; + + (void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ); + + if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) + return; + + if (lifr.lifr_flags & IFF_DEPRECATED) + return; + + lifr.lifr_flags |= IFF_DEPRECATED; + (void) ioctl(fd, SIOCSLIFFLAGS, &lifr); + lif->lif_flags = lifr.lifr_flags; } /* - * schedule_ifs_timer(): schedules a lease-related timer on an interface + * clear_lif_deprecated(): Clear the "deprecated" flag to tell users that this + * address will not be going away. This happens if we + * get a renewal after preferred lifetime but before + * the valid lifetime. * - * input: 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 + * input: dhcp_lif_t *: the logical interface + * output: boolean_t: B_TRUE on success. */ -int -schedule_ifs_timer(struct ifslist *ifsp, int timer_id, uint32_t sec, - iu_tq_callback_t *expire) +boolean_t +clear_lif_deprecated(dhcp_lif_t *lif) { - cancel_ifs_timer(ifsp, timer_id); /* just in case */ + int fd; + struct lifreq lifr; + + fd = lif->lif_pif->pif_isv6 ? v6_sock_fd : v4_sock_fd; - 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); + (void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ); + + if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) { + dhcpmsg(MSG_ERR, "clear_lif_deprecated: SIOCGLIFFLAGS for %s", + lif->lif_name); + return (B_FALSE); + } + + /* + * Check for conflicting sources of address control, and other + * unacceptable configurations. + */ + if (lifr.lifr_flags & (IFF_LOOPBACK|IFF_ADDRCONF|IFF_MIPRUNNING| + IFF_TEMPORARY|IFF_VIRTUAL)) { + dhcpmsg(MSG_ERR, "clear_lif_deprecated: cannot use %s: flags " + "are %llx", lif->lif_name, lifr.lifr_flags); + return (B_FALSE); } - hold_ifs(ifsp); - return (1); + if (!(lifr.lifr_flags & IFF_DEPRECATED)) + return (B_TRUE); + + lifr.lifr_flags &= ~IFF_DEPRECATED; + if (ioctl(fd, SIOCSLIFFLAGS, &lifr) == -1) { + dhcpmsg(MSG_ERR, "clear_lif_deprecated: SIOCSLIFFLAGS for %s", + lif->lif_name); + return (B_FALSE); + } else { + lif->lif_flags = lifr.lifr_flags; + return (B_TRUE); + } } /* - * Get the value of the named property on the named node in devinfo root. + * open_ip_lif(): open up an IP socket for I/O on a given LIF (v4 only). * - * 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. + * input: dhcp_lif_t *: the logical interface to operate on + * output: boolean_t: B_TRUE if the socket was opened successfully. */ -static boolean_t -get_prom_prop(const char *nodename, const char *propname, uchar_t **propvaluep, - unsigned int *lenp) +boolean_t +open_ip_lif(dhcp_lif_t *lif) { - 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; + if (lif->lif_sock_ip_fd != -1) { + dhcpmsg(MSG_WARNING, "open_ip_lif: socket already open on %s", + lif->lif_name); + return (B_FALSE); } - /* - * locate nodename within '/' - */ + lif->lif_sock_ip_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (lif->lif_sock_ip_fd == -1) { + dhcpmsg(MSG_ERR, "open_ip_lif: cannot create v4 socket on %s", + lif->lif_name); + return (B_FALSE); + } - 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 (!bind_sock(lif->lif_sock_ip_fd, IPPORT_BOOTPC, + ntohl(lif->lif_addr))) { + dhcpmsg(MSG_ERR, "open_ip_lif: cannot bind v4 socket on %s", + lif->lif_name); + return (B_FALSE); } - if (node == DI_NODE_NIL) { - dhcpmsg(MSG_DEBUG, "get_prom_prop: node not found"); - goto get_prom_prop_cleanup; + lif->lif_acknak_id = iu_register_event(eh, lif->lif_sock_ip_fd, POLLIN, + dhcp_acknak_lif, lif); + if (lif->lif_acknak_id == -1) { + dhcpmsg(MSG_WARNING, "open_ip_lif: cannot register to " + "receive IP unicast"); + close_ip_lif(lif); + return (B_FALSE); } + return (B_TRUE); +} - /* - * scan all properties of /nodename for the 'propname' property - */ +/* + * close_ip_lif(): close an IP socket for I/O on a given LIF. + * + * input: dhcp_lif_t *: the logical interface to operate on + * output: none + */ - 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)) { +void +close_ip_lif(dhcp_lif_t *lif) +{ + if (lif->lif_acknak_id != -1) { + (void) iu_unregister_event(eh, lif->lif_acknak_id, NULL); + lif->lif_acknak_id = -1; + } + if (lif->lif_sock_ip_fd != -1) { + (void) close(lif->lif_sock_ip_fd); + lif->lif_sock_ip_fd = -1; + } +} - dhcpmsg(MSG_DEBUG, "get_prom_prop: property = %s", - di_prom_prop_name(pp)); +/* + * lif_mark_decline(): mark a LIF as having been declined due to a duplicate + * address or some other conflict. This is used in + * send_declines() to report failure back to the server. + * + * input: dhcp_lif_t *: the logical interface to operate on + * const char *: text string explaining why the address is declined + * output: none + */ - if (strcmp(propname, di_prom_prop_name(pp)) == 0) { - break; - } - } +void +lif_mark_decline(dhcp_lif_t *lif, const char *reason) +{ + if (lif->lif_declined == NULL) { + dhcp_lease_t *dlp; - if (pp == DI_PROM_PROP_NIL) { - dhcpmsg(MSG_DEBUG, "get_prom_prop: property not found"); - goto get_prom_prop_cleanup; + lif->lif_declined = reason; + if ((dlp = lif->lif_lease) != NULL) + dlp->dl_smach->dsm_lif_down++; } +} + +/* + * schedule_lif_timer(): schedules the LIF-related timer + * + * input: dhcp_lif_t *: the logical interface to operate on + * dhcp_timer_t *: the timer to schedule + * iu_tq_callback_t *: the callback to call upon firing + * output: boolean_t: B_TRUE if the timer was scheduled successfully + */ +boolean_t +schedule_lif_timer(dhcp_lif_t *lif, dhcp_timer_t *dt, iu_tq_callback_t *expire) +{ /* - * get the property; allocate some memory copy it out + * If there's a timer running, cancel it and release its lease + * reference. */ + if (dt->dt_id != -1) { + if (!cancel_timer(dt)) + return (B_FALSE); + release_lif(lif); + } - len = di_prom_prop_data(pp, (uchar_t **)&value); + if (schedule_timer(dt, expire, lif)) { + hold_lif(lif); + return (B_TRUE); + } else { + dhcpmsg(MSG_WARNING, + "schedule_lif_timer: cannot schedule timer"); + return (B_FALSE); + } +} - if (value == NULL) { - /* - * property data read problems - */ +/* + * cancel_lif_timer(): cancels a LIF-related timer + * + * input: dhcp_lif_t *: the logical interface to operate on + * dhcp_timer_t *: the timer to cancel + * output: none + */ - success = B_FALSE; - dhcpmsg(MSG_ERR, "get_prom_prop: cannot read property data"); - goto get_prom_prop_cleanup; +static void +cancel_lif_timer(dhcp_lif_t *lif, dhcp_timer_t *dt) +{ + if (dt->dt_id == -1) + return; + if (cancel_timer(dt)) { + dhcpmsg(MSG_DEBUG2, + "cancel_lif_timer: canceled expiry timer on %s", + lif->lif_name); + release_lif(lif); + } else { + dhcpmsg(MSG_WARNING, + "cancel_lif_timer: cannot cancel timer on %s", + lif->lif_name); } +} - if (propvaluep != NULL) { - /* - * allocate somewhere to copy the property value to - */ +/* + * cancel_lif_timers(): cancels the LIF-related timers + * + * input: dhcp_lif_t *: the logical interface to operate on + * output: none + */ - *propvaluep = calloc(len, sizeof (uchar_t)); +void +cancel_lif_timers(dhcp_lif_t *lif) +{ + cancel_lif_timer(lif, &lif->lif_preferred); + cancel_lif_timer(lif, &lif->lif_expire); +} - if (*propvaluep == NULL) { - /* - * allocation problems - */ +/* + * get_max_mtu(): find the maximum MTU of all interfaces for I/O on common + * file descriptors (v4_sock_fd and v6_sock_fd). + * + * input: boolean_t: B_TRUE for IPv6, B_FALSE for IPv4 + * output: none + */ - success = B_FALSE; - dhcpmsg(MSG_ERR, "get_prom_prop: cannot allocate " - "memory for property value"); - goto get_prom_prop_cleanup; +uint_t +get_max_mtu(boolean_t isv6) +{ + uint_t *mtup = isv6 ? &cached_v6_max_mtu : &cached_v4_max_mtu; + + if (*mtup == 0) { + dhcp_pif_t *pif; + dhcp_lif_t *lif; + struct lifreq lifr; + + /* Set an arbitrary lower bound */ + *mtup = 1024; + pif = isv6 ? v6root : v4root; + for (; pif != NULL; pif = pif->pif_next) { + for (lif = pif->pif_lifs; lif != NULL; + lif = lif->lif_next) { + (void) strlcpy(lifr.lifr_name, lif->lif_name, + LIFNAMSIZ); + if (ioctl(v4_sock_fd, SIOCGLIFMTU, &lifr) != + -1 && lifr.lifr_mtu > *mtup) { + *mtup = lifr.lifr_mtu; + } + } } + } + return (*mtup); +} - /* - * copy data out - */ +/* + * expired_lif_state(): summarize the state of expired LIFs on a given state + * machine. + * + * input: dhcp_smach_t *: the state machine to scan + * output: dhcp_expire_t: overall state + */ - (void) memcpy(*propvaluep, value, len); +dhcp_expire_t +expired_lif_state(dhcp_smach_t *dsmp) +{ + dhcp_lease_t *dlp; + dhcp_lif_t *lif; + uint_t nlifs; + uint_t numlifs; + uint_t numexp; + + numlifs = numexp = 0; + for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) { + lif = dlp->dl_lifs; + nlifs = dlp->dl_nlifs; + numlifs += nlifs; + for (; nlifs > 0; nlifs--, lif = lif->lif_next) { + if (lif->lif_expired) + numexp++; + } + } + if (numlifs == 0) + return (DHCP_EXP_NOLIFS); + else if (numexp == 0) + return (DHCP_EXP_NOEXP); + else if (numlifs == numexp) + return (DHCP_EXP_ALLEXP); + else + return (DHCP_EXP_SOMEEXP); +} - /* - * copy out the length if a suitable pointer has - * been supplied - */ +/* + * find_expired_lif(): find the first expired LIF on a given state machine + * + * input: dhcp_smach_t *: the state machine to scan + * output: dhcp_lif_t *: the first expired LIF, or NULL if none. + */ - if (lenp != NULL) { - *lenp = len; +dhcp_lif_t * +find_expired_lif(dhcp_smach_t *dsmp) +{ + dhcp_lease_t *dlp; + dhcp_lif_t *lif; + uint_t nlifs; + + for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) { + lif = dlp->dl_lifs; + nlifs = dlp->dl_nlifs; + for (; nlifs > 0; nlifs--, lif = lif->lif_next) { + if (lif->lif_expired) + return (lif); } - - dhcpmsg(MSG_DEBUG, "get_prom_prop: property value " - "length = %d", len); } + return (NULL); +} + +/* + * remove_v6_strays(): remove any stray interfaces marked as DHCPRUNNING. Used + * only for DHCPv6. + * + * input: none + * output: none + */ -get_prom_prop_cleanup: +void +remove_v6_strays(void) +{ + struct lifnum lifn; + struct lifconf lifc; + struct lifreq *lifrp, *lifrmax; + uint_t numifs; + uint64_t flags; - if (phdl != DI_PROM_HANDLE_NIL) { - di_prom_fini(phdl); + /* + * Get the approximate number of interfaces in the system. It's only + * approximate because the system is dynamic -- interfaces may be + * plumbed or unplumbed at any time. This is also the reason for the + * "+ 10" fudge factor: we're trying to avoid unnecessary looping. + */ + (void) memset(&lifn, 0, sizeof (lifn)); + lifn.lifn_family = AF_INET6; + lifn.lifn_flags = LIFC_ALLZONES | LIFC_NOXMIT | LIFC_TEMPORARY; + if (ioctl(v6_sock_fd, SIOCGLIFNUM, &lifn) == -1) { + dhcpmsg(MSG_ERR, + "remove_v6_strays: cannot read number of interfaces"); + numifs = 10; + } else { + numifs = lifn.lifn_count + 10; } - if (root_node != DI_NODE_NIL) { - di_fini(root_node); + /* + * Get the interface information. We do this in a loop so that we can + * recover from EINVAL from the kernel -- delivered when the buffer is + * too small. + */ + (void) memset(&lifc, 0, sizeof (lifc)); + lifc.lifc_family = AF_INET6; + lifc.lifc_flags = LIFC_ALLZONES | LIFC_NOXMIT | LIFC_TEMPORARY; + for (;;) { + lifc.lifc_len = numifs * sizeof (*lifrp); + lifrp = realloc(lifc.lifc_buf, lifc.lifc_len); + if (lifrp == NULL) { + dhcpmsg(MSG_ERR, + "remove_v6_strays: cannot allocate memory"); + free(lifc.lifc_buf); + return; + } + lifc.lifc_buf = (caddr_t)lifrp; + errno = 0; + if (ioctl(v6_sock_fd, SIOCGLIFCONF, &lifc) == 0 && + lifc.lifc_len < numifs * sizeof (*lifrp)) + break; + if (errno == 0 || errno == EINVAL) { + numifs <<= 1; + } else { + dhcpmsg(MSG_ERR, "remove_v6_strays: SIOCGLIFCONF"); + free(lifc.lifc_buf); + return; + } } - return (success); + lifrmax = lifrp + lifc.lifc_len / sizeof (*lifrp); + for (; lifrp < lifrmax; lifrp++) { + /* + * Get the interface flags; we're interested in the DHCP ones. + */ + if (ioctl(v6_sock_fd, SIOCGLIFFLAGS, lifrp) == -1) + continue; + flags = lifrp->lifr_flags; + if (!(flags & IFF_DHCPRUNNING)) + continue; + /* + * If the interface has a link-local address, then we don't + * control it. Just remove the flag. + */ + if (ioctl(v6_sock_fd, SIOCGLIFADDR, lifrp) == -1) + continue; + if (IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *)&lifrp-> + lifr_addr)->sin6_addr)) { + lifrp->lifr_flags = flags & ~IFF_DHCPRUNNING; + (void) ioctl(v6_sock_fd, SIOCSLIFFLAGS, lifrp); + continue; + } + /* + * All others are (or were) under our control. Clean up by + * removing them. + */ + if (ioctl(v6_sock_fd, SIOCLIFREMOVEIF, lifrp) == 0) { + dhcpmsg(MSG_DEBUG, "remove_v6_strays: removed %s", + lifrp->lifr_name); + } else if (errno != ENXIO) { + dhcpmsg(MSG_ERR, + "remove_v6_strays: SIOCLIFREMOVEIF %s", + lifrp->lifr_name); + } + } + free(lifc.lifc_buf); } diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/interface.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/interface.h index 24b4598e31..cc00809c8e 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/interface.h +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/interface.h @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -29,12 +29,12 @@ #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. + * Interface.[ch] encapsulate all of the agent's knowledge of network + * interfaces from the DHCP agent's perspective. See interface.c for + * documentation on how to use the exported functions. Note that there are not + * functional interfaces for manipulating all of the fields in a PIF or LIF -- + * please read the comments in the structure definitions below for the rules on + * accessing various fields. */ #ifdef __cplusplus @@ -42,344 +42,177 @@ 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 "common.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]; - - uint32_t if_index; /* interface index */ - - 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; +#define V4_PART_OF_V6(v6) v6._S6_un._S6_u32[3] + +struct dhcp_pif_s { + dhcp_pif_t *pif_next; /* Note: must be first */ + dhcp_pif_t *pif_prev; + dhcp_lif_t *pif_lifs; /* pointer to logical interface list */ + uint32_t pif_index; /* interface index */ + uint16_t pif_max; /* largest DHCP packet on this if */ + uchar_t *pif_hwaddr; /* our link-layer address */ + uchar_t pif_hwlen; /* our link-layer address len */ + uchar_t pif_hwtype; /* type of link-layer */ + boolean_t pif_isv6; + boolean_t pif_running; /* interface is running */ + int pif_dlpi_fd; + int pif_dlpi_count; + iu_event_id_t pif_dlpi_id; /* event id for ack/nak/offer */ + uint_t pif_hold_count; /* reference count */ /* - * other miscellaneous variables set or needed in the process - * of acquiring a lease. + * 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 depends on the requirements of the + * underlying driver). We store it as a token like this because it's + * generally how we need to use it. */ - int if_offer_wait; /* seconds between sending offers */ - iu_timer_id_t if_offer_timer; /* timer associated with offer wait */ - 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; + uchar_t *pif_daddr; /* our destination address */ + uchar_t pif_dlen; /* our destination address len */ - /* - * 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() - */ + uint_t pif_saplen; /* the SAP len */ + boolean_t pif_sap_before; /* does SAP come before address? */ + char pif_name[LIFNAMSIZ]; +}; - PKT_LIST *if_recv_pkt_list; +struct dhcp_lif_s { + dhcp_lif_t *lif_next; /* Note: must be first */ + dhcp_lif_t *lif_prev; + dhcp_pif_t *lif_pif; /* backpointer to parent physical if */ + dhcp_smach_t *lif_smachs; /* pointer to list of state machines */ + dhcp_lease_t *lif_lease; /* backpointer to lease holding LIF */ + uint64_t lif_flags; /* Interface flags (IFF_*) */ + int lif_sock_ip_fd; /* Bound to addr.BOOTPC for src addr */ + iu_event_id_t lif_acknak_id; /* event acknak id */ + uint_t lif_max; /* maximum IP message size */ + uint_t lif_hold_count; /* reference count */ + boolean_t lif_dad_wait; /* waiting for DAD resolution */ + boolean_t lif_removed; /* removed from list */ + boolean_t lif_plumbed; /* interface plumbed by dhcpagent */ + boolean_t lif_expired; /* lease has evaporated */ + const char *lif_declined; /* reason to refuse this address */ + uint32_t lif_iaid; /* unique and stable identifier */ + iu_event_id_t lif_iaid_id; /* for delayed writes to /etc */ /* - * 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). + * 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. */ - 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. - */ + in6_addr_t lif_v6addr; /* our IP address */ + in6_addr_t lif_v6mask; /* our netmask */ + in6_addr_t lif_v6peer; /* our broadcast or peer address */ - dhcp_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; + dhcp_timer_t lif_preferred; /* lease preferred timer (v6 only) */ + dhcp_timer_t lif_expire; /* lease expire 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; + char lif_name[LIFNAMSIZ]; }; +#define lif_addr V4_PART_OF_V6(lif_v6addr) +#define lif_netmask V4_PART_OF_V6(lif_v6mask) +#define lif_peer V4_PART_OF_V6(lif_v6peer) +#define lif_broadcast V4_PART_OF_V6(lif_v6peer) + +/* used by expired_lif_state to express state of DHCP interfaces */ +typedef enum dhcp_expire_e { + DHCP_EXP_NOLIFS, + DHCP_EXP_NOEXP, + DHCP_EXP_ALLEXP, + DHCP_EXP_SOMEEXP +} dhcp_expire_t; /* - * a word on memory management and ifslists: + * A word on memory management and LIFs and PIFs: * - * 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: + * Since LIFs are often passed as context to callback functions, they cannot be + * freed when the interface they represent is dropped or released (or when + * those callbacks finally go off, they will be hosed). To handle this + * situation, the structures are reference counted. Here are the rules for + * managing these counts: * - * an ifslist is created through insert_ifs(). along with - * initializing the ifslist, this puts a hold on the ifslist through - * hold_ifs(). + * A PIF is created through insert_pif(). Along with initializing the PIF, + * this puts a hold on the PIF. A LIF is created through insert_lif(). This + * also initializes the LIF and places a hold on it. The caller's hold on the + * underlying PIF is transferred to the LIF. * - * whenever 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 lease is released or dropped (implicitly or explicitly), + * remove_lif() is called, which sets the lif_removed flag and removes the + * interface from the internal list of managed interfaces. Lastly, + * remove_lif() calls release_lif() to remove the hold acquired in + * insert_lif(). If this decrements the hold count on the interface to zero, + * then free() is called and the hold on the PIF is dropped. If there are + * holds other than the hold acquired in insert_lif(), the hold count will + * still be > 0, and the interface will remain allocated (though dormant). * - * whenever a callback is scheduled against an ifslist, another hold - * must be put on the ifslist through hold_ifs(). + * Whenever a callback is scheduled against a LIF, another hold must be put on + * the ifslist through hold_lif(). * - * whenever a callback is called back against 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. + * Whenever a callback is called back against a LIF, release_lif() must be + * called to decrement the hold count, which may end up freeing the LIF if the + * hold count becomes zero. * - * 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_lif() if the + * cancellation succeeds. This is done in remove_lif() for the lease preferred + * and expire callbacks. * - * 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. + * In general, a callback may also call verify_lif() when it gets called back + * in addition to release_lif(), to make sure that the interface is still in + * fact under the dhcpagent's control. To make coding simpler, there is a + * third function, verify_smach(), which performs both the release_lif() and + * the verify_lif() on all LIFs controlled by a state machine. */ -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); -struct ifslist *lookup_ifs_by_uindex(uint16_t, struct ifslist *); -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, +extern dhcp_pif_t *v4root; +extern dhcp_pif_t *v6root; + +dhcp_pif_t *insert_pif(const char *, boolean_t, int *); +void hold_pif(dhcp_pif_t *); +void release_pif(dhcp_pif_t *); +dhcp_pif_t *lookup_pif_by_index(uint_t, boolean_t); +dhcp_pif_t *lookup_pif_by_uindex(uint16_t, dhcp_pif_t *, boolean_t); +dhcp_pif_t *lookup_pif_by_name(const char *, boolean_t); +boolean_t open_dlpi_pif(dhcp_pif_t *); +void close_dlpi_pif(dhcp_pif_t *); +void pif_status(dhcp_pif_t *, boolean_t); + +dhcp_lif_t *insert_lif(dhcp_pif_t *, const char *, int *); +void hold_lif(dhcp_lif_t *); +void release_lif(dhcp_lif_t *); +void remove_lif(dhcp_lif_t *); +dhcp_lif_t *lookup_lif_by_name(const char *, const dhcp_pif_t *); +boolean_t verify_lif(const dhcp_lif_t *); +dhcp_lif_t *plumb_lif(dhcp_pif_t *, const in6_addr_t *); +void unplumb_lif(dhcp_lif_t *); +dhcp_lif_t *attach_lif(const char *, boolean_t, int *); +int set_lif_dhcp(dhcp_lif_t *, boolean_t); +void set_lif_deprecated(dhcp_lif_t *); +boolean_t clear_lif_deprecated(dhcp_lif_t *); +boolean_t open_ip_lif(dhcp_lif_t *); +void close_ip_lif(dhcp_lif_t *); +void lif_mark_decline(dhcp_lif_t *, const char *); +boolean_t schedule_lif_timer(dhcp_lif_t *, dhcp_timer_t *, iu_tq_callback_t *); +void cancel_lif_timers(dhcp_lif_t *); +dhcp_expire_t expired_lif_state(dhcp_smach_t *); +dhcp_lif_t *find_expired_lif(dhcp_smach_t *); + +uint_t get_max_mtu(boolean_t); +void remove_v6_strays(void); #ifdef __cplusplus } diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/ipc_action.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/ipc_action.c index f29730d377..4f33b2593e 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/ipc_action.c +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/ipc_action.c @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,16 +19,19 @@ * CDDL HEADER END */ /* - * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" +#include <stdlib.h> #include <sys/types.h> -#include <sys/poll.h> #include <dhcpmsg.h> +#include <dhcpagent_ipc.h> +#include "agent.h" +#include "states.h" #include "interface.h" #include "ipc_action.h" #include "util.h" @@ -39,105 +41,135 @@ 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 + * input: ipc_action_t *: the structure to initialize * output: void */ void -ipc_action_init(struct ifslist *ifsp) +ipc_action_init(ipc_action_t *ia) { - ifsp->if_ia.ia_tid = -1; + ia->ia_cmd = 0; + ia->ia_fd = -1; + ia->ia_tid = -1; + ia->ia_eid = -1; + ia->ia_request = NULL; } /* - * ipc_action_start(): starts an ipc_action request on an interface + * ipc_action_start(): starts an ipc_action request on a DHCP state machine * - * 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 + * input: dhcp_smach_t *: the state machine to start the action on + * ipc_action_t *: request structure + * output: B_TRUE if the request is started successfully, B_FALSE otherwise + * original request is still valid on failure, consumed otherwise. */ -int -ipc_action_start(struct ifslist *ifsp, dhcp_ipc_request_t *request, int fd) +boolean_t +ipc_action_start(dhcp_smach_t *dsmp, ipc_action_t *iareq) { - if (request->timeout == DHCP_IPC_WAIT_DEFAULT) - request->timeout = DHCP_IPC_DEFAULT_WAIT; + struct ipc_action *ia = &dsmp->dsm_ia; - ifsp->if_ia.ia_request = request; - ifsp->if_ia.ia_fd = fd; - ifsp->if_ia.ia_cmd = DHCP_IPC_CMD(request->message_type); + if (ia->ia_fd != -1 || ia->ia_tid != -1 || iareq->ia_fd == -1) { + dhcpmsg(MSG_CRIT, "ipc_action_start: attempted restart on %s", + dsmp->dsm_name); + return (B_FALSE); + } - if (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 (!async_cancel(dsmp)) { + dhcpmsg(MSG_WARNING, "ipc_action_start: unable to cancel " + "action on %s", dsmp->dsm_name); + return (B_FALSE); + } - if (ifsp->if_ia.ia_tid == -1) - return (0); + if (iareq->ia_request->timeout == DHCP_IPC_WAIT_DEFAULT) + iareq->ia_request->timeout = DHCP_IPC_DEFAULT_WAIT; - hold_ifs(ifsp); - } + if (iareq->ia_request->timeout == DHCP_IPC_WAIT_FOREVER) { + iareq->ia_tid = -1; + } else { + iareq->ia_tid = iu_schedule_timer(tq, + iareq->ia_request->timeout, ipc_action_timeout, dsmp); - return (1); -} + if (iareq->ia_tid == -1) { + dhcpmsg(MSG_ERROR, "ipc_action_start: failed to set " + "timer for %s on %s", + dhcp_ipc_type_to_string(iareq->ia_cmd), + dsmp->dsm_name); + return (B_FALSE); + } -/* - * 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 - */ + hold_smach(dsmp); + } -boolean_t -ipc_action_pending(struct ifslist *ifsp) -{ - struct pollfd pollfd; + *ia = *iareq; - if (ifsp->if_ia.ia_fd > 0) { + /* We've taken ownership, so the input request is now invalid */ + ipc_action_init(iareq); - pollfd.events = POLLIN; - pollfd.fd = ifsp->if_ia.ia_fd; + dhcpmsg(MSG_DEBUG, "ipc_action_start: started %s (command %d) on %s", + dhcp_ipc_type_to_string(ia->ia_cmd), ia->ia_cmd, dsmp->dsm_name); - if (poll(&pollfd, 1, 0) == 0) - return (B_TRUE); - } + dsmp->dsm_dflags |= DHCP_IF_BUSY; + + /* This cannot fail due to the async_cancel above */ + (void) async_start(dsmp, ia->ia_cmd, B_TRUE); - return (B_FALSE); + return (B_TRUE); } /* * ipc_action_finish(): completes an ipc_action request on an interface * - * input: struct ifslist *: the interface to complete the action on + * input: dhcp_smach_t *: the state machine to complete the action on * int: the reason why the action finished (nonzero on error) * output: void */ void -ipc_action_finish(struct ifslist *ifsp, int reason) +ipc_action_finish(dhcp_smach_t *dsmp, int reason) { - struct ipc_action *ia = &ifsp->if_ia; + struct ipc_action *ia = &dsmp->dsm_ia; - if (ipc_action_pending(ifsp) == B_FALSE) + dsmp->dsm_dflags &= ~DHCP_IF_BUSY; + + if (dsmp->dsm_ia.ia_fd == -1) { + dhcpmsg(MSG_ERROR, + "ipc_action_finish: attempted to finish unknown action " + "on %s", dsmp->dsm_name); return; + } + + dhcpmsg(MSG_DEBUG, + "ipc_action_finish: finished %s (command %d) on %s: %d", + dhcp_ipc_type_to_string(ia->ia_cmd), (int)ia->ia_cmd, + dsmp->dsm_name, reason); + + /* + * if we can't cancel this timer, we're really in the + * twilight zone. however, as long as we don't drop the + * reference to the state machine, it shouldn't hurt us + */ + + if (dsmp->dsm_ia.ia_tid != -1 && + iu_cancel_timer(tq, dsmp->dsm_ia.ia_tid, NULL) == 1) { + dsmp->dsm_ia.ia_tid = -1; + release_smach(dsmp); + } if (reason == 0) - send_ok_reply(ia->ia_request, &ia->ia_fd); + send_ok_reply(ia); else - send_error_reply(ia->ia_request, reason, &ia->ia_fd); + send_error_reply(ia, reason); - ipc_action_cancel_timer(ifsp); + async_finish(dsmp); } /* - * ipc_action_timeout(): times out an ipc_action on an interface (the request - * continues asynchronously, however) + * ipc_action_timeout(): times out an ipc_action on a state machine (the + * request continues asynchronously, however) * * input: iu_tq_t *: unused - * void *: the struct ifslist * the ipc_action was pending on + * void *: the dhcp_smach_t * the ipc_action was pending on * output: void */ @@ -145,43 +177,99 @@ ipc_action_finish(struct ifslist *ifsp, int reason) static void ipc_action_timeout(iu_tq_t *tq, void *arg) { - struct ifslist *ifsp = (struct ifslist *)arg; - struct ipc_action *ia = &ifsp->if_ia; + dhcp_smach_t *dsmp = arg; + struct ipc_action *ia = &dsmp->dsm_ia; - if (check_ifs(ifsp) == 0) { - (void) release_ifs(ifsp); - return; - } + dsmp->dsm_dflags &= ~DHCP_IF_BUSY; + + ia->ia_tid = -1; dhcpmsg(MSG_VERBOSE, "ipc timeout waiting for agent to complete " - "command %d for %s", ia->ia_cmd, ifsp->if_name); + "%s (command %d) for %s", dhcp_ipc_type_to_string(ia->ia_cmd), + ia->ia_cmd, dsmp->dsm_name); - send_error_reply(ia->ia_request, DHCP_IPC_E_TIMEOUT, &ia->ia_fd); - ia->ia_tid = -1; + send_error_reply(ia, DHCP_IPC_E_TIMEOUT); + + async_finish(dsmp); + release_smach(dsmp); +} + +/* + * send_ok_reply(): sends an "ok" reply to a request and closes the ipc + * connection + * + * input: ipc_action_t *: the request to reply to + * output: void + * note: the request is freed (thus the request must be on the heap). + */ + +void +send_ok_reply(ipc_action_t *ia) +{ + send_error_reply(ia, 0); } /* - * ipc_action_cancel_timer(): cancels the pending ipc_action timer for this - * request + * send_error_reply(): sends an "error" reply to a request and closes the ipc + * connection * - * input: struct ifslist *: the interface with a pending request to cancel + * input: ipc_action_t *: the request to reply to + * int: the error to send back on the ipc connection * output: void + * note: the request is freed (thus the request must be on the heap). */ void -ipc_action_cancel_timer(struct ifslist *ifsp) +send_error_reply(ipc_action_t *ia, int error) { - if (ifsp->if_ia.ia_tid == -1) + send_data_reply(ia, error, DHCP_TYPE_NONE, NULL, 0); +} + +/* + * send_data_reply(): sends a reply to a request and closes the ipc connection + * + * input: ipc_action_t *: the request to reply to + * int: the status to send back on the ipc connection (zero for + * success, DHCP_IPC_E_* otherwise). + * dhcp_data_type_t: the type of the payload in the reply + * const void *: the payload for the reply, or NULL if there is no + * payload + * size_t: the size of the payload + * output: void + * note: the request is freed (thus the request must be on the heap). + */ + +void +send_data_reply(ipc_action_t *ia, int error, dhcp_data_type_t type, + const void *buffer, size_t size) +{ + dhcp_ipc_reply_t *reply; + int retval; + + if (ia->ia_fd == -1 || ia->ia_request == NULL) return; + reply = dhcp_ipc_alloc_reply(ia->ia_request, error, buffer, size, + type); + if (reply == NULL) { + dhcpmsg(MSG_ERR, "send_data_reply: cannot allocate reply"); + + } else if ((retval = dhcp_ipc_send_reply(ia->ia_fd, reply)) != 0) { + dhcpmsg(MSG_ERROR, "send_data_reply: dhcp_ipc_send_reply: %s", + dhcp_ipc_strerror(retval)); + } + /* - * 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 + * 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. */ - if (iu_cancel_timer(tq, ifsp->if_ia.ia_tid, NULL) == 1) { - ifsp->if_ia.ia_tid = -1; - (void) release_ifs(ifsp); - } + free(reply); + free(ia->ia_request); + if (ia->ia_eid != -1) + (void) iu_unregister_event(eh, ia->ia_eid, NULL); + (void) dhcp_ipc_close(ia->ia_fd); + ia->ia_request = NULL; + ia->ia_fd = -1; + ia->ia_eid = -1; } diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/ipc_action.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/ipc_action.h index 8ecc62189e..920f04d18e 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/ipc_action.h +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/ipc_action.h @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -35,7 +34,7 @@ #include <dhcpagent_ipc.h> #include <libinetutil.h> -#include "agent.h" +#include "common.h" /* * ipc_action.[ch] make up the interface used to control the current @@ -47,22 +46,21 @@ 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 { - +typedef struct ipc_action { dhcp_ipc_type_t ia_cmd; /* command/action requested */ int ia_fd; /* ipc channel descriptor */ iu_timer_id_t ia_tid; /* ipc timer id */ + iu_event_id_t ia_eid; /* ipc event ID */ dhcp_ipc_request_t *ia_request; /* ipc request pointer */ -}; +} ipc_action_t; + +void ipc_action_init(ipc_action_t *); +boolean_t ipc_action_start(dhcp_smach_t *, ipc_action_t *); +void ipc_action_finish(dhcp_smach_t *, int); +void send_error_reply(ipc_action_t *, int); +void send_ok_reply(ipc_action_t *); +void send_data_reply(ipc_action_t *, int, dhcp_data_type_t, + const void *, size_t); #ifdef __cplusplus } diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/packet.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/packet.c index 4beb5524c0..0fd7e2aa74 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/packet.c +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/packet.c @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2005 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -29,62 +28,61 @@ #include <string.h> #include <sys/types.h> #include <stdlib.h> -#include <arpa/inet.h> #include <dhcpmsg.h> #include <stddef.h> #include <assert.h> +#include <search.h> +#include <alloca.h> +#include <limits.h> +#include <stropts.h> +#include <netinet/dhcp6.h> +#include <arpa/inet.h> +#include <sys/sysmacros.h> +#include <sys/sockio.h> +#include <inet/ip6_asp.h> #include "states.h" #include "interface.h" #include "agent.h" #include "packet.h" #include "util.h" +#include "dlpi_io.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 *); +int v6_sock_fd = -1; +int v4_sock_fd = -1; + +const in6_addr_t ipv6_all_dhcp_relay_and_servers = { + 0xff, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x02 +}; /* - * 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() + * We have our own version of this constant because dhcpagent is compiled with + * -lxnet. */ +const in6_addr_t my_in6addr_any = IN6ADDR_ANY_INIT; -static 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); -} +static void retransmit(iu_tq_t *, void *); +static void next_retransmission(dhcp_smach_t *, boolean_t, boolean_t); +static boolean_t send_pkt_internal(dhcp_smach_t *); /* - * pkt_type(): returns an integer representing the packet's type; only - * for use with outbound packets. + * pkt_send_type(): returns an integer representing the packet's type; only + * for use with outbound packets. * - * input: PKT *: the packet to examine + * input: dhcp_pkt_t *: the packet to examine * output: uchar_t: the packet type (0 if unknown) */ static uchar_t -pkt_type(PKT *pkt) +pkt_send_type(const dhcp_pkt_t *dpkt) { - uchar_t *option = pkt->options; + const uchar_t *option; + + if (dpkt->pkt_isv6) + return (((const dhcpv6_message_t *)dpkt->pkt)->d6m_msg_type); /* * this is a little dirty but it should get the job done. @@ -92,288 +90,740 @@ pkt_type(PKT *pkt) * of the options field. */ - while (*option != CD_DHCP_TYPE) { - if (option + 2 - pkt->options >= sizeof (pkt->options)) + option = dpkt->pkt->options; + for (;;) { + if (*option == CD_PAD) { + option++; + continue; + } + if (*option == CD_END || + option + 2 - dpkt->pkt->options >= + sizeof (dpkt->pkt->options)) return (0); - + if (*option == CD_DHCP_TYPE) + break; option++; - option += *option; + option += *option + 1; } return (option[2]); } /* + * pkt_recv_type(): returns an integer representing the packet's type; only + * for use with inbound packets. + * + * input: dhcp_pkt_t *: the packet to examine + * output: uchar_t: the packet type (0 if unknown) + */ + +uchar_t +pkt_recv_type(const PKT_LIST *plp) +{ + if (plp->isv6) + return (((const dhcpv6_message_t *)plp->pkt)->d6m_msg_type); + else if (plp->opts[CD_DHCP_TYPE] != NULL) + return (plp->opts[CD_DHCP_TYPE]->value[0]); + else + return (0); +} + +/* + * pkt_get_xid(): returns transaction ID from a DHCP packet. + * + * input: const PKT *: the packet to examine + * output: uint_t: the transaction ID (0 if unknown) + */ + +uint_t +pkt_get_xid(const PKT *pkt, boolean_t isv6) +{ + if (pkt == NULL) + return (0); + if (isv6) + return (DHCPV6_GET_TRANSID((const dhcpv6_message_t *)pkt)); + else + return (pkt->xid); +} + +/* * init_pkt(): initializes and returns a packet of a given type * - * input: struct ifslist *: the interface the packet will be going out + * input: dhcp_smach_t *: the state machine that will send the packet * uchar_t: the packet type (DHCP message type) - * output: dhcp_pkt_t *: a pointer to the initialized packet + * output: dhcp_pkt_t *: a pointer to the initialized packet; may be NULL */ dhcp_pkt_t * -init_pkt(struct ifslist *ifsp, uchar_t type) +init_pkt(dhcp_smach_t *dsmp, uchar_t type) { - uint8_t bootmagic[] = BOOTMAGIC; - dhcp_pkt_t *dpkt = &ifsp->if_send_pkt; + uint_t mtu; + dhcp_pkt_t *dpkt = &dsmp->dsm_send_pkt; + dhcp_lif_t *lif = dsmp->dsm_lif; + dhcp_pif_t *pif = lif->lif_pif; uint32_t xid; + boolean_t isv6; - 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); - } + mtu = dsmp->dsm_using_dlpi ? pif->pif_max : lif->lif_max; + dpkt->pkt_isv6 = isv6 = pif->pif_isv6; /* * since multiple dhcp leases may be maintained over the same dlpi * device (e.g. "hme0" and "hme0:1"), make sure the xid is unique. + * + * Note that transaction ID zero is intentionally never assigned. + * That's used to represent "no ID." Also note that transaction IDs + * are only 24 bits long in DHCPv6. */ do { xid = mrand48(); - } while (lookup_ifs_by_xid(xid) != NULL); + if (isv6) + xid &= 0xFFFFFF; + } while (xid == 0 || + lookup_smach_by_xid(xid, NULL, dpkt->pkt_isv6) != NULL); + + if (isv6) { + dhcpv6_message_t *v6; + + if (mtu != dpkt->pkt_max_len && + (v6 = realloc(dpkt->pkt, mtu)) != NULL) { + /* LINTED: alignment known to be correct */ + dpkt->pkt = (PKT *)v6; + dpkt->pkt_max_len = mtu; + } + + if (sizeof (*v6) > dpkt->pkt_max_len) { + dhcpmsg(MSG_ERR, "init_pkt: cannot allocate v6 pkt: %u", + mtu); + return (NULL); + } + + v6 = (dhcpv6_message_t *)dpkt->pkt; + dpkt->pkt_cur_len = sizeof (*v6); - dpkt->pkt->xid = xid; - dpkt->pkt->op = BOOTREQUEST; - dpkt->pkt->htype = ifsp->if_hwtype; + (void) memset(v6, 0, dpkt->pkt_max_len); - add_pkt_opt(dpkt, CD_DHCP_TYPE, &type, 1); - add_pkt_opt(dpkt, CD_CLIENT_ID, ifsp->if_cid, ifsp->if_cidlen); + v6->d6m_msg_type = type; + DHCPV6_SET_TRANSID(v6, xid); + + if (dsmp->dsm_cidlen > 0 && + add_pkt_opt(dpkt, DHCPV6_OPT_CLIENTID, dsmp->dsm_cid, + dsmp->dsm_cidlen) == NULL) { + dhcpmsg(MSG_WARNING, + "init_pkt: cannot insert client ID"); + return (NULL); + } + + /* For v6, time starts with the creation of a transaction */ + dsmp->dsm_neg_hrtime = gethrtime(); + dsmp->dsm_newstart_monosec = monosec(); + } else { + static uint8_t bootmagic[] = BOOTMAGIC; + PKT *v4; + + if (mtu != dpkt->pkt_max_len && + (v4 = realloc(dpkt->pkt, mtu)) != NULL) { + dpkt->pkt = v4; + dpkt->pkt_max_len = mtu; + } + + if (offsetof(PKT, options) > dpkt->pkt_max_len) { + dhcpmsg(MSG_ERR, "init_pkt: cannot allocate v4 pkt: %u", + mtu); + return (NULL); + } + + v4 = dpkt->pkt; + dpkt->pkt_cur_len = offsetof(PKT, options); + + (void) memset(v4, 0, dpkt->pkt_max_len); + (void) memcpy(v4->cookie, bootmagic, sizeof (bootmagic)); + if (pif->pif_hwlen <= sizeof (v4->chaddr)) { + v4->hlen = pif->pif_hwlen; + (void) memcpy(v4->chaddr, pif->pif_hwaddr, + pif->pif_hwlen); + } else { + /* + * The mac address does not fit in the chaddr + * field, thus it can not be sent to the server, + * thus server can not unicast the reply. Per + * RFC 2131 4.4.1, client can set this bit in + * DISCOVER/REQUEST. If the client is already + * in 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 && dsmp->dsm_state != RENEWING && + dsmp->dsm_state != REBINDING && + dsmp->dsm_state != BOUND)) + v4->flags = htons(BCAST_MASK); + } + + v4->xid = xid; + v4->op = BOOTREQUEST; + v4->htype = pif->pif_hwtype; + + if (add_pkt_opt(dpkt, CD_DHCP_TYPE, &type, 1) == NULL) { + dhcpmsg(MSG_WARNING, + "init_pkt: cannot set DHCP packet type"); + return (NULL); + } + + if (dsmp->dsm_cidlen > 0 && + add_pkt_opt(dpkt, CD_CLIENT_ID, dsmp->dsm_cid, + dsmp->dsm_cidlen) == NULL) { + dhcpmsg(MSG_WARNING, + "init_pkt: cannot insert client ID"); + return (NULL); + } + } return (dpkt); } /* + * remove_pkt_opt(): removes the first instance of an option from a dhcp_pkt_t + * + * input: dhcp_pkt_t *: the packet to remove the option from + * uint_t: the type of option being added + * output: boolean_t: B_TRUE on success, B_FALSE on failure + * note: currently does not work with DHCPv6 suboptions, or to remove + * arbitrary option instances. + */ + +boolean_t +remove_pkt_opt(dhcp_pkt_t *dpkt, uint_t opt_type) +{ + uchar_t *raw_pkt, *raw_end, *next; + uint_t len; + + raw_pkt = (uchar_t *)dpkt->pkt; + raw_end = raw_pkt + dpkt->pkt_cur_len; + if (dpkt->pkt_isv6) { + dhcpv6_option_t d6o; + + raw_pkt += sizeof (dhcpv6_message_t); + + opt_type = htons(opt_type); + while (raw_pkt + sizeof (d6o) <= raw_end) { + (void) memcpy(&d6o, raw_pkt, sizeof (d6o)); + len = ntohs(d6o.d6o_len) + sizeof (d6o); + if (len > raw_end - raw_pkt) + break; + next = raw_pkt + len; + if (d6o.d6o_code == opt_type) { + if (next < raw_end) { + (void) memmove(raw_pkt, next, + raw_end - next); + } + dpkt->pkt_cur_len -= len; + return (B_TRUE); + } + raw_pkt = next; + } + } else { + uchar_t *pstart, *padrun; + + raw_pkt += offsetof(PKT, options); + pstart = raw_pkt; + + if (opt_type == CD_END || opt_type == CD_PAD) + return (B_FALSE); + + padrun = NULL; + while (raw_pkt + 1 <= raw_end) { + if (*raw_pkt == CD_END) + break; + if (*raw_pkt == CD_PAD) { + if (padrun == NULL) + padrun = raw_pkt; + raw_pkt++; + continue; + } + if (raw_pkt + 2 > raw_end) + break; + len = raw_pkt[1]; + if (len > raw_end - raw_pkt || len < 2) + break; + next = raw_pkt + len; + if (*raw_pkt == opt_type) { + if (next < raw_end) { + int toadd = (4 + ((next-pstart)&3) - + ((raw_pkt-pstart)&3)) & 3; + int torem = 4 - toadd; + + if (torem != 4 && padrun != NULL && + (raw_pkt - padrun) >= torem) { + raw_pkt -= torem; + dpkt->pkt_cur_len -= torem; + } else if (toadd > 0) { + (void) memset(raw_pkt, CD_PAD, + toadd); + raw_pkt += toadd; + /* max is not an issue here */ + dpkt->pkt_cur_len += toadd; + } + if (raw_pkt != next) { + (void) memmove(raw_pkt, next, + raw_end - next); + } + } + dpkt->pkt_cur_len -= len; + return (B_TRUE); + } + padrun = NULL; + raw_pkt = next; + } + } + return (B_FALSE); +} + +/* + * update_v6opt_len(): updates the length field of a DHCPv6 option. + * + * input: dhcpv6_option_t *: option to be updated + * int: number of octets to add or subtract + * output: boolean_t: B_TRUE on success, B_FALSE on failure + */ + +boolean_t +update_v6opt_len(dhcpv6_option_t *opt, int adjust) +{ + dhcpv6_option_t optval; + + (void) memcpy(&optval, opt, sizeof (optval)); + adjust += ntohs(optval.d6o_len); + if (adjust < 0 || adjust > UINT16_MAX) { + return (B_FALSE); + } else { + optval.d6o_len = htons(adjust); + (void) memcpy(opt, &optval, sizeof (optval)); + return (B_TRUE); + } +} + +/* * add_pkt_opt(): adds an option to a dhcp_pkt_t * * input: dhcp_pkt_t *: the packet to add the option to - * uchar_t: the type of option being added + * uint_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 + * uint_t: the length of the value of the option + * output: void *: pointer to the option that was added, or NULL on failure. */ -void -add_pkt_opt(dhcp_pkt_t *dpkt, uchar_t opt_type, const void *opt_val, - uchar_t opt_len) +void * +add_pkt_opt(dhcp_pkt_t *dpkt, uint_t opt_type, const void *opt_val, + uint_t opt_len) { - caddr_t raw_pkt = (caddr_t)dpkt->pkt; - int16_t req_len = opt_len + 2; /* + 2 for code & length bytes */ + uchar_t *raw_pkt; + int req_len; + void *optr; + + raw_pkt = (uchar_t *)dpkt->pkt; + optr = raw_pkt + dpkt->pkt_cur_len; + if (dpkt->pkt_isv6) { + dhcpv6_option_t d6o; + + req_len = opt_len + sizeof (d6o); + + if (dpkt->pkt_cur_len + req_len > dpkt->pkt_max_len) { + dhcpmsg(MSG_WARNING, + "add_pkt_opt: not enough room for v6 option %u in " + "packet (%u + %u > %u)", opt_type, + dpkt->pkt_cur_len, req_len, dpkt->pkt_max_len); + return (NULL); + } + d6o.d6o_code = htons(opt_type); + d6o.d6o_len = htons(opt_len); + (void) memcpy(&raw_pkt[dpkt->pkt_cur_len], &d6o, sizeof (d6o)); + dpkt->pkt_cur_len += sizeof (d6o); + if (opt_len > 0) { + (void) memcpy(&raw_pkt[dpkt->pkt_cur_len], opt_val, + opt_len); + dpkt->pkt_cur_len += opt_len; + } + } else { + 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 = 1; + } else if (opt_val == NULL) { + dhcpmsg(MSG_ERROR, "add_pkt_opt: option type %d is " + "missing required value", opt_type); + return (NULL); + } - /* 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 v4 option %u in " + "packet", opt_type); + return (NULL); + } - if ((dpkt->pkt_cur_len + req_len) > dpkt->pkt_max_len) { - dhcpmsg(MSG_WARNING, "add_pkt_opt: not enough room for option " - "%d in packet", opt_type); - return; + raw_pkt[dpkt->pkt_cur_len++] = opt_type; + + if (req_len > 1) { + raw_pkt[dpkt->pkt_cur_len++] = opt_len; + if (opt_len > 0) { + (void) memcpy(&raw_pkt[dpkt->pkt_cur_len], + opt_val, opt_len); + dpkt->pkt_cur_len += opt_len; + } + } } + return (optr); +} - raw_pkt[dpkt->pkt_cur_len++] = opt_type; +/* + * add_pkt_subopt(): adds an option to a dhcp_pkt_t option. DHCPv6-specific, + * but could be extended to IPv4 DHCP if necessary. Assumes + * that if the parent isn't a top-level option, the caller + * will adjust any upper-level options recursively using + * update_v6opt_len. + * + * input: dhcp_pkt_t *: the packet to add the suboption to + * dhcpv6_option_t *: the start of the option to that should contain + * it (parent) + * uint_t: the type of suboption being added + * const void *: the value of that option + * uint_t: the length of the value of the option + * output: void *: pointer to the suboption that was added, or NULL on + * failure. + */ - 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; +void * +add_pkt_subopt(dhcp_pkt_t *dpkt, dhcpv6_option_t *parentopt, uint_t opt_type, + const void *opt_val, uint_t opt_len) +{ + uchar_t *raw_pkt; + int req_len; + void *optr; + dhcpv6_option_t d6o; + uchar_t *optend; + int olen; + + if (!dpkt->pkt_isv6) + return (NULL); + + raw_pkt = (uchar_t *)dpkt->pkt; + req_len = opt_len + sizeof (d6o); + + if (dpkt->pkt_cur_len + req_len > dpkt->pkt_max_len) { + dhcpmsg(MSG_WARNING, + "add_pkt_subopt: not enough room for v6 suboption %u in " + "packet (%u + %u > %u)", opt_type, + dpkt->pkt_cur_len, req_len, dpkt->pkt_max_len); + return (NULL); } + + /* + * Update the parent option to include room for this option, + * and compute the insertion point. + */ + (void) memcpy(&d6o, parentopt, sizeof (d6o)); + olen = ntohs(d6o.d6o_len); + optend = (uchar_t *)(parentopt + 1) + olen; + olen += req_len; + d6o.d6o_len = htons(olen); + (void) memcpy(parentopt, &d6o, sizeof (d6o)); + + /* + * If there's anything at the end to move, then move it. Also bump up + * the packet size. + */ + if (optend < raw_pkt + dpkt->pkt_cur_len) { + (void) memmove(optend + req_len, optend, + (raw_pkt + dpkt->pkt_cur_len) - optend); + } + dpkt->pkt_cur_len += req_len; + + /* + * Now format the suboption and add it in. + */ + optr = optend; + d6o.d6o_code = htons(opt_type); + d6o.d6o_len = htons(opt_len); + (void) memcpy(optend, &d6o, sizeof (d6o)); + if (opt_len > 0) + (void) memcpy(optend + sizeof (d6o), opt_val, opt_len); + return (optr); } /* * add_pkt_opt16(): adds an option with a 16-bit value to a dhcp_pkt_t * * input: dhcp_pkt_t *: the packet to add the option to - * uchar_t: the type of option being added + * uint_t: the type of option being added * uint16_t: the value of that option - * output: void + * output: void *: pointer to the option that was added, or NULL on failure. */ -void -add_pkt_opt16(dhcp_pkt_t *dpkt, uchar_t opt_type, uint16_t opt_value) +void * +add_pkt_opt16(dhcp_pkt_t *dpkt, uint_t opt_type, uint16_t opt_value) { - add_pkt_opt(dpkt, opt_type, &opt_value, 2); + return (add_pkt_opt(dpkt, opt_type, &opt_value, 2)); } /* * add_pkt_opt32(): adds an option with a 32-bit value to a dhcp_pkt_t * * input: dhcp_pkt_t *: the packet to add the option to - * uchar_t: the type of option being added + * uint_t: the type of option being added * uint32_t: the value of that option - * output: void + * output: void *: pointer to the option that was added, or NULL on failure. */ -void -add_pkt_opt32(dhcp_pkt_t *dpkt, uchar_t opt_type, uint32_t opt_value) +void * +add_pkt_opt32(dhcp_pkt_t *dpkt, uint_t opt_type, uint32_t opt_value) { - add_pkt_opt(dpkt, opt_type, &opt_value, 4); + return (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 + * add_pkt_prl(): adds the parameter request option to the packet * - * 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 + * input: dhcp_pkt_t *: the packet to add the option to + * dhcp_smach_t *: state machine with request option + * output: void *: pointer to the option that was added, or NULL on failure. */ -void -get_pkt_times(PKT_LIST *ack, lease_t *lease, lease_t *t1, lease_t *t2) +void * +add_pkt_prl(dhcp_pkt_t *dpkt, dhcp_smach_t *dsmp) { - *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; + uint_t len; - (void) memcpy(lease, ack->opts[CD_LEASE_TIME]->value, sizeof (lease_t)); - *lease = ntohl(*lease); + if (dsmp->dsm_prllen == 0) + return (0); - if (*lease == DHCP_PERM) - return; + if (dpkt->pkt_isv6) { + uint16_t *prl; - 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); - } + /* + * RFC 3315 requires that we include the option, even if we + * have nothing to request. + */ + if (dsmp->dsm_prllen == 0) + prl = NULL; + else + prl = alloca(dsmp->dsm_prllen * sizeof (uint16_t)); - if ((*t1 == DHCP_PERM) || (*t1 >= *lease)) - *t1 = (lease_t)fuzzify(*lease, DHCP_T1_FACT); + for (len = 0; len < dsmp->dsm_prllen; len++) + prl[len] = htons(dsmp->dsm_prl[len]); + return (add_pkt_opt(dpkt, DHCPV6_OPT_ORO, prl, + len * sizeof (uint16_t))); + } else { + uint8_t *prl = alloca(dsmp->dsm_prllen); - 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); + for (len = 0; len < dsmp->dsm_prllen; len++) + prl[len] = dsmp->dsm_prl[len]; + return (add_pkt_opt(dpkt, CD_REQUEST_LIST, prl, len)); } - - 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 + * add_pkt_lif(): Adds CD_REQUESTED_IP_ADDR (IPv4 DHCP) or IA_NA and IAADDR + * (DHCPv6) options to the packet to represent the given LIF. * - * input: 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) + * input: dhcp_pkt_t *: the packet to add the options to + * dhcp_lif_t *: the logical interface to represent + * int: status code (unused for IPv4 DHCP) + * const char *: message to include with status option, or NULL + * output: boolean_t: B_TRUE on success, B_FALSE on failure */ -static double -fuzzify(uint32_t sec, double pct) +boolean_t +add_pkt_lif(dhcp_pkt_t *dpkt, dhcp_lif_t *lif, int status, const char *msg) { - return (sec * (pct + (drand48() - 0.5) / 25.0)); -} + if (lif->lif_pif->pif_isv6) { + dhcp_smach_t *dsmp; + dhcpv6_message_t *d6m; + dhcpv6_ia_na_t d6in; + dhcpv6_iaaddr_t d6ia; + uint32_t iaid; + uint16_t *statusopt; + dhcpv6_option_t *d6o, *d6so; + uint_t olen; -/* - * free_pkt_list(): frees a packet list - * - * input: PKT_LIST **: the packet list to free - * output: void - */ + /* + * Currently, we support just one IAID related to the primary + * LIF on the state machine. + */ + dsmp = lif->lif_lease->dl_smach; + iaid = dsmp->dsm_lif->lif_iaid; + iaid = htonl(iaid); -void -free_pkt_list(PKT_LIST **plp) -{ - PKT_LIST *plp_next; + d6m = (dhcpv6_message_t *)dpkt->pkt; + + /* + * Find or create the IA_NA needed for this LIF. If we + * supported IA_TA, we'd check the IFF_TEMPORARY bit here. + */ + d6o = NULL; + while ((d6o = dhcpv6_find_option(d6m + 1, + dpkt->pkt_cur_len - sizeof (*d6m), d6o, DHCPV6_OPT_IA_NA, + &olen)) != NULL) { + if (olen < sizeof (d6in)) + continue; + (void) memcpy(&d6in, d6o, sizeof (d6in)); + if (d6in.d6in_iaid == iaid) + break; + } + if (d6o == NULL) { + d6in.d6in_iaid = iaid; + d6in.d6in_t1 = 0; + d6in.d6in_t2 = 0; + d6o = add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA, + (dhcpv6_option_t *)&d6in + 1, + sizeof (d6in) - sizeof (*d6o)); + if (d6o == NULL) + return (B_FALSE); + } - for (; *plp != NULL; *plp = plp_next) { - plp_next = (*plp)->next; - free((*plp)->pkt); - free(*plp); + /* + * Now add the IAADDR suboption for this LIF. No need to + * search here, as we know that this is unique. + */ + d6ia.d6ia_addr = lif->lif_v6addr; + + /* + * For Release and Decline, we zero out the lifetime. For + * Renew and Rebind, we report the original time as the + * preferred and valid lifetimes. + */ + if (d6m->d6m_msg_type == DHCPV6_MSG_RELEASE || + d6m->d6m_msg_type == DHCPV6_MSG_DECLINE) { + d6ia.d6ia_preflife = 0; + d6ia.d6ia_vallife = 0; + } else { + d6ia.d6ia_preflife = htonl(lif->lif_preferred.dt_start); + d6ia.d6ia_vallife = htonl(lif->lif_expire.dt_start); + } + d6so = add_pkt_subopt(dpkt, d6o, DHCPV6_OPT_IAADDR, + (dhcpv6_option_t *)&d6ia + 1, + sizeof (d6ia) - sizeof (*d6o)); + if (d6so == NULL) + return (B_FALSE); + + /* + * Add a status code suboption to the IAADDR to tell the server + * why we're declining the address. Note that we must manually + * update the enclosing IA_NA, as add_pkt_subopt doesn't know + * how to do that. + */ + if (status != DHCPV6_STAT_SUCCESS || msg != NULL) { + olen = sizeof (*statusopt) + + (msg == NULL ? 0 : strlen(msg)); + statusopt = alloca(olen); + *statusopt = htons(status); + if (msg != NULL) { + (void) memcpy((char *)(statusopt + 1), msg, + olen - sizeof (*statusopt)); + } + d6so = add_pkt_subopt(dpkt, d6so, + DHCPV6_OPT_STATUS_CODE, statusopt, olen); + if (d6so != NULL) { + /* + * Update for length of suboption header and + * suboption contents. + */ + (void) update_v6opt_len(d6o, sizeof (*d6so) + + olen); + } + } + } else { + /* + * For DECLINE, we need to add the CD_REQUESTED_IP_ADDR option. + * In all other cases (RELEASE and REQUEST), we need to set + * ciadr. + */ + if (pkt_send_type(dpkt) == DECLINE) { + if (!add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR, + lif->lif_addr)) + return (B_FALSE); + } else { + dpkt->pkt->ciaddr.s_addr = lif->lif_addr; + } + + /* + * It's not too worrisome if the message fails to fit in the + * packet. The result will still be valid. + */ + if (msg != NULL) + (void) add_pkt_opt(dpkt, CD_MESSAGE, msg, + strlen(msg) + 1); } + return (B_TRUE); } /* - * prepend_to_pkt_list(): prepends a packet to a packet list + * free_pkt_entry(): frees a packet list list entry * - * input: PKT_LIST **: the packet list - * PKT_LIST *: the packet to prepend + * input: PKT_LIST *: the packet list entry to free * output: void */ - -static void -prepend_to_pkt_list(PKT_LIST **list_head, PKT_LIST *new_entry) +void +free_pkt_entry(PKT_LIST *plp) { - new_entry->next = *list_head; - new_entry->prev = NULL; - - if (*list_head != NULL) - (*list_head)->prev = new_entry; - - *list_head = new_entry; + if (plp != NULL) { + free(plp->pkt); + free(plp); + } } /* - * remove_from_pkt_list(): removes a given packet from a packet list + * free_pkt_list(): frees an entire packet list * - * input: PKT_LIST **: the packet list - * PKT_LIST *: the packet to remove + * input: PKT_LIST **: the packet list to free * output: void */ void -remove_from_pkt_list(PKT_LIST **list_head, PKT_LIST *remove) +free_pkt_list(PKT_LIST **head) { - if (*list_head == NULL) - return; + PKT_LIST *plp; - 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; + while ((plp = *head) != NULL) { + remque(plp); + free_pkt_entry(plp); } - - 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 + * input: dhcp_smach_t *: the state machine with a packet to send + * output: boolean_t: B_TRUE if the packet is sent, B_FALSE otherwise */ -static int -send_pkt_internal(struct ifslist *ifsp) +static boolean_t +send_pkt_internal(dhcp_smach_t *dsmp) { ssize_t n_bytes; - dhcp_pkt_t *dpkt = &ifsp->if_send_pkt; - const char *pkt_name = pkt_type_to_string(pkt_type(dpkt->pkt)); + dhcp_lif_t *lif = dsmp->dsm_lif; + dhcp_pif_t *pif = lif->lif_pif; + dhcp_pkt_t *dpkt = &dsmp->dsm_send_pkt; + uchar_t ptype = pkt_send_type(dpkt); + const char *pkt_name; + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cmsg; + struct in6_pktinfo *ipi6; + boolean_t ismcast; + + /* + * Timer should not be running at the point we go to send a packet. + */ + if (dsmp->dsm_retrans_timer != -1) { + dhcpmsg(MSG_CRIT, "send_pkt_internal: unexpected retransmit " + "timer on %s", dsmp->dsm_name); + stop_pkt_retransmission(dsmp); + } + + pkt_name = pkt_type_to_string(ptype, dpkt->pkt_isv6); /* * if needed, schedule a retransmission timer, then attempt to @@ -383,113 +833,206 @@ send_pkt_internal(struct ifslist *ifsp) * 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) + if (dsmp->dsm_send_timeout != 0) { + if ((dsmp->dsm_retrans_timer = iu_schedule_timer_ms(tq, + dsmp->dsm_send_timeout, retransmit, dsmp)) == -1) dhcpmsg(MSG_WARNING, "send_pkt_internal: cannot " "schedule retransmit timer for %s packet", pkt_name); else - hold_ifs(ifsp); + hold_smach(dsmp); } - /* - * 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). - */ + if (dpkt->pkt_isv6) { + hrtime_t delta; - switch (pkt_type(dpkt->pkt)) { + /* + * Convert current time into centiseconds since transaction + * started. This is what DHCPv6 expects to see in the Elapsed + * Time option. + */ + delta = (gethrtime() - dsmp->dsm_neg_hrtime) / + (NANOSEC / 100); + if (delta > DHCPV6_FOREVER) + delta = DHCPV6_FOREVER; + (void) remove_pkt_opt(dpkt, DHCPV6_OPT_ELAPSED_TIME); + (void) add_pkt_opt16(dpkt, DHCPV6_OPT_ELAPSED_TIME, + htons(delta)); + } else { + /* + * set the `pkt->secs' field depending on the type of packet. + * it should be zero, except in the following cases: + * + * DISCOVER: set to the number of seconds since we started + * trying to obtain a lease. + * + * INFORM: set to the number of seconds since we started + * trying to get configuration parameters. + * + * REQUEST: if in the REQUESTING state, then same value as + * DISCOVER, otherwise the number of seconds + * since we started trying to obtain a lease. + * + * we also set `dsm_newstart_monosec', to the time we sent a + * REQUEST or DISCOVER packet, so we know the lease start + * time (the DISCOVER case is for handling BOOTP servers). + */ - 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; + switch (ptype) { - case INFORM: - dpkt->pkt->secs = htons(monosec() - ifsp->if_neg_monosec); - break; + case DISCOVER: + dsmp->dsm_newstart_monosec = monosec(); + dsmp->dsm_disc_secs = dsmp->dsm_newstart_monosec - + hrtime_to_monosec(dsmp->dsm_neg_hrtime); + dpkt->pkt->secs = htons(dsmp->dsm_disc_secs); + break; - case REQUEST: - ifsp->if_newstart_monosec = monosec(); + case INFORM: + dpkt->pkt->secs = htons(monosec() - + hrtime_to_monosec(dsmp->dsm_neg_hrtime)); + break; - if (ifsp->if_state == REQUESTING) { - dpkt->pkt->secs = htons(ifsp->if_disc_secs); + case REQUEST: + dsmp->dsm_newstart_monosec = monosec(); + + if (dsmp->dsm_state == REQUESTING) { + dpkt->pkt->secs = htons(dsmp->dsm_disc_secs); + break; + } + + dpkt->pkt->secs = htons(monosec() - + hrtime_to_monosec(dsmp->dsm_neg_hrtime)); + break; + + default: + dpkt->pkt->secs = htons(0); break; } + } - dpkt->pkt->secs = htons(monosec() - ifsp->if_neg_monosec); - break; + if (dpkt->pkt_isv6) { + struct sockaddr_in6 sin6; - default: - dpkt->pkt->secs = htons(0); - } + (void) memset(&iov, 0, sizeof (iov)); + iov.iov_base = dpkt->pkt; + iov.iov_len = dpkt->pkt_cur_len; - switch (ifsp->if_state) { + (void) memset(&msg, 0, sizeof (msg)); + msg.msg_name = &dsmp->dsm_send_dest.v6; + msg.msg_namelen = sizeof (struct sockaddr_in6); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; - 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; + /* + * If the address that's requested cannot be reached, then fall + * back to the multcast address. + */ + if (IN6_IS_ADDR_MULTICAST(&dsmp->dsm_send_dest.v6.sin6_addr)) { + ismcast = B_TRUE; + } else { + struct dstinforeq dinfo; + struct strioctl str; + + ismcast = B_FALSE; + (void) memset(&dinfo, 0, sizeof (dinfo)); + dinfo.dir_daddr = dsmp->dsm_send_dest.v6.sin6_addr; + str.ic_cmd = SIOCGDSTINFO; + str.ic_timout = 0; + str.ic_len = sizeof (dinfo); + str.ic_dp = (char *)&dinfo; + if (ioctl(v6_sock_fd, I_STR, &str) == -1) { + dhcpmsg(MSG_ERR, + "send_pkt_internal: ioctl SIOCGDSTINFO"); + } else if (!dinfo.dir_dreachable) { + char abuf[INET6_ADDRSTRLEN]; + + dhcpmsg(MSG_DEBUG, "send_pkt_internal: %s is " + "not reachable; using multicast instead", + inet_ntop(AF_INET6, &dinfo.dir_daddr, abuf, + sizeof (abuf))); + sin6 = dsmp->dsm_send_dest.v6; + sin6.sin6_addr = + ipv6_all_dhcp_relay_and_servers; + msg.msg_name = &sin6; + ismcast = B_TRUE; + } + } - 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; + /* + * Make room for our ancillary data option as well as a dummy + * option used by CMSG_NXTHDR. + */ + msg.msg_controllen = sizeof (*cmsg) + _MAX_ALIGNMENT + + sizeof (*ipi6) + _MAX_ALIGNMENT + sizeof (*cmsg); + msg.msg_control = alloca(msg.msg_controllen); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + /* LINTED: alignment */ + ipi6 = (struct in6_pktinfo *)CMSG_DATA(cmsg); + if (ismcast) + ipi6->ipi6_addr = lif->lif_v6addr; + else + ipi6->ipi6_addr = my_in6addr_any; + ipi6->ipi6_ifindex = lif->lif_pif->pif_index; + cmsg->cmsg_len = (char *)(ipi6 + 1) - (char *)cmsg; + + /* + * Now correct the control message length. + */ + cmsg = CMSG_NXTHDR(&msg, cmsg); + msg.msg_controllen = (char *)cmsg - (char *)msg.msg_control; + + n_bytes = sendmsg(v6_sock_fd, &msg, 0); + } else { + if (dsmp->dsm_using_dlpi) { + n_bytes = dlpi_sendto(pif->pif_dlpi_fd, dpkt->pkt, + dpkt->pkt_cur_len, &dsmp->dsm_send_dest.v4, + pif->pif_daddr, pif->pif_dlen); + /* dlpi_sendto calls putmsg */ + if (n_bytes == 0) + n_bytes = dpkt->pkt_cur_len; + } else { + n_bytes = sendto(lif->lif_sock_ip_fd, dpkt->pkt, + dpkt->pkt_cur_len, 0, + (struct sockaddr *)&dsmp->dsm_send_dest.v4, + sizeof (struct sockaddr_in)); + } } if (n_bytes != dpkt->pkt_cur_len) { - if (ifsp->if_retrans_timer == -1) + if (dsmp->dsm_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); + pkt_name, dsmp->dsm_send_timeout / MILLISEC); + return (B_FALSE); } - dhcpmsg(MSG_VERBOSE, "sent %s packet out %s", pkt_name, - ifsp->if_name); + dhcpmsg(MSG_VERBOSE, "sent %s xid %x packet out %s", pkt_name, + pkt_get_xid(dpkt->pkt, dpkt->pkt_isv6), dsmp->dsm_name); - ifsp->if_packet_sent++; - ifsp->if_sent++; - return (1); + dsmp->dsm_packet_sent++; + dsmp->dsm_sent++; + return (B_TRUE); } /* - * send_pkt(): sends a packet out on an interface + * send_pkt(): sends a packet out * - * input: struct ifslist *: the interface to send the packet out on + * input: dhcp_smach_t *: the state machine sending the packet * dhcp_pkt_t *: the packet to send out * in_addr_t: the destination IP address for the packet * stop_func_t *: a pointer to function to indicate when to stop * retransmitting the packet (if NULL, packet is * not retransmitted) - * output: int: 1 if the packet was sent, 0 otherwise + * output: boolean_t: B_TRUE if the packet was sent, B_FALSE otherwise */ -int -send_pkt(struct ifslist *ifsp, dhcp_pkt_t *dpkt, in_addr_t dest, +boolean_t +send_pkt(dhcp_smach_t *dsmp, dhcp_pkt_t *dpkt, in_addr_t dest, stop_func_t *stop) { /* @@ -499,42 +1042,100 @@ send_pkt(struct ifslist *ifsp, dhcp_pkt_t *dpkt, in_addr_t dest, dpkt->pkt_cur_len = MAX(dpkt->pkt_cur_len, sizeof (PKT)); - ifsp->if_packet_sent = 0; + dsmp->dsm_packet_sent = 0; + + (void) memset(&dsmp->dsm_send_dest.v4, 0, + sizeof (dsmp->dsm_send_dest.v4)); + dsmp->dsm_send_dest.v4.sin_addr.s_addr = dest; + dsmp->dsm_send_dest.v4.sin_family = AF_INET; + dsmp->dsm_send_dest.v4.sin_port = htons(IPPORT_BOOTPS); + dsmp->dsm_send_stop_func = stop; + + /* + * TODO: dispose of this gruesome assumption (there's no real + * technical gain from doing so, but it would be cleaner) + */ + + assert(dpkt == &dsmp->dsm_send_pkt); + + /* + * clear out any packets which had been previously received + * but not pulled off of the recv_packet queue. + */ + + free_pkt_list(&dsmp->dsm_recv_pkt_list); + + if (stop == NULL) + dsmp->dsm_send_timeout = 0; /* prevents retransmissions */ + else + next_retransmission(dsmp, B_TRUE, B_FALSE); + + return (send_pkt_internal(dsmp)); +} + +/* + * send_pkt_v6(): sends a DHCPv6 packet out + * + * input: dhcp_smach_t *: the state machine sending the packet + * dhcp_pkt_t *: the packet to send out + * in6_addr_t: the destination IPv6 address for the packet + * stop_func_t *: a pointer to function to indicate when to stop + * retransmitting the packet (if NULL, packet is + * not retransmitted) + * uint_t: Initial Retransmit Timer value + * uint_t: Maximum Retransmit Timer value, zero if none + * output: boolean_t: B_TRUE if the packet was sent, B_FALSE otherwise + */ - (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; +boolean_t +send_pkt_v6(dhcp_smach_t *dsmp, dhcp_pkt_t *dpkt, in6_addr_t dest, + stop_func_t *stop, uint_t irt, uint_t mrt) +{ + dsmp->dsm_packet_sent = 0; + + (void) memset(&dsmp->dsm_send_dest.v6, 0, + sizeof (dsmp->dsm_send_dest.v6)); + dsmp->dsm_send_dest.v6.sin6_addr = dest; + dsmp->dsm_send_dest.v6.sin6_family = AF_INET6; + dsmp->dsm_send_dest.v6.sin6_port = htons(IPPORT_DHCPV6S); + dsmp->dsm_send_stop_func = stop; /* * TODO: dispose of this gruesome assumption (there's no real * technical gain from doing so, but it would be cleaner) */ - assert(dpkt == &ifsp->if_send_pkt); + assert(dpkt == &dsmp->dsm_send_pkt); /* * clear out any packets which had been previously received * but not pulled off of the recv_packet queue. */ - free_pkt_list(&ifsp->if_recv_pkt_list); + free_pkt_list(&dsmp->dsm_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); + dsmp->dsm_send_timeout = 0; /* prevents retransmissions */ + } else { + dsmp->dsm_send_timeout = irt; + dsmp->dsm_send_tcenter = mrt; + /* + * This is quite ugly, but RFC 3315 section 17.1.2 requires + * that the RAND value for the very first retransmission of a + * Solicit message is strictly greater than zero. + */ + next_retransmission(dsmp, B_TRUE, + pkt_send_type(dpkt) == DHCPV6_MSG_SOLICIT); + } - return (send_pkt_internal(ifsp)); + return (send_pkt_internal(dsmp)); } /* * retransmit(): retransmits the current packet on an interface * * input: iu_tq_t *: unused - * void *: the struct ifslist * to send the packet on + * void *: the dhcp_smach_t * (state machine) sending a packet * output: void */ @@ -542,188 +1143,470 @@ send_pkt(struct ifslist *ifsp, dhcp_pkt_t *dpkt, in_addr_t dest, static void retransmit(iu_tq_t *tqp, void *arg) { - struct ifslist *ifsp = (struct ifslist *)arg; + dhcp_smach_t *dsmp = arg; + + dsmp->dsm_retrans_timer = -1; - if (check_ifs(ifsp) == 0) { - (void) release_ifs(ifsp); + if (!verify_smach(dsmp)) return; - } /* - * check the callback to see if we should keep sending retransmissions + * Check the callback to see if we should keep sending retransmissions. + * Compute the next retransmission time first, so that the callback can + * cap the value if need be. (Required for DHCPv6 Confirm messages.) + * + * Hold the state machine across the callback so that the called + * function can remove the state machine from the system without + * disturbing the string used subsequently for verbose logging. The + * Release function destroys the state machine when the retry count + * expires. */ - 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); + next_retransmission(dsmp, B_FALSE, B_FALSE); + hold_smach(dsmp); + if (dsmp->dsm_send_stop_func(dsmp, dsmp->dsm_packet_sent)) { + dhcpmsg(MSG_VERBOSE, "retransmit: time to stop on %s", + dsmp->dsm_name); + } else { + dhcpmsg(MSG_VERBOSE, "retransmit: sending another on %s", + dsmp->dsm_name); + (void) send_pkt_internal(dsmp); + } + release_smach(dsmp); } /* * stop_pkt_retransmission(): stops retransmission of last sent packet * - * input: struct ifslist *: the interface to stop retransmission on + * input: dhcp_smach_t *: the state machine to stop retransmission on + * output: void + */ + +void +stop_pkt_retransmission(dhcp_smach_t *dsmp) +{ + if (dsmp->dsm_retrans_timer != -1 && + iu_cancel_timer(tq, dsmp->dsm_retrans_timer, NULL) == 1) { + dhcpmsg(MSG_VERBOSE, "stop_pkt_retransmission: stopped on %s", + dsmp->dsm_name); + dsmp->dsm_retrans_timer = -1; + release_smach(dsmp); + } +} + +/* + * retransmit_now(): force a packet retransmission right now. Used only with + * the DHCPv6 UseMulticast status code. Use with caution; + * triggered retransmissions can cause packet storms. + * + * input: dhcp_smach_t *: the state machine to force retransmission on * output: void */ void -stop_pkt_retransmission(struct ifslist *ifsp) +retransmit_now(dhcp_smach_t *dsmp) +{ + stop_pkt_retransmission(dsmp); + (void) send_pkt_internal(dsmp); +} + +/* + * alloc_pkt_entry(): Allocates a packet list entry with a given data area + * size. + * + * input: size_t: size of data area for packet + * boolean_t: B_TRUE for IPv6 + * output: PKT_LIST *: allocated packet list entry + */ + +PKT_LIST * +alloc_pkt_entry(size_t psize, boolean_t isv6) +{ + PKT_LIST *plp; + + if ((plp = calloc(1, sizeof (*plp))) == NULL || + (plp->pkt = malloc(psize)) == NULL) { + free(plp); + plp = NULL; + } else { + plp->len = psize; + plp->isv6 = isv6; + } + + return (plp); +} + +/* + * sock_recvpkt(): read from the given socket into an allocated buffer and + * handles any ancillary data options. + * + * input: int: file descriptor to read + * PKT_LIST *: allocated buffer + * output: ssize_t: number of bytes read, or -1 on error + */ + +static ssize_t +sock_recvpkt(int fd, PKT_LIST *plp) { - 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; + struct iovec iov; + struct msghdr msg; + long ctrl[8192 / sizeof (long)]; + ssize_t msglen; + + (void) memset(&iov, 0, sizeof (iov)); + iov.iov_base = (caddr_t)plp->pkt; + iov.iov_len = plp->len; + + (void) memset(&msg, 0, sizeof (msg)); + msg.msg_name = &plp->pktfrom; + msg.msg_namelen = sizeof (plp->pktfrom); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = ctrl; + msg.msg_controllen = sizeof (ctrl); + + if ((msglen = recvmsg(fd, &msg, 0)) != -1) { + struct cmsghdr *cmsg; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + struct sockaddr_in *sinp; + struct sockaddr_in6 *sin6; + struct in6_pktinfo *ipi6; + + switch (cmsg->cmsg_level) { + case IPPROTO_IP: + switch (cmsg->cmsg_type) { + case IP_RECVDSTADDR: + sinp = (struct sockaddr_in *) + &plp->pktto; + sinp->sin_family = AF_INET; + (void) memcpy(&sinp->sin_addr.s_addr, + CMSG_DATA(cmsg), + sizeof (ipaddr_t)); + break; + + case IP_RECVIF: + (void) memcpy(&plp->ifindex, + CMSG_DATA(cmsg), sizeof (uint_t)); + break; + } + break; + + case IPPROTO_IPV6: + switch (cmsg->cmsg_type) { + case IPV6_PKTINFO: + /* LINTED: alignment */ + ipi6 = (struct in6_pktinfo *) + CMSG_DATA(cmsg); + sin6 = (struct sockaddr_in6 *) + &plp->pktto; + sin6->sin6_family = AF_INET6; + (void) memcpy(&sin6->sin6_addr, + &ipi6->ipi6_addr, + sizeof (ipi6->ipi6_addr)); + (void) memcpy(&plp->ifindex, + &ipi6->ipi6_ifindex, + sizeof (uint_t)); + break; + } + } } } + return (msglen); } /* - * recv_pkt(): receives packets on an interface (put on ifsp->if_recv_pkt_list) + * recv_pkt(): receives a single DHCP packet on a given file descriptor. * - * 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 + * input: int: the file descriptor to receive the packet + * int: the maximum packet size to allow + * boolean_t: B_TRUE for IPv6 + * boolean_t: B_TRUE if using DLPI + * output: PKT_LIST *: the received packet */ -int -recv_pkt(struct ifslist *ifsp, int fd, dhcp_message_type_t type, - boolean_t chain) +PKT_LIST * +recv_pkt(int fd, int mtu, boolean_t isv6, boolean_t isdlpi) { 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; + if ((plp = alloc_pkt_entry(mtu, isv6)) == NULL) { + dhcpmsg(MSG_ERROR, + "recv_pkt: allocation failure; dropped packet"); + return (NULL); } - plp->pkt = pkt; + if (isv6) { + retval = sock_recvpkt(fd, plp); - switch (ifsp->if_state) { + if (retval == -1) { + dhcpmsg(MSG_ERR, + "recv_pkt: recvfrom v6 failed, dropped"); + goto failure; + } - case BOUND: - case RENEWING: - case REBINDING: - retval = recvfrom(fd, pkt, ifsp->if_max, 0, NULL, 0); - break; + plp->len = retval; - default: - retval = dlpi_recvfrom(fd, pkt, ifsp->if_max, 0); - break; - } + if (retval < sizeof (dhcpv6_message_t)) { + dhcpmsg(MSG_WARNING, "recv_pkt: runt message"); + goto failure; + } + } else { + if (isdlpi) { + retval = dlpi_recvfrom(fd, plp->pkt, mtu, + (struct sockaddr_in *)&plp->pktfrom, + (struct sockaddr_in *)&plp->pktto); + } else { + retval = sock_recvpkt(fd, plp); + } - if (retval == -1) { - dhcpmsg(MSG_ERR, "recv_pkt: recvfrom failed, dropped"); - goto failure; - } + if (retval == -1) { + dhcpmsg(MSG_ERR, + "recv_pkt: %srecvfrom v4 failed, dropped", + isdlpi ? "dlpi_" : ""); + goto failure; + } - plp->len = retval; + plp->len = retval; - switch (dhcp_options_scan(plp, B_TRUE)) { + switch (dhcp_options_scan(plp, B_TRUE)) { - case DHCP_WRONG_MSG_TYPE: - dhcpmsg(MSG_WARNING, "recv_pkt: unexpected DHCP message"); - goto failure; + 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_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 DHCP_BAD_OPT_OVLD: + dhcpmsg(MSG_WARNING, "recv_pkt: bad option overload"); + goto failure; - case 0: - break; + case 0: + break; - default: - dhcpmsg(MSG_WARNING, "recv_pkt: packet corrupted, dropped"); - goto failure; + default: + dhcpmsg(MSG_WARNING, + "recv_pkt: packet corrupted, dropped"); + goto failure; + } } + return (plp); + +failure: + free_pkt_entry(plp); + return (NULL); +} + +/* + * pkt_v4_match(): check if a given DHCPv4 message type is in a given set + * + * input: uchar_t: packet type + * dhcp_message_type_t: bit-wise OR of DHCP_P* values. + * output: boolean_t: B_TRUE if packet type is in the set + */ +boolean_t +pkt_v4_match(uchar_t type, dhcp_message_type_t match_type) +{ /* - * 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. + * note: the ordering here allows direct indexing of the table + * based on the RFC2131 packet type value passed in. */ - if (plp->opts[CD_DHCP_TYPE] != NULL) - recv_pkt_type = *plp->opts[CD_DHCP_TYPE]->value; + static dhcp_message_type_t type_map[] = { + DHCP_PUNTYPED, DHCP_PDISCOVER, DHCP_POFFER, DHCP_PREQUEST, + DHCP_PDECLINE, DHCP_PACK, DHCP_PNAK, DHCP_PRELEASE, + DHCP_PINFORM + }; + + if (type < (sizeof (type_map) / sizeof (*type_map))) + return ((type_map[type] & match_type) ? B_TRUE : B_FALSE); else - recv_pkt_type = 0; + return (B_FALSE); +} + +/* + * pkt_smach_enqueue(): enqueue a packet on a given state machine + * + * input: dhcp_smach_t: state machine + * PKT_LIST *: packet to enqueue + * output: none + */ - recv_pkt_name = pkt_type_to_string(recv_pkt_type); +void +pkt_smach_enqueue(dhcp_smach_t *dsmp, PKT_LIST *plp) +{ + dhcpmsg(MSG_VERBOSE, "pkt_smach_enqueue: received %s %s packet on %s", + pkt_type_to_string(pkt_recv_type(plp), dsmp->dsm_isv6), + dsmp->dsm_isv6 ? "v6" : "v4", dsmp->dsm_name); - 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; - } + /* add to front of list */ + insque(plp, &dsmp->dsm_recv_pkt_list); + dsmp->dsm_received++; +} - /* 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; - } +/* + * next_retransmission(): computes the number of seconds until the next + * retransmission, based on the algorithms in RFCs 2131 + * 3315. + * + * input: dhcp_smach_t *: state machine that needs a new timer + * boolean_t: B_TRUE if this is the first time sending the message + * boolean_t: B_TRUE for positive RAND values only (RFC 3315 17.1.2) + * output: none + */ - 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; +static void +next_retransmission(dhcp_smach_t *dsmp, boolean_t first_send, + boolean_t positive_only) +{ + uint32_t timeout_ms; + + if (dsmp->dsm_isv6) { + double randval; + + /* + * The RFC specifies 0 to 10% jitter for the initial + * solicitation, and plus or minus 10% jitter for all others. + * This works out to 100 milliseconds on the shortest timer we + * use. + */ + if (positive_only) + randval = drand48() / 10.0; + else + randval = (drand48() - 0.5) / 5.0; + + /* The RFC specifies doubling *after* the first transmission */ + timeout_ms = dsmp->dsm_send_timeout; + if (!first_send) + timeout_ms *= 2; + timeout_ms += (int)(randval * dsmp->dsm_send_timeout); + + /* This checks the MRT (maximum retransmission time) */ + if (dsmp->dsm_send_tcenter != 0 && + timeout_ms > dsmp->dsm_send_tcenter) { + timeout_ms = dsmp->dsm_send_tcenter + + (uint_t)(randval * dsmp->dsm_send_tcenter); } - } - dhcpmsg(MSG_VERBOSE, "received %s packet on %s", recv_pkt_name, - ifsp->if_name); + dsmp->dsm_send_timeout = timeout_ms; + } else { + if (dsmp->dsm_state == RENEWING || + dsmp->dsm_state == REBINDING) { + monosec_t mono; + + timeout_ms = dsmp->dsm_state == RENEWING ? + dsmp->dsm_leases->dl_t2.dt_start : + dsmp->dsm_leases->dl_lifs->lif_expire.dt_start; + timeout_ms += dsmp->dsm_curstart_monosec; + mono = monosec(); + if (mono > timeout_ms) + timeout_ms = 0; + else + timeout_ms -= mono; + timeout_ms *= MILLISEC / 2; + } else { + /* + * Start at 4, and increase by a factor of 2 up to 64. + */ + if (first_send) { + timeout_ms = 4 * MILLISEC; + } else { + timeout_ms = MIN(dsmp->dsm_send_tcenter << 1, + 64 * MILLISEC); + } + } - prepend_to_pkt_list(&ifsp->if_recv_pkt_list, plp); - ifsp->if_received++; - return (1); + dsmp->dsm_send_tcenter = timeout_ms; -failure: - free(pkt); - free(plp); - return (0); + /* + * At each iteration, jitter the timeout by some fraction of a + * second. + */ + dsmp->dsm_send_timeout = timeout_ms + + ((lrand48() % (2 * MILLISEC)) - MILLISEC); + } } /* - * next_retransmission(): returns the number of seconds until the next - * retransmission, based on the algorithm in RFC2131 + * dhcp_ip_default(): open and bind the default IP sockets used for I/O and + * interface control. * - * input: uint32_t: the number of milliseconds for the last retransmission - * output: uint32_t: the number of milliseconds until the next retransmission + * input: none + * output: B_TRUE on success */ -static uint32_t -next_retransmission(uint32_t last_timeout_ms) +boolean_t +dhcp_ip_default(void) { - uint32_t timeout_ms; + int on; - /* - * 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); + if ((v4_sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { + dhcpmsg(MSG_ERR, + "dhcp_ip_default: unable to create IPv4 socket"); + return (B_FALSE); + } + + if (setsockopt(v4_sock_fd, IPPROTO_IP, IP_RECVDSTADDR, &on, + sizeof (on)) == -1) { + dhcpmsg(MSG_ERR, + "dhcp_ip_default: unable to enable IP_RECVDSTADDR"); + return (B_FALSE); + } + + if (setsockopt(v4_sock_fd, IPPROTO_IP, IP_RECVIF, &on, sizeof (on)) == + -1) { + dhcpmsg(MSG_ERR, + "dhcp_ip_default: unable to enable IP_RECVIF"); + return (B_FALSE); + } + + if (!bind_sock(v4_sock_fd, IPPORT_BOOTPC, INADDR_ANY)) { + dhcpmsg(MSG_ERROR, + "dhcp_ip_default: unable to bind IPv4 socket to port %d", + IPPORT_BOOTPC); + return (B_FALSE); + } + + if (iu_register_event(eh, v4_sock_fd, POLLIN, dhcp_acknak_common, + NULL) == -1) { + dhcpmsg(MSG_WARNING, "dhcp_ip_default: cannot register to " + "receive IPv4 broadcasts"); + return (B_FALSE); + } + + if ((v6_sock_fd = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) { + dhcpmsg(MSG_ERR, + "dhcp_ip_default: unable to create IPv6 socket"); + return (B_FALSE); + } + + if (setsockopt(v6_sock_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, + sizeof (on)) == -1) { + dhcpmsg(MSG_ERR, + "dhcp_ip_default: unable to enable IPV6_RECVPKTINFO"); + return (B_FALSE); + } + + if (!bind_sock_v6(v6_sock_fd, IPPORT_DHCPV6C, NULL)) { + dhcpmsg(MSG_ERROR, + "dhcp_ip_default: unable to bind IPv6 socket to port %d", + IPPORT_DHCPV6C); + return (B_FALSE); + } + + if (iu_register_event(eh, v6_sock_fd, POLLIN, dhcp_acknak_common, + NULL) == -1) { + dhcpmsg(MSG_WARNING, "dhcp_ip_default: cannot register to " + "receive IPv6 packets"); + return (B_FALSE); + } - return (timeout_ms + ((lrand48() % (2 * MILLISEC)) - MILLISEC)); + return (B_TRUE); } diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/packet.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/packet.h index 865bc25bd3..54e52995e7 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/packet.h +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/packet.h @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,8 +19,8 @@ * CDDL HEADER END */ /* - * Copyright (c) 1999-2001 by Sun Microsystems, Inc. - * All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. */ #ifndef _PACKET_H @@ -30,12 +29,12 @@ #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 <netinet/dhcp6.h> #include <dhcp_impl.h> -#include "agent.h" +#include "common.h" /* * packet.[ch] contain routines for manipulating, setting, and @@ -47,43 +46,52 @@ 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. + * + * Note that for DHCPv6, types 4 (CONFIRM), 5 (RENEW), 6 (REBIND), 12 + * (RELAY-FORW, and 13 (RELAY-REPL) are not in the table. They're never + * received by a client, so there's no reason to process them. (SOLICIT, + * REQUEST, DECLINE, RELEASE, and INFORMATION-REQUEST are also never seen by + * clients, but are included for consistency.) + * + * Note also that the symbols are named for the DHCPv4 message types, and that + * DHCPv6 has analogous message types. */ typedef enum { DHCP_PUNTYPED = 0x001, /* untyped (BOOTP) message */ - DHCP_PDISCOVER = 0x002, - DHCP_POFFER = 0x004, - DHCP_PREQUEST = 0x008, - DHCP_PDECLINE = 0x010, - DHCP_PACK = 0x020, - DHCP_PNAK = 0x040, - DHCP_PRELEASE = 0x080, - DHCP_PINFORM = 0x100 + DHCP_PDISCOVER = 0x002, /* in v6: SOLICIT (1) */ + DHCP_POFFER = 0x004, /* in v6: ADVERTISE (2) */ + DHCP_PREQUEST = 0x008, /* in v6: REQUEST (3) */ + DHCP_PDECLINE = 0x010, /* in v6: DECLINE (9) */ + DHCP_PACK = 0x020, /* in v6: REPLY (7), status == 0 */ + DHCP_PNAK = 0x040, /* in v6: REPLY (7), status != 0 */ + DHCP_PRELEASE = 0x080, /* in v6: RELEASE (8) */ + DHCP_PINFORM = 0x100, /* in v6: INFORMATION-REQUEST (11) */ + DHCP_PRECONFIG = 0x200 /* v6 only: RECONFIGURE (10) */ } dhcp_message_type_t; /* - * a dhcp_pkt_t is (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. + * A dhcp_pkt_t is used by the output-side packet manipulation functions. + * While the structure is not strictly necessary, it allows a better separation + * of functionality since metadata about the packet (such as its current + * length) is stored along with the packet. + * + * Note that 'pkt' points to a dhcpv6_message_t if the packet is IPv6. */ -typedef struct dhcp_pkt { - +typedef struct dhcp_pkt_s { PKT *pkt; /* the real underlying packet */ unsigned int pkt_max_len; /* its maximum length */ unsigned int pkt_cur_len; /* its current length */ - + boolean_t pkt_isv6; } dhcp_pkt_t; /* @@ -94,19 +102,42 @@ typedef struct dhcp_pkt { * packet.c */ -typedef boolean_t stop_func_t(struct ifslist *, unsigned int); +typedef boolean_t stop_func_t(dhcp_smach_t *, unsigned int); + +/* + * Default I/O and interface control sockets. + */ +extern int v6_sock_fd; +extern int v4_sock_fd; + +extern const in6_addr_t ipv6_all_dhcp_relay_and_servers; +extern const in6_addr_t my_in6addr_any; -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); +PKT_LIST *alloc_pkt_entry(size_t, boolean_t); +void free_pkt_entry(PKT_LIST *); 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, +uchar_t pkt_recv_type(const PKT_LIST *); +uint_t pkt_get_xid(const PKT *, boolean_t); +dhcp_pkt_t *init_pkt(dhcp_smach_t *, uchar_t); +boolean_t remove_pkt_opt(dhcp_pkt_t *, uint_t); +boolean_t update_v6opt_len(dhcpv6_option_t *, int); +void *add_pkt_opt(dhcp_pkt_t *, uint_t, const void *, uint_t); +void *add_pkt_subopt(dhcp_pkt_t *, dhcpv6_option_t *, uint_t, + const void *, uint_t); +void *add_pkt_opt16(dhcp_pkt_t *, uint_t, uint16_t); +void *add_pkt_opt32(dhcp_pkt_t *, uint_t, uint32_t); +void *add_pkt_prl(dhcp_pkt_t *, dhcp_smach_t *); +boolean_t add_pkt_lif(dhcp_pkt_t *, dhcp_lif_t *, int, const char *); +void stop_pkt_retransmission(dhcp_smach_t *); +void retransmit_now(dhcp_smach_t *); +PKT_LIST *recv_pkt(int, int, boolean_t, boolean_t); +boolean_t pkt_v4_match(uchar_t, dhcp_message_type_t); +void pkt_smach_enqueue(dhcp_smach_t *, PKT_LIST *); +boolean_t send_pkt(dhcp_smach_t *, dhcp_pkt_t *, in_addr_t, stop_func_t *); -void get_pkt_times(PKT_LIST *, uint32_t *, uint32_t *, uint32_t *); +boolean_t send_pkt_v6(dhcp_smach_t *, dhcp_pkt_t *, in6_addr_t, + stop_func_t *, uint_t, uint_t); +boolean_t dhcp_ip_default(void); #ifdef __cplusplus } diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/release.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/release.c index 3d217c77b8..a841364f2f 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/release.c +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/release.c @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * DECLINE/RELEASE configuration functionality for the DHCP client. @@ -29,132 +28,269 @@ #pragma ident "%Z%%M% %I% %E% SMI" #include <sys/types.h> +#include <unistd.h> #include <string.h> #include <netinet/in.h> -#include <sys/socket.h> #include <net/if.h> #include <netinet/dhcp.h> +#include <netinet/dhcp6.h> #include <dhcpmsg.h> #include <dhcp_hostconf.h> -#include <unistd.h> +#include "agent.h" #include "packet.h" #include "interface.h" #include "states.h" +static boolean_t stop_release_decline(dhcp_smach_t *, unsigned int); + /* - * send_decline(): sends a DECLINE message (broadcasted) + * send_declines(): sends a DECLINE message (broadcasted for IPv4) to the + * server to indicate a problem with the offered addresses. + * The failing addresses are removed from the leases. * - * input: 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 + * input: dhcp_smach_t *: the state machine sending DECLINE * output: void */ void -send_decline(struct ifslist *ifsp, char *msg, struct in_addr *declined_ip) +send_declines(dhcp_smach_t *dsmp) { dhcp_pkt_t *dpkt; + dhcp_lease_t *dlp, *dlpn; + uint_t nlifs; + dhcp_lif_t *lif, *lifn; + boolean_t got_one; - dpkt = init_pkt(ifsp, DECLINE); - add_pkt_opt32(dpkt, CD_SERVER_ID, ifsp->if_server.s_addr); + /* + * Create an empty DECLINE message. We'll stuff the information into + * this message as we find it. + */ + if (dsmp->dsm_isv6) { + if ((dpkt = init_pkt(dsmp, DHCPV6_MSG_DECLINE)) == NULL) + return; + (void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID, + dsmp->dsm_serverid, dsmp->dsm_serveridlen); + } else { + ipaddr_t serverip; - if (msg != NULL) - add_pkt_opt(dpkt, CD_MESSAGE, msg, strlen(msg) + 1); + /* + * If this ack is from BOOTP, then there's no way to send a + * decline. Note that since we haven't bound yet, we can't + * just check the BOOTP flag. + */ + if (dsmp->dsm_ack->opts[CD_DHCP_TYPE] == NULL) + return; - add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR, declined_ip->s_addr); - add_pkt_opt(dpkt, CD_END, NULL, 0); + if ((dpkt = init_pkt(dsmp, DECLINE)) == NULL) + return; + IN6_V4MAPPED_TO_IPADDR(&dsmp->dsm_server, serverip); + (void) add_pkt_opt32(dpkt, CD_SERVER_ID, serverip); + } - (void) send_pkt(ifsp, dpkt, htonl(INADDR_BROADCAST), NULL); + /* + * Loop over the leases, looking for ones with now-broken LIFs. Add + * each one found to the DECLINE message, and remove it from the list. + * Also remove any completely declined leases. + */ + got_one = B_FALSE; + for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlpn) { + dlpn = dlp->dl_next; + lif = dlp->dl_lifs; + for (nlifs = dlp->dl_nlifs; nlifs > 0; nlifs--, lif = lifn) { + lifn = lif->lif_next; + if (lif->lif_declined != NULL) { + (void) add_pkt_lif(dpkt, lif, + DHCPV6_STAT_UNSPECFAIL, lif->lif_declined); + unplumb_lif(lif); + got_one = B_TRUE; + } + } + if (dlp->dl_nlifs == 0) + remove_lease(dlp); + } + + if (!got_one) + return; + + (void) set_smach_state(dsmp, DECLINING); + + if (dsmp->dsm_isv6) { + (void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server, + stop_release_decline, DHCPV6_DEC_TIMEOUT, 0); + } else { + (void) add_pkt_opt(dpkt, CD_END, NULL, 0); + + (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), NULL); + } } /* * dhcp_release(): sends a RELEASE message to a DHCP server and removes - * the interface from DHCP control + * the all interfaces for the given state machine from DHCP + * control. Called back by script handler. * - * input: struct ifslist *: the interface to send the RELEASE on and remove - * const char *: an optional text explanation to send with the message + * input: dhcp_smach_t *: the state machine to send the RELEASE on and remove + * void *: an optional text explanation to send with the message * output: int: 1 on success, 0 on failure */ int -dhcp_release(struct ifslist *ifsp, const char *msg) +dhcp_release(dhcp_smach_t *dsmp, void *arg) { - int retval = 0; - int error = DHCP_IPC_E_INT; + const char *msg = arg; dhcp_pkt_t *dpkt; + dhcp_lease_t *dlp; + dhcp_lif_t *lif; + ipaddr_t serverip; + uint_t nlifs; - if (ifsp->if_dflags & DHCP_IF_BOOTP) - goto out; + if ((dsmp->dsm_dflags & DHCP_IF_BOOTP) || + !check_cmd_allowed(dsmp->dsm_state, DHCP_RELEASE)) { + ipc_action_finish(dsmp, DHCP_IPC_E_INT); + return (0); + } - if (ifsp->if_state != BOUND && ifsp->if_state != RENEWING && - ifsp->if_state != REBINDING) - goto out; + dhcpmsg(MSG_INFO, "releasing leases for state machine %s", + dsmp->dsm_name); + (void) set_smach_state(dsmp, RELEASING); - dhcpmsg(MSG_INFO, "releasing interface %s", ifsp->if_name); + if (dsmp->dsm_isv6) { + dpkt = init_pkt(dsmp, DHCPV6_MSG_RELEASE); + (void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID, + dsmp->dsm_serverid, dsmp->dsm_serveridlen); - dpkt = init_pkt(ifsp, RELEASE); - dpkt->pkt->ciaddr.s_addr = ifsp->if_addr.s_addr; + for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) { + lif = dlp->dl_lifs; + for (nlifs = dlp->dl_nlifs; nlifs > 0; + nlifs--, lif = lif->lif_next) { + (void) add_pkt_lif(dpkt, lif, + DHCPV6_STAT_SUCCESS, NULL); + } + } - if (msg != NULL) - add_pkt_opt(dpkt, CD_MESSAGE, msg, strlen(msg) + 1); + /* + * Must kill off the leases before attempting to tell the + * server. + */ + deprecate_leases(dsmp); - add_pkt_opt32(dpkt, CD_SERVER_ID, ifsp->if_server.s_addr); - add_pkt_opt(dpkt, CD_END, NULL, 0); + /* + * For DHCPv6, this is a transaction, rather than just a + * one-shot message. When this transaction is done, we'll + * finish the invoking async operation. + */ + (void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server, + stop_release_decline, DHCPV6_REL_TIMEOUT, 0); + } else { + if ((dlp = dsmp->dsm_leases) != NULL && dlp->dl_nlifs > 0) { + dpkt = init_pkt(dsmp, RELEASE); + if (msg != NULL) { + (void) add_pkt_opt(dpkt, CD_MESSAGE, msg, + strlen(msg) + 1); + } + lif = dlp->dl_lifs; + (void) add_pkt_lif(dpkt, dlp->dl_lifs, 0, NULL); - (void) send_pkt(ifsp, dpkt, ifsp->if_server.s_addr, NULL); + IN6_V4MAPPED_TO_IPADDR(&dsmp->dsm_server, serverip); + (void) add_pkt_opt32(dpkt, CD_SERVER_ID, serverip); + (void) add_pkt_opt(dpkt, CD_END, NULL, 0); + (void) send_pkt(dsmp, dpkt, serverip, NULL); + } - /* - * XXX this totally sucks, but since udp is best-effort, - * without this delay, there's a good chance that the packet - * that we just enqueued for sending will get pitched - * when we canonize the interface below. - */ + /* + * XXX this totally sucks, but since udp is best-effort, + * without this delay, there's a good chance that the packet + * that we just enqueued for sending will get pitched + * when we canonize the interface through remove_smach. + */ - (void) usleep(500); - (void) canonize_ifs(ifsp); + (void) usleep(500); + deprecate_leases(dsmp); - remove_ifs(ifsp); - error = DHCP_IPC_SUCCESS; - retval = 1; -out: - ipc_action_finish(ifsp, error); - async_finish(ifsp); - return (retval); + finished_smach(dsmp, DHCP_IPC_SUCCESS); + } + return (1); } /* - * dhcp_drop(): drops the interface from DHCP control + * dhcp_drop(): drops the interface from DHCP control; callback from script + * handler * - * input: struct ifslist *: the interface to drop - * const char *: unused + * input: dhcp_smach_t *: the state machine dropping leases + * void *: unused * output: int: always 1 */ -/* ARGSUSED */ +/* ARGSUSED1 */ int -dhcp_drop(struct ifslist *ifsp, const char *msg) +dhcp_drop(dhcp_smach_t *dsmp, void *arg) { - PKT_LIST *plp[2]; + dhcpmsg(MSG_INFO, "dropping leases for state machine %s", + dsmp->dsm_name); - dhcpmsg(MSG_INFO, "dropping interface %s", ifsp->if_name); + if (dsmp->dsm_state == PRE_BOUND || dsmp->dsm_state == BOUND || + dsmp->dsm_state == RENEWING || dsmp->dsm_state == REBINDING) { + if (dsmp->dsm_dflags & DHCP_IF_BOOTP) { + dhcpmsg(MSG_INFO, + "used bootp; not writing lease file for %s", + dsmp->dsm_name); + } else { + PKT_LIST *plp[2]; - 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) + plp[0] = dsmp->dsm_ack; + plp[1] = dsmp->dsm_orig_ack; + if (write_hostconf(dsmp->dsm_name, plp, 2, + monosec_to_time(dsmp->dsm_curstart_monosec), + dsmp->dsm_isv6) == -1) { dhcpmsg(MSG_ERR, "cannot write %s (reboot will " "not use cached configuration)", - ifname_to_hostconf(ifsp->if_name)); + ifname_to_hostconf(dsmp->dsm_name, + dsmp->dsm_isv6)); + } } - (void) canonize_ifs(ifsp); } - remove_ifs(ifsp); - ipc_action_finish(ifsp, DHCP_IPC_SUCCESS); - async_finish(ifsp); + deprecate_leases(dsmp); + finished_smach(dsmp, DHCP_IPC_SUCCESS); return (1); } + +/* + * stop_release_decline(): decides when to stop retransmitting RELEASE/DECLINE + * messages for DHCPv6. When we stop, if there are no + * more leases left, then restart the state machine. + * + * input: dhcp_smach_t *: the state machine messages are being sent from + * unsigned int: the number of messages sent so far + * output: boolean_t: B_TRUE if retransmissions should stop + */ + +static boolean_t +stop_release_decline(dhcp_smach_t *dsmp, unsigned int n_requests) +{ + if (dsmp->dsm_state == RELEASING) { + if (n_requests >= DHCPV6_REL_MAX_RC) { + dhcpmsg(MSG_INFO, "no Reply to Release, finishing " + "transaction on %s", dsmp->dsm_name); + finished_smach(dsmp, DHCP_IPC_SUCCESS); + return (B_TRUE); + } else { + return (B_FALSE); + } + } else { + if (n_requests >= DHCPV6_DEC_MAX_RC) { + dhcpmsg(MSG_INFO, "no Reply to Decline on %s", + dsmp->dsm_name); + + if (dsmp->dsm_leases == NULL) { + dhcpmsg(MSG_VERBOSE, "stop_release_decline: " + "%s has no leases left; restarting", + dsmp->dsm_name); + dhcp_restart(dsmp); + } + return (B_TRUE); + } else { + return (B_FALSE); + } + } +} diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/renew.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/renew.c index 751e6be5ac..d6a81ad904 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/renew.c +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/renew.c @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -27,7 +27,6 @@ #include <sys/types.h> #include <time.h> -#include <unistd.h> #include <netinet/in.h> #include <netinet/dhcp.h> #include <netinet/udp.h> @@ -45,177 +44,239 @@ #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 + * Number of seconds to wait for a retry if the user is interacting with the + * daemon. */ +#define RETRY_DELAY 10 -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); +/* + * If the renew timer fires within this number of seconds of the rebind timer, + * then skip renew. This prevents us from sending back-to-back renew and + * rebind messages -- a pointless activity. + */ +#define TOO_CLOSE 2 - return ((limit_monosec - current_monosec) / 2); -} +static boolean_t stop_extending(dhcp_smach_t *, unsigned int); /* - * dhcp_renew(): attempts to renew a DHCP lease + * dhcp_renew(): attempts to renew a DHCP lease on expiration of the T1 timer. * * input: iu_tq_t *: unused - * void *: the ifslist to renew the lease on + * void *: the lease to renew (dhcp_lease_t) * output: void + * + * notes: The primary expense involved with DHCP (like most UDP protocols) is + * with the generation and handling of packets, not the contents of + * those packets. Thus, we try to reduce the number of packets that + * are sent. It would be nice to just renew all leases here (each one + * added has trivial added overhead), but the DHCPv6 RFC doesn't + * explicitly allow that behavior. Rather than having that argument, + * we settle for ones that are close in expiry to the one that fired. + * For v4, we repeatedly reschedule the T1 timer to do the + * retransmissions. For v6, we rely on the common timer computation + * in packet.c. */ /* ARGSUSED */ void dhcp_renew(iu_tq_t *tqp, void *arg) { - struct ifslist *ifsp = (struct ifslist *)arg; - uint32_t next; + dhcp_lease_t *dlp = arg; + dhcp_smach_t *dsmp = dlp->dl_smach; + uint32_t t2; + dhcpmsg(MSG_VERBOSE, "dhcp_renew: T1 timer expired on %s", + dsmp->dsm_name); - ifsp->if_timer[DHCP_T1_TIMER] = -1; + dlp->dl_t1.dt_id = -1; - if (check_ifs(ifsp) == 0) { - (void) release_ifs(ifsp); + if (dsmp->dsm_state == RENEWING || dsmp->dsm_state == REBINDING) { + dhcpmsg(MSG_DEBUG, "dhcp_renew: already renewing"); + release_lease(dlp); return; } /* - * sanity check: don't send packets if we're past t2. + * Sanity check: don't send packets if we're past T2, or if we're + * extremely close. */ - if (monosec() > (ifsp->if_curstart_monosec + ifsp->if_t2)) + t2 = dsmp->dsm_curstart_monosec + dlp->dl_t2.dt_start; + if (monosec() + TOO_CLOSE >= t2) { + dhcpmsg(MSG_DEBUG, "dhcp_renew: %spast T2 on %s", + monosec() > t2 ? "" : "almost ", dsmp->dsm_name); + release_lease(dlp); return; - - next = next_extend_time(ifsp->if_curstart_monosec + ifsp->if_t2); + } /* - * if there isn't an async event pending, then try to renew. + * If there isn't an async event pending, or if we can cancel the one + * that's there, then try to renew by sending an extension request. If + * that fails, we'll try again when the next timer fires. */ - - if (!async_pending(ifsp)) - if (async_start(ifsp, DHCP_EXTEND, B_FALSE) != 0) - + if (!async_cancel(dsmp) || !async_start(dsmp, DHCP_EXTEND, B_FALSE) || + !dhcp_extending(dsmp)) { + if (monosec() + RETRY_DELAY < t2) { /* - * try to send extend. if we don't succeed, - * async_timeout() will clean us up. + * Try again in RETRY_DELAY seconds; user command + * should be gone. */ - - (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); + init_timer(&dlp->dl_t1, RETRY_DELAY); + (void) set_smach_state(dsmp, BOUND); + if (!schedule_lease_timer(dlp, &dlp->dl_t1, + dhcp_renew)) { + dhcpmsg(MSG_INFO, "dhcp_renew: unable to " + "reschedule renewal around user command " + "on %s; will wait for rebind", + dsmp->dsm_name); + } + } else { + dhcpmsg(MSG_DEBUG, "dhcp_renew: user busy on %s; will " + "wait for rebind", dsmp->dsm_name); + } + } + release_lease(dlp); } /* - * dhcp_rebind(): attempts to renew a DHCP lease from the REBINDING state + * dhcp_rebind(): attempts to renew a DHCP lease from the REBINDING state (T2 + * timer expiry). * * input: iu_tq_t *: unused - * void *: the ifslist to renew the lease on + * void *: the lease to renew * output: void + * notes: For v4, we repeatedly reschedule the T2 timer to do the + * retransmissions. For v6, we rely on the common timer computation + * in packet.c. */ /* ARGSUSED */ void dhcp_rebind(iu_tq_t *tqp, void *arg) { - 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); + dhcp_lease_t *dlp = arg; + dhcp_smach_t *dsmp = dlp->dl_smach; + int nlifs; + dhcp_lif_t *lif; + boolean_t some_valid; + uint32_t expiremax; + DHCPSTATE oldstate; + + dhcpmsg(MSG_VERBOSE, "dhcp_rebind: T2 timer expired on %s", + dsmp->dsm_name); + + dlp->dl_t2.dt_id = -1; + + if ((oldstate = dsmp->dsm_state) == REBINDING) { + dhcpmsg(MSG_DEBUG, "dhcp_renew: already rebinding"); + release_lease(dlp); return; } /* - * sanity check: don't send packets if we've already expired. + * Sanity check: don't send packets if we've already expired on all of + * the addresses. We compute the maximum expiration time here, because + * it won't matter for v4 (there's only one lease) and for v6 we need + * to know when the last lease ages away. */ - if (monosec() > (ifsp->if_curstart_monosec + ifsp->if_lease)) - return; + some_valid = B_FALSE; + expiremax = monosec(); + lif = dlp->dl_lifs; + for (nlifs = dlp->dl_nlifs; nlifs > 0; nlifs--, lif = lif->lif_next) { + uint32_t expire; - next = next_extend_time(ifsp->if_curstart_monosec + ifsp->if_lease); + expire = dsmp->dsm_curstart_monosec + lif->lif_expire.dt_start; + if (expire > expiremax) { + expiremax = expire; + some_valid = B_TRUE; + } + } + if (!some_valid) { + dhcpmsg(MSG_DEBUG, "dhcp_rebind: all leases expired on %s", + dsmp->dsm_name); + release_lease(dlp); + return; + } /* - * 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). + * This is our first venture into the REBINDING state, so reset the + * server address. We know the renew timer has already been cancelled + * (or we wouldn't be here). */ - - if (ifsp->if_state == RENEWING) { - ifsp->if_state = REBINDING; - ifsp->if_server.s_addr = htonl(INADDR_BROADCAST); + if (dsmp->dsm_isv6) { + dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers; + } else { + IN6_IPADDR_TO_V4MAPPED(htonl(INADDR_BROADCAST), + &dsmp->dsm_server); } + /* {Bound,Renew}->rebind transitions cannot fail */ + (void) set_smach_state(dsmp, REBINDING); + /* - * if there isn't an async event pending, then try to rebind. + * If there isn't an async event pending, or if we can cancel the one + * that's there, then try to rebind by sending an extension request. + * If that fails, we'll clean up when the lease expires. */ - - if (!async_pending(ifsp)) - if (async_start(ifsp, DHCP_EXTEND, B_FALSE) != 0) - + if (!async_cancel(dsmp) || !async_start(dsmp, DHCP_EXTEND, B_FALSE) || + !dhcp_extending(dsmp)) { + if (monosec() + RETRY_DELAY < expiremax) { /* - * try to send extend. if we don't succeed, - * async_timeout() will clean us up. + * Try again in RETRY_DELAY seconds; user command + * should be gone. */ - - (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; + init_timer(&dlp->dl_t2, RETRY_DELAY); + (void) set_smach_state(dsmp, oldstate); + if (!schedule_lease_timer(dlp, &dlp->dl_t2, + dhcp_rebind)) { + dhcpmsg(MSG_INFO, "dhcp_rebind: unable to " + "reschedule rebind around user command on " + "%s; lease may expire", dsmp->dsm_name); + } + } else { + dhcpmsg(MSG_WARNING, "dhcp_rebind: user busy on %s; " + "will expire", dsmp->dsm_name); + } } - - 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); + release_lease(dlp); } /* - * dhcp_restart_lease(): callback function to script_start + * dhcp_finish_expire(): finish expiration of a lease after the user script + * runs. If this is the last lease, then restart DHCP. + * The caller has a reference to the LIF, which will be + * dropped. * - * input: struct ifslist *: the interface to be restarted - * const char *: unused + * input: dhcp_smach_t *: the state machine to be restarted + * void *: logical interface that has expired * output: int: always 1 */ -/* ARGSUSED */ static int -dhcp_restart_lease(struct ifslist *ifsp, const char *msg) +dhcp_finish_expire(dhcp_smach_t *dsmp, void *arg) { - dhcpmsg(MSG_INFO, "lease expired on %s -- restarting DHCP", - ifsp->if_name); + dhcp_lif_t *lif = arg; + dhcp_lease_t *dlp; + + dhcpmsg(MSG_DEBUG, "lease expired on %s; removing", lif->lif_name); + + dlp = lif->lif_lease; + unplumb_lif(lif); + if (dlp->dl_nlifs == 0) + remove_lease(dlp); + release_lif(lif); + + /* If some valid leases remain, then drive on */ + if (dsmp->dsm_leases != NULL) { + dhcpmsg(MSG_DEBUG, + "dhcp_finish_expire: some leases remain on %s", + dsmp->dsm_name); + return (1); + } + + dhcpmsg(MSG_INFO, "last lease expired on %s -- restarting DHCP", + dsmp->dsm_name); /* * in the case where the lease is less than DHCP_REBIND_MIN @@ -223,24 +284,45 @@ dhcp_restart_lease(struct ifslist *ifsp, const char *msg) * 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; + if (dsmp->dsm_state == BOUND) { + dsmp->dsm_bad_offers = 0; + dsmp->dsm_sent = 0; + dsmp->dsm_received = 0; } - (void) canonize_ifs(ifsp); + deprecate_leases(dsmp); + + /* reset_smach() in dhcp_selecting() will clean up any leftover state */ + dhcp_selecting(dsmp); - /* 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 + * dhcp_deprecate(): deprecates an address on a given logical interface when + * the preferred lifetime expires. * * input: iu_tq_t *: unused - * void *: the ifslist to expire the lease on + * void *: the logical interface whose lease is expiring + * output: void + */ + +/* ARGSUSED */ +void +dhcp_deprecate(iu_tq_t *tqp, void *arg) +{ + dhcp_lif_t *lif = arg; + + set_lif_deprecated(lif); + release_lif(lif); +} + +/* + * dhcp_expire(): expires a lease on a given logical interface and, if there + * are no more leases, restarts DHCP. + * + * input: iu_tq_t *: unused + * void *: the logical interface whose lease has expired * output: void */ @@ -248,38 +330,58 @@ dhcp_restart_lease(struct ifslist *ifsp, const char *msg) void dhcp_expire(iu_tq_t *tqp, void *arg) { - struct ifslist *ifsp = (struct ifslist *)arg; + dhcp_lif_t *lif = arg; + dhcp_smach_t *dsmp; + const char *event; - ifsp->if_timer[DHCP_LEASE_TIMER] = -1; + dhcpmsg(MSG_VERBOSE, "dhcp_expire: lease timer expired on %s", + lif->lif_name); - if (check_ifs(ifsp) == 0) { - (void) release_ifs(ifsp); + lif->lif_expire.dt_id = -1; + if (lif->lif_lease == NULL) { + release_lif(lif); return; } - if (async_pending(ifsp)) + set_lif_deprecated(lif); - if (async_cancel(ifsp) == 0) { + dsmp = lif->lif_lease->dl_smach; - dhcpmsg(MSG_WARNING, "dhcp_expire: cannot cancel " - "current asynchronous command against %s", - ifsp->if_name); + if (!async_cancel(dsmp)) { - /* - * try to schedule ourselves for callback. - * we're really situation critical here - * there's not much hope for us if this fails. - */ + dhcpmsg(MSG_WARNING, + "dhcp_expire: cannot cancel current asynchronous command " + "on %s", dsmp->dsm_name); - if (iu_schedule_timer(tq, DHCP_EXPIRE_WAIT, dhcp_expire, - ifsp) != -1) { - hold_ifs(ifsp); - return; - } + /* + * Try to schedule ourselves for callback. We're really + * situation-critical here; there's not much hope for us if + * this fails. + */ + init_timer(&lif->lif_expire, DHCP_EXPIRE_WAIT); + if (schedule_lif_timer(lif, &lif->lif_expire, dhcp_expire)) + return; - dhcpmsg(MSG_CRIT, "dhcp_expire: cannot reschedule " - "dhcp_expire to get called back, proceeding..."); - } + dhcpmsg(MSG_CRIT, "dhcp_expire: cannot reschedule dhcp_expire " + "to get called back, proceeding..."); + } + + if (!async_start(dsmp, DHCP_START, B_FALSE)) + dhcpmsg(MSG_WARNING, "dhcp_expire: cannot start asynchronous " + "transaction on %s, continuing...", dsmp->dsm_name); + + /* + * Determine if this state machine has any non-expired LIFs left in it. + * If it doesn't, then this is an "expire" event. Otherwise, if some + * valid leases remain, it's a "loss" event. The SOMEEXP case can + * occur only with DHCPv6. + */ + if (expired_lif_state(dsmp) == DHCP_EXP_SOMEEXP) + event = EVENT_LOSS6; + else if (dsmp->dsm_isv6) + event = EVENT_EXPIRE6; + else + event = EVENT_EXPIRE; /* * just march on if this fails; at worst someone will be able @@ -287,80 +389,165 @@ dhcp_expire(iu_tq_t *tqp, void *arg) * 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_lease, NULL, NULL); + (void) script_start(dsmp, event, dhcp_finish_expire, lif, NULL); } /* - * dhcp_extending(): sends a REQUEST to extend a lease on a given interface - * and registers to receive the ACK/NAK server reply + * dhcp_extending(): sends a REQUEST (IPv4 DHCP) or Rebind/Renew (DHCPv6) to + * extend a lease on a given state machine * - * input: struct ifslist *: the interface to send the REQUEST on - * output: int: 1 if the extension request was sent, 0 otherwise + * input: dhcp_smach_t *: the state machine to send the message from + * output: boolean_t: B_TRUE if the extension request was sent */ -int -dhcp_extending(struct ifslist *ifsp) +boolean_t +dhcp_extending(dhcp_smach_t *dsmp) { 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; + stop_pkt_retransmission(dsmp); + + /* + * We change state here because this function is also called when + * adopting a lease and on demand by the user. + */ + if (dsmp->dsm_state == BOUND) { + dsmp->dsm_neg_hrtime = gethrtime(); + dsmp->dsm_bad_offers = 0; + dsmp->dsm_sent = 0; + dsmp->dsm_received = 0; + /* Bound->renew can't fail */ + (void) set_smach_state(dsmp, RENEWING); } - dhcpmsg(MSG_DEBUG, "dhcp_extending: registering dhcp_acknak on %s", - ifsp->if_name); + dhcpmsg(MSG_DEBUG, "dhcp_extending: sending request on %s", + dsmp->dsm_name); - if (register_acknak(ifsp) == 0) { + if (dsmp->dsm_isv6) { + dhcp_lease_t *dlp; + dhcp_lif_t *lif; + uint_t nlifs; + uint_t irt, mrt; - ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY); - async_finish(ifsp); + /* + * Start constructing the Renew/Rebind message. Only Renew has + * a server ID, as we still think our server might be + * reachable. + */ + if (dsmp->dsm_state == RENEWING) { + dpkt = init_pkt(dsmp, DHCPV6_MSG_RENEW); + (void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID, + dsmp->dsm_serverid, dsmp->dsm_serveridlen); + irt = DHCPV6_REN_TIMEOUT; + mrt = DHCPV6_REN_MAX_RT; + } else { + dpkt = init_pkt(dsmp, DHCPV6_MSG_REBIND); + irt = DHCPV6_REB_TIMEOUT; + mrt = DHCPV6_REB_MAX_RT; + } - dhcpmsg(MSG_WARNING, "dhcp_extending: cannot register " - "dhcp_acknak for %s, not sending renew request", - ifsp->if_name); + /* + * Loop over the leases, and add an IA_NA for each and an + * IAADDR for each address. + */ + for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) { + lif = dlp->dl_lifs; + for (nlifs = dlp->dl_nlifs; nlifs > 0; + nlifs--, lif = lif->lif_next) { + (void) add_pkt_lif(dpkt, lif, + DHCPV6_STAT_SUCCESS, NULL); + } + } - return (0); - } + /* Add required Option Request option */ + (void) add_pkt_prl(dpkt, dsmp); - /* - * assemble DHCPREQUEST message. The max dhcp message size - * option is set to the interface max, minus the size of the udp and - * ip headers. - */ + return (send_pkt_v6(dsmp, dpkt, dsmp->dsm_server, + stop_extending, irt, mrt)); + } else { + dhcp_lif_t *lif = dsmp->dsm_lif; + ipaddr_t server; - dpkt = init_pkt(ifsp, REQUEST); - dpkt->pkt->ciaddr.s_addr = ifsp->if_addr.s_addr; + /* assemble the DHCPREQUEST message. */ + dpkt = init_pkt(dsmp, REQUEST); + dpkt->pkt->ciaddr.s_addr = lif->lif_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)); + /* + * The max dhcp message size option is set to the interface + * max, minus the size of the udp and ip headers. + */ + (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, + htons(lif->lif_max - sizeof (struct udpiphdr))); + (void) add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM)); + + (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len); + (void) add_pkt_prl(dpkt, dsmp); + /* + * dsm_reqhost was set for this state machine in + * dhcp_selecting() if the REQUEST_HOSTNAME option was set and + * a host name was found. + */ + if (dsmp->dsm_reqhost != NULL) { + (void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost, + strlen(dsmp->dsm_reqhost)); + } + (void) add_pkt_opt(dpkt, CD_END, NULL, 0); + + IN6_V4MAPPED_TO_IPADDR(&dsmp->dsm_server, server); + return (send_pkt(dsmp, dpkt, server, stop_extending)); + } +} + +/* + * stop_extending(): decides when to stop retransmitting v4 REQUEST or v6 + * Renew/Rebind messages. If we're renewing, then stop if + * T2 is soon approaching. + * + * input: dhcp_smach_t *: the state machine REQUESTs are being sent from + * unsigned int: the number of REQUESTs sent so far + * output: boolean_t: B_TRUE if retransmissions should stop + */ + +/* ARGSUSED */ +static boolean_t +stop_extending(dhcp_smach_t *dsmp, unsigned int n_requests) +{ + dhcp_lease_t *dlp; - 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 we're renewing and rebind time is soon approaching, then don't + * schedule */ - if (ifsp->if_reqhost != NULL) { - add_pkt_opt(dpkt, CD_HOSTNAME, ifsp->if_reqhost, - strlen(ifsp->if_reqhost)); + if (dsmp->dsm_state == RENEWING) { + monosec_t t2; + + t2 = 0; + for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) { + if (dlp->dl_t2.dt_start > t2) + t2 = dlp->dl_t2.dt_start; + } + t2 += dsmp->dsm_curstart_monosec; + if (monosec() + TOO_CLOSE >= t2) { + dhcpmsg(MSG_DEBUG, "stop_extending: %spast T2 on %s", + monosec() > t2 ? "" : "almost ", dsmp->dsm_name); + return (B_TRUE); + } } - 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. + * Note that returning B_TRUE cancels both this transmission and the + * one that would occur at dsm_send_timeout, and that for v4 we cut the + * time in half for each retransmission. Thus we check here against + * half of the minimum. */ + if (!dsmp->dsm_isv6 && + dsmp->dsm_send_timeout < DHCP_REBIND_MIN * MILLISEC / 2) { + dhcpmsg(MSG_DEBUG, "stop_extending: next retry would be in " + "%d.%03d; stopping", dsmp->dsm_send_timeout / MILLISEC, + dsmp->dsm_send_timeout % MILLISEC); + return (B_TRUE); + } - return (send_pkt(ifsp, dpkt, ifsp->if_server.s_addr, NULL)); + /* Otherwise, w stop only when the next timer (rebind, expire) fires */ + return (B_FALSE); } diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/request.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/request.c index a4f418059a..f6e418f210 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/request.c +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/request.c @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * REQUESTING state of the client state machine. @@ -27,17 +27,18 @@ #pragma ident "%Z%%M% %I% %E% SMI" +#include <stdlib.h> +#include <string.h> +#include <search.h> #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 <unistd.h> +#include <dhcp_hostconf.h> +#include <dhcpagent_util.h> #include <dhcpmsg.h> #include "states.h" @@ -46,53 +47,148 @@ #include "interface.h" #include "agent.h" -static PKT_LIST *select_best(PKT_LIST **); +static PKT_LIST *select_best(dhcp_smach_t *); +static void request_failed(dhcp_smach_t *); static stop_func_t stop_requesting; /* + * send_v6_request(): sends a DHCPv6 Request message and switches to REQUESTING + * state. This is a separate function because a NoBinding + * response can also cause us to do this. + * + * input: dhcp_smach_t *: the state machine + * output: none + */ + +void +send_v6_request(dhcp_smach_t *dsmp) +{ + dhcp_pkt_t *dpkt; + dhcpv6_ia_na_t d6in; + + dpkt = init_pkt(dsmp, DHCPV6_MSG_REQUEST); + (void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID, dsmp->dsm_serverid, + dsmp->dsm_serveridlen); + + /* Add an IA_NA option for our controlling LIF */ + d6in.d6in_iaid = htonl(dsmp->dsm_lif->lif_iaid); + d6in.d6in_t1 = htonl(0); + d6in.d6in_t2 = htonl(0); + (void) add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA, + (dhcpv6_option_t *)&d6in + 1, + sizeof (d6in) - sizeof (dhcpv6_option_t)); + + /* Add required Option Request option */ + (void) add_pkt_prl(dpkt, dsmp); + + (void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server, stop_requesting, + DHCPV6_REQ_TIMEOUT, DHCPV6_REQ_MAX_RT); + + /* For DHCPv6, state switch cannot fail */ + (void) set_smach_state(dsmp, REQUESTING); +} + +/* + * server_unicast_option(): determines the server address to use based on the + * DHCPv6 Server Unicast option present in the given + * packet. + * + * input: dhcp_smach_t *: the state machine + * PKT_LIST *: received packet (Advertisement or Reply) + * output: none + */ + +void +server_unicast_option(dhcp_smach_t *dsmp, PKT_LIST *plp) +{ + const dhcpv6_option_t *d6o; + uint_t olen; + + d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_UNICAST, &olen); + olen -= sizeof (*d6o); + /* LINTED: no consequent */ + if (d6o == NULL) { + /* No Server Unicast option specified */ + } else if (olen != sizeof (dsmp->dsm_server)) { + dhcpmsg(MSG_WARNING, "server_unicast_option: %s has Server " + "Unicast option with bad length", + pkt_type_to_string(pkt_recv_type(plp), B_TRUE)); + } else { + in6_addr_t addr; + + (void) memcpy(&addr, d6o + 1, olen); + if (IN6_IS_ADDR_UNSPECIFIED(&addr)) { + dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " + "to unspecified address ignored"); + } else if (IN6_IS_ADDR_MULTICAST(&addr)) { + dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " + "to multicast address ignored"); + } else if (IN6_IS_ADDR_V4COMPAT(&addr) || + IN6_IS_ADDR_V4MAPPED(&addr)) { + dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " + "to invalid address ignored"); + } else { + dsmp->dsm_server = addr; + } + } +} + +/* * dhcp_requesting(): checks if OFFER packets to come in from DHCP servers. * if so, chooses the best one, sends a REQUEST to the * server and registers an event handler to receive - * the ACK/NAK + * the ACK/NAK. This may be called by the offer timer or + * by any function that wants to check for offers after + * canceling that timer. * - * input: iu_tq_t *: unused - * void *: the interface receiving OFFER packets + * input: iu_tq_t *: timer queue; non-NULL if this is a timer callback + * void *: the state machine receiving OFFER packets * output: void */ -/* ARGSUSED */ void dhcp_requesting(iu_tq_t *tqp, void *arg) { - struct ifslist *ifsp = (struct ifslist *)arg; + dhcp_smach_t *dsmp = arg; dhcp_pkt_t *dpkt; PKT_LIST *offer; lease_t lease; + boolean_t isv6 = dsmp->dsm_isv6; - ifsp->if_offer_timer = -1; - - if (check_ifs(ifsp) == 0) { - (void) release_ifs(ifsp); - return; + /* + * We assume here that if tqp is set, then this means we're being + * called back by the offer wait timer. If so, then drop our hold + * on the state machine. Otherwise, cancel the timer if it's running. + */ + if (tqp != NULL) { + dhcpmsg(MSG_VERBOSE, + "dhcp_requesting: offer wait timer on v%d %s", + isv6 ? 6 : 4, dsmp->dsm_name); + dsmp->dsm_offer_timer = -1; + if (!verify_smach(dsmp)) + return; + } else { + cancel_offer_timer(dsmp); } /* * select the best OFFER; all others pitched. */ - offer = select_best(&ifsp->if_recv_pkt_list); + offer = select_best(dsmp); if (offer == NULL) { - dhcpmsg(MSG_VERBOSE, "no OFFERs on %s, waiting...", - ifsp->if_name); + dhcpmsg(MSG_VERBOSE, + "no OFFERs/Advertisements on %s, waiting...", + dsmp->dsm_name); /* * no acceptable OFFERs have come in. reschedule - * ourselves for callback. + * ourself for callback. */ - if ((ifsp->if_offer_timer = iu_schedule_timer(tq, - ifsp->if_offer_wait, dhcp_requesting, ifsp)) == -1) { + if ((dsmp->dsm_offer_timer = iu_schedule_timer(tq, + dsmp->dsm_offer_wait, dhcp_requesting, dsmp)) == -1) { /* * ugh. the best we can do at this point is @@ -100,216 +196,382 @@ dhcp_requesting(iu_tq_t *tqp, void *arg) * 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); + "%s", dsmp->dsm_name); + + stop_pkt_retransmission(dsmp); + (void) set_smach_state(dsmp, INIT); + dsmp->dsm_dflags |= DHCP_IF_FAILED; + ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); + } else { + hold_smach(dsmp); + } return; } - 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. + * With IPv4, the DHCPREQUEST packet we're about to transmit implicitly + * declines all other offers we've received. We can no longer use any + * cached offers, so we must discard them now. With DHCPv6, though, + * we're permitted to hang onto the advertisements (offers) and try + * them if the preferred one doesn't pan out. */ + if (!isv6) + free_pkt_list(&dsmp->dsm_recv_pkt_list); - if (iu_unregister_event(eh, ifsp->if_offer_id, NULL) != 0) { - (void) release_ifs(ifsp); - ifsp->if_offer_id = -1; - } + /* stop collecting packets. */ - if (offer->opts[CD_DHCP_TYPE] == NULL) { + stop_pkt_retransmission(dsmp); - ifsp->if_state = REQUESTING; + /* + * For IPv4, check to see whether we got an OFFER or a BOOTP packet. + * If we got a BOOTP packet, go to the BOUND state now. + */ + if (!isv6 && offer->opts[CD_DHCP_TYPE] == NULL) { + free_pkt_list(&dsmp->dsm_recv_pkt_list); + + if (!set_smach_state(dsmp, REQUESTING)) { + dhcp_restart(dsmp); + return; + } - if (dhcp_bound(ifsp, offer) == 0) { + if (!dhcp_bound(dsmp, offer)) { dhcpmsg(MSG_WARNING, "dhcp_requesting: dhcp_bound " - "failed for %s", ifsp->if_name); - dhcp_restart(ifsp); + "failed for %s", dsmp->dsm_name); + dhcp_restart(dsmp); return; } return; } - /* - * if we got a message from the server, display it. - */ + if (isv6) { + const char *estr, *msg; + const dhcpv6_option_t *d6o; + uint_t olen, msglen; + + /* If there's a Status Code option, print the message */ + d6o = dhcpv6_pkt_option(offer, NULL, DHCPV6_OPT_STATUS_CODE, + &olen); + (void) dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen); + print_server_msg(dsmp, msg, msglen); - if (offer->opts[CD_MESSAGE] != NULL) - print_server_msg(ifsp, offer->opts[CD_MESSAGE]); + /* Copy in the Server ID (guaranteed to be present now) */ + if (!save_server_id(dsmp, offer)) + goto failure; + + /* + * Determine how to send this message. If the Advertisement + * (offer) has the unicast option, then use the address + * specified in the option. Otherwise, send via multicast. + */ + server_unicast_option(dsmp, offer); + + send_v6_request(dsmp); + } else { + /* if we got a message from the server, display it. */ + if (offer->opts[CD_MESSAGE] != NULL) { + print_server_msg(dsmp, + (char *)offer->opts[CD_MESSAGE]->value, + offer->opts[CD_MESSAGE]->len); + } + + /* + * assemble a DHCPREQUEST, with the ciaddr field set to 0, + * since we got here from the INIT state. + */ + + dpkt = init_pkt(dsmp, REQUEST); + + /* + * Grab the lease out of the OFFER; we know it's valid because + * select_best() already checked. The max dhcp message size + * option is set to the interface max, minus the size of the + * udp and ip headers. + */ + + (void) memcpy(&lease, offer->opts[CD_LEASE_TIME]->value, + sizeof (lease_t)); + + (void) add_pkt_opt32(dpkt, CD_LEASE_TIME, lease); + (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, + htons(dsmp->dsm_lif->lif_max - sizeof (struct udpiphdr))); + (void) add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR, + offer->pkt->yiaddr.s_addr); + (void) add_pkt_opt(dpkt, CD_SERVER_ID, + offer->opts[CD_SERVER_ID]->value, + offer->opts[CD_SERVER_ID]->len); + + (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len); + (void) add_pkt_prl(dpkt, dsmp); + + /* + * dsm_reqhost was set for this state machine in + * dhcp_selecting() if the DF_REQUEST_HOSTNAME option set and a + * host name was found + */ + if (dsmp->dsm_reqhost != NULL) { + (void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost, + strlen(dsmp->dsm_reqhost)); + } + (void) add_pkt_opt(dpkt, CD_END, NULL, 0); + + /* + * send out the REQUEST, trying retransmissions. either a NAK + * or too many REQUEST attempts will revert us to SELECTING. + */ + + if (!set_smach_state(dsmp, REQUESTING)) { + dhcpmsg(MSG_ERROR, "dhcp_requesting: cannot switch to " + "REQUESTING state; reverting to INIT on %s", + dsmp->dsm_name); + goto failure; + } + + (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), + stop_requesting); + } + + /* all done with the offer */ + free_pkt_entry(offer); + + return; + +failure: + dsmp->dsm_dflags |= DHCP_IF_FAILED; + (void) set_smach_state(dsmp, INIT); + ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); + free_pkt_list(&dsmp->dsm_recv_pkt_list); +} + +/* + * compute_points_v6(): compute the number of "points" for a given v6 + * advertisement. + * + * input: const PKT_LIST *: packet to inspect + * const dhcp_smach_t *: state machine that received the packet + * output: int: -1 to discard, -2 to accept immediately, >=0 for preference. + */ + +static int +compute_points_v6(const PKT_LIST *pkt, const dhcp_smach_t *dsmp) +{ + char abuf[INET6_ADDRSTRLEN]; + int points = 0; + const dhcpv6_option_t *d6o, *d6so; + uint_t olen, solen; + int i; + const char *estr, *msg; + uint_t msglen; /* - * assemble a DHCPREQUEST, with the ciaddr field set to 0, - * since we got here from the INIT state. + * Look through the packet contents. Valid packets must have our + * client ID and a server ID, which has already been checked by + * dhcp_acknak_lif. Bonus points for each option. */ - dpkt = init_pkt(ifsp, REQUEST); + /* One point for having a valid message. */ + points++; /* - * 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. + * Per RFC 3315, if the Advertise message says, "yes, we have no + * bananas today," then ignore the entire message. (Why it's just + * _this_ error and no other is a bit of a mystery, but a standard is a + * standard.) */ + d6o = dhcpv6_pkt_option(pkt, NULL, DHCPV6_OPT_STATUS_CODE, &olen); + if (dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen) == + DHCPV6_STAT_NOADDRS) { + dhcpmsg(MSG_INFO, + "discard advertisement from %s on %s: no address status", + inet_ntop(AF_INET6, + &((struct sockaddr_in6 *)&pkt->pktfrom)->sin6_addr, + abuf, sizeof (abuf)), dsmp->dsm_name); + return (-1); + } - (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); + /* Two points for each batch of offered IP addresses */ + d6o = NULL; + while ((d6o = dhcpv6_pkt_option(pkt, d6o, DHCPV6_OPT_IA_NA, + &olen)) != NULL) { - 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); + /* + * Note that it's possible to have "no bananas" on an + * individual IA. We must look for that here. + * + * RFC 3315 section 17.1.3 does not refer to the status code + * embedded in the IA itself. However, the TAHI test suite + * checks for this specific case. Because it's extremely + * unlikely that any usable server is going to report that it + * has no addresses on a network using DHCP for address + * assignment, we allow such messages to be dropped. + */ + d6so = dhcpv6_find_option( + (const char *)d6o + sizeof (dhcpv6_ia_na_t), + olen - sizeof (dhcpv6_ia_na_t), NULL, + DHCPV6_OPT_STATUS_CODE, &solen); + if (dhcpv6_status_code(d6so, solen, &estr, &msg, &msglen) == + DHCPV6_STAT_NOADDRS) + return (-1); + points += 2; + } /* - * if_reqhost was set for this interface in dhcp_selecting() - * if the DF_REQUEST_HOSTNAME option set and a host name was - * found + * Note that we drive on in the case where there are no addresses. The + * hope here is that we'll at least get some useful configuration + * information. */ - 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); + /* One point for each requested option */ + for (i = 0; i < dsmp->dsm_prllen; i++) { + if (dhcpv6_pkt_option(pkt, NULL, dsmp->dsm_prl[i], NULL) != + NULL) + points++; + } /* - * send out the REQUEST, trying retransmissions. either a NAK - * or too many REQUEST attempts will revert us to SELECTING. + * Ten points for each point of "preference." Note: the value 255 is + * special. It means "stop right now and select this server." */ + d6o = dhcpv6_pkt_option(pkt, NULL, DHCPV6_OPT_PREFERENCE, &olen); + if (d6o != NULL && olen == sizeof (*d6o) + 1) { + int pref = *(const uchar_t *)(d6o + 1); + + if (pref == 255) + return (-2); + points += 10 * pref; + } + + return (points); +} + +/* + * compute_points_v4(): compute the number of "points" for a given v4 offer. + * + * input: const PKT_LIST *: packet to inspect + * const dhcp_smach_t *: state machine that received the packet + * output: int: -1 to discard, >=0 for preference. + */ + +static int +compute_points_v4(const PKT_LIST *pkt) +{ + int points = 0; + + if (pkt->opts[CD_DHCP_TYPE] == NULL) { + dhcpmsg(MSG_VERBOSE, "compute_points_v4: valid BOOTP reply"); + goto valid_offer; + } + + if (pkt->opts[CD_LEASE_TIME] == NULL) { + dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER without lease " + "time"); + return (-1); + } + + if (pkt->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) { + dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER with garbled " + "lease time"); + return (-1); + } + + if (pkt->opts[CD_SERVER_ID] == NULL) { + dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER without server " + "id"); + return (-1); + } + + if (pkt->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { + dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER with garbled " + "server id"); + return (-1); + } + + /* valid DHCP OFFER. see if we got our parameters. */ + dhcpmsg(MSG_VERBOSE, "compute_points_v4: valid OFFER packet"); + points += 30; - ifsp->if_state = REQUESTING; - (void) send_pkt(ifsp, dpkt, htonl(INADDR_BROADCAST), stop_requesting); +valid_offer: + if (pkt->rfc1048) + points += 5; /* - * 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. + * 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 (register_acknak(ifsp) == 0) { + if (pkt->opts[CD_VENDOR_SPEC] != NULL) + points += 80; - ifsp->if_state = INIT; - ifsp->if_dflags |= DHCP_IF_FAILED; + if (pkt->opts[CD_SUBNETMASK] != NULL) + points++; - ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY); - async_finish(ifsp); + if (pkt->opts[CD_ROUTER] != NULL) + points++; - dhcpmsg(MSG_ERROR, "dhcp_requesting: cannot register to " - "collect ACK/NAK packets, reverting to INIT on %s", - ifsp->if_name); - } + if (pkt->opts[CD_HOSTNAME] != NULL) + points += 5; + + return (points); } /* - * select_best(): selects the best OFFER packet from a list of OFFER packets + * select_best(): selects the best offer from a list of IPv4 OFFER packets or + * DHCPv6 Advertise packets. * - * input: PKT_LIST **: a list of packets to select the best from + * input: dhcp_smach_t *: state machine with enqueued offers * output: PKT_LIST *: the best packet, or NULL if none are acceptable */ static PKT_LIST * -select_best(PKT_LIST **pkts) +select_best(dhcp_smach_t *dsmp) { - PKT_LIST *current, *best = NULL; - uint32_t points, best_points = 0; + PKT_LIST *current = dsmp->dsm_recv_pkt_list; + PKT_LIST *next, *best = NULL; + int points, best_points = -1; /* * pick out the best offer. point system. - * what's important? + * what's important for IPv4? * - * 0) DHCP + * 0) DHCP (30 points) * 1) no option overload - * 2) encapsulated vendor option + * 2) encapsulated vendor option (80 points) * 3) non-null sname and siaddr fields * 4) non-null file field - * 5) hostname - * 6) subnetmask - * 7) router + * 5) hostname (5 points) + * 6) subnetmask (1 point) + * 7) router (1 point) */ - 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; - } + for (; current != NULL; current = next) { + next = current->next; - if (current->opts[CD_LEASE_TIME] == NULL) { - dhcpmsg(MSG_WARNING, "select_best: OFFER without " - "lease time"); - continue; - } + points = current->isv6 ? + compute_points_v6(current, dsmp) : + compute_points_v4(current); - if (current->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) { - dhcpmsg(MSG_WARNING, "select_best: OFFER with garbled " - "lease time"); + /* + * Just discard any unacceptable entries we encounter. + */ + if (points == -1) { + remque(current); + free_pkt_entry(current); continue; } - if (current->opts[CD_SERVER_ID] == NULL) { - dhcpmsg(MSG_WARNING, "select_best: OFFER without " - "server id"); - continue; - } + dhcpmsg(MSG_DEBUG, "select_best: OFFER had %d points", points); - if (current->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { - dhcpmsg(MSG_WARNING, "select_best: OFFER with garbled " - "server id"); - continue; + /* Special case: stop now and select */ + if (points == -2) { + best = current; + break; } - /* 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; @@ -318,76 +580,49 @@ valid_offer: if (best != NULL) { dhcpmsg(MSG_DEBUG, "select_best: most points: %d", best_points); - remove_from_pkt_list(pkts, best); - } else + remque(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 + * accept_v4_acknak(): determine what to do with a DHCPv4 ACK/NAK based on the + * current state. If we're renewing or rebinding, the ACK + * must be for the same address and must have a new lease + * time. If it's a NAK, then our cache is garbage, and we + * must restart. Finally, call dhcp_bound on accepted + * ACKs. * - * input: 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 + * input: dhcp_smach_t *: the state machine to handle the ACK/NAK + * PKT_LIST *: the ACK/NAK message * output: void */ -/* ARGSUSED */ -void -dhcp_acknak(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) +static void +accept_v4_acknak(dhcp_smach_t *dsmp, PKT_LIST *plp) { - 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); + dhcpmsg(MSG_WARNING, "accept_v4_acknak: ACK packet on " + "%s missing mandatory lease option, ignored", + dsmp->dsm_name); + dsmp->dsm_bad_offers++; + free_pkt_entry(plp); return; } - if ((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); + if ((dsmp->dsm_state == RENEWING || + dsmp->dsm_state == REBINDING) && + dsmp->dsm_leases->dl_lifs->lif_addr != + plp->pkt->yiaddr.s_addr) { + dhcpmsg(MSG_WARNING, "accept_v4_acknak: renewal ACK " + "packet has a different IP address (%s), ignored", + inet_ntoa(plp->pkt->yiaddr)); + dsmp->dsm_bad_offers++; + free_pkt_entry(plp); return; } } @@ -397,15 +632,14 @@ dhcp_acknak(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) * the acknak handler. ACK to BOUND, NAK back to SELECTING. */ - stop_pkt_retransmission(ifsp); - (void) unregister_acknak(ifsp); + stop_pkt_retransmission(dsmp); - 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); - dhcp_restart(ifsp); + if (*plp->opts[CD_DHCP_TYPE]->value == NAK) { + dhcpmsg(MSG_WARNING, "accept_v4_acknak: NAK on interface %s", + dsmp->dsm_name); + dsmp->dsm_bad_offers++; + free_pkt_entry(plp); + dhcp_restart(dsmp); /* * remove any bogus cached configuration we might have @@ -413,78 +647,523 @@ dhcp_acknak(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) * from INIT_REBOOT). */ - (void) remove_hostconf(ifsp->if_name); + (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); return; } if (plp->opts[CD_SERVER_ID] == NULL || plp->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { - dhcpmsg(MSG_ERROR, "dhcp_acknak: ACK with no valid server id, " - "restarting DHCP on %s", ifsp->if_name); - ifsp->if_bad_offers++; - free_pkt_list(&plp); - dhcp_restart(ifsp); + dhcpmsg(MSG_ERROR, "accept_v4_acknak: ACK with no valid " + "server id, restarting DHCP on %s", dsmp->dsm_name); + dsmp->dsm_bad_offers++; + free_pkt_entry(plp); + dhcp_restart(dsmp); return; } - if (plp->opts[CD_MESSAGE] != NULL) - print_server_msg(ifsp, plp->opts[CD_MESSAGE]); + if (plp->opts[CD_MESSAGE] != NULL) { + print_server_msg(dsmp, (char *)plp->opts[CD_MESSAGE]->value, + plp->opts[CD_MESSAGE]->len); + } + + dhcpmsg(MSG_VERBOSE, "accept_v4_acknak: ACK on %s", dsmp->dsm_name); + if (!dhcp_bound(dsmp, plp)) { + dhcpmsg(MSG_WARNING, "accept_v4_acknak: dhcp_bound failed " + "for %s", dsmp->dsm_name); + dhcp_restart(dsmp); + } +} + +/* + * accept_v6_message(): determine what to do with a DHCPv6 message based on the + * current state. + * + * input: dhcp_smach_t *: the state machine to handle the message + * PKT_LIST *: the DHCPv6 message + * const char *: type of message (for logging) + * uchar_t: type of message (extracted from packet) + * output: void + */ + +static void +accept_v6_message(dhcp_smach_t *dsmp, PKT_LIST *plp, const char *pname, + uchar_t recv_type) +{ + const dhcpv6_option_t *d6o; + uint_t olen; + const char *estr, *msg; + uint_t msglen; + int status; - if (dhcp_bound(ifsp, plp) == 0) { - dhcpmsg(MSG_WARNING, "dhcp_acknak: dhcp_bound failed " - "for %s", ifsp->if_name); - dhcp_restart(ifsp); + /* + * All valid DHCPv6 messages must have our Client ID specified. + */ + d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_CLIENTID, &olen); + olen -= sizeof (*d6o); + if (d6o == NULL || olen != dsmp->dsm_cidlen || + memcmp(d6o + 1, dsmp->dsm_cid, olen) != 0) { + dhcpmsg(MSG_VERBOSE, + "accept_v6_message: discarded %s on %s: %s Client ID", + pname, dsmp->dsm_name, d6o == NULL ? "no" : "wrong"); + free_pkt_entry(plp); return; } - dhcpmsg(MSG_VERBOSE, "ACK on interface %s", ifsp->if_name); + /* + * All valid DHCPv6 messages must have a Server ID specified. + * + * If this is a Reply and it's not in response to Solicit, Confirm, + * Rebind, or Information-Request, then it must also match the Server + * ID we're expecting. + * + * For Reply in the Solicit, Confirm, Rebind, and Information-Request + * cases, the Server ID needs to be saved. This is done inside of + * dhcp_bound(). + */ + d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_SERVERID, &olen); + if (d6o == NULL) { + dhcpmsg(MSG_DEBUG, + "accept_v6_message: discarded %s on %s: no Server ID", + pname, dsmp->dsm_name); + free_pkt_entry(plp); + return; + } + if (recv_type == DHCPV6_MSG_REPLY && dsmp->dsm_state != SELECTING && + dsmp->dsm_state != INIT_REBOOT && dsmp->dsm_state != REBINDING && + dsmp->dsm_state != INFORM_SENT) { + olen -= sizeof (*d6o); + if (olen != dsmp->dsm_serveridlen || + memcmp(d6o + 1, dsmp->dsm_serverid, olen) != 0) { + dhcpmsg(MSG_DEBUG, "accept_v6_message: discarded %s on " + "%s: wrong Server ID", pname, dsmp->dsm_name); + free_pkt_entry(plp); + return; + } + } + + /* + * Break out of the switch if the input message needs to be discarded. + * Return from the function if the message has been enqueued or + * consumed. + */ + switch (dsmp->dsm_state) { + case SELECTING: + /* A Reply message signifies a Rapid-Commit. */ + if (recv_type == DHCPV6_MSG_REPLY) { + if (dhcpv6_pkt_option(plp, NULL, + DHCPV6_OPT_RAPID_COMMIT, &olen) == NULL) { + dhcpmsg(MSG_DEBUG, "accept_v6_message: Reply " + "on %s lacks Rapid-Commit; ignoring", + dsmp->dsm_name); + break; + } + dhcpmsg(MSG_VERBOSE, + "accept_v6_message: rapid-commit Reply on %s", + dsmp->dsm_name); + cancel_offer_timer(dsmp); + goto rapid_commit; + } + + /* Otherwise, we're looking for Advertisements. */ + if (recv_type != DHCPV6_MSG_ADVERTISE) + break; + + /* + * Special case: if this advertisement has preference 255, then + * we must stop right now and select this server. + */ + d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_PREFERENCE, + &olen); + if (d6o != NULL && olen == sizeof (*d6o) + 1 && + *(const uchar_t *)(d6o + 1) == 255) { + pkt_smach_enqueue(dsmp, plp); + dhcpmsg(MSG_DEBUG, "accept_v6_message: preference 255;" + " immediate Request on %s", dsmp->dsm_name); + dhcp_requesting(NULL, dsmp); + } else { + pkt_smach_enqueue(dsmp, plp); + } + return; + + case PRE_BOUND: + case BOUND: + /* + * Not looking for anything in these states. (If we + * implemented reconfigure, that might go here.) + */ + break; + + case REQUESTING: + case INIT_REBOOT: + case RENEWING: + case REBINDING: + case INFORM_SENT: + /* + * We're looking for Reply messages. + */ + if (recv_type != DHCPV6_MSG_REPLY) + break; + dhcpmsg(MSG_VERBOSE, + "accept_v6_message: received Reply message on %s", + dsmp->dsm_name); + rapid_commit: + /* + * Extract the status code option. If one is present and the + * request failed, then try to go to another advertisement in + * the list or restart the selection machinery. + */ + d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, + &olen); + status = dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen); + /* + * Check for the UseMulticast status code. If this is present, + * and if we were actually using unicast, then drop back and + * try again. If we weren't using unicast, then just pretend + * we never saw this message -- the peer is confused. (TAHI + * does this.) + */ + if (status == DHCPV6_STAT_USEMCAST) { + if (IN6_IS_ADDR_MULTICAST( + &dsmp->dsm_send_dest.v6.sin6_addr)) { + break; + } else { + free_pkt_entry(plp); + dsmp->dsm_send_dest.v6.sin6_addr = + ipv6_all_dhcp_relay_and_servers; + retransmit_now(dsmp); + return; + } + } + print_server_msg(dsmp, msg, msglen); + /* + * We treat NoBinding at the top level as "success." Granted, + * this doesn't make much sense, but the TAHI test suite does + * this. NoBinding really only makes sense in the context of a + * specific IA, as it refers to the GUID:IAID binding, so + * ignoring it at the top level is safe. + */ + if (status == DHCPV6_STAT_SUCCESS || + status == DHCPV6_STAT_NOBINDING) { + if (dhcp_bound(dsmp, plp)) { + /* + * dhcp_bound will stop retransmission on + * success, if that's called for. + */ + server_unicast_option(dsmp, plp); + } else { + stop_pkt_retransmission(dsmp); + dhcpmsg(MSG_WARNING, "accept_v6_message: " + "dhcp_bound failed for %s", dsmp->dsm_name); + (void) remove_hostconf(dsmp->dsm_name, + dsmp->dsm_isv6); + if (dsmp->dsm_state != INFORM_SENT) + dhcp_restart(dsmp); + } + } else { + dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", + estr); + stop_pkt_retransmission(dsmp); + free_pkt_entry(plp); + if (dsmp->dsm_state == INFORM_SENT) { + (void) set_smach_state(dsmp, INIT); + ipc_action_finish(dsmp, DHCP_IPC_E_SRVFAILED); + } else { + (void) remove_hostconf(dsmp->dsm_name, + dsmp->dsm_isv6); + request_failed(dsmp); + } + } + return; + + case DECLINING: + /* + * We're looking for Reply messages. + */ + if (recv_type != DHCPV6_MSG_REPLY) + break; + stop_pkt_retransmission(dsmp); + /* + * Extract the status code option. Note that it's not a + * failure if the server reports an error. + */ + d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, + &olen); + if (dhcpv6_status_code(d6o, olen, &estr, &msg, + &msglen) == DHCPV6_STAT_SUCCESS) { + print_server_msg(dsmp, msg, msglen); + } else { + dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", + estr); + } + free_pkt_entry(plp); + if (dsmp->dsm_leases == NULL) { + dhcpmsg(MSG_VERBOSE, "accept_v6_message: %s has no " + "leases left; restarting", dsmp->dsm_name); + dhcp_restart(dsmp); + } else if (dsmp->dsm_lif_wait == 0) { + (void) set_smach_state(dsmp, BOUND); + } else { + (void) set_smach_state(dsmp, PRE_BOUND); + } + return; + + case RELEASING: + /* + * We're looking for Reply messages. + */ + if (recv_type != DHCPV6_MSG_REPLY) + break; + stop_pkt_retransmission(dsmp); + /* + * Extract the status code option. + */ + d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, + &olen); + if (dhcpv6_status_code(d6o, olen, &estr, &msg, + &msglen) == DHCPV6_STAT_SUCCESS) { + print_server_msg(dsmp, msg, msglen); + } else { + dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", + estr); + } + free_pkt_entry(plp); + finished_smach(dsmp, DHCP_IPC_SUCCESS); + return; + } + + /* + * Break from above switch means that the message must be discarded. + */ + dhcpmsg(MSG_VERBOSE, + "accept_v6_message: discarded v6 %s on %s; state %s", + pname, dsmp->dsm_name, dhcp_state_to_string(dsmp->dsm_state)); + free_pkt_entry(plp); +} + +/* + * dhcp_acknak_common(): Processes reception of an ACK or NAK packet on the + * global socket -- broadcast packets for IPv4, all + * packets for DHCPv6. + * + * input: iu_eh_t *: unused + * int: the global file descriptor the ACK/NAK arrived on + * short: unused + * iu_event_id_t: unused + * void *: unused + * output: void + */ + +/* ARGSUSED */ +void +dhcp_acknak_common(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, + void *arg) +{ + PKT_LIST *plp; + dhcp_pif_t *pif; + uchar_t recv_type; + const char *pname; + uint_t xid; + dhcp_smach_t *dsmp; + boolean_t isv6 = (fd == v6_sock_fd); + + if ((plp = recv_pkt(fd, get_max_mtu(isv6), isv6, B_FALSE)) == NULL) + return; + + pif = lookup_pif_by_index(plp->ifindex, isv6); + if (pif == NULL) { + dhcpmsg(MSG_VERBOSE, "dhcp_acknak_common: ignored packet " + "received on v%d ifIndex %d", isv6 ? 6 : 4, plp->ifindex); + free_pkt_entry(plp); + return; + } + + recv_type = pkt_recv_type(plp); + pname = pkt_type_to_string(recv_type, isv6); + if (!isv6 && !pkt_v4_match(recv_type, DHCP_PACK|DHCP_PNAK)) { + dhcpmsg(MSG_VERBOSE, "dhcp_acknak_common: ignored %s packet " + "received via broadcast on %s", pname, pif->pif_name); + free_pkt_entry(plp); + return; + } + + if (isv6 && recv_type == DHCPV6_MSG_RECONFIGURE) { + dhcpmsg(MSG_VERBOSE, "dhcp_acknak_common: ignored v6 " + "Reconfigure received via %s", pif->pif_name); + free_pkt_entry(plp); + return; + } + + /* + * Find the corresponding state machine not using DLPI. + * + * Note that DHCPv6 Reconfigure would be special: it's not the reply to + * any transaction, and thus we would need to search on transaction ID + * zero (all state machines) to find the match. However, Reconfigure + * is not yet supported. + */ + xid = pkt_get_xid(plp->pkt, isv6); + for (dsmp = lookup_smach_by_xid(xid, NULL, isv6); dsmp != NULL; + dsmp = lookup_smach_by_xid(xid, dsmp, isv6)) { + if (dsmp->dsm_lif->lif_pif == pif) + break; + } + if (dsmp == NULL || dsmp->dsm_using_dlpi) { + dhcpmsg(MSG_VERBOSE, "dhcp_acknak_common: ignored %s packet " + "received via broadcast %s; %s", pname, pif->pif_name, + dsmp == NULL ? "unknown state machine" : "not using DLPI"); + free_pkt_entry(plp); + return; + } + + /* + * We've got a packet; make sure it's acceptable and cancel the REQUEST + * retransmissions. + */ + if (isv6) + accept_v6_message(dsmp, plp, pname, recv_type); + else + accept_v4_acknak(dsmp, plp); +} + +/* + * request_failed(): Attempt to request an address has failed. Take an + * appropriate action. + * + * input: dhcp_smach_t *: state machine that has failed + * output: void + */ + +static void +request_failed(dhcp_smach_t *dsmp) +{ + PKT_LIST *offer; + + dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers; + if ((offer = select_best(dsmp)) != NULL) { + insque(offer, &dsmp->dsm_recv_pkt_list); + dhcp_requesting(NULL, dsmp); + } else { + dhcpmsg(MSG_INFO, "no offers left on %s; restarting", + dsmp->dsm_name); + dhcp_selecting(dsmp); + } } /* - * dhcp_restart(): restarts DHCP (from INIT) on a given interface + * dhcp_acknak_lif(): Processes reception of an ACK or NAK packet on a given + * logical interface for IPv4 (only). * - * input: struct ifslist *: the interface to restart DHCP on + * input: iu_eh_t *: unused + * int: the global file descriptor the ACK/NAK arrived on + * short: unused + * iu_event_id_t: the id of this event callback with the handler + * void *: pointer to logical interface receiving message * output: void */ +/* ARGSUSED */ void -dhcp_restart(struct ifslist *ifsp) +dhcp_acknak_lif(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, + void *arg) { - if (iu_schedule_timer(tq, DHCP_RESTART_WAIT, dhcp_start, ifsp) == -1) { + dhcp_lif_t *lif = arg; + PKT_LIST *plp; + uchar_t recv_type; + const char *pname; + uint_t xid; + dhcp_smach_t *dsmp; + + if ((plp = recv_pkt(fd, lif->lif_max, B_FALSE, B_FALSE)) == NULL) + return; - ifsp->if_state = INIT; - ifsp->if_dflags |= DHCP_IF_FAILED; + recv_type = pkt_recv_type(plp); + pname = pkt_type_to_string(recv_type, B_FALSE); - ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY); - async_finish(ifsp); + if (!pkt_v4_match(recv_type, DHCP_PACK | DHCP_PNAK)) { + dhcpmsg(MSG_VERBOSE, "dhcp_acknak_lif: ignored v4 %s packet " + "received via LIF %s", pname, lif->lif_name); + free_pkt_entry(plp); + return; + } + /* + * Find the corresponding state machine not using DLPI. + */ + xid = pkt_get_xid(plp->pkt, B_FALSE); + for (dsmp = lookup_smach_by_xid(xid, NULL, B_FALSE); dsmp != NULL; + dsmp = lookup_smach_by_xid(xid, dsmp, B_FALSE)) { + if (dsmp->dsm_lif == lif) + break; + } + if (dsmp == NULL || dsmp->dsm_using_dlpi) { + dhcpmsg(MSG_VERBOSE, "dhcp_acknak_lif: ignored %s packet xid " + "%x received via LIF %s; %s", pname, xid, lif->lif_name, + dsmp == NULL ? "unknown state machine" : "not using DLPI"); + free_pkt_entry(plp); + return; + } + + /* + * We've got a packet; make sure it's acceptable and cancel the REQUEST + * retransmissions. + */ + accept_v4_acknak(dsmp, plp); +} + +/* + * dhcp_restart(): restarts DHCP (from INIT) on a given state machine + * + * input: dhcp_smach_t *: the state machine to restart DHCP on + * output: void + */ + +void +dhcp_restart(dhcp_smach_t *dsmp) +{ + /* + * As we're returning to INIT state, we need to discard any leases we + * may have, and (for v4) canonize the LIF. There's a bit of tension + * between keeping around a possibly still working address, and obeying + * the RFCs. A more elaborate design would be to mark the addresses as + * DEPRECATED, and then start a removal timer. Such a design would + * probably compromise testing. + */ + deprecate_leases(dsmp); + + if (iu_schedule_timer(tq, DHCP_RESTART_WAIT, dhcp_start, dsmp) == -1) { dhcpmsg(MSG_ERROR, "dhcp_restart: cannot schedule dhcp_start, " - "reverting to INIT state on %s", ifsp->if_name); - } else - hold_ifs(ifsp); + "reverting to INIT state on %s", dsmp->dsm_name); + + (void) set_smach_state(dsmp, INIT); + dsmp->dsm_dflags |= DHCP_IF_FAILED; + ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); + } else { + hold_smach(dsmp); + } } /* * stop_requesting(): decides when to stop retransmitting REQUESTs * - * input: struct ifslist *: the interface REQUESTs are being sent on + * input: dhcp_smach_t *: the state machine REQUESTs are being sent from * unsigned int: the number of REQUESTs sent so far * output: boolean_t: B_TRUE if retransmissions should stop */ static boolean_t -stop_requesting(struct ifslist *ifsp, unsigned int n_requests) +stop_requesting(dhcp_smach_t *dsmp, unsigned int n_requests) { - if (n_requests >= DHCP_MAX_REQUESTS) { + uint_t maxreq; - (void) unregister_acknak(ifsp); + maxreq = dsmp->dsm_isv6 ? DHCPV6_REQ_MAX_RC : DHCP_MAX_REQUESTS; + if (n_requests >= maxreq) { - dhcpmsg(MSG_INFO, "no ACK/NAK to REQUESTING REQUEST, " - "restarting DHCP on %s", ifsp->if_name); + dhcpmsg(MSG_INFO, "no ACK/NAK/Reply to REQUEST on %s", + dsmp->dsm_name); - dhcp_selecting(ifsp); + request_failed(dsmp); return (B_TRUE); + } else { + return (B_FALSE); } - - 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 index 9dd9690748..44b427e51c 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/script_handler.c +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/script_handler.c @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -33,12 +32,15 @@ #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 "agent.h" #include "script_handler.h" +#include "states.h" +#include "interface.h" /* * scripts are directly managed by a script helper process. dhcpagent creates @@ -110,38 +112,40 @@ sigterm_handler(int sig) /* * run_script(): it forks a process to execute the script * - * input: struct ifslist *: the interface + * input: dhcp_smach_t *: the state machine * const char *: the event name * int: the pipe end owned by the script helper process * output: void */ + static void -run_script(struct ifslist *ifsp, const char *event, int fd) +run_script(dhcp_smach_t *dsmp, const char *event, int fd) { int n; char c; + char *path; char *name; pid_t pid; time_t now; - extern int errno; if ((pid = fork()) == -1) { return; } if (pid == 0) { - name = strrchr(SCRIPT_PATH, '/') + 1; + path = SCRIPT_PATH; + name = strrchr(path, '/') + 1; /* close all files */ closefrom(0); /* redirect stdin, stdout and stderr to /dev/null */ if ((n = open("/dev/null", O_RDWR)) < 0) - exit(-1); + _exit(127); (void) dup2(n, STDOUT_FILENO); (void) dup2(n, STDERR_FILENO); - (void) execl(SCRIPT_PATH, name, ifsp->if_name, event, NULL); + (void) execl(path, name, dsmp->dsm_name, event, NULL); _exit(127); } @@ -188,30 +192,30 @@ run_script(struct ifslist *ifsp, const char *event, int fd) /* * script_cleanup(): cleanup helper function * - * input: struct ifslist *: the interface + * input: dhcp_smach_t *: the state machine * output: void */ static void -script_cleanup(struct ifslist *ifsp) +script_cleanup(dhcp_smach_t *dsmp) { - 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); + dsmp->dsm_script_helper_pid = -1; + dsmp->dsm_script_pid = -1; + + if (dsmp->dsm_script_fd != -1) { + assert(dsmp->dsm_script_event_id != -1); + assert(dsmp->dsm_script_callback != NULL); + + (void) iu_unregister_event(eh, dsmp->dsm_script_event_id, NULL); + (void) close(dsmp->dsm_script_fd); + dsmp->dsm_script_event_id = -1; + dsmp->dsm_script_fd = -1; + dsmp->dsm_script_callback(dsmp, dsmp->dsm_callback_arg); + dsmp->dsm_script_callback = NULL; + dsmp->dsm_script_event = NULL; + dsmp->dsm_callback_arg = NULL; + + release_smach(dsmp); script_count--; } } @@ -223,7 +227,7 @@ script_cleanup(struct ifslist *ifsp) * int: the end of pipe owned by dhcpagent * short: unused * eh_event_id_t: unused - * void *: the interface + * void *: the state machine * output: void */ @@ -251,17 +255,18 @@ script_exit(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) /* * script_start(): tries to run the script * - * input: struct ifslist *: the interface + * input: dhcp_smach_t *: the state machine * const char *: the event name * script_callback_t: callback function * void *: data to the callback function - * output: int: 1 if script starts successfully + * output: boolean_t: B_TRUE 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) + +boolean_t +script_start(dhcp_smach_t *dsmp, const char *event, + script_callback_t *callback, void *arg, int *status) { int n; int fds[2]; @@ -274,10 +279,10 @@ script_start(struct ifslist *ifsp, const char *event, /* script does not exist */ goto out; } - if (ifsp->if_script_pid != -1) { + if (dsmp->dsm_script_pid != -1) { /* script is running, stop it */ dhcpmsg(MSG_ERROR, "script_start: stop script"); - script_stop(ifsp); + script_stop(dsmp); } /* @@ -305,67 +310,68 @@ script_start(struct ifslist *ifsp, const char *event, (void) close(fds[0]); (void) sigset(SIGCHLD, SIG_DFL); (void) sigset(SIGTERM, sigterm_handler); - run_script(ifsp, event, fds[1]); + run_script(dsmp, 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)) != + if (read(fds[0], &dsmp->dsm_script_pid, sizeof (pid_t)) != sizeof (pid_t)) { (void) kill(pid, SIGKILL); - ifsp->if_script_pid = -1; + dsmp->dsm_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); + dsmp->dsm_script_helper_pid = pid; + event_id = iu_register_event(eh, fds[0], POLLIN, script_exit, dsmp); if (event_id == -1) { (void) close(fds[0]); - script_stop(ifsp); + script_stop(dsmp); 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); + dsmp->dsm_script_event_id = event_id; + dsmp->dsm_script_callback = callback; + dsmp->dsm_script_event = event; + dsmp->dsm_callback_arg = arg; + dsmp->dsm_script_fd = fds[0]; + hold_smach(dsmp); + return (B_TRUE); out: /* callback won't be called in script_exit, so call it here */ - n = callback(ifsp, msg); + n = callback(dsmp, arg); if (status != NULL) *status = n; - return (0); + return (B_FALSE); } /* * script_stop(): stops the script if it is running * - * input: struct ifslist *: the interface + * input: dhcp_smach_t *: the state machine * output: void */ + void -script_stop(struct ifslist *ifsp) +script_stop(dhcp_smach_t *dsmp) { - if (ifsp->if_script_pid != -1) { - assert(ifsp->if_script_helper_pid != -1); + if (dsmp->dsm_script_pid != -1) { + assert(dsmp->dsm_script_helper_pid != -1); /* * sends SIGTERM to the script and asks the helper process * to send SIGKILL if it does not exit after * SCRIPT_TIMEOUT_GRACE seconds. */ - (void) kill(ifsp->if_script_pid, SIGTERM); - (void) kill(ifsp->if_script_helper_pid, SIGTERM); + (void) kill(dsmp->dsm_script_pid, SIGTERM); + (void) kill(dsmp->dsm_script_helper_pid, SIGTERM); } - script_cleanup(ifsp); + script_cleanup(dsmp); } diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/script_handler.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/script_handler.h index caf334a10d..8cb0e90aa6 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/script_handler.h +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/script_handler.h @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -29,7 +28,7 @@ #pragma ident "%Z%%M% %I% %E% SMI" -#include "interface.h" +#include "common.h" #ifdef __cplusplus extern "C" { @@ -61,8 +60,17 @@ enum { SCRIPT_OK, SCRIPT_KILLED, SCRIPT_FAILED }; #define EVENT_EXTEND "EXTEND" #define EVENT_EXPIRE "EXPIRE" #define EVENT_DROP "DROP" +#define EVENT_INFORM "INFORM" #define EVENT_RELEASE "RELEASE" +#define EVENT_BOUND6 "BOUND6" +#define EVENT_EXTEND6 "EXTEND6" +#define EVENT_EXPIRE6 "EXPIRE6" +#define EVENT_DROP6 "DROP6" +#define EVENT_INFORM6 "INFORM6" +#define EVENT_LOSS6 "LOSS6" +#define EVENT_RELEASE6 "RELEASE6" + /* * script location. */ @@ -73,9 +81,9 @@ enum { SCRIPT_OK, SCRIPT_KILLED, SCRIPT_FAILED }; */ extern unsigned int script_count; -int script_start(struct ifslist *, const char *, - script_callback_t *, const char *, int *); -void script_stop(struct ifslist *); +boolean_t script_start(dhcp_smach_t *, const char *, script_callback_t *, + void *, int *); +void script_stop(dhcp_smach_t *); #ifdef __cplusplus } diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/select.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/select.c index a582e7b008..41cc76f7dc 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/select.c +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/select.c @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * SELECTING state of the client state machine. @@ -28,20 +28,17 @@ #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" @@ -51,14 +48,13 @@ #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 + * dhcp_start(): starts DHCP on a state machine * * input: iu_tq_t *: unused - * void *: the interface to start DHCP on + * void *: the state machine on which to start DHCP * output: void */ @@ -66,160 +62,267 @@ static stop_func_t stop_selecting; void dhcp_start(iu_tq_t *tqp, void *arg) { - struct ifslist *ifsp = (struct ifslist *)arg; + dhcp_smach_t *dsmp = arg; - if (check_ifs(ifsp) == 0) { - (void) release_ifs(ifsp); - return; - } + release_smach(dsmp); - dhcpmsg(MSG_VERBOSE, "starting DHCP on %s", ifsp->if_name); - dhcp_selecting(ifsp); + dhcpmsg(MSG_VERBOSE, "starting DHCP on %s", dsmp->dsm_name); + dhcp_selecting(dsmp); } /* - * dhcp_selecting(): sends a DISCOVER and sets up reception for an OFFER + * dhcp_selecting(): sends a DISCOVER and sets up reception of OFFERs for + * IPv4, or sends a Solicit and sets up reception of + * Advertisements for DHCPv6. * - * input: struct ifslist *: the interface to send the DISCOVER on, ... + * input: dhcp_smach_t *: the state machine on which to send the DISCOVER * output: void */ void -dhcp_selecting(struct ifslist *ifsp) +dhcp_selecting(dhcp_smach_t *dsmp) { 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.) + * We first set up to collect OFFER/Advertise packets as they arrive. + * We then send out DISCOVER/Solicit probes. Then we wait a + * user-tunable number of seconds before seeing if OFFERs/ + * Advertisements have come in response to our DISCOVER/Solicit. If + * none have come in, we continue to wait, sending out our DISCOVER/ + * Solicit probes with exponential backoff. If no OFFER/Advertisement + * is ever received, we will wait forever (note that since we're + * event-driven though, we're still able to service other state + * machines). * - * note that we do an reset_ifs() here because we may be - * landing in dhcp_selecting() as a result of restarting DHCP, - * so the ifs may not be fresh. + * Note that we do an reset_smach() here because we may be landing in + * dhcp_selecting() as a result of restarting DHCP, so the state + * machine may not be fresh. */ - reset_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); + reset_smach(dsmp); + if (!set_smach_state(dsmp, SELECTING)) { + dhcpmsg(MSG_ERROR, + "dhcp_selecting: cannot switch to SELECTING state; " + "reverting to INIT on %s", dsmp->dsm_name); + goto failed; + } - if ((ifsp->if_offer_timer = iu_schedule_timer(tq, - ifsp->if_offer_wait, dhcp_requesting, ifsp)) == -1) { - + dsmp->dsm_offer_timer = iu_schedule_timer(tq, + dsmp->dsm_offer_wait, dhcp_requesting, dsmp); + if (dsmp->dsm_offer_timer == -1) { dhcpmsg(MSG_ERROR, "dhcp_selecting: cannot schedule to read " - "OFFER packets"); + "%s packets", dsmp->dsm_isv6 ? "Advertise" : "OFFER"); + goto failed; + } - 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); + hold_smach(dsmp); /* - * Assemble DHCPDISCOVER message. The max dhcp message size - * option is set to the interface max, minus the size of the udp and - * ip headers. + * Assemble and send the DHCPDISCOVER or Solicit message. + * + * If this fails, we'll wait for the select timer to go off + * before trying again. */ + if (dsmp->dsm_isv6) { + dhcpv6_ia_na_t d6in; - 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 ((dpkt = init_pkt(dsmp, DHCPV6_MSG_SOLICIT)) == NULL) { + dhcpmsg(MSG_ERROR, "dhcp_selecting: unable to set up " + "Solicit packet"); + return; + } - 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); + /* Add an IA_NA option for our controlling LIF */ + d6in.d6in_iaid = htonl(dsmp->dsm_lif->lif_iaid); + d6in.d6in_t1 = htonl(0); + d6in.d6in_t2 = htonl(0); + (void) add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA, + (dhcpv6_option_t *)&d6in + 1, + sizeof (d6in) - sizeof (dhcpv6_option_t)); + + /* Option Request option for desired information */ + (void) add_pkt_prl(dpkt, dsmp); + + /* Enable Rapid-Commit */ + (void) add_pkt_opt(dpkt, DHCPV6_OPT_RAPID_COMMIT, NULL, 0); + + /* xxx add Reconfigure Accept */ + + (void) send_pkt_v6(dsmp, dpkt, ipv6_all_dhcp_relay_and_servers, + stop_selecting, DHCPV6_SOL_TIMEOUT, DHCPV6_SOL_MAX_RT); + } else { + if ((dpkt = init_pkt(dsmp, DISCOVER)) == NULL) { + dhcpmsg(MSG_ERROR, "dhcp_selecting: unable to set up " + "DISCOVER packet"); + return; + } - 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"); + /* + * The max DHCP message size option is set to the interface + * MTU, minus the size of the UDP and IP headers. + */ + (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, + htons(dsmp->dsm_lif->lif_max - sizeof (struct udpiphdr))); + (void) add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM)); + + (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len); + (void) add_pkt_prl(dpkt, dsmp); + + if (df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, + DF_REQUEST_HOSTNAME)) { + dhcpmsg(MSG_DEBUG, + "dhcp_selecting: DF_REQUEST_HOSTNAME"); + (void) snprintf(hostfile, sizeof (hostfile), + "/etc/hostname.%s", dsmp->dsm_name); + + if ((reqhost = iffile_to_hostname(hostfile)) != NULL) { + dhcpmsg(MSG_DEBUG, "dhcp_selecting: host %s", + reqhost); + dsmp->dsm_reqhost = strdup(reqhost); + if (dsmp->dsm_reqhost != NULL) + (void) add_pkt_opt(dpkt, CD_HOSTNAME, + dsmp->dsm_reqhost, + strlen(dsmp->dsm_reqhost)); + else + dhcpmsg(MSG_WARNING, + "dhcp_selecting: cannot allocate " + "memory for host name option"); + } } + (void) add_pkt_opt(dpkt, CD_END, NULL, 0); + + (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), + stop_selecting); } - add_pkt_opt(dpkt, CD_END, NULL, 0); + return; - (void) send_pkt(ifsp, dpkt, htonl(INADDR_BROADCAST), stop_selecting); +failed: + (void) set_smach_state(dsmp, INIT); + dsmp->dsm_dflags |= DHCP_IF_FAILED; + ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); } /* - * dhcp_collect_offers(): collects incoming OFFERs to a DISCOVER + * dhcp_collect_dlpi(): collects incoming OFFERs, ACKs, and NAKs via DLPI. * * input: iu_eh_t *: unused - * int: the file descriptor the OFFER arrived on + * int: the file descriptor the mesage arrived on * short: unused * iu_event_id_t: the id of this event callback with the handler - * void *: the interface that received the OFFER + * void *: the physical interface that received the message * output: void */ /* ARGSUSED */ -static void -dhcp_collect_offers(iu_eh_t *eh, int fd, short events, iu_event_id_t id, +void +dhcp_collect_dlpi(iu_eh_t *eh, int fd, short events, iu_event_id_t id, void *arg) { - struct ifslist *ifsp = (struct ifslist *)arg; + dhcp_pif_t *pif = arg; + PKT_LIST *plp; + uchar_t recv_type; + const char *pname; + dhcp_smach_t *dsmp; + uint_t xid; + + if ((plp = recv_pkt(fd, pif->pif_max, B_FALSE, B_TRUE)) == NULL) + return; + + recv_type = pkt_recv_type(plp); + pname = pkt_type_to_string(recv_type, B_FALSE); - if (verify_ifs(ifsp) == 0) { - (void) ioctl(fd, I_FLUSH, FLUSHR|FLUSHW); + /* + * DHCP_PUNTYPED messages are BOOTP server responses. + */ + if (!pkt_v4_match(recv_type, + DHCP_PACK | DHCP_PNAK | DHCP_POFFER | DHCP_PUNTYPED)) { + dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: ignored %s packet " + "received via DLPI on %s", pname, pif->pif_name); + free_pkt_entry(plp); return; } /* - * DHCP_PUNTYPED messages are BOOTP server responses. + * Loop through the state machines that match on XID to find one that's + * interested in this offer. If there are none, then discard. */ + xid = pkt_get_xid(plp->pkt, B_FALSE); + for (dsmp = lookup_smach_by_xid(xid, NULL, B_FALSE); dsmp != NULL; + dsmp = lookup_smach_by_xid(xid, dsmp, B_FALSE)) { + + /* + * Find state machine on correct interface. + */ + if (dsmp->dsm_lif->lif_pif == pif) + break; + } - (void) recv_pkt(ifsp, fd, DHCP_POFFER|DHCP_PUNTYPED, B_TRUE); + if (dsmp == NULL) { + dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: no matching state " + "machine for %s packet XID %#x received via DLPI on %s", + pname, xid, pif->pif_name); + free_pkt_entry(plp); + return; + } + + /* + * Ignore state machines that aren't looking for DLPI messages. + */ + if (!dsmp->dsm_using_dlpi) { + dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: ignore state " + "machine for %s packet XID %#x received via DLPI on %s", + pname, xid, pif->pif_name); + free_pkt_entry(plp); + return; + } + + if (pkt_v4_match(recv_type, DHCP_PACK | DHCP_PNAK)) { + if (!dhcp_bound(dsmp, plp)) { + dhcpmsg(MSG_WARNING, "dhcp_collect_dlpi: dhcp_bound " + "failed for %s", dsmp->dsm_name); + dhcp_restart(dsmp); + return; + } + dhcpmsg(MSG_VERBOSE, "dhcp_collect_dlpi: %s on %s", + pname, dsmp->dsm_name); + } else { + pkt_smach_enqueue(dsmp, plp); + } } /* - * stop_selecting(): decides when to stop retransmitting DISCOVERs (never) + * stop_selecting(): decides when to stop retransmitting DISCOVERs -- only when + * abandoning the state machine. For DHCPv6, this timer may + * go off before the offer wait timer. If so, then this is a + * good time to check for valid Advertisements, so cancel the + * timer and go check. * - * input: struct ifslist *: the interface DISCOVERs are being sent on + * input: dhcp_smach_t *: the state machine DISCOVERs are being sent on * unsigned int: the number of DISCOVERs sent so far * output: boolean_t: B_TRUE if retransmissions should stop */ -/* ARGSUSED */ +/* ARGSUSED1 */ static boolean_t -stop_selecting(struct ifslist *ifsp, unsigned int n_discovers) +stop_selecting(dhcp_smach_t *dsmp, unsigned int n_discovers) { + /* + * If we're using v4 and the underlying LIF we're trying to configure + * has been touched by the user, then bail out. + */ + if (!dsmp->dsm_isv6 && !verify_lif(dsmp->dsm_lif)) { + finished_smach(dsmp, DHCP_IPC_E_UNKIF); + return (B_TRUE); + } + + if (dsmp->dsm_recv_pkt_list != NULL) { + dhcp_requesting(NULL, dsmp); + if (dsmp->dsm_state != SELECTING) + return (B_TRUE); + } return (B_FALSE); } diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/states.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/states.c new file mode 100644 index 0000000000..01214f64fb --- /dev/null +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/states.c @@ -0,0 +1,1489 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + * + * This module contains core functions for managing DHCP state machine + * instances. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdlib.h> +#include <search.h> +#include <string.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/arp.h> +#include <arpa/inet.h> +#include <dhcpmsg.h> +#include <dhcpagent_util.h> +#include <dhcp_stable.h> + +#include "agent.h" +#include "states.h" +#include "interface.h" +#include "defaults.h" +#include "script_handler.h" + +static uint_t global_smach_count; + +static uchar_t *global_duid; +static size_t global_duidlen; + +static void remove_smach(dhcp_smach_t *); + +/* + * iaid_retry(): attempt to write LIF IAID again + * + * input: iu_tq_t *: ignored + * void *: pointer to LIF + * output: none + */ + +/* ARGSUSED */ +static void +iaid_retry(iu_tq_t *tqp, void *arg) +{ + dhcp_lif_t *lif = arg; + + if (write_stable_iaid(lif->lif_name, lif->lif_iaid) == -1) { + if (errno != EROFS) { + dhcpmsg(MSG_ERR, + "iaid_retry: unable to write out IAID for %s", + lif->lif_name); + release_lif(lif); + } else { + lif->lif_iaid_id = iu_schedule_timer(tq, 60, + iaid_retry, lif); + } + } else { + release_lif(lif); + } +} + +/* + * insert_smach(): Create a state machine instance on a given logical + * interface. The state machine holds the caller's LIF + * reference on success, and frees it on failure. + * + * input: dhcp_lif_t *: logical interface name + * int *: set to DHCP_IPC_E_* if creation fails + * output: dhcp_smach_t *: state machine instance + */ + +dhcp_smach_t * +insert_smach(dhcp_lif_t *lif, int *error) +{ + dhcp_smach_t *dsmp, *alt_primary; + boolean_t isv6; + const char *prl; + + if ((dsmp = calloc(1, sizeof (*dsmp))) == NULL) { + dhcpmsg(MSG_ERR, "cannot allocate state machine entry for %s", + lif->lif_name); + remove_lif(lif); + release_lif(lif); + *error = DHCP_IPC_E_MEMORY; + return (NULL); + } + dsmp->dsm_name = lif->lif_name; + dsmp->dsm_lif = lif; + dsmp->dsm_hold_count = 1; + dsmp->dsm_state = INIT; + dsmp->dsm_dflags = DHCP_IF_REMOVED; /* until added to list */ + isv6 = lif->lif_pif->pif_isv6; + + /* + * Now that we have a controlling LIF, we need to assign an IAID to + * that LIF. + */ + if (lif->lif_iaid == 0 && + (lif->lif_iaid = read_stable_iaid(lif->lif_name)) == 0) { + static uint32_t iaidctr = 0x80000000u; + + /* + * If this is a logical interface, then use an arbitrary seed + * value. Otherwise, use the ifIndex. + */ + lif->lif_iaid = make_stable_iaid(lif->lif_name, + strchr(lif->lif_name, ':') != NULL ? iaidctr++ : + lif->lif_pif->pif_index); + dhcpmsg(MSG_INFO, + "insert_smach: manufactured IAID %u for v%d %s", + lif->lif_iaid, isv6 ? 6 : 4, lif->lif_name); + hold_lif(lif); + iaid_retry(NULL, lif); + } + + if (isv6) { + dsmp->dsm_dflags |= DHCP_IF_V6; + dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers; + + /* + * With DHCPv6, we do all of our I/O using the common + * v6_sock_fd. There's no need for per-interface file + * descriptors because we have IPV6_PKTINFO. + */ + } else { + IN6_IPADDR_TO_V4MAPPED(htonl(INADDR_BROADCAST), + &dsmp->dsm_server); + + /* + * With IPv4 DHCP, we start off doing our I/O via DLPI, so open + * that up now. + */ + if (!open_dlpi_pif(lif->lif_pif)) { + dhcpmsg(MSG_ERR, "unable to open DLPI for %s", + lif->lif_name); + /* This will also dispose of the LIF */ + release_smach(dsmp); + *error = DHCP_IPC_E_SOCKET; + return (NULL); + } + dsmp->dsm_using_dlpi = B_TRUE; + } + dsmp->dsm_retrans_timer = -1; + dsmp->dsm_offer_timer = -1; + dsmp->dsm_neg_hrtime = gethrtime(); + dsmp->dsm_script_fd = -1; + dsmp->dsm_script_pid = -1; + dsmp->dsm_script_helper_pid = -1; + dsmp->dsm_script_event_id = -1; + + ipc_action_init(&dsmp->dsm_ia); + + /* + * initialize the parameter request list, if there is one. + */ + + prl = df_get_string(dsmp->dsm_name, isv6, DF_PARAM_REQUEST_LIST); + if (prl == NULL) { + dsmp->dsm_prl = NULL; + } else { + int i; + + for (dsmp->dsm_prllen = 1, i = 0; prl[i] != '\0'; i++) { + if (prl[i] == ',') + dsmp->dsm_prllen++; + } + + dsmp->dsm_prl = malloc(dsmp->dsm_prllen * + sizeof (*dsmp->dsm_prl)); + if (dsmp->dsm_prl == NULL) { + dhcpmsg(MSG_WARNING, "insert_smach: cannot allocate " + "parameter request list for %s (continuing)", + dsmp->dsm_name); + } else { + for (i = 0; i < dsmp->dsm_prllen; prl++, i++) { + dsmp->dsm_prl[i] = strtoul(prl, NULL, 0); + while (*prl != ',' && *prl != '\0') + prl++; + if (*prl == '\0') + break; + } + } + } + + dsmp->dsm_offer_wait = df_get_int(dsmp->dsm_name, isv6, + DF_OFFER_WAIT); + + /* + * If there is no primary of this type, and there is one of the other, + * then make this one primary if it's on the same named PIF. + */ + if (primary_smach(isv6) == NULL && + (alt_primary = primary_smach(!isv6)) != NULL) { + if (strcmp(lif->lif_pif->pif_name, + alt_primary->dsm_lif->lif_pif->pif_name) == 0) { + dhcpmsg(MSG_DEBUG, + "insert_smach: making %s primary for v%d", + dsmp->dsm_name, isv6 ? 6 : 4); + dsmp->dsm_dflags |= DHCP_IF_PRIMARY; + } + } + + /* + * We now have at least one state machine running, so cancel any + * running inactivity timer. + */ + if (inactivity_id != -1 && + iu_cancel_timer(tq, inactivity_id, NULL) == 1) + inactivity_id = -1; + + dsmp->dsm_dflags &= ~DHCP_IF_REMOVED; + insque(dsmp, &lif->lif_smachs); + global_smach_count++; + dhcpmsg(MSG_DEBUG2, "insert_smach: inserted %s", dsmp->dsm_name); + + return (dsmp); +} + +/* + * hold_smach(): acquires a hold on a state machine + * + * input: dhcp_smach_t *: the state machine to acquire a hold on + * output: void + */ + +void +hold_smach(dhcp_smach_t *dsmp) +{ + dsmp->dsm_hold_count++; + + dhcpmsg(MSG_DEBUG2, "hold_smach: hold count on %s: %d", + dsmp->dsm_name, dsmp->dsm_hold_count); +} + +/* + * free_smach(): frees the memory occupied by a state machine + * + * input: dhcp_smach_t *: the DHCP state machine to free + * output: void + */ + +static void +free_smach(dhcp_smach_t *dsmp) +{ + dhcpmsg(MSG_DEBUG, "free_smach: freeing state machine %s", + dsmp->dsm_name); + + deprecate_leases(dsmp); + remove_lif(dsmp->dsm_lif); + release_lif(dsmp->dsm_lif); + free_pkt_list(&dsmp->dsm_recv_pkt_list); + if (dsmp->dsm_ack != dsmp->dsm_orig_ack) + free_pkt_entry(dsmp->dsm_orig_ack); + free_pkt_entry(dsmp->dsm_ack); + free(dsmp->dsm_send_pkt.pkt); + free(dsmp->dsm_cid); + free(dsmp->dsm_prl); + free(dsmp->dsm_routers); + free(dsmp->dsm_reqhost); + free(dsmp); + + /* no big deal if this fails */ + if (global_smach_count == 0 && inactivity_id == -1) { + inactivity_id = iu_schedule_timer(tq, DHCP_INACTIVITY_WAIT, + inactivity_shutdown, NULL); + } +} + +/* + * release_smach(): releases a hold previously acquired on a state machine. + * If the hold count reaches 0, the state machine is freed. + * + * input: dhcp_smach_t *: the state machine entry to release the hold on + * output: void + */ + +void +release_smach(dhcp_smach_t *dsmp) +{ + if (dsmp->dsm_hold_count == 0) { + dhcpmsg(MSG_CRIT, "release_smach: extraneous release"); + return; + } + + if (dsmp->dsm_hold_count == 1 && + !(dsmp->dsm_dflags & DHCP_IF_REMOVED)) { + dhcpmsg(MSG_CRIT, "release_smach: missing removal"); + return; + } + + if (--dsmp->dsm_hold_count == 0) { + free_smach(dsmp); + } else { + dhcpmsg(MSG_DEBUG2, "release_smach: hold count on %s: %d", + dsmp->dsm_name, dsmp->dsm_hold_count); + } +} + +/* + * next_smach(): state machine iterator function + * + * input: dhcp_smach_t *: current state machine (or NULL for list start) + * boolean_t: B_TRUE if DHCPv6, B_FALSE otherwise + * output: dhcp_smach_t *: next state machine in list + */ + +dhcp_smach_t * +next_smach(dhcp_smach_t *dsmp, boolean_t isv6) +{ + dhcp_lif_t *lif; + dhcp_pif_t *pif; + + if (dsmp != NULL) { + if (dsmp->dsm_next != NULL) + return (dsmp->dsm_next); + + if ((lif = dsmp->dsm_lif) != NULL) + lif = lif->lif_next; + for (; lif != NULL; lif = lif->lif_next) { + if (lif->lif_smachs != NULL) + return (lif->lif_smachs); + } + + if ((pif = dsmp->dsm_lif->lif_pif) != NULL) + pif = pif->pif_next; + } else { + pif = isv6 ? v6root : v4root; + } + for (; pif != NULL; pif = pif->pif_next) { + for (lif = pif->pif_lifs; lif != NULL; lif = lif->lif_next) { + if (lif->lif_smachs != NULL) + return (lif->lif_smachs); + } + } + return (NULL); +} + +/* + * primary_smach(): loop through all state machines of the given type (v4 or + * v6) in the system, and locate the one that's primary. + * + * input: boolean_t: B_TRUE for IPv6 + * output: dhcp_smach_t *: the primary state machine + */ + +dhcp_smach_t * +primary_smach(boolean_t isv6) +{ + dhcp_smach_t *dsmp; + + for (dsmp = next_smach(NULL, isv6); dsmp != NULL; + dsmp = next_smach(dsmp, isv6)) { + if (dsmp->dsm_dflags & DHCP_IF_PRIMARY) + break; + } + return (dsmp); +} + +/* + * make_primary(): designate a given state machine as being the primary + * instance on the primary interface. Note that the user often + * thinks in terms of a primary "interface" (rather than just + * an instance), so we go to lengths here to keep v4 and v6 in + * sync. + * + * input: dhcp_smach_t *: the primary state machine + * output: none + */ + +void +make_primary(dhcp_smach_t *dsmp) +{ + dhcp_smach_t *old_primary, *alt_primary; + dhcp_pif_t *pif; + + if ((old_primary = primary_smach(dsmp->dsm_isv6)) != NULL) + old_primary->dsm_dflags &= ~DHCP_IF_PRIMARY; + dsmp->dsm_dflags |= DHCP_IF_PRIMARY; + + /* + * Find the primary for the other protocol. + */ + alt_primary = primary_smach(!dsmp->dsm_isv6); + + /* + * If it's on a different interface, then cancel that. If it's on the + * same interface, then we're done. + */ + if (alt_primary != NULL) { + if (strcmp(alt_primary->dsm_lif->lif_pif->pif_name, + dsmp->dsm_lif->lif_pif->pif_name) == 0) + return; + alt_primary->dsm_dflags &= ~DHCP_IF_PRIMARY; + } + + /* + * We need a new primary for the other protocol. If the PIF exists, + * there must be at least one state machine. Just choose the first for + * consistency with insert_smach(). + */ + if ((pif = lookup_pif_by_name(dsmp->dsm_lif->lif_pif->pif_name, + !dsmp->dsm_isv6)) != NULL) { + pif->pif_lifs->lif_smachs->dsm_dflags |= DHCP_IF_PRIMARY; + } +} + +/* + * lookup_smach(): finds a state machine by name and type; used for dispatching + * user commands. + * + * input: const char *: the name of the state machine + * boolean_t: B_TRUE if DHCPv6, B_FALSE otherwise + * output: dhcp_smach_t *: the state machine found + */ + +dhcp_smach_t * +lookup_smach(const char *smname, boolean_t isv6) +{ + dhcp_smach_t *dsmp; + + for (dsmp = next_smach(NULL, isv6); dsmp != NULL; + dsmp = next_smach(dsmp, isv6)) { + if (strcmp(dsmp->dsm_name, smname) == 0) + break; + } + return (dsmp); +} + +/* + * lookup_smach_by_uindex(): iterate through running state machines by + * truncated interface index. + * + * input: uint16_t: the interface index (truncated) + * dhcp_smach_t *: the previous state machine, or NULL for start + * boolean_t: B_TRUE for DHCPv6, B_FALSE for IPv4 DHCP + * output: dhcp_smach_t *: next state machine, or NULL at end of list + */ + +dhcp_smach_t * +lookup_smach_by_uindex(uint16_t ifindex, dhcp_smach_t *dsmp, boolean_t isv6) +{ + dhcp_pif_t *pif; + dhcp_lif_t *lif; + + /* + * If the user gives us a state machine, then check that the next one + * available is on the same physical interface. If so, then go ahead + * and return that. + */ + if (dsmp != NULL) { + pif = dsmp->dsm_lif->lif_pif; + if ((dsmp = next_smach(dsmp, isv6)) == NULL) + return (NULL); + if (pif == dsmp->dsm_lif->lif_pif) + return (dsmp); + } else { + /* Otherwise, start at the beginning of the list */ + pif = NULL; + } + + /* + * Find the next physical interface with the same truncated interface + * index, and return the first state machine on that. If there are no + * more physical interfaces that match, then we're done. + */ + do { + pif = lookup_pif_by_uindex(ifindex, pif, isv6); + if (pif == NULL) + return (NULL); + for (lif = pif->pif_lifs; lif != NULL; lif = lif->lif_next) { + if ((dsmp = lif->lif_smachs) != NULL) + break; + } + } while (dsmp == NULL); + return (dsmp); +} + +/* + * lookup_smach_by_xid(): iterate through running state machines by transaction + * id. Transaction ID zero means "all state machines." + * + * input: uint32_t: the transaction id to look up + * dhcp_smach_t *: the previous state machine, or NULL for start + * boolean_t: B_TRUE if DHCPv6, B_FALSE otherwise + * output: dhcp_smach_t *: next state machine, or NULL at end of list + */ + +dhcp_smach_t * +lookup_smach_by_xid(uint32_t xid, dhcp_smach_t *dsmp, boolean_t isv6) +{ + for (dsmp = next_smach(dsmp, isv6); dsmp != NULL; + dsmp = next_smach(dsmp, isv6)) { + if (xid == 0 || + pkt_get_xid(dsmp->dsm_send_pkt.pkt, isv6) == xid) + break; + } + + return (dsmp); +} + +/* + * lookup_smach_by_event(): find a state machine busy with a particular event + * ID. This is used only for error handling. + * + * input: iu_event_id_t: the event id to look up + * output: dhcp_smach_t *: matching state machine, or NULL if none + */ + +dhcp_smach_t * +lookup_smach_by_event(iu_event_id_t eid) +{ + dhcp_smach_t *dsmp; + boolean_t isv6 = B_FALSE; + + for (;;) { + for (dsmp = next_smach(NULL, isv6); dsmp != NULL; + dsmp = next_smach(dsmp, isv6)) { + if ((dsmp->dsm_dflags & DHCP_IF_BUSY) && + eid == dsmp->dsm_ia.ia_eid) + return (dsmp); + } + if (isv6) + break; + isv6 = B_TRUE; + } + + return (dsmp); +} + +/* + * cancel_offer_timer(): stop the offer polling timer on a given state machine + * + * input: dhcp_smach_t *: state machine on which to stop polling for offers + * output: none + */ + +void +cancel_offer_timer(dhcp_smach_t *dsmp) +{ + int retval; + + if (dsmp->dsm_offer_timer != -1) { + retval = iu_cancel_timer(tq, dsmp->dsm_offer_timer, NULL); + dsmp->dsm_offer_timer = -1; + if (retval == 1) + release_smach(dsmp); + } +} + +/* + * cancel_smach_timers(): stop all of the timers related to a given state + * machine, including lease and LIF expiry. + * + * input: dhcp_smach_t *: state machine to cancel + * output: none + */ + +static void +cancel_smach_timers(dhcp_smach_t *dsmp) +{ + dhcp_lease_t *dlp; + dhcp_lif_t *lif; + uint_t nlifs; + + for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) { + cancel_lease_timers(dlp); + lif = dlp->dl_lifs; + nlifs = dlp->dl_nlifs; + for (; nlifs > 0; nlifs--, lif = lif->lif_next) + cancel_lif_timers(lif); + } + + cancel_offer_timer(dsmp); + stop_pkt_retransmission(dsmp); +} + +/* + * remove_smach(): removes a given state machine from the system. marks it + * for being freed (but may not actually free it). + * + * input: dhcp_smach_t *: the state machine to remove + * output: void + */ + +static void +remove_smach(dhcp_smach_t *dsmp) +{ + if (dsmp->dsm_dflags & DHCP_IF_REMOVED) + return; + + dhcpmsg(MSG_DEBUG2, "remove_smach: removing %s", dsmp->dsm_name); + dsmp->dsm_dflags |= DHCP_IF_REMOVED; + remque(dsmp); + global_smach_count--; + + /* + * if we have long term timers, cancel them so that state machine + * resources can be reclaimed in a reasonable amount of time. + */ + cancel_smach_timers(dsmp); + + /* Drop the hold that the LIF's state machine list had on us */ + release_smach(dsmp); +} + +/* + * finished_smach(): we're finished with a given state machine; remove it from + * the system and tell the user (who may have initiated the + * removal process). Note that we remove it from the system + * first to allow back-to-back drop and create invocations. + * + * input: dhcp_smach_t *: the state machine to remove + * int: error for IPC + * output: void + */ + +void +finished_smach(dhcp_smach_t *dsmp, int error) +{ + hold_smach(dsmp); + remove_smach(dsmp); + if (dsmp->dsm_ia.ia_fd != -1) + ipc_action_finish(dsmp, error); + else + (void) async_cancel(dsmp); + release_smach(dsmp); +} + +/* + * set_smach_state(): changes state and updates I/O + * + * input: dhcp_smach_t *: the state machine to change + * DHCPSTATE: the new state + * output: boolean_t: B_TRUE on success, B_FALSE on failure + */ + +boolean_t +set_smach_state(dhcp_smach_t *dsmp, DHCPSTATE state) +{ + if (dsmp->dsm_state != state) { + boolean_t is_bound; + + dhcpmsg(MSG_DEBUG, + "set_smach_state: changing from %s to %s on %s", + dhcp_state_to_string(dsmp->dsm_state), + dhcp_state_to_string(state), dsmp->dsm_name); + + if (!dsmp->dsm_isv6) { + /* + * When we're in a bound state for IPv4, we receive our + * packets through our LIF. Otherwise, we receive them + * through DLPI. Make sure the right one is connected. + * For IPv6, no such change is necessary. + */ + is_bound = (state == BOUND || state == REBINDING || + state == RENEWING || state == RELEASING); + if (dsmp->dsm_using_dlpi && is_bound) { + if (!open_ip_lif(dsmp->dsm_lif)) + return (B_FALSE); + dsmp->dsm_using_dlpi = B_FALSE; + close_dlpi_pif(dsmp->dsm_lif->lif_pif); + } + if (!dsmp->dsm_using_dlpi && !is_bound) { + if (!open_dlpi_pif(dsmp->dsm_lif->lif_pif)) + return (B_FALSE); + dsmp->dsm_using_dlpi = B_TRUE; + close_ip_lif(dsmp->dsm_lif); + } + } + + dsmp->dsm_state = state; + } + return (B_TRUE); +} + +/* + * duid_retry(): attempt to write DUID again + * + * input: iu_tq_t *: ignored + * void *: ignored + * output: none + */ + +/* ARGSUSED */ +static void +duid_retry(iu_tq_t *tqp, void *arg) +{ + if (write_stable_duid(global_duid, global_duidlen) == -1) { + if (errno != EROFS) { + dhcpmsg(MSG_ERR, + "duid_retry: unable to write out DUID"); + } else { + (void) iu_schedule_timer(tq, 60, duid_retry, NULL); + } + } +} + +/* + * get_smach_cid(): gets the client ID for a given state machine. + * + * input: dhcp_smach_t *: the state machine to set up + * output: int: DHCP_IPC_SUCCESS or one of DHCP_IPC_E_* on failure. + */ + +int +get_smach_cid(dhcp_smach_t *dsmp) +{ + uchar_t *client_id; + uint_t client_id_len; + dhcp_lif_t *lif = dsmp->dsm_lif; + dhcp_pif_t *pif = lif->lif_pif; + const char *value; + size_t slen; + + /* + * Look in defaults file for the client-id. If present, this takes + * precedence over all other forms of ID. + */ + + dhcpmsg(MSG_DEBUG, "get_smach_cid: getting default client-id " + "property on %s", dsmp->dsm_name); + value = df_get_string(dsmp->dsm_name, pif->pif_isv6, DF_CLIENT_ID); + if (value != NULL) { + /* + * The Client ID string can have one of three basic forms: + * <decimal>,<data...> + * 0x<hex...> + * <string...> + * + * The first form is an RFC 3315 DUID. This is legal for both + * IPv4 DHCP and DHCPv6. For IPv4, an RFC 4361 Client ID is + * constructed from this value. + * + * The second and third forms are legal for IPv4 only. This is + * a raw Client ID, in hex or ASCII string format. + */ + + if (isdigit(*value) && + value[strspn(value, "0123456789")] == ',') { + char *cp; + ulong_t duidtype; + ulong_t subtype; + + errno = 0; + duidtype = strtoul(value, &cp, 0); + if (value == cp || errno != 0 || *cp != ',' || + duidtype > 65535) { + dhcpmsg(MSG_ERR, "get_smach_cid: cannot parse " + "DUID type in %s", value); + goto no_specified_id; + } + value = cp + 1; + switch (duidtype) { + case DHCPV6_DUID_LL: + case DHCPV6_DUID_LLT: { + int num; + char chr; + + errno = 0; + subtype = strtoul(value, &cp, 0); + if (value == cp || errno != 0 || *cp != ',' || + subtype > 65535) { + dhcpmsg(MSG_ERR, "get_smach_cid: " + "cannot parse MAC type in %s", + value); + goto no_specified_id; + } + value = cp + 1; + client_id_len = pif->pif_isv6 ? 1 : 5; + for (; *cp != '\0'; cp++) { + if (*cp == ':') + client_id_len++; + else if (!isxdigit(*cp)) + break; + } + if (duidtype == DHCPV6_DUID_LL) { + duid_llt_t *dllt; + time_t now; + + client_id_len += sizeof (*dllt); + dllt = malloc(client_id_len); + if (dllt == NULL) + goto alloc_failure; + dsmp->dsm_cid = (uchar_t *)dllt; + dllt->dllt_dutype = htons(duidtype); + dllt->dllt_hwtype = htons(subtype); + now = time(NULL) - DUID_TIME_BASE; + dllt->dllt_time = htonl(now); + cp = (char *)(dllt + 1); + } else { + duid_ll_t *dll; + + client_id_len += sizeof (*dll); + dll = malloc(client_id_len); + if (dll == NULL) + goto alloc_failure; + dsmp->dsm_cid = (uchar_t *)dll; + dll->dll_dutype = htons(duidtype); + dll->dll_hwtype = htons(subtype); + cp = (char *)(dll + 1); + } + num = 0; + while ((chr = *value) != '\0') { + if (isdigit(chr)) { + num = (num << 4) + chr - '0'; + } else if (isxdigit(chr)) { + num = (num << 4) + 10 + chr - + (isupper(chr) ? 'A' : 'a'); + } else if (chr == ':') { + *cp++ = num; + num = 0; + } else { + break; + } + } + break; + } + case DHCPV6_DUID_EN: { + duid_en_t *den; + + errno = 0; + subtype = strtoul(value, &cp, 0); + if (value == cp || errno != 0 || *cp != ',') { + dhcpmsg(MSG_ERR, "get_smach_cid: " + "cannot parse enterprise in %s", + value); + goto no_specified_id; + } + value = cp + 1; + slen = strlen(value); + client_id_len = (slen + 1) / 2; + den = malloc(sizeof (*den) + client_id_len); + if (den == NULL) + goto alloc_failure; + den->den_dutype = htons(duidtype); + DHCPV6_SET_ENTNUM(den, subtype); + if (hexascii_to_octet(value, slen, den + 1, + &client_id_len) != 0) { + dhcpmsg(MSG_ERROR, "get_smach_cid: " + "cannot parse hex string in %s", + value); + free(den); + goto no_specified_id; + } + dsmp->dsm_cid = (uchar_t *)den; + break; + } + default: + slen = strlen(value); + client_id_len = (slen + 1) / 2; + cp = malloc(client_id_len); + if (cp == NULL) + goto alloc_failure; + if (hexascii_to_octet(value, slen, cp, + &client_id_len) != 0) { + dhcpmsg(MSG_ERROR, "get_smach_cid: " + "cannot parse hex string in %s", + value); + free(cp); + goto no_specified_id; + } + dsmp->dsm_cid = (uchar_t *)cp; + break; + } + dsmp->dsm_cidlen = client_id_len; + if (!pif->pif_isv6) { + (void) memmove(dsmp->dsm_cid + 5, + dsmp->dsm_cid, client_id_len - 5); + dsmp->dsm_cid[0] = 255; + dsmp->dsm_cid[1] = lif->lif_iaid >> 24; + dsmp->dsm_cid[2] = lif->lif_iaid >> 16; + dsmp->dsm_cid[3] = lif->lif_iaid >> 8; + dsmp->dsm_cid[4] = lif->lif_iaid; + } + return (DHCP_IPC_SUCCESS); + } + + if (pif->pif_isv6) { + dhcpmsg(MSG_ERROR, + "get_smach_cid: client ID for %s invalid: %s", + dsmp->dsm_name, value); + } else if (strncasecmp("0x", value, 2) == 0 && + value[2] != '\0') { + /* skip past the 0x and convert the value to binary */ + value += 2; + slen = strlen(value); + client_id_len = (slen + 1) / 2; + dsmp->dsm_cid = malloc(client_id_len); + if (dsmp->dsm_cid == NULL) + goto alloc_failure; + if (hexascii_to_octet(value, slen, dsmp->dsm_cid, + &client_id_len) == 0) { + dsmp->dsm_cidlen = client_id_len; + return (DHCP_IPC_SUCCESS); + } + dhcpmsg(MSG_WARNING, "get_smach_cid: cannot convert " + "hex value for Client ID on %s", dsmp->dsm_name); + } else { + client_id_len = strlen(value); + dsmp->dsm_cid = malloc(client_id_len); + if (dsmp->dsm_cid == NULL) + goto alloc_failure; + (void) memcpy(dsmp->dsm_cid, value, client_id_len); + return (DHCP_IPC_SUCCESS); + } + } +no_specified_id: + + /* + * There was either no user-specified Client ID value, or we were + * unable to parse it. We need to determine if a Client ID is required + * and, if so, generate one. + * + * If it's IPv4 and not a logical interface, then we need to preserve + * backward-compatibility by avoiding new-fangled DUID/IAID + * construction. + */ + if (!pif->pif_isv6 && strchr(dsmp->dsm_name, ':') == NULL) { + if (pif->pif_hwtype == ARPHRD_IB) { + /* + * This comes from the DHCP over IPoIB specification. + * In the absence of an user specified client id, IPoIB + * automatically uses the required format, with the + * unique 4 octet value set to 0 (since IPoIB driver + * allows only a single interface on a port with a + * specific GID to belong to an IP subnet (PSARC + * 2001/289, FWARC 2002/702). + * + * Type Client-Identifier + * +-----+-----+-----+-----+-----+----....----+ + * | 0 | 0 (4 octets) | GID (16 octets)| + * +-----+-----+-----+-----+-----+----....----+ + */ + dsmp->dsm_cidlen = 1 + 4 + 16; + dsmp->dsm_cid = client_id = malloc(dsmp->dsm_cidlen); + if (dsmp->dsm_cid == NULL) + goto alloc_failure; + + /* + * Pick the GID from the mac address. The format + * of the hardware address is: + * +-----+-----+-----+-----+----....----+ + * | QPN (4 octets) | GID (16 octets)| + * +-----+-----+-----+-----+----....----+ + */ + (void) memcpy(client_id + 5, pif->pif_hwaddr + 4, + pif->pif_hwlen - 4); + (void) memset(client_id, 0, 5); + } + return (DHCP_IPC_SUCCESS); + } + + /* + * Now check for a saved DUID. If there is one, then use it. If there + * isn't, then generate a new one. For IPv4, we need to construct the + * RFC 4361 Client ID with this value and the LIF's IAID. + */ + if (global_duid == NULL && + (global_duid = read_stable_duid(&global_duidlen)) == NULL) { + global_duid = make_stable_duid(pif->pif_name, &global_duidlen); + if (global_duid == NULL) + goto alloc_failure; + duid_retry(NULL, NULL); + } + + if (pif->pif_isv6) { + dsmp->dsm_cid = malloc(global_duidlen); + if (dsmp->dsm_cid == NULL) + goto alloc_failure; + (void) memcpy(dsmp->dsm_cid, global_duid, global_duidlen); + dsmp->dsm_cidlen = global_duidlen; + } else { + dsmp->dsm_cid = malloc(5 + global_duidlen); + if (dsmp->dsm_cid == NULL) + goto alloc_failure; + dsmp->dsm_cid[0] = 255; + dsmp->dsm_cid[1] = lif->lif_iaid >> 24; + dsmp->dsm_cid[2] = lif->lif_iaid >> 16; + dsmp->dsm_cid[3] = lif->lif_iaid >> 8; + dsmp->dsm_cid[4] = lif->lif_iaid; + (void) memcpy(dsmp->dsm_cid + 5, global_duid, global_duidlen); + dsmp->dsm_cidlen = 5 + global_duidlen; + } + + return (DHCP_IPC_SUCCESS); + +alloc_failure: + dhcpmsg(MSG_ERR, "get_smach_cid: cannot allocate Client Id for %s", + dsmp->dsm_name); + return (DHCP_IPC_E_MEMORY); +} + +/* + * smach_count(): returns the number of state machines running + * + * input: void + * output: uint_t: the number of state machines + */ + +uint_t +smach_count(void) +{ + return (global_smach_count); +} + +/* + * remove_default_routes(): removes a state machine's default routes + * + * input: dhcp_smach_t *: the state machine whose default routes need to be + * removed + * output: void + */ + +void +remove_default_routes(dhcp_smach_t *dsmp) +{ + int idx; + + if (dsmp->dsm_routers != NULL) { + for (idx = dsmp->dsm_nrouters - 1; idx >= 0; idx--) { + if (del_default_route(dsmp->dsm_name, + &dsmp->dsm_routers[idx])) { + dhcpmsg(MSG_DEBUG, "remove_default_routes: " + "removed %s from %s", + inet_ntoa(dsmp->dsm_routers[idx]), + dsmp->dsm_name); + } else { + dhcpmsg(MSG_INFO, "remove_default_routes: " + "unable to remove %s from %s", + inet_ntoa(dsmp->dsm_routers[idx]), + dsmp->dsm_name); + } + } + free(dsmp->dsm_routers); + dsmp->dsm_routers = NULL; + dsmp->dsm_nrouters = 0; + } +} + +/* + * reset_smach(): resets a state machine to its initial state + * + * input: dhcp_smach_t *: the state machine to reset + * output: void + */ + +void +reset_smach(dhcp_smach_t *dsmp) +{ + dsmp->dsm_dflags &= ~DHCP_IF_FAILED; + + remove_default_routes(dsmp); + + free_pkt_list(&dsmp->dsm_recv_pkt_list); + + if (dsmp->dsm_orig_ack != dsmp->dsm_ack) + free_pkt_entry(dsmp->dsm_orig_ack); + + free_pkt_entry(dsmp->dsm_ack); + + dsmp->dsm_ack = dsmp->dsm_orig_ack = NULL; + + cancel_smach_timers(dsmp); + + (void) set_smach_state(dsmp, INIT); + if (dsmp->dsm_isv6) { + dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers; + } else { + IN6_IPADDR_TO_V4MAPPED(htonl(INADDR_BROADCAST), + &dsmp->dsm_server); + } + dsmp->dsm_neg_hrtime = gethrtime(); + dsmp->dsm_script_fd = -1; + dsmp->dsm_script_pid = -1; + dsmp->dsm_script_helper_pid = -1; + dsmp->dsm_script_callback = NULL; + dsmp->dsm_callback_arg = NULL; + dsmp->dsm_script_event_id = -1; + free(dsmp->dsm_reqhost); + dsmp->dsm_reqhost = NULL; +} + +/* + * refresh_smach(): refreshes a given state machine, as though awakened from + * hibernation or by lower layer "link up." + * + * input: dhcp_smach_t *: state machine to refresh + * output: void + */ + +void +refresh_smach(dhcp_smach_t *dsmp) +{ + if (dsmp->dsm_state == BOUND || dsmp->dsm_state == RENEWING || + dsmp->dsm_state == REBINDING) { + dhcpmsg(MSG_WARNING, "refreshing lease on %s", dsmp->dsm_name); + cancel_smach_timers(dsmp); + dhcp_init_reboot(dsmp); + } +} + +/* + * refresh_smachs(): refreshes all finite leases under DHCP control + * + * input: iu_eh_t *: unused + * int: unused + * void *: unused + * output: void + */ + +/* ARGSUSED */ +void +refresh_smachs(iu_eh_t *eh, int sig, void *arg) +{ + boolean_t isv6 = B_FALSE; + dhcp_smach_t *dsmp; + + for (;;) { + for (dsmp = next_smach(NULL, isv6); dsmp != NULL; + dsmp = next_smach(dsmp, isv6)) { + refresh_smach(dsmp); + } + if (isv6) + break; + isv6 = B_TRUE; + } +} + +/* + * nuke_smach_list(): delete the state machine list. For use when the + * dhcpagent is exiting. + * + * input: none + * output: none + */ + +void +nuke_smach_list(void) +{ + boolean_t isv6 = B_FALSE; + dhcp_smach_t *dsmp, *dsmp_next; + + for (;;) { + for (dsmp = next_smach(NULL, isv6); dsmp != NULL; + dsmp = dsmp_next) { + int status; + const char *drop = isv6 ? EVENT_DROP6 : EVENT_DROP; + const char *release = isv6 ? EVENT_RELEASE6 : + EVENT_RELEASE; + + dsmp_next = next_smach(dsmp, isv6); + + cancel_smach_timers(dsmp); + if (dsmp->dsm_script_pid != -1) { + /* + * Stop a script if it is not for DROP or + * RELEASE + */ + if (strcmp(dsmp->dsm_script_event, drop) == 0 || + strcmp(dsmp->dsm_script_event, release) == + 0) { + continue; + } + script_stop(dsmp); + } + + /* + * If the script is started by script_start, dhcp_drop + * and dhcp_release should and will only be called + * after the script exits. + */ + if (df_get_bool(dsmp->dsm_name, isv6, + DF_RELEASE_ON_SIGTERM)) { + if (script_start(dsmp, release, dhcp_release, + "DHCP agent is exiting", &status) == 1) { + continue; + } + if (status == 1) + continue; + } + (void) script_start(dsmp, drop, dhcp_drop, NULL, NULL); + } + if (isv6) + break; + isv6 = B_TRUE; + } +} + +/* + * insert_lease(): Create a lease structure on a given state machine. The + * lease holds a reference to the state machine. + * + * input: dhcp_smach_t *: state machine + * output: dhcp_lease_t *: newly-created lease + */ + +dhcp_lease_t * +insert_lease(dhcp_smach_t *dsmp) +{ + dhcp_lease_t *dlp; + + if ((dlp = calloc(1, sizeof (*dlp))) == NULL) + return (NULL); + dlp->dl_smach = dsmp; + dlp->dl_hold_count = 1; + init_timer(&dlp->dl_t1, 0); + init_timer(&dlp->dl_t2, 0); + insque(dlp, &dsmp->dsm_leases); + dhcpmsg(MSG_DEBUG2, "insert_lease: new lease for %s", dsmp->dsm_name); + return (dlp); +} + +/* + * hold_lease(): acquires a hold on a lease + * + * input: dhcp_lease_t *: the lease to acquire a hold on + * output: void + */ + +void +hold_lease(dhcp_lease_t *dlp) +{ + dlp->dl_hold_count++; + + dhcpmsg(MSG_DEBUG2, "hold_lease: hold count on lease for %s: %d", + dlp->dl_smach->dsm_name, dlp->dl_hold_count); +} + +/* + * release_lease(): releases a hold previously acquired on a lease. + * If the hold count reaches 0, the lease is freed. + * + * input: dhcp_lease_t *: the lease to release the hold on + * output: void + */ + +void +release_lease(dhcp_lease_t *dlp) +{ + if (dlp->dl_hold_count == 0) { + dhcpmsg(MSG_CRIT, "release_lease: extraneous release"); + return; + } + + if (dlp->dl_hold_count == 1 && !dlp->dl_removed) { + dhcpmsg(MSG_CRIT, "release_lease: missing removal"); + return; + } + + if (--dlp->dl_hold_count == 0) { + dhcpmsg(MSG_DEBUG, + "release_lease: freeing lease on state machine %s", + dlp->dl_smach->dsm_name); + free(dlp); + } else { + dhcpmsg(MSG_DEBUG2, + "release_lease: hold count on lease for %s: %d", + dlp->dl_smach->dsm_name, dlp->dl_hold_count); + } +} + +/* + * remove_lease(): removes a given lease from the state machine and drops the + * state machine's hold on the lease. + * + * input: dhcp_lease_t *: the lease to remove + * output: void + */ + +void +remove_lease(dhcp_lease_t *dlp) +{ + if (dlp->dl_removed) { + dhcpmsg(MSG_CRIT, "remove_lease: extraneous removal"); + } else { + dhcp_lif_t *lif, *lifnext; + uint_t nlifs; + + dhcpmsg(MSG_DEBUG, + "remove_lease: removed lease from state machine %s", + dlp->dl_smach->dsm_name); + dlp->dl_removed = B_TRUE; + remque(dlp); + + cancel_lease_timers(dlp); + + lif = dlp->dl_lifs; + nlifs = dlp->dl_nlifs; + for (; nlifs > 0; nlifs--, lif = lifnext) { + lifnext = lif->lif_next; + unplumb_lif(lif); + } + + release_lease(dlp); + } +} + +/* + * cancel_lease_timer(): cancels a lease-related timer + * + * input: dhcp_lease_t *: the lease to operate on + * dhcp_timer_t *: the timer to cancel + * output: void + */ + +static void +cancel_lease_timer(dhcp_lease_t *dlp, dhcp_timer_t *dt) +{ + if (dt->dt_id == -1) + return; + if (cancel_timer(dt)) { + release_lease(dlp); + } else { + dhcpmsg(MSG_WARNING, + "cancel_lease_timer: cannot cancel timer"); + } +} + +/* + * cancel_lease_timers(): cancels an lease's pending timers + * + * input: dhcp_lease_t *: the lease to operate on + * output: void + */ + +void +cancel_lease_timers(dhcp_lease_t *dlp) +{ + cancel_lease_timer(dlp, &dlp->dl_t1); + cancel_lease_timer(dlp, &dlp->dl_t2); +} + +/* + * schedule_lease_timer(): schedules a lease-related timer + * + * input: dhcp_lease_t *: the lease to operate on + * dhcp_timer_t *: the timer to schedule + * iu_tq_callback_t *: the callback to call upon firing + * output: boolean_t: B_TRUE if the timer was scheduled successfully + */ + +boolean_t +schedule_lease_timer(dhcp_lease_t *dlp, dhcp_timer_t *dt, + iu_tq_callback_t *expire) +{ + /* + * If there's a timer running, cancel it and release its lease + * reference. + */ + if (dt->dt_id != -1) { + if (!cancel_timer(dt)) + return (B_FALSE); + release_lease(dlp); + } + + if (schedule_timer(dt, expire, dlp)) { + hold_lease(dlp); + return (B_TRUE); + } else { + dhcpmsg(MSG_WARNING, + "schedule_lease_timer: cannot schedule timer"); + return (B_FALSE); + } +} + +/* + * deprecate_leases(): remove all of the leases from a given state machine + * + * input: dhcp_smach_t *: the state machine + * output: none + */ + +void +deprecate_leases(dhcp_smach_t *dsmp) +{ + dhcp_lease_t *dlp; + + /* + * note that due to infelicities in the routing code, any default + * routes must be removed prior to canonizing or deprecating the LIF. + */ + + remove_default_routes(dsmp); + + while ((dlp = dsmp->dsm_leases) != NULL) + remove_lease(dlp); +} + +/* + * verify_smach(): if the state machine is in a bound state, then verify the + * standing of the configured interfaces. Abandon those that + * the user has modified. If we end up with no valid leases, + * then just terminate the state machine. + * + * input: dhcp_smach_t *: the state machine + * output: boolean_t: B_TRUE if the state machine is still valid. + * note: assumes caller holds a state machine reference; as with most + * callback functions. + */ + +boolean_t +verify_smach(dhcp_smach_t *dsmp) +{ + dhcp_lease_t *dlp, *dlpn; + + if (dsmp->dsm_dflags & DHCP_IF_REMOVED) { + release_smach(dsmp); + return (B_FALSE); + } + + if (!dsmp->dsm_isv6) { + /* + * If this is DHCPv4, then verify the main LIF. + */ + if (!verify_lif(dsmp->dsm_lif)) + goto smach_terminate; + } + + /* + * If we're not in one of the bound states, then there are no LIFs to + * verify here. + */ + if (dsmp->dsm_state != BOUND && + dsmp->dsm_state != RENEWING && + dsmp->dsm_state != REBINDING) { + release_smach(dsmp); + return (B_TRUE); + } + + for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlpn) { + dhcp_lif_t *lif, *lifnext; + uint_t nlifs; + + dlpn = dlp->dl_next; + lif = dlp->dl_lifs; + nlifs = dlp->dl_nlifs; + for (; nlifs > 0; lif = lifnext, nlifs--) { + lifnext = lif->lif_next; + if (!verify_lif(lif)) { + /* + * User has manipulated the interface. Even + * if we plumbed it, we must now disown it. + */ + lif->lif_plumbed = B_FALSE; + remove_lif(lif); + } + } + if (dlp->dl_nlifs == 0) + remove_lease(dlp); + } + + /* + * If there are leases left, then everything's ok. + */ + if (dsmp->dsm_leases != NULL) { + release_smach(dsmp); + return (B_TRUE); + } + +smach_terminate: + finished_smach(dsmp, DHCP_IPC_E_UNKIF); + release_smach(dsmp); + + 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 index f8f6c27572..bd4cfaadcd 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/states.h +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/states.h @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -33,7 +33,11 @@ #include <netinet/dhcp.h> #include <libinetutil.h> -#include "interface.h" +#include "common.h" +#include "ipc_action.h" +#include "async.h" +#include "packet.h" +#include "util.h" /* * interfaces for state transition/action functions. these functions @@ -45,24 +49,274 @@ extern "C" { #endif -void dhcp_acknak(iu_eh_t *, int, short, iu_event_id_t, void *); -int dhcp_adopt(void); -void dhcp_adopt_complete(struct ifslist *); -int dhcp_bound(struct ifslist *, PKT_LIST *); -void dhcp_bound_complete(struct ifslist *); -int dhcp_drop(struct ifslist *, const char *); +/* + * DHCP state machine representation: includes all of the information used for + * a state machine instance. For IPv4, this represents a single logical + * interface and (usually) a leased address. For IPv6, it represents a + * DUID+IAID combination. Note that if multiple DUID+IAID instances are one + * day allowed per interface, this will need to become a list. + */ +struct dhcp_smach_s { + dhcp_smach_t *dsm_next; /* Note: must be first */ + dhcp_smach_t *dsm_prev; + + /* + * The name of the state machine. This is currently just a pointer to + * the controlling LIF's name, but could be otherwise. + */ + const char *dsm_name; + dhcp_lif_t *dsm_lif; /* Controlling LIF */ + uint_t dsm_hold_count; /* reference count */ + + dhcp_lease_t *dsm_leases; /* List of leases */ + uint_t dsm_lif_wait; /* LIFs waiting on DAD */ + uint_t dsm_lif_down; /* LIFs failed */ + + boolean_t dsm_using_dlpi; + + /* + * each state machine can have at most one pending asynchronous + * action, which is represented in a `struct async_action'. + * if that asynchronous action was a result of a user request, + * then the `struct ipc_action' is used to hold information + * about the user request. these structures are opaque to + * users of the ifslist, and the functional interfaces + * provided in async.[ch] and ipc_action.[ch] should be used + * to maintain them. + */ + + ipc_action_t dsm_ia; + async_action_t dsm_async; + + uchar_t *dsm_cid; /* client id */ + uchar_t dsm_cidlen; /* client id len */ + + /* + * current state of the machine + */ + + DHCPSTATE dsm_state; + + uint16_t dsm_dflags; /* DHCP_IF_* (shared with IPC) */ + + uint16_t *dsm_prl; /* if non-NULL, param request list */ + uint_t dsm_prllen; /* param request list len */ + + uint_t dsm_nrouters; /* the number of default routers */ + struct in_addr *dsm_routers; /* an array of default routers */ + + in6_addr_t dsm_server; /* our DHCP server */ + uchar_t *dsm_serverid; /* server DUID for v6 */ + uint_t dsm_serveridlen; /* DUID length */ + + /* + * We retain the very first ack obtained on the state machine to + * provide access to options which were originally assigned by + * the server but may not have been included in subsequent + * acks, as there are servers which do this and customers have + * had unsatisfactory results when using our agent with them. + * ipc_event() in agent.c provides a fallback to the original + * ack when the current ack doesn't have the information + * requested. + * + * Note that neither of these is actually a list of packets. There's + * exactly one packet here, so use free_pkt_entry. + */ + PKT_LIST *dsm_ack; + PKT_LIST *dsm_orig_ack; + + /* + * other miscellaneous variables set or needed in the process + * of acquiring a lease. + */ + + int dsm_offer_wait; /* seconds between sending offers */ + iu_timer_id_t dsm_offer_timer; /* timer associated with offer wait */ + + /* + * time we sent the DISCOVER relative to dsm_neg_hrtime, so that the + * REQUEST can have the same pkt->secs. + */ + + uint16_t dsm_disc_secs; + + /* + * this is a chain of packets which have been received on this + * state machine over some interval of time. the packets may have + * to meet some criteria in order to be put on this list. in + * general, packets are put on this list through recv_pkt() + */ + + PKT_LIST *dsm_recv_pkt_list; + + /* + * these three fields are initially zero, and get incremented + * as the ifslist goes from INIT -> BOUND. if and when the + * ifslist moves to the RENEWING state, these fields are + * reset, so they always either indicate the number of packets + * sent, received, and declined while obtaining the current + * lease (if BOUND), or the number of packets sent, received, + * and declined while attempting to obtain a future lease + * (if any other state). + */ + + uint32_t dsm_sent; + uint32_t dsm_received; + uint32_t dsm_bad_offers; + + /* + * dsm_send_pkt.pkt is dynamically allocated to be as big a + * packet as we can send out on this state machine. the remainder + * of this information is needed to make it easy to handle + * retransmissions. note that other than dsm_bad_offers, all + * of these fields are maintained internally in send_pkt(), + * and consequently should never need to be modified by any + * other functions. + */ + + dhcp_pkt_t dsm_send_pkt; + union { + struct sockaddr_in v4; + struct sockaddr_in6 v6; + } dsm_send_dest; + + /* + * For v4, dsm_send_tcenter is used to track the central timer value in + * milliseconds (4000, 8000, 16000, 32000, 64000), and dsm_send_timeout + * is that value plus the +/- 1000 millisecond fuzz. + * + * For v6, dsm_send_tcenter is the MRT (maximum retransmit timer) + * value, and dsm_send_timeout must be set to the IRT (initial + * retransmit timer) value by the sender. + */ + uint_t dsm_send_timeout; + uint_t dsm_send_tcenter; + stop_func_t *dsm_send_stop_func; + uint32_t dsm_packet_sent; + iu_timer_id_t dsm_retrans_timer; + + /* + * The host name we've been asked to request is remembered + * here between the DISCOVER and the REQUEST. (v4 only) + */ + char *dsm_reqhost; + + /* + * V4 and V6 use slightly different timers. For v4, we must count + * seconds from the point where we first try to configure the + * interface. For v6, only seconds while performing a transaction + * matter. + * + * In v4, `dsm_neg_hrtime' represents the time since DHCP started + * configuring the interface, and is used for computing the pkt->secs + * field in v4. In v6, it represents the time since the current + * transaction (if any) was started, and is used for the ELAPSED_TIME + * option. + * + * `dsm_newstart_monosec' represents the time the ACKed REQUEST was + * sent, which represents the start time of a new batch of leases. + * When the lease time actually begins (and thus becomes current), + * `dsm_curstart_monosec' is set to `dsm_newstart_monosec'. + */ + hrtime_t dsm_neg_hrtime; + monosec_t dsm_newstart_monosec; + monosec_t dsm_curstart_monosec; + + int dsm_script_fd; + pid_t dsm_script_pid; + pid_t dsm_script_helper_pid; + const char *dsm_script_event; + iu_event_id_t dsm_script_event_id; + void *dsm_callback_arg; + script_callback_t *dsm_script_callback; +}; + +#define dsm_isv6 dsm_lif->lif_pif->pif_isv6 +#define dsm_hwtype dsm_lif->lif_pif->pif_hwtype + +struct dhcp_lease_s { + dhcp_lease_t *dl_next; /* Note: must be first */ + dhcp_lease_t *dl_prev; + + dhcp_smach_t *dl_smach; /* back pointer to state machine */ + dhcp_lif_t *dl_lifs; /* LIFs configured by this lease */ + uint_t dl_nlifs; /* Number of configured LIFs */ + uint_t dl_hold_count; /* reference counter */ + boolean_t dl_removed; /* Set if removed from list */ + boolean_t dl_stale; /* not updated by Renew/bind */ + + /* + * the following fields are set when a lease is acquired, and + * may be updated over the lifetime of the lease. they are + * all reset by reset_smach(). + */ + + dhcp_timer_t dl_t1; /* relative renewal start time, hbo */ + dhcp_timer_t dl_t2; /* relative rebinding start time, hbo */ +}; + +/* The IU event callback functions */ +iu_eh_callback_t dhcp_acknak_common; +iu_eh_callback_t dhcp_acknak_lif; +iu_eh_callback_t dhcp_collect_dlpi; + +/* Common state-machine related routines throughout dhcpagent */ +boolean_t dhcp_adopt(void); +void dhcp_adopt_complete(dhcp_smach_t *); +boolean_t dhcp_bound(dhcp_smach_t *, PKT_LIST *); +void dhcp_bound_complete(dhcp_smach_t *); +int dhcp_drop(dhcp_smach_t *, void *); +void dhcp_deprecate(iu_tq_t *, void *); void dhcp_expire(iu_tq_t *, void *); -int dhcp_extending(struct ifslist *); -void dhcp_inform(struct ifslist *); -void dhcp_init_reboot(struct ifslist *); +boolean_t dhcp_extending(dhcp_smach_t *); +void dhcp_inform(dhcp_smach_t *); +void dhcp_init_reboot(dhcp_smach_t *); void dhcp_rebind(iu_tq_t *, void *); -int dhcp_release(struct ifslist *, const char *); +int dhcp_release(dhcp_smach_t *, void *); void dhcp_renew(iu_tq_t *, void *); void dhcp_requesting(iu_tq_t *, void *); -void dhcp_restart(struct ifslist *); -void dhcp_selecting(struct ifslist *); +void dhcp_restart(dhcp_smach_t *); +void dhcp_selecting(dhcp_smach_t *); void dhcp_start(iu_tq_t *, void *); -void send_decline(struct ifslist *, char *, struct in_addr *); +void send_declines(dhcp_smach_t *); +void send_v6_request(dhcp_smach_t *); +boolean_t save_server_id(dhcp_smach_t *, PKT_LIST *); +void server_unicast_option(dhcp_smach_t *, PKT_LIST *); + +/* State machine support functions in states.c */ +dhcp_smach_t *insert_smach(dhcp_lif_t *, int *); +void hold_smach(dhcp_smach_t *); +void release_smach(dhcp_smach_t *); +dhcp_smach_t *next_smach(dhcp_smach_t *, boolean_t); +dhcp_smach_t *primary_smach(boolean_t); +void make_primary(dhcp_smach_t *); +dhcp_smach_t *lookup_smach(const char *, boolean_t); +dhcp_smach_t *lookup_smach_by_uindex(uint16_t, dhcp_smach_t *, boolean_t); +dhcp_smach_t *lookup_smach_by_xid(uint32_t, dhcp_smach_t *, boolean_t); +dhcp_smach_t *lookup_smach_by_event(iu_event_id_t); +void finished_smach(dhcp_smach_t *, int); +boolean_t set_smach_state(dhcp_smach_t *, DHCPSTATE); +int get_smach_cid(dhcp_smach_t *); +boolean_t verify_smach(dhcp_smach_t *); +uint_t smach_count(void); +void reset_smach(dhcp_smach_t *); +void refresh_smachs(iu_eh_t *, int, void *); +void refresh_smach(dhcp_smach_t *); +void nuke_smach_list(void); +boolean_t schedule_smach_timer(dhcp_smach_t *, int, uint32_t, + iu_tq_callback_t *); +void cancel_offer_timer(dhcp_smach_t *); +void remove_default_routes(dhcp_smach_t *); + +/* Lease-related support functions in states.c */ +dhcp_lease_t *insert_lease(dhcp_smach_t *); +void hold_lease(dhcp_lease_t *); +void release_lease(dhcp_lease_t *); +void remove_lease(dhcp_lease_t *); +void deprecate_leases(dhcp_smach_t *); +void cancel_lease_timers(dhcp_lease_t *); +boolean_t schedule_lease_timer(dhcp_lease_t *, dhcp_timer_t *, + iu_tq_callback_t *); #ifdef __cplusplus } diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.c b/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.c index ff2d574053..0c63ba8dcb 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.c +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.c @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -32,7 +32,6 @@ #include <netinet/in.h> /* struct in_addr */ #include <netinet/dhcp.h> #include <signal.h> -#include <sys/dlpi.h> #include <sys/socket.h> #include <net/route.h> #include <net/if_arp.h> @@ -56,6 +55,9 @@ * o conversion functions -- functions to turn integers into strings, * or to convert between units of a similar measure. * + * o time and timer functions -- functions to handle time measurement + * and events. + * * o ipc-related functions -- functions to simplify the generation of * ipc messages to the agent's clients. * @@ -64,75 +66,49 @@ * * 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 + * input: uchar_t: a DHCP packet type value, RFC 2131 or 3315 + * boolean_t: B_TRUE if IPv6 * output: const char *: the stringified packet type */ const char * -pkt_type_to_string(uchar_t type) +pkt_type_to_string(uchar_t type, boolean_t isv6) { /* - * note: the ordering here allows direct indexing of the table - * based on the RFC2131 packet type value passed in. + * note: the ordering in these arrays allows direct indexing of the + * table based on the RFC packet type value passed in. */ - static const char *types[] = { + static const char *v4types[] = { "BOOTP", "DISCOVER", "OFFER", "REQUEST", "DECLINE", "ACK", "NAK", "RELEASE", "INFORM" }; + static const char *v6types[] = { + NULL, "SOLICIT", "ADVERTISE", "REQUEST", + "CONFIRM", "RENEW", "REBIND", "REPLY", + "RELEASE", "DECLINE", "RECONFIGURE", "INFORMATION-REQUEST", + "RELAY-FORW", "RELAY-REPL" + }; - if (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); + if (isv6) { + if (type >= sizeof (v6types) / sizeof (*v6types) || + v6types[type] == NULL) + return ("<unknown>"); + else + return (v6types[type]); + } else { + if (type >= sizeof (v4types) / sizeof (*v4types) || + v4types[type] == NULL) + return ("<unknown>"); + else + return (v4types[type]); } - - return (0); } /* @@ -181,92 +157,34 @@ monosec_to_time(monosec_t abs_monosec) } /* - * send_ok_reply(): sends an "ok" reply to a request and closes the ipc - * connection + * hrtime_to_monosec(): converts a hrtime_t to monosec_t * - * 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). + * input: hrtime_t: the time to convert + * output: monosec_t: the time in monosec_t */ -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) +monosec_t +hrtime_to_monosec(hrtime_t hrtime) { - 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; + return (hrtime / NANOSEC); } /* * 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 + * input: dhcp_smach_t *: the state machine the message is associated with + * const char *: the string to display + * uint_t: length of string * output: void */ void -print_server_msg(struct ifslist *ifsp, DHCP_OPT *p) +print_server_msg(dhcp_smach_t *dsmp, const char *msg, uint_t msglen) { - dhcpmsg(MSG_INFO, "%s: message from server: %.*s", ifsp->if_name, - p->len, p->value); + if (msglen > 0) { + dhcpmsg(MSG_INFO, "%s: message from server: %.*s", + dsmp->dsm_name, msglen, msg); + } } /* @@ -365,12 +283,19 @@ daemonize(void) default: if (grandparent != 0) { (void) signal(SIGCHLD, SIG_IGN); - dhcpmsg(MSG_DEBUG, "dhcpagent: daemonize: " + /* + * Note that we're not the agent here, so the DHCP + * logging subsystem hasn't been configured yet. + */ + syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: " "waiting for adoption to complete."); if (sleep(DHCP_ADOPT_SLEEP) == 0) { - dhcpmsg(MSG_WARNING, "dhcpagent: daemonize: " - "timed out awaiting adoption."); + syslog(LOG_WARNING | LOG_DAEMON, + "dhcpagent: daemonize: timed out awaiting " + "adoption."); } + syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: " + "wait finished"); } _exit(EXIT_SUCCESS); } @@ -385,10 +310,10 @@ daemonize(void) * 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 + * output: boolean_t: B_TRUE on success, B_FALSE on failure */ -static int +static boolean_t update_default_route(const char *ifname, int type, struct in_addr *gateway_nbo, int flags) { @@ -428,14 +353,14 @@ update_default_route(const char *ifname, int type, struct in_addr *gateway_nbo, * * 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 + * output: boolean_t: B_TRUE on success, B_FALSE otherwise */ -int +boolean_t add_default_route(const char *ifname, struct in_addr *gateway_nbo) { if (strchr(ifname, ':') != NULL) /* see README */ - return (1); + return (B_TRUE); return (update_default_route(ifname, RTM_ADD, gateway_nbo, RTF_UP)); } @@ -445,23 +370,24 @@ add_default_route(const char *ifname, struct in_addr *gateway_nbo) * * 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 + * output: boolean_t: B_TRUE on success, B_FALSE on failure */ -int +boolean_t del_default_route(const char *ifname, struct in_addr *gateway_nbo) { if (strchr(ifname, ':') != NULL) - return (1); + return (B_TRUE); if (gateway_nbo->s_addr == htonl(INADDR_ANY)) /* no router */ - return (1); + return (B_TRUE); return (update_default_route(ifname, RTM_DELETE, gateway_nbo, 0)); } /* - * inactivity_shutdown(): shuts down agent if there are no interfaces to manage + * inactivity_shutdown(): shuts down agent if there are no state machines left + * to manage * * input: iu_tq_t *: unused * void *: unused @@ -472,9 +398,11 @@ del_default_route(const char *ifname, struct in_addr *gateway_nbo) void inactivity_shutdown(iu_tq_t *tqp, void *arg) { - if (ifs_count() > 0) /* shouldn't happen, but... */ + if (smach_count() > 0) /* shouldn't happen, but... */ return; + dhcpmsg(MSG_VERBOSE, "inactivity_shutdown: timed out"); + iu_stop_handling_events(eh, DHCP_REASON_INACTIVITY, NULL, NULL); } @@ -493,144 +421,56 @@ graceful_shutdown(int sig) } /* - * 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 + * bind_sock(): binds a socket to a given IP address and port number * - * input: struct ifslist *: the interface to unregister for - * output: int: 1 on success, 0 on failure + * input: int: the socket to bind + * in_port_t: the port number to bind to, host byte order + * in_addr_t: the address to bind to, host byte order + * output: boolean_t: B_TRUE on success, B_FALSE on failure */ -int -unregister_acknak(struct ifslist *ifsp) +boolean_t +bind_sock(int fd, in_port_t port_hbo, in_addr_t addr_hbo) { - 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); - } + struct sockaddr_in sin; + int on = 1; - dhcpmsg(MSG_DEBUG, "unregister_acknak: unregistered " - "broadcast id %d", ifsp->if_acknak_bcast_id); + (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); - ifsp->if_acknak_bcast_id = -1; - (void) release_ifs(ifsp); - } + (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int)); - return (1); + return (bind(fd, (struct sockaddr *)&sin, sizeof (sin)) == 0); } /* - * bind_sock(): binds a socket to a given IP address and port number + * bind_sock_v6(): binds a socket to a given IP address and port number * * input: int: the socket to bind * in_port_t: the port number to bind to, host byte order - * in_addr_t: the address to bind to, host byte order - * output: int: 1 on success, 0 on failure + * in6_addr_t: the address to bind to, network byte order + * output: boolean_t: B_TRUE on success, B_FALSE on failure */ -int -bind_sock(int fd, in_port_t port_hbo, in_addr_t addr_hbo) +boolean_t +bind_sock_v6(int fd, in_port_t port_hbo, const in6_addr_t *addr_nbo) { - struct sockaddr_in sin; + struct sockaddr_in6 sin6; 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) memset(&sin6, 0, sizeof (struct sockaddr_in6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(port_hbo); + if (addr_nbo != NULL) { + (void) memcpy(&sin6.sin6_addr, addr_nbo, + sizeof (sin6.sin6_addr)); + } (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int)); - return (bind(fd, (struct sockaddr *)&sin, sizeof (sin)) == 0); + return (bind(fd, (struct sockaddr *)&sin6, sizeof (sin6)) == 0); } /* @@ -742,3 +582,112 @@ iffile_to_hostname(const char *path) (void) fclose(fp); return (NULL); } + +/* + * init_timer(): set up a DHCP timer + * + * input: dhcp_timer_t *: the timer to set up + * output: void + */ + +void +init_timer(dhcp_timer_t *dt, lease_t startval) +{ + dt->dt_id = -1; + dt->dt_start = startval; +} + +/* + * cancel_timer(): cancel a DHCP timer + * + * input: dhcp_timer_t *: the timer to cancel + * output: boolean_t: B_TRUE on success, B_FALSE otherwise + */ + +boolean_t +cancel_timer(dhcp_timer_t *dt) +{ + if (dt->dt_id == -1) + return (B_TRUE); + + if (iu_cancel_timer(tq, dt->dt_id, NULL) == 1) { + dt->dt_id = -1; + return (B_TRUE); + } + + return (B_FALSE); +} + +/* + * schedule_timer(): schedule a DHCP timer. Note that it must not be already + * running, and that we can't cancel here. If it were, and + * we did, we'd leak a reference to the callback argument. + * + * input: dhcp_timer_t *: the timer to schedule + * output: boolean_t: B_TRUE on success, B_FALSE otherwise + */ + +boolean_t +schedule_timer(dhcp_timer_t *dt, iu_tq_callback_t *cbfunc, void *arg) +{ + if (dt->dt_id != -1) + return (B_FALSE); + dt->dt_id = iu_schedule_timer(tq, dt->dt_start, cbfunc, arg); + return (dt->dt_id != -1); +} + +/* + * dhcpv6_status_code(): report on a DHCPv6 status code found in an option + * buffer. + * + * input: const dhcpv6_option_t *: pointer to option + * uint_t: option length + * const char **: error string (nul-terminated) + * const char **: message from server (unterminated) + * uint_t *: length of server message + * output: int: -1 on error, or >= 0 for a DHCPv6 status code + */ + +int +dhcpv6_status_code(const dhcpv6_option_t *d6o, uint_t olen, const char **estr, + const char **msg, uint_t *msglenp) +{ + uint16_t status; + static const char *v6_status[] = { + NULL, + "Unknown reason", + "Server has no addresses available", + "Client record unavailable", + "Prefix inappropriate for link", + "Client must use multicast", + "No prefix available" + }; + static char sbuf[32]; + + *estr = ""; + *msg = ""; + *msglenp = 0; + if (d6o == NULL) + return (0); + olen -= sizeof (*d6o); + if (olen < 2) { + *estr = "garbled status code"; + return (-1); + } + + *msg = (const char *)(d6o + 1) + 2; + *msglenp = olen - 2; + + (void) memcpy(&status, d6o + 1, sizeof (status)); + status = ntohs(status); + if (status > 0) { + if (status > DHCPV6_STAT_NOPREFIX) { + (void) snprintf(sbuf, sizeof (sbuf), "status %u", + status); + *estr = sbuf; + } else { + *estr = v6_status[status]; + } + } + return (status); +} diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.h b/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.h index 51070ab658..a426a0e601 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.h +++ b/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.h @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -32,9 +31,12 @@ #include <sys/types.h> #include <netinet/in.h> #include <netinet/dhcp.h> +#include <netinet/dhcp6.h> #include <libinetutil.h> #include <dhcpagent_ipc.h> +#include "common.h" + /* * general utility functions which have no better home. see util.c * for documentation on how to use the exported functions. @@ -44,38 +46,37 @@ extern "C" { #endif -struct ifslist; /* forward declaration */ - -typedef int64_t monosec_t; /* see README for details */ +struct dhcp_timer_s { + iu_timer_id_t dt_id; + lease_t dt_start; /* Initial timer value */ +}; /* conversion functions */ -const char *pkt_type_to_string(uchar_t); +const char *pkt_type_to_string(uchar_t, boolean_t); const char *monosec_to_string(monosec_t); time_t monosec_to_time(monosec_t); -uchar_t dlpi_to_arp(uchar_t); +monosec_t hrtime_to_monosec(hrtime_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); +/* timer functions */ +void init_timer(dhcp_timer_t *, lease_t); +boolean_t cancel_timer(dhcp_timer_t *); +boolean_t schedule_timer(dhcp_timer_t *, iu_tq_callback_t *, void *); /* miscellaneous */ -int add_default_route(const char *, struct in_addr *); -int del_default_route(const char *, struct in_addr *); +boolean_t add_default_route(const char *, struct in_addr *); +boolean_t 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); +void print_server_msg(dhcp_smach_t *, const char *, uint_t); +boolean_t bind_sock(int, in_port_t, in_addr_t); +boolean_t bind_sock_v6(int, in_port_t, const in6_addr_t *); const char *iffile_to_hostname(const char *); +int dhcpv6_status_code(const dhcpv6_option_t *, uint_t, + const char **, const char **, uint_t *); #ifdef __cplusplus } diff --git a/usr/src/cmd/cmd-inet/sbin/dhcpinfo/dhcpinfo.c b/usr/src/cmd/cmd-inet/sbin/dhcpinfo/dhcpinfo.c index ef3d6d1274..d28286f959 100644 --- a/usr/src/cmd/cmd-inet/sbin/dhcpinfo/dhcpinfo.c +++ b/usr/src/cmd/cmd-inet/sbin/dhcpinfo/dhcpinfo.c @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,8 +19,8 @@ * CDDL HEADER END */ /* - * Copyright (c) 1999-2001 by Sun Microsystems, Inc. - * All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" @@ -30,18 +29,20 @@ #include <stdlib.h> #include <stdio.h> #include <ctype.h> +#include <string.h> #include <dhcpagent_ipc.h> #include <dhcp_inittab.h> #include <dhcp_symbol.h> -#define DHCP_INFO_VENDOR_START 256 +#define DHCP_INFO_VENDOR_START_V4 256 +#define DHCP_INFO_VENDOR_START_V6 65536 static void usage(const char *program) { (void) fprintf(stderr, - "usage: %s [-i interface] [-n limit] [-c] code\n" - " %s [-i interface] [-n limit] [-c] identifier\n", + "usage: %s [-c] [-i interface] [-n limit] [-v {4|6}] code\n" + " %s [-c] [-i interface] [-n limit] [-v {4|6}] identifier\n", program, program); exit(DHCP_EXIT_BADARGS); @@ -62,8 +63,11 @@ main(int argc, char **argv) DHCP_OPT *opt; size_t opt_len; boolean_t is_canonical = B_FALSE; + long version = 4; + boolean_t isv6; + uint8_t *valptr; - while ((c = getopt(argc, argv, "cn:i:")) != EOF) { + while ((c = getopt(argc, argv, "ci:n:v:")) != EOF) { switch (c) { @@ -79,6 +83,12 @@ main(int argc, char **argv) max_lines = strtoul(optarg, NULL, 0); break; + case 'v': + version = strtol(optarg, NULL, 0); + if (version != 4 && version != 6) + usage(argv[0]); + break; + case '?': usage(argv[0]); @@ -97,10 +107,13 @@ main(int argc, char **argv) * identifier into a code, then send the request over the wire. */ + isv6 = (version == 6); + if (isalpha(*argv[optind])) { - entry = inittab_getbyname(ITAB_CAT_SITE|ITAB_CAT_STANDARD| - ITAB_CAT_VENDOR|ITAB_CAT_FIELD, ITAB_CONS_INFO, + entry = inittab_getbyname(ITAB_CAT_SITE | ITAB_CAT_STANDARD | + ITAB_CAT_VENDOR | ITAB_CAT_FIELD | + (isv6 ? ITAB_CAT_V6 : 0), ITAB_CONS_INFO, argv[optind]); if (entry == NULL) { @@ -113,20 +126,26 @@ main(int argc, char **argv) optnum.category = entry->ds_category; } else { + ulong_t start; optnum.code = strtoul(argv[optind], 0, 0); - optnum.category = ITAB_CAT_STANDARD|ITAB_CAT_SITE; + optnum.category = ITAB_CAT_STANDARD | ITAB_CAT_SITE; /* * sigh. this is a hack, but it's needed for backward * compatibility with the CA dhcpinfo program. */ - if (optnum.code > DHCP_INFO_VENDOR_START) { - optnum.code -= DHCP_INFO_VENDOR_START; + start = isv6 ? DHCP_INFO_VENDOR_START_V6 : + DHCP_INFO_VENDOR_START_V4; + if (optnum.code > start) { + optnum.code -= start; optnum.category = ITAB_CAT_VENDOR; } + if (isv6) + optnum.category |= ITAB_CAT_V6; + entry = inittab_getbycode(optnum.category, ITAB_CONS_INFO, optnum.code); @@ -144,8 +163,8 @@ main(int argc, char **argv) * send the request to the agent and reap the reply */ - request = dhcp_ipc_alloc_request(DHCP_GET_TAG, ifname, &optnum, - sizeof (dhcp_optnum_t), DHCP_TYPE_OPTNUM); + request = dhcp_ipc_alloc_request(DHCP_GET_TAG | (isv6 ? DHCP_V6 : 0), + ifname, &optnum, sizeof (dhcp_optnum_t), DHCP_TYPE_OPTNUM); if (request == NULL) return (DHCP_EXIT_SYSTEM); @@ -179,26 +198,40 @@ main(int argc, char **argv) * check for protocol error */ - if (opt_len < 2 || (opt_len - 2 != opt->len)) - return (DHCP_EXIT_FAILURE); + if (isv6) { + dhcpv6_option_t d6o; + + if (opt_len < sizeof (d6o)) + return (DHCP_EXIT_FAILURE); + (void) memcpy(&d6o, opt, sizeof (d6o)); + if (opt_len != ntohs(d6o.d6o_len) + sizeof (d6o)) + return (DHCP_EXIT_FAILURE); + valptr = (uint8_t *)opt + sizeof (d6o); + opt_len -= sizeof (d6o); + } else { + if (opt_len < 2 || (opt_len - 2 != opt->len)) + return (DHCP_EXIT_FAILURE); + opt_len -= 2; + valptr = opt->value; + } if (is_canonical) { - value = malloc(opt->len * (sizeof ("0xNN") + 1)); + value = malloc(opt_len * (sizeof ("0xNN") + 1)); if (value == NULL) { (void) fprintf(stderr, "%s: out of memory\n", argv[0]); return (DHCP_EXIT_FAILURE); } - for (i = 0, valuep = value; i < opt->len; i++) - valuep += sprintf(valuep, "0x%02X ", opt->value[i]); + for (i = 0, valuep = value; i < opt_len; i++) + valuep += sprintf(valuep, "0x%02X ", valptr[i]); valuep[-1] = '\0'; gran = 1; } else { - value = inittab_decode(entry, opt->value, opt->len, B_TRUE); + value = inittab_decode(entry, valptr, opt_len, B_TRUE); if (value == NULL) { (void) fprintf(stderr, "%s: cannot decode agent's " "reply\n", argv[0]); diff --git a/usr/src/cmd/cmd-inet/usr.bin/netstat/netstat.c b/usr/src/cmd/cmd-inet/usr.bin/netstat/netstat.c index 0a401766da..f3c1bcc2db 100644 --- a/usr/src/cmd/cmd-inet/usr.bin/netstat/netstat.c +++ b/usr/src/cmd/cmd-inet/usr.bin/netstat/netstat.c @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -107,6 +107,15 @@ extern void unixpr(kstat_ctl_t *kc); (v6)._S6_un._S6_u32[1] == 0xfffffffful && \ (v6)._S6_un._S6_u32[2] == 0xfffffffful) +/* + * This is used as a cushion in the buffer allocation directed by SIOCGLIFNUM. + * Because there's no locking between SIOCGLIFNUM and SIOCGLIFCONF, it's + * possible for an administrator to plumb new interfaces between those two + * calls, resulting in the failure of the latter. This addition makes that + * less likely. + */ +#define LIFN_GUARD_VALUE 10 + typedef struct mib_item_s { struct mib_item_s *next_item; int group; @@ -3286,8 +3295,8 @@ if_report_ip6(mib2_ipv6AddrEntry_t *ap6, /* --------------------- DHCP_REPORT (netstat -D) ------------------------- */ -dhcp_ipc_reply_t * -dhcp_do_ipc(dhcp_ipc_type_t type, const char *ifname) +static boolean_t +dhcp_do_ipc(dhcp_ipc_type_t type, const char *ifname, boolean_t printed_one) { dhcp_ipc_request_t *request; dhcp_ipc_reply_t *reply; @@ -3305,100 +3314,116 @@ dhcp_do_ipc(dhcp_ipc_type_t type, const char *ifname) free(request); error = reply->return_code; + if (error == DHCP_IPC_E_UNKIF) { + free(reply); + return (printed_one); + } if (error != 0) { free(reply); fail(0, "dhcp_do_ipc: %s", dhcp_ipc_strerror(error)); } - return (reply); + if (!printed_one) + (void) printf("%s", dhcp_status_hdr_string()); + + (void) printf("%s", dhcp_status_reply_to_string(reply)); + free(reply); + return (B_TRUE); } /* - * get_ifnames: return a dynamically allocated string of all interface - * names which have all of the IFF_* flags listed in `flags_on' on and - * all of the IFF_* flags in `flags_off' off. If no such interfaces - * are found, "" is returned. If an unexpected failure occurs, NULL - * is returned. + * dhcp_walk_interfaces: walk the list of interfaces that have a given set of + * flags turned on (flags_on) and a given set turned off (flags_off) for a + * given address family (af). For each, print out the DHCP status using + * dhcp_do_ipc. */ -static char * -get_ifnames(int flags_on, int flags_off) +static boolean_t +dhcp_walk_interfaces(uint_t flags_on, uint_t flags_off, int af, + boolean_t printed_one) { - struct ifconf ifc; + struct lifnum lifn; + struct lifconf lifc; int n_ifs, i, sock_fd; - char *ifnames; - sock_fd = socket(AF_INET, SOCK_DGRAM, 0); + sock_fd = socket(af, SOCK_DGRAM, 0); if (sock_fd == -1) - return (NULL); + return (printed_one); - if ((ioctl(sock_fd, SIOCGIFNUM, &n_ifs) == -1) || (n_ifs <= 0)) { - (void) close(sock_fd); - return (NULL); - } + /* + * SIOCGLIFNUM is just an estimate. If the ioctl fails, we don't care; + * just drive on and use SIOCGLIFCONF with increasing buffer sizes, as + * is traditional. + */ + (void) memset(&lifn, 0, sizeof (lifn)); + lifn.lifn_family = af; + lifn.lifn_flags = LIFC_ALLZONES | LIFC_NOXMIT; + if (ioctl(sock_fd, SIOCGLIFNUM, &lifn) == -1) + n_ifs = LIFN_GUARD_VALUE; + else + n_ifs = lifn.lifn_count + LIFN_GUARD_VALUE; - ifnames = calloc(1, n_ifs * (IFNAMSIZ + 1)); - ifc.ifc_len = n_ifs * sizeof (struct ifreq); - ifc.ifc_req = calloc(n_ifs, sizeof (struct ifreq)); - if (ifc.ifc_req != NULL && ifnames != NULL) { + (void) memset(&lifc, 0, sizeof (lifc)); + lifc.lifc_family = af; + lifc.lifc_flags = lifn.lifn_flags; + lifc.lifc_len = n_ifs * sizeof (struct lifreq); + lifc.lifc_buf = malloc(lifc.lifc_len); + if (lifc.lifc_buf != NULL) { - if (ioctl(sock_fd, SIOCGIFCONF, &ifc) == -1) { + if (ioctl(sock_fd, SIOCGLIFCONF, &lifc) == -1) { (void) close(sock_fd); - free(ifnames); - free(ifc.ifc_req); + free(lifc.lifc_buf); return (NULL); } - /* 'for' loop 1: */ - for (i = 0; i < n_ifs; i++) { - - if (ioctl(sock_fd, SIOCGIFFLAGS, &ifc.ifc_req[i]) == 0) - if ((ifc.ifc_req[i].ifr_flags & - (flags_on | flags_off)) != flags_on) - continue; /* 'for' loop 1 */ + n_ifs = lifc.lifc_len / sizeof (struct lifreq); - (void) strcat(ifnames, ifc.ifc_req[i].ifr_name); - (void) strcat(ifnames, " "); - } /* 'for' loop 1 ends */ - - if (strlen(ifnames) > 1) - ifnames[strlen(ifnames) - 1] = '\0'; + for (i = 0; i < n_ifs; i++) { + if (ioctl(sock_fd, SIOCGLIFFLAGS, &lifc.lifc_req[i]) == + 0 && (lifc.lifc_req[i].lifr_flags & (flags_on | + flags_off)) != flags_on) + continue; + printed_one = dhcp_do_ipc(DHCP_STATUS | + (af == AF_INET6 ? DHCP_V6 : 0), + lifc.lifc_req[i].lifr_name, printed_one); + } } - (void) close(sock_fd); - if (ifc.ifc_req != NULL) - free(ifc.ifc_req); - return (ifnames); + free(lifc.lifc_buf); + return (printed_one); } static void dhcp_report(char *ifname) { - int did_alloc = 0; - dhcp_ipc_reply_t *reply; + boolean_t printed_one; - if (!(family_selected(AF_INET))) + if (!family_selected(AF_INET) && !family_selected(AF_INET6)) return; - if (ifname == NULL) { - ifname = get_ifnames(IFF_DHCPRUNNING, 0); - if (ifname == NULL) - fail(0, "dhcp_report: unable to retrieve list of" - " interfaces using DHCP"); - did_alloc++; - } - - (void) printf("%s", dhcp_status_hdr_string()); - - for (ifname = strtok(ifname, " "); - ifname != NULL; - ifname = strtok(NULL, " ")) { - reply = dhcp_do_ipc(DHCP_STATUS, ifname); - (void) printf("%s", dhcp_status_reply_to_string(reply)); - free(reply); + printed_one = B_FALSE; + if (ifname != NULL) { + if (family_selected(AF_INET)) { + printed_one = dhcp_do_ipc(DHCP_STATUS, ifname, + printed_one); + } + if (family_selected(AF_INET6)) { + printed_one = dhcp_do_ipc(DHCP_STATUS | DHCP_V6, + ifname, printed_one); + } + if (!printed_one) { + fail(0, "%s: %s", ifname, + dhcp_ipc_strerror(DHCP_IPC_E_UNKIF)); + } + } else { + if (family_selected(AF_INET)) { + printed_one = dhcp_walk_interfaces(IFF_DHCPRUNNING, + 0, AF_INET, printed_one); + } + if (family_selected(AF_INET6)) { + (void) dhcp_walk_interfaces(IFF_DHCPRUNNING, + IFF_ADDRCONF, AF_INET6, printed_one); + } } - - if (did_alloc) - free(ifname); } /* --------------------- GROUP_REPORT (netstat -g) ------------------------- */ diff --git a/usr/src/cmd/cmd-inet/usr.lib/dsvclockd/datastore.c b/usr/src/cmd/cmd-inet/usr.lib/dsvclockd/datastore.c index c6cc98eddf..e0d0def375 100644 --- a/usr/src/cmd/cmd-inet/usr.lib/dsvclockd/datastore.c +++ b/usr/src/cmd/cmd-inet/usr.lib/dsvclockd/datastore.c @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2005 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -101,7 +100,7 @@ ds_create(const char *ds_name, dsvcd_svc_t *ds_callback) fd = open(door_path, O_RDWR); if (fd != -1) { if (door_info(fd, &info) == 0 && info.di_target != -1) { - dhcpmsg(MSG_ERROR, "%s is in use by process %u", + dhcpmsg(MSG_ERROR, "%s is in use by process %lu", door_path, info.di_target); (void) close(fd); (void) close(ds->ds_doorfd); diff --git a/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/Makefile b/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/Makefile index 7c8b6b78b1..3aca7d0fd9 100644 --- a/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/Makefile +++ b/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/Makefile @@ -18,7 +18,7 @@ # # CDDL HEADER END # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" @@ -41,7 +41,7 @@ ROOTMANIFESTDIR= $(ROOTSVCNETWORKROUTING) # be accessed by -lxnet. In addition -lsocket and -lnsl are used to # capture new not-yet-standard interfaces. Someday -lxnet alone should be enough # when IPv6 inspired new interfaces are part of standards. -LDLIBS += -lxnet -lsocket -lnsl +LDLIBS += -ldhcpagent -lxnet -lsocket -lnsl # these #defines are required to use UNIX 98 interfaces _D_UNIX98_EXTN= -D_XOPEN_SOURCE=500 -D__EXTENSIONS__ diff --git a/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/config.c b/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/config.c index 314a083c47..f1a018308e 100644 --- a/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/config.c +++ b/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/config.c @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2003 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -106,6 +105,7 @@ static struct configinfo iflist[] = { * Default: 3 * MaxRtrAdvInterval */ { "StatelessAddrConf", 0, 1, 1, I_StatelessAddrConf, parse_onoff }, + { "StatefulAddrConf", 0, 1, 1, I_StatefulAddrConf, parse_onoff }, /* * Tmp* variables from RFC 3041, where defaults are defined. */ diff --git a/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/defs.h b/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/defs.h index 5e16cc1fa2..54c6fb6e35 100644 --- a/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/defs.h +++ b/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/defs.h @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -98,6 +98,7 @@ extern struct sockaddr_dl *rta_ifp; #define D_PKTBAD 0x0400 /* Malformed packet */ #define D_PKTOUT 0x0800 /* Sent packet */ #define D_TMP 0x1000 /* RFC3041 mechanism */ +#define D_DHCP 0x2000 /* RFC3315 DHCPv6 (stateful addrs) */ #define IF_SEPARATOR ':' #define IPV6_MAX_HOPS 255 diff --git a/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/main.c b/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/main.c index 2a3507a86d..d29ccc361e 100644 --- a/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/main.c +++ b/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/main.c @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -541,6 +541,7 @@ if_process(int s, char *ifname, boolean_t first) pr->pr_in_use = _B_TRUE; if ((lifr.lifr_flags & IFF_DUPLICATE) && + !(lifr.lifr_flags & IFF_DHCPRUNNING) && (pr->pr_flags & IFF_TEMPORARY)) { in6_addr_t *token; int i; @@ -1041,9 +1042,25 @@ solicit_event(struct phyint *pi, enum solicit_events event, uint_t elapsed) check_daemonize(); return (TIMER_INFINITY); + case RESTART_INIT_SOLICIT: + /* + * This event allows us to start solicitation over again + * without losing the RA flags. We start solicitation over + * when we are missing an interface prefix for a newly- + * encountered DHCP interface. + */ + if (pi->pi_sol_state == INIT_SOLICIT) + return (pi->pi_sol_time_left); + pi->pi_sol_count = ND_MAX_RTR_SOLICITATIONS; + pi->pi_sol_time_left = + GET_RANDOM(0, ND_MAX_RTR_SOLICITATION_DELAY); + pi->pi_sol_state = INIT_SOLICIT; + break; + case START_INIT_SOLICIT: if (pi->pi_sol_state == INIT_SOLICIT) return (pi->pi_sol_time_left); + pi->pi_ra_flags = 0; pi->pi_sol_count = ND_MAX_RTR_SOLICITATIONS; pi->pi_sol_time_left = GET_RANDOM(0, ND_MAX_RTR_SOLICITATION_DELAY); @@ -1073,6 +1090,13 @@ solicit_event(struct phyint *pi, enum solicit_events event, uint_t elapsed) case INIT_SOLICIT: solicit(&v6allrouters, pi); if (--pi->pi_sol_count == 0) { + logmsg(LOG_DEBUG, "solicit_event: giving up on %s\n", + pi->pi_name); + if (pi->pi_StatefulAddrConf) { + pi->pi_ra_flags |= ND_RA_FLAG_MANAGED | + ND_RA_FLAG_OTHER; + start_dhcp(pi); + } pi->pi_sol_state = DONE_SOLICIT; check_daemonize(); return (TIMER_INFINITY); diff --git a/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/ndp.c b/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/ndp.c index e02c12ff8c..2b3f25b189 100644 --- a/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/ndp.c +++ b/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/ndp.c @@ -20,7 +20,7 @@ */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -31,6 +31,9 @@ #include <sys/sysmacros.h> +#include <dhcpagent_ipc.h> +#include <dhcpagent_util.h> + static boolean_t verify_opt_len(struct nd_opt_hdr *opt, int optlen, struct phyint *pi, struct sockaddr_in6 *from); @@ -41,10 +44,10 @@ void incoming_ra(struct phyint *pi, struct nd_router_advert *ra, int len, struct sockaddr_in6 *from, boolean_t loopback); static void incoming_prefix_opt(struct phyint *pi, uchar_t *opt, struct sockaddr_in6 *from, boolean_t loopback); -static void incoming_prefix_onlink(struct phyint *pi, uchar_t *opt, - struct sockaddr_in6 *from, boolean_t loopback); +static void incoming_prefix_onlink(struct phyint *pi, uchar_t *opt); void incoming_prefix_onlink_process(struct prefix *pr, uchar_t *opt); +static void incoming_prefix_stateful(struct phyint *, uchar_t *); static boolean_t incoming_prefix_addrconf(struct phyint *pi, uchar_t *opt, struct sockaddr_in6 *from, boolean_t loopback); @@ -68,8 +71,6 @@ static void verify_mtu_opt(struct phyint *pi, uchar_t *opt, static void update_ra_flag(const struct phyint *pi, const struct sockaddr_in6 *from, int isrouter); -static uint_t ra_flags; /* Global to detect when to trigger DHCP */ - /* * Return a pointer to the specified option buffer. * If not found return NULL. @@ -308,6 +309,61 @@ incoming_rs(struct phyint *pi, struct nd_router_solicit *rs, int len, } /* + * Start up DHCPv6 on a given physical interface. Does not wait for a message + * to be returned from the daemon. + */ +void +start_dhcp(struct phyint *pi) +{ + dhcp_ipc_request_t *request; + dhcp_ipc_reply_t *reply = NULL; + int error; + int type; + + if (dhcp_start_agent(DHCP_IPC_MAX_WAIT) == -1) { + logmsg(LOG_ERR, "start_dhcp: unable to start %s\n", + DHCP_AGENT_PATH); + /* make sure we try again next time there's a chance */ + pi->pi_ra_flags &= ~ND_RA_FLAG_MANAGED & ~ND_RA_FLAG_OTHER; + return; + } + + type = (pi->pi_ra_flags & ND_RA_FLAG_MANAGED) ? DHCP_START : + DHCP_INFORM; + + request = dhcp_ipc_alloc_request(type | DHCP_V6, pi->pi_name, NULL, 0, + DHCP_TYPE_NONE); + if (request == NULL) { + logmsg(LOG_ERR, "start_dhcp: out of memory\n"); + /* make sure we try again next time there's a chance */ + pi->pi_ra_flags &= ~ND_RA_FLAG_MANAGED & ~ND_RA_FLAG_OTHER; + return; + } + + error = dhcp_ipc_make_request(request, &reply, 0); + free(request); + if (error != 0) { + logmsg(LOG_ERR, "start_dhcp: err: %s: %s\n", pi->pi_name, + dhcp_ipc_strerror(error)); + return; + } + + error = reply->return_code; + free(reply); + + /* + * Timeout is considered to be "success" because we don't wait for DHCP + * to do its exchange. + */ + if (error != DHCP_IPC_SUCCESS && error != DHCP_IPC_E_RUNNING && + error != DHCP_IPC_E_TIMEOUT) { + logmsg(LOG_ERR, "start_dhcp: ret: %s: %s\n", pi->pi_name, + dhcp_ipc_strerror(error)); + return; + } +} + +/* * Process a received router advertisement. * Called both when packets arrive as well as when we send RAs. * In the latter case 'loopback' is set. @@ -385,20 +441,34 @@ incoming_ra(struct phyint *pi, struct nd_router_advert *ra, int len, } } - if ((ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) && - !(ra_flags & ND_RA_FLAG_MANAGED)) { - ra_flags |= ND_RA_FLAG_MANAGED; - /* TODO trigger dhcpv6 */ - logmsg(LOG_DEBUG, "incoming_ra: trigger dhcp MANAGED\n"); - } - if ((ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER) && - !(ra_flags & ND_RA_FLAG_OTHER)) { - ra_flags |= ND_RA_FLAG_OTHER; - if (!(ra_flags & ND_RA_FLAG_MANAGED)) { - /* TODO trigger dhcpv6 for non-address info */ - logmsg(LOG_DEBUG, "incoming_ra: trigger dhcp OTHER\n"); + /* + * If the "managed" flag is set, then just assume that the "other" flag + * is set as well. It's not legal to get addresses alone without + * getting other data. + */ + if (ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) + ra->nd_ra_flags_reserved |= ND_RA_FLAG_OTHER; + + /* + * If either the "managed" or "other" bits have turned on, then it's + * now time to invoke DHCP. If only the "other" bit is set, then don't + * get addresses via DHCP; only "other" data. If "managed" is set, + * then we must always get both addresses and "other" data. + */ + if (pi->pi_StatefulAddrConf && + (ra->nd_ra_flags_reserved & ~pi->pi_ra_flags & + (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER))) { + if (debug & D_DHCP) { + logmsg(LOG_DEBUG, + "incoming_ra: trigger dhcp %s on %s\n", + (ra->nd_ra_flags_reserved & ~pi->pi_ra_flags & + ND_RA_FLAG_MANAGED) ? "MANAGED" : "OTHER", + pi->pi_name); } + pi->pi_ra_flags |= ra->nd_ra_flags_reserved; + start_dhcp(pi); } + /* Skip default router code if sent from ourselves */ if (!loopback) { /* Find and update or add default router in list */ @@ -494,8 +564,10 @@ incoming_prefix_opt(struct phyint *pi, uchar_t *opt, } if ((po->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) && good_prefix) { - incoming_prefix_onlink(pi, opt, from, loopback); + incoming_prefix_onlink(pi, opt); } + if (pi->pi_StatefulAddrConf) + incoming_prefix_stateful(pi, opt); } /* @@ -509,10 +581,8 @@ incoming_prefix_opt(struct phyint *pi, uchar_t *opt, * as if a failover happened earlier, the addresses belonging to * a different interface may be found here on this interface. */ -/* ARGSUSED2 */ static void -incoming_prefix_onlink(struct phyint *pi, uchar_t *opt, - struct sockaddr_in6 *from, boolean_t loopback) +incoming_prefix_onlink(struct phyint *pi, uchar_t *opt) { struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt; int plen; @@ -592,13 +662,76 @@ incoming_prefix_onlink_process(struct prefix *pr, uchar_t *opt) } /* + * Process all prefix options by locating the DHCPv6-configured interfaces, and + * applying the netmasks as needed. + */ +static void +incoming_prefix_stateful(struct phyint *pi, uchar_t *opt) +{ + struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt; + struct prefix *pr; + boolean_t foundpref; + char abuf[INET6_ADDRSTRLEN]; + + /* Make sure it's a valid prefix. */ + if (ntohl(po->nd_opt_pi_valid_time) == 0) { + if (debug & D_DHCP) + logmsg(LOG_DEBUG, "incoming_prefix_stateful: ignoring " + "prefix with no valid time\n"); + return; + } + + if (debug & D_DHCP) + logmsg(LOG_DEBUG, "incoming_prefix_stateful(%s, %s/%d)\n", + pi->pi_name, inet_ntop(AF_INET6, + (void *)&po->nd_opt_pi_prefix, abuf, sizeof (abuf)), + po->nd_opt_pi_prefix_len); + foundpref = _B_FALSE; + for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { + if (prefix_equal(po->nd_opt_pi_prefix, pr->pr_address, + po->nd_opt_pi_prefix_len)) { + if ((pr->pr_flags & IFF_DHCPRUNNING) && + pr->pr_prefix_len != po->nd_opt_pi_prefix_len) { + pr->pr_prefix_len = po->nd_opt_pi_prefix_len; + if (pr->pr_flags & IFF_UP) { + if (debug & D_DHCP) + logmsg(LOG_DEBUG, + "incoming_prefix_stateful:" + " set mask on DHCP %s\n", + pr->pr_name); + prefix_update_dhcp(pr); + } + } + if (pr->pr_prefix_len == po->nd_opt_pi_prefix_len && + (!(pr->pr_state & PR_STATIC) || + (pr->pr_flags & IFF_DHCPRUNNING))) + foundpref = _B_TRUE; + } + } + /* + * If there's no matching DHCPv6 prefix present, then create an empty + * one so that we'll be able to configure it later. + */ + if (!foundpref) { + pr = prefix_create(pi, po->nd_opt_pi_prefix, + po->nd_opt_pi_prefix_len, IFF_DHCPRUNNING); + if (pr != NULL) { + pr->pr_state = PR_STATIC; + if (debug & D_DHCP) + logmsg(LOG_DEBUG, + "incoming_prefix_stateful: created dummy " + "prefix for later\n"); + } + } +} + +/* * Process prefix options with the autonomous flag set. * Returns false if this prefix results in a bad address (duplicate) * This function needs to loop to find the same prefix multiple times * as if a failover happened earlier, the addresses belonging to * a different interface may be found here on this interface. */ -/* ARGSUSED2 */ static boolean_t incoming_prefix_addrconf(struct phyint *pi, uchar_t *opt, struct sockaddr_in6 *from, boolean_t loopback) @@ -635,8 +768,9 @@ incoming_prefix_addrconf(struct phyint *pi, uchar_t *opt, if (pr->pr_prefix_len == plen && prefix_equal(po->nd_opt_pi_prefix, pr->pr_prefix, plen)) { - /* Exclude static prefixes */ - if (pr->pr_state & PR_STATIC) + /* Exclude static prefixes and DHCP */ + if ((pr->pr_state & PR_STATIC) || + (pr->pr_flags & IFF_DHCPRUNNING)) continue; if (pr->pr_flags & IFF_TEMPORARY) { /* @@ -1196,7 +1330,7 @@ verify_prefix_opt(struct phyint *pi, uchar_t *opt, char *frombuf) myflag = (adv_pr->adv_pr_AdvAutonomousFlag != 0); if (pktflag != myflag) { logmsg(LOG_INFO, - "RA from %s on %s inconsistent autonumous flag for \n\t" + "RA from %s on %s inconsistent autonomous flag for \n\t" "prefix %s/%u: received %s configuration %s\n", frombuf, pi->pi_name, prefixbuf, adv_pr->adv_pr_prefix_len, (pktflag ? "ON" : "OFF"), diff --git a/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/ndp.xml b/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/ndp.xml index 6d45cbe769..3c9afca895 100644 --- a/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/ndp.xml +++ b/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/ndp.xml @@ -1,7 +1,7 @@ <?xml version="1.0"?> <!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1"> <!-- - Copyright 2006 Sun Microsystems, Inc. All rights reserved. + Copyright 2007 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms. CDDL HEADER START @@ -56,8 +56,7 @@ exec='/lib/svc/method/svc-ndp' timeout_seconds='60'> <method_context> - <method_credential user='root' group='root' -privileges='basic,proc_owner,proc_fork,proc_exec,proc_info,proc_session,file_chown,sys_net_config,net_privaddr,net_icmpaccess'/> + <method_credential user='root' group='root' /> </method_context> </exec_method> diff --git a/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/tables.c b/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/tables.c index 3717f5d6ee..006ce3ab7c 100644 --- a/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/tables.c +++ b/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/tables.c @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -1192,9 +1192,10 @@ prefix_insert(struct phyint *pi, struct prefix *pr) /* * Initialize the prefix from the content of the kernel. * If IFF_ADDRCONF is set we treat it as PR_AUTO (i.e. an addrconf - * prefix). However, we not derive the lifetimes from - * the kernel thus they are set to 1 week. + * prefix). However, we cannot derive the lifetime from + * the kernel, thus it is set to 1 week. * Ignore the prefix if the interface is not IFF_UP. + * If it's from DHCPv6, then we set the netmask. */ int prefix_init_from_k(struct prefix *pr) @@ -1223,38 +1224,92 @@ prefix_init_from_k(struct prefix *pr) } pr->pr_flags = lifr.lifr_flags; - if (ioctl(sock, SIOCGLIFSUBNET, (char *)&lifr) < 0) { - logperror_pr(pr, "prefix_init_from_k: ioctl (get subnet)"); - goto error; - } - if (lifr.lifr_subnet.ss_family != AF_INET6) { - logmsg(LOG_ERR, "ignoring interface %s: not AF_INET6\n", - pr->pr_name); - goto error; - } /* - * Guard against the prefix having non-zero bits after the prefix - * len bits. + * If this is a DHCPv6 interface, then we control the netmask. */ - sin6 = (struct sockaddr_in6 *)&lifr.lifr_subnet; - pr->pr_prefix_len = lifr.lifr_addrlen; - prefix_set(&pr->pr_prefix, sin6->sin6_addr, pr->pr_prefix_len); + if (lifr.lifr_flags & IFF_DHCPRUNNING) { + struct phyint *pi = pr->pr_physical; + struct prefix *pr2; + + pr->pr_prefix_len = IPV6_ABITS; + if (!(lifr.lifr_flags & IFF_UP) || + IN6_IS_ADDR_UNSPECIFIED(&pr->pr_address) || + IN6_IS_ADDR_LINKLOCAL(&pr->pr_address)) { + if (debug & D_DHCP) + logmsg(LOG_DEBUG, "prefix_init_from_k: " + "ignoring DHCP %s not ready\n", + pr->pr_name); + return (0); + } - if (pr->pr_prefix_len != IPV6_ABITS && (pr->pr_flags & IFF_UP) && - IN6_ARE_ADDR_EQUAL(&pr->pr_address, &pr->pr_prefix)) { - char abuf[INET6_ADDRSTRLEN]; + for (pr2 = pi->pi_prefix_list; pr2 != NULL; + pr2 = pr2->pr_next) { + /* + * Examine any non-static (autoconfigured) prefixes as + * well as existing DHCP-controlled prefixes for valid + * prefix length information. + */ + if (pr2->pr_prefix_len != IPV6_ABITS && + (!(pr2->pr_state & PR_STATIC) || + (pr2->pr_flags & IFF_DHCPRUNNING)) && + prefix_equal(pr->pr_prefix, pr2->pr_prefix, + pr2->pr_prefix_len)) { + pr->pr_prefix_len = pr2->pr_prefix_len; + break; + } + } + if (pr2 == NULL) { + if (debug & D_DHCP) + logmsg(LOG_DEBUG, "prefix_init_from_k: no " + "saved mask for DHCP %s; need to " + "resolicit\n", pr->pr_name); + (void) check_to_solicit(pi, RESTART_INIT_SOLICIT); + } else { + if (debug & D_DHCP) + logmsg(LOG_DEBUG, "prefix_init_from_k: using " + "%s mask for DHCP %s\n", + pr2->pr_name[0] == '\0' ? "saved" : + pr2->pr_name, pr->pr_name); + prefix_update_dhcp(pr); + } + } else { + if (ioctl(sock, SIOCGLIFSUBNET, (char *)&lifr) < 0) { + logperror_pr(pr, + "prefix_init_from_k: ioctl (get subnet)"); + goto error; + } + if (lifr.lifr_subnet.ss_family != AF_INET6) { + logmsg(LOG_ERR, + "ignoring interface %s: not AF_INET6\n", + pr->pr_name); + goto error; + } + /* + * Guard against the prefix having non-zero bits after the + * prefix len bits. + */ + sin6 = (struct sockaddr_in6 *)&lifr.lifr_subnet; + pr->pr_prefix_len = lifr.lifr_addrlen; + prefix_set(&pr->pr_prefix, sin6->sin6_addr, pr->pr_prefix_len); - logmsg(LOG_ERR, "ingoring interface %s: it appears to be " - "configured with an invalid interface id (%s/%u)\n", - pr->pr_name, - inet_ntop(AF_INET6, (void *)&pr->pr_address, - abuf, sizeof (abuf)), pr->pr_prefix_len); - goto error; + if (pr->pr_prefix_len != IPV6_ABITS && + (pr->pr_flags & IFF_UP) && + IN6_ARE_ADDR_EQUAL(&pr->pr_address, &pr->pr_prefix)) { + char abuf[INET6_ADDRSTRLEN]; + + logmsg(LOG_ERR, "ingoring interface %s: it appears to " + "be configured with an invalid interface id " + "(%s/%u)\n", + pr->pr_name, + inet_ntop(AF_INET6, (void *)&pr->pr_address, + abuf, sizeof (abuf)), pr->pr_prefix_len); + goto error; + } } pr->pr_kernel_state = 0; if (pr->pr_prefix_len != IPV6_ABITS) pr->pr_kernel_state |= PR_ONLINK; - if (!(pr->pr_flags & IFF_NOLOCAL)) + if (!(pr->pr_flags & (IFF_NOLOCAL | IFF_DHCPRUNNING))) pr->pr_kernel_state |= PR_AUTO; if ((pr->pr_flags & IFF_DEPRECATED) && (pr->pr_kernel_state & PR_AUTO)) pr->pr_kernel_state |= PR_DEPRECATED; @@ -1388,6 +1443,29 @@ prefix_modify_flags(struct prefix *pr, uint64_t onflags, uint64_t offflags) } /* + * Update the subnet mask for this interface under DHCPv6 control. + */ +void +prefix_update_dhcp(struct prefix *pr) +{ + struct lifreq lifr; + + (void) memset(&lifr, 0, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); + lifr.lifr_addr.ss_family = AF_INET6; + prefix_set(&((struct sockaddr_in6 *)&lifr.lifr_addr)->sin6_addr, + pr->pr_address, pr->pr_prefix_len); + lifr.lifr_addrlen = pr->pr_prefix_len; + /* + * Ignore ENXIO, as the dhcpagent process is responsible for plumbing + * and unplumbing these. + */ + if (ioctl(pr->pr_physical->pi_sock, SIOCSLIFSUBNET, (char *)&lifr) == + -1 && errno != ENXIO) + logperror_pr(pr, "prefix_update_dhcp: ioctl (set subnet)"); +} + +/* * Make the kernel state match what is in the prefix structure. * This includes creating the prefix (allocating a new interface name) * as well as setting the local address and on-link subnet prefix diff --git a/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/tables.h b/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/tables.h index d03c428993..3d4b54b1ae 100644 --- a/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/tables.h +++ b/usr/src/cmd/cmd-inet/usr.lib/in.ndpd/tables.h @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -40,7 +40,8 @@ enum adv_events { ADV_OFF, START_INIT_ADV, START_FINAL_ADV, RECEIVED_SOLICIT, enum solicit_states { NO_SOLICIT = 0, INIT_SOLICIT, DONE_SOLICIT }; enum solicit_events { SOLICIT_OFF, START_INIT_SOLICIT, SOL_TIMER, - SOLICIT_DONE }; + SOLICIT_DONE, RESTART_INIT_SOLICIT }; + /* * A doubly linked list of all physical interfaces that each contain a * doubly linked list of prefixes (i.e. logical interfaces) and default @@ -101,6 +102,7 @@ struct phyint { #define pi_TmpPreferredLifetime pi_config[I_TmpPreferredLifetime].cf_value #define pi_TmpRegenAdvance pi_config[I_TmpRegenAdvance].cf_value #define pi_TmpMaxDesyncFactor pi_config[I_TmpMaxDesyncFactor].cf_value +#define pi_StatefulAddrConf pi_config[I_StatefulAddrConf].cf_value /* Recorded variables for RFC3041 addresses */ uint_t pi_TmpDesyncFactor; /* In milliseconds */ @@ -123,6 +125,8 @@ struct phyint { */ uint_t pi_RetransTimer; /* In milliseconds */ char *pi_group_name; + + uint_t pi_ra_flags; /* Detect when to start DHCP */ }; /* @@ -144,7 +148,8 @@ struct phyint { #define I_PREFIXSIZE 6 /* # of variables */ /* - * A doubly linked list of prefixes for onlink and addrconf. + * A doubly-linked list of prefixes for onlink and addrconf. + * ("Prefixes" in this context are identical to logical interfaces.) */ struct prefix { struct prefix *pr_next; /* Next prefix for this physical */ @@ -272,6 +277,7 @@ extern int prefix_init_from_k(struct prefix *pr); extern void prefix_delete(struct prefix *pr); extern boolean_t prefix_equal(struct in6_addr p1, struct in6_addr p2, int bits); +extern void prefix_update_dhcp(struct prefix *pr); extern void prefix_update_k(struct prefix *pr); extern uint_t prefix_timer(struct prefix *pr, uint_t elapsed); extern uint_t adv_prefix_timer(struct adv_prefix *adv_pr, @@ -312,6 +318,8 @@ extern void print_prefixlist(struct confvar *confvar); extern void in_data(struct phyint *pi); +extern void start_dhcp(struct phyint *pi); + extern void incoming_ra(struct phyint *pi, struct nd_router_advert *ra, int len, struct sockaddr_in6 *from, boolean_t loopback); diff --git a/usr/src/cmd/cmd-inet/usr.sbin/ifconfig/ifconfig.c b/usr/src/cmd/cmd-inet/usr.sbin/ifconfig/ifconfig.c index f6d04b34ec..c1f32fd6aa 100644 --- a/usr/src/cmd/cmd-inet/usr.sbin/ifconfig/ifconfig.c +++ b/usr/src/cmd/cmd-inet/usr.sbin/ifconfig/ifconfig.c @@ -1,5 +1,5 @@ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* @@ -203,7 +203,6 @@ static int updownifs(iface_t *ifs, int up); */ #define DHCP_EXIT_IF_FAILURE -1 -#define DHCP_IPC_MAX_WAIT 15 /* max seconds to wait to start agent */ #define NEXTARG 0xffffff /* command takes an argument */ #define OPTARG 0xfffffe /* command takes an optional argument */ @@ -714,21 +713,17 @@ ifconfig(int argc, char *argv[], int af, struct lifreq *lifrp) } if (strcmp(*argv, "auto-dhcp") == 0 || strcmp(*argv, "dhcp") == 0) { - if (af == AF_INET) { - - /* - * some errors are ignored in the case where - * more than one interface is being operated on. - */ - ret = setifdhcp("ifconfig", name, argc, argv); - if (ret == DHCP_EXIT_IF_FAILURE) { - if (!all) - exit(DHCP_EXIT_FAILURE); - } else if (ret != DHCP_EXIT_SUCCESS) - exit(ret); - } else - (void) fprintf(stderr, "ifconfig: dhcp not supported " - "for inet6\n"); + /* + * Some errors are ignored in the case where more than one + * interface is being operated on. + */ + ret = setifdhcp("ifconfig", name, argc, argv); + if (ret == DHCP_EXIT_IF_FAILURE) { + if (!all) + exit(DHCP_EXIT_FAILURE); + } else if (ret != DHCP_EXIT_SUCCESS) { + exit(ret); + } return; } @@ -4489,7 +4484,10 @@ setifdhcp(const char *caller, const char *ifname, int argc, char *argv[]) } if (is_primary) - type = type | DHCP_PRIMARY; + type |= DHCP_PRIMARY; + + if (af != AF_INET) + type |= DHCP_V6; request = dhcp_ipc_alloc_request(type, ifname, NULL, 0, DHCP_TYPE_NONE); if (request == NULL) { diff --git a/usr/src/cmd/cmd-inet/usr.sbin/snoop/Makefile b/usr/src/cmd/cmd-inet/usr.sbin/snoop/Makefile index d853d7f37f..4be340b128 100644 --- a/usr/src/cmd/cmd-inet/usr.sbin/snoop/Makefile +++ b/usr/src/cmd/cmd-inet/usr.sbin/snoop/Makefile @@ -21,7 +21,7 @@ # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" @@ -30,7 +30,8 @@ PROG= snoop OBJS= dlprims.o nfs4_xdr.o snoop.o snoop_aarp.o snoop_adsp.o snoop_aecho.o \ snoop_apple.o snoop_arp.o snoop_atp.o snoop_bparam.o \ - snoop_capture.o snoop_dhcp.o snoop_display.o snoop_dns.o snoop_ether.o \ + snoop_capture.o snoop_dhcp.o snoop_dhcpv6.o snoop_display.o \ + snoop_dns.o snoop_ether.o \ snoop_filter.o snoop_http.o snoop_icmp.o snoop_igmp.o snoop_ip.o \ snoop_ipaddr.o snoop_ipsec.o snoop_ldap.o snoop_mip.o snoop_mount.o \ snoop_nbp.o snoop_netbios.o snoop_nfs.o snoop_nfs3.o snoop_nfs4.o \ diff --git a/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop.h b/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop.h index a27a39ce32..ff40866bd4 100644 --- a/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop.h +++ b/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop.h @@ -20,7 +20,7 @@ */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -237,6 +237,7 @@ extern void interpret_sctp(int, struct sctp_hdr *, int, int); extern void interpret_mip_cntrlmsg(int, uchar_t *, int); struct dhcp; extern int interpret_dhcp(int, struct dhcp *, int); +extern int interpret_dhcpv6(int, const uint8_t *, int); struct tftphdr; extern int interpret_tftp(int, struct tftphdr *, int); extern int interpret_http(int, char *, int); @@ -258,6 +259,7 @@ extern char *ether_ouiname(uint32_t); extern char *tohex(char *p, int len); extern char *printether(struct ether_addr *); extern char *print_ethertype(int); +extern const char *arp_htype(int); /* * Describes characteristics of the Media Access Layer. diff --git a/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_arp.c b/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_arp.c index 2af76ced99..c41507c20e 100644 --- a/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_arp.c +++ b/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_arp.c @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 1991-2001, 2003 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -138,7 +137,8 @@ interpret_arp(int flags, struct arphdr *ap, int alen) show_header("ARP: ", "ARP/RARP Frame", alen); show_space(); (void) snprintf(get_line(0, 0), get_line_remain(), - "Hardware type = %d", ntohs(ap->ar_hrd)); + "Hardware type = %d (%s)", ntohs(ap->ar_hrd), + arp_htype(ntohs(ap->ar_hrd))); (void) snprintf(get_line(0, 0), get_line_remain(), "Protocol type = %04x (%s)", ntohs(ap->ar_pro), print_ethertype(ntohs(ap->ar_pro))); @@ -207,3 +207,57 @@ addrtoname_align(unsigned char *p) memcpy(&a, p, 4); return ((char *)addrtoname(AF_INET, &a)); } + +/* + * These numbers are assigned by the IANA. See the arp-parameters registry. + * Only those values that are used within Solaris have #defines. + */ +const char * +arp_htype(int t) +{ + switch (t) { + case ARPHRD_ETHER: + return ("Ethernet (10Mb)"); + case 2: + return ("Experimental Ethernet (3MB)"); + case 3: + return ("Amateur Radio AX.25"); + case 4: + return ("Proteon ProNET Token Ring"); + case 5: + return ("Chaos"); + case ARPHRD_IEEE802: + return ("IEEE 802"); + case 7: + return ("ARCNET"); + case 8: + return ("Hyperchannel"); + case 9: + return ("Lanstar"); + case 10: + return ("Autonet"); + case 11: + return ("LocalTalk"); + case 12: + return ("LocalNet"); + case 13: + return ("Ultra Link"); + case 14: + return ("SMDS"); + case ARPHRD_FRAME: + return ("Frame Relay"); + case ARPHRD_ATM: + return ("ATM"); + case ARPHRD_HDLC: + return ("HDLC"); + case ARPHRD_FC: + return ("Fibre Channel"); + case ARPHRD_IPATM: + return ("IP-ATM"); + case ARPHRD_TUNNEL: + return ("Tunnel"); + case ARPHRD_IB: + return ("IPIB"); + }; + return ("UNKNOWN"); +} diff --git a/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_dhcp.c b/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_dhcp.c index f188a242a8..7e746d57c3 100644 --- a/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_dhcp.c +++ b/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_dhcp.c @@ -20,7 +20,7 @@ */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -41,8 +41,6 @@ #include <dhcp_symbol.h> #include "snoop.h" -extern char *dlc_header; -static char *show_htype(int); static const char *show_msgtype(unsigned char); static int show_options(unsigned char *, int); static void display_ip(int, char *, char *, unsigned char **); @@ -163,7 +161,7 @@ interpret_dhcp(int flags, struct dhcp *dp, int len) (void) sprintf(get_line((char *)(uintptr_t)dp->htype - dlc_header, 1), "Hardware address type (htype) = %d (%s)", dp->htype, - show_htype(dp->htype)); + arp_htype(dp->htype)); (void) sprintf(get_line((char *)(uintptr_t)dp->hlen - dlc_header, 1), "Hardware address length (hlen) = %d octets", dp->hlen); @@ -264,6 +262,7 @@ interpret_dhcp(int flags, struct dhcp *dp, int len) } return (len); } + static int show_options(unsigned char *cp, int len) { @@ -612,47 +611,7 @@ show_options(unsigned char *cp, int len) } return (nooverload); } -static char * -show_htype(int t) -{ - switch (t) { - case 1: - return ("Ethernet (10Mb)"); - case 2: - return ("Experimental Ethernet (3MB)"); - case 3: - return ("Amateur Radio AX.25"); - case 4: - return ("Proteon ProNET Token Ring"); - case 5: - return ("Chaos"); - case 6: - return ("IEEE 802"); - case 7: - return ("ARCNET"); - case 8: - return ("Hyperchannel"); - case 9: - return ("Lanstar"); - case 10: - return ("Autonet"); - case 11: - return ("LocalTalk"); - case 12: - return ("LocalNet"); - case 13: - return ("Ultra Link"); - case 14: - return ("SMDS"); - case 15: - return ("Frame Relay"); - case 16: - return ("ATM"); - case ARPHRD_IB: - return ("IPIB"); - }; - return ("UNKNOWN"); -} + static const char * show_msgtype(unsigned char type) { @@ -672,6 +631,7 @@ show_msgtype(unsigned char type) return (types[type]); } + static void display_ip(int items, char *fmt, char *msg, unsigned char **opt) { @@ -684,6 +644,7 @@ display_ip(int items, char *fmt, char *msg, unsigned char **opt) *opt += 4; } } + static void display_ascii(char *fmt, char *msg, unsigned char **opt) { @@ -699,6 +660,7 @@ display_ascii(char *fmt, char *msg, unsigned char **opt) (void) sprintf(get_line(0, 0), fmt, msg, buf); (*opt) += slen; } + static void display_number(char *fmt, char *msg, unsigned char **opt) { @@ -737,6 +699,7 @@ display_number(char *fmt, char *msg, unsigned char **opt) } (*opt) += len; } + static void display_ascii_hex(char *msg, unsigned char **opt) { diff --git a/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_dhcpv6.c b/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_dhcpv6.c new file mode 100644 index 0000000000..862ec2792b --- /dev/null +++ b/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_dhcpv6.c @@ -0,0 +1,1058 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Dynamic Host Configuration Protocol version 6, for IPv6. Supports + * RFCs 3315, 3319, 3646, 3898, 4075, 4242, 4280, 4580, 4649, and 4704. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/dhcp6.h> +#include <arpa/inet.h> +#include <dhcp_impl.h> +#include <dhcp_inittab.h> + +#include "snoop.h" + +static const char *mtype_to_str(uint8_t); +static const char *option_to_str(uint8_t); +static const char *duidtype_to_str(uint16_t); +static const char *status_to_str(uint16_t); +static const char *entr_to_str(uint32_t); +static const char *reconf_to_str(uint8_t); +static const char *authproto_to_str(uint8_t); +static const char *authalg_to_str(uint8_t, uint8_t); +static const char *authrdm_to_str(uint8_t); +static const char *cwhat_to_str(uint8_t); +static const char *catype_to_str(uint8_t); +static void show_hex(const uint8_t *, int, const char *); +static void show_ascii(const uint8_t *, int, const char *); +static void show_address(const char *, const void *); +static void show_options(const uint8_t *, int); + +int +interpret_dhcpv6(int flags, const uint8_t *data, int len) +{ + int olen = len; + char *line, *lstart; + dhcpv6_relay_t d6r; + dhcpv6_message_t d6m; + uint_t optlen; + uint16_t statuscode; + + if (len <= 0) { + (void) strlcpy(get_sum_line(), "DHCPv6?", MAXLINE); + return (0); + } + if (flags & F_SUM) { + uint_t ias; + dhcpv6_option_t *d6o; + in6_addr_t link, peer; + char linkstr[INET6_ADDRSTRLEN]; + char peerstr[INET6_ADDRSTRLEN]; + + line = lstart = get_sum_line(); + line += snprintf(line, MAXLINE, "DHCPv6 %s", + mtype_to_str(data[0])); + if (data[0] == DHCPV6_MSG_RELAY_FORW || + data[0] == DHCPV6_MSG_RELAY_REPL) { + if (len < sizeof (d6r)) { + (void) strlcpy(line, "?", + MAXLINE - (line - lstart)); + return (olen); + } + /* Not much in DHCPv6 is aligned. */ + (void) memcpy(&d6r, data, sizeof (d6r)); + (void) memcpy(&link, d6r.d6r_linkaddr, sizeof (link)); + (void) memcpy(&peer, d6r.d6r_peeraddr, sizeof (peer)); + line += snprintf(line, MAXLINE - (line - lstart), + " HC=%d link=%s peer=%s", d6r.d6r_hop_count, + inet_ntop(AF_INET6, &link, linkstr, + sizeof (linkstr)), + inet_ntop(AF_INET6, &peer, peerstr, + sizeof (peerstr))); + data += sizeof (d6r); + len -= sizeof (d6r); + } else { + if (len < sizeof (d6m)) { + (void) strlcpy(line, "?", + MAXLINE - (line - lstart)); + return (olen); + } + (void) memcpy(&d6m, data, sizeof (d6m)); + line += snprintf(line, MAXLINE - (line - lstart), + " xid=%x", DHCPV6_GET_TRANSID(&d6m)); + data += sizeof (d6m); + len -= sizeof (d6m); + } + ias = 0; + d6o = NULL; + while ((d6o = dhcpv6_find_option(data, len, d6o, + DHCPV6_OPT_IA_NA, NULL)) != NULL) + ias++; + if (ias > 0) + line += snprintf(line, MAXLINE - (line - lstart), + " IAs=%u", ias); + d6o = dhcpv6_find_option(data, len, NULL, + DHCPV6_OPT_STATUS_CODE, &optlen); + optlen -= sizeof (*d6o); + if (d6o != NULL && optlen >= sizeof (statuscode)) { + (void) memcpy(&statuscode, d6o + 1, + sizeof (statuscode)); + line += snprintf(line, MAXLINE - (line - lstart), + " status=%u", ntohs(statuscode)); + optlen -= sizeof (statuscode); + if (optlen > 0) { + line += snprintf(line, + MAXLINE - (line - lstart), " \"%.*s\"", + optlen, (char *)(d6o + 1) + 2); + } + } + d6o = dhcpv6_find_option(data, len, NULL, + DHCPV6_OPT_RELAY_MSG, &optlen); + optlen -= sizeof (*d6o); + if (d6o != NULL && optlen >= 1) { + line += snprintf(line, MAXLINE - (line - lstart), + " relay=%s", mtype_to_str(*(uint8_t *)(d6o + 1))); + } + } else if (flags & F_DTAIL) { + show_header("DHCPv6: ", + "Dynamic Host Configuration Protocol Version 6", len); + show_space(); + (void) snprintf(get_line(0, 0), get_line_remain(), + "Message type (msg-type) = %u (%s)", data[0], + mtype_to_str(data[0])); + if (data[0] == DHCPV6_MSG_RELAY_FORW || + data[0] == DHCPV6_MSG_RELAY_REPL) { + if (len < sizeof (d6r)) { + (void) strlcpy(get_line(0, 0), "Truncated", + get_line_remain()); + return (olen); + } + (void) memcpy(&d6r, data, sizeof (d6r)); + (void) snprintf(get_line(0, 0), get_line_remain(), + "Hop count = %u", d6r.d6r_hop_count); + show_address("Link address", d6r.d6r_linkaddr); + show_address("Peer address", d6r.d6r_peeraddr); + data += sizeof (d6r); + len -= sizeof (d6r); + } else { + if (len < sizeof (d6m)) { + (void) strlcpy(get_line(0, 0), "Truncated", + get_line_remain()); + return (olen); + } + (void) memcpy(&d6m, data, sizeof (d6m)); + (void) snprintf(get_line(0, 0), get_line_remain(), + "Transaction ID = %x", DHCPV6_GET_TRANSID(&d6m)); + data += sizeof (d6m); + len -= sizeof (d6m); + } + show_space(); + show_options(data, len); + show_space(); + } + return (olen); +} + +static const char * +mtype_to_str(uint8_t mtype) +{ + switch (mtype) { + case DHCPV6_MSG_SOLICIT: + return ("Solicit"); + case DHCPV6_MSG_ADVERTISE: + return ("Advertise"); + case DHCPV6_MSG_REQUEST: + return ("Request"); + case DHCPV6_MSG_CONFIRM: + return ("Confirm"); + case DHCPV6_MSG_RENEW: + return ("Renew"); + case DHCPV6_MSG_REBIND: + return ("Rebind"); + case DHCPV6_MSG_REPLY: + return ("Reply"); + case DHCPV6_MSG_RELEASE: + return ("Release"); + case DHCPV6_MSG_DECLINE: + return ("Decline"); + case DHCPV6_MSG_RECONFIGURE: + return ("Reconfigure"); + case DHCPV6_MSG_INFO_REQ: + return ("Information-Request"); + case DHCPV6_MSG_RELAY_FORW: + return ("Relay-Forward"); + case DHCPV6_MSG_RELAY_REPL: + return ("Relay-Reply"); + default: + return ("Unknown"); + } +} + +static const char * +option_to_str(uint8_t mtype) +{ + switch (mtype) { + case DHCPV6_OPT_CLIENTID: + return ("Client Identifier"); + case DHCPV6_OPT_SERVERID: + return ("Server Identifier"); + case DHCPV6_OPT_IA_NA: + return ("Identity Association for Non-temporary Addresses"); + case DHCPV6_OPT_IA_TA: + return ("Identity Association for Temporary Addresses"); + case DHCPV6_OPT_IAADDR: + return ("IA Address"); + case DHCPV6_OPT_ORO: + return ("Option Request"); + case DHCPV6_OPT_PREFERENCE: + return ("Preference"); + case DHCPV6_OPT_ELAPSED_TIME: + return ("Elapsed Time"); + case DHCPV6_OPT_RELAY_MSG: + return ("Relay Message"); + case DHCPV6_OPT_AUTH: + return ("Authentication"); + case DHCPV6_OPT_UNICAST: + return ("Server Unicast"); + case DHCPV6_OPT_STATUS_CODE: + return ("Status Code"); + case DHCPV6_OPT_RAPID_COMMIT: + return ("Rapid Commit"); + case DHCPV6_OPT_USER_CLASS: + return ("User Class"); + case DHCPV6_OPT_VENDOR_CLASS: + return ("Vendor Class"); + case DHCPV6_OPT_VENDOR_OPT: + return ("Vendor-specific Information"); + case DHCPV6_OPT_INTERFACE_ID: + return ("Interface-Id"); + case DHCPV6_OPT_RECONF_MSG: + return ("Reconfigure Message"); + case DHCPV6_OPT_RECONF_ACC: + return ("Reconfigure Accept"); + case DHCPV6_OPT_SIP_NAMES: + return ("SIP Servers Domain Name List"); + case DHCPV6_OPT_SIP_ADDR: + return ("SIP Servers IPv6 Address List"); + case DHCPV6_OPT_DNS_ADDR: + return ("DNS Recursive Name Server"); + case DHCPV6_OPT_DNS_SEARCH: + return ("Domain Search List"); + case DHCPV6_OPT_IA_PD: + return ("Identity Association for Prefix Delegation"); + case DHCPV6_OPT_IAPREFIX: + return ("IA_PD Prefix"); + case DHCPV6_OPT_NIS_SERVERS: + return ("Network Information Service Servers"); + case DHCPV6_OPT_NISP_SERVERS: + return ("Network Information Service V2 Servers"); + case DHCPV6_OPT_NIS_DOMAIN: + return ("Network Information Service Domain Name"); + case DHCPV6_OPT_NISP_DOMAIN: + return ("Network Information Service V2 Domain Name"); + case DHCPV6_OPT_SNTP_SERVERS: + return ("Simple Network Time Protocol Servers"); + case DHCPV6_OPT_INFO_REFTIME: + return ("Information Refresh Time"); + case DHCPV6_OPT_BCMCS_SRV_D: + return ("BCMCS Controller Domain Name List"); + case DHCPV6_OPT_BCMCS_SRV_A: + return ("BCMCS Controller IPv6 Address"); + case DHCPV6_OPT_GEOCONF_CVC: + return ("Civic Location"); + case DHCPV6_OPT_REMOTE_ID: + return ("Relay Agent Remote-ID"); + case DHCPV6_OPT_SUBSCRIBER: + return ("Relay Agent Subscriber-ID"); + case DHCPV6_OPT_CLIENT_FQDN: + return ("Client FQDN"); + default: + return ("Unknown"); + } +} + +static const char * +duidtype_to_str(uint16_t dtype) +{ + switch (dtype) { + case DHCPV6_DUID_LLT: + return ("Link-layer Address Plus Time"); + case DHCPV6_DUID_EN: + return ("Enterprise Number"); + case DHCPV6_DUID_LL: + return ("Link-layer Address"); + default: + return ("Unknown"); + } +} + +static const char * +status_to_str(uint16_t status) +{ + switch (status) { + case DHCPV6_STAT_SUCCESS: + return ("Success"); + case DHCPV6_STAT_UNSPECFAIL: + return ("Failure, reason unspecified"); + case DHCPV6_STAT_NOADDRS: + return ("No addresses for IAs"); + case DHCPV6_STAT_NOBINDING: + return ("Client binding unavailable"); + case DHCPV6_STAT_NOTONLINK: + return ("Prefix not on link"); + case DHCPV6_STAT_USEMCAST: + return ("Use multicast"); + case DHCPV6_STAT_NOPREFIX: + return ("No prefix available"); + default: + return ("Unknown"); + } +} + +static const char * +entr_to_str(uint32_t entr) +{ + switch (entr) { + case DHCPV6_SUN_ENT: + return ("Sun Microsystems"); + default: + return ("Unknown"); + } +} + +static const char * +reconf_to_str(uint8_t msgtype) +{ + switch (msgtype) { + case DHCPV6_RECONF_RENEW: + return ("Renew"); + case DHCPV6_RECONF_INFO: + return ("Information-request"); + default: + return ("Unknown"); + } +} + +static const char * +authproto_to_str(uint8_t aproto) +{ + switch (aproto) { + case DHCPV6_PROTO_DELAYED: + return ("Delayed"); + case DHCPV6_PROTO_RECONFIG: + return ("Reconfigure Key"); + default: + return ("Unknown"); + } +} + +static const char * +authalg_to_str(uint8_t aproto, uint8_t aalg) +{ + switch (aproto) { + case DHCPV6_PROTO_DELAYED: + case DHCPV6_PROTO_RECONFIG: + switch (aalg) { + case DHCPV6_ALG_HMAC_MD5: + return ("HMAC-MD5 Signature"); + default: + return ("Unknown"); + } + break; + default: + return ("Unknown"); + } +} + +static const char * +authrdm_to_str(uint8_t ardm) +{ + switch (ardm) { + case DHCPV6_RDM_MONOCNT: + return ("Monotonic Counter"); + default: + return ("Unknown"); + } +} + +static const char * +cwhat_to_str(uint8_t what) +{ + switch (what) { + case DHCPV6_CWHAT_SERVER: + return ("Server"); + case DHCPV6_CWHAT_NETWORK: + return ("Network"); + case DHCPV6_CWHAT_CLIENT: + return ("Client"); + default: + return ("Unknown"); + } +} + +static const char * +catype_to_str(uint8_t catype) +{ + switch (catype) { + case CIVICADDR_LANG: + return ("Language; RFC 2277"); + case CIVICADDR_A1: + return ("National division (state)"); + case CIVICADDR_A2: + return ("County"); + case CIVICADDR_A3: + return ("City"); + case CIVICADDR_A4: + return ("City division"); + case CIVICADDR_A5: + return ("Neighborhood"); + case CIVICADDR_A6: + return ("Street group"); + case CIVICADDR_PRD: + return ("Leading street direction"); + case CIVICADDR_POD: + return ("Trailing street suffix"); + case CIVICADDR_STS: + return ("Street suffix or type"); + case CIVICADDR_HNO: + return ("House number"); + case CIVICADDR_HNS: + return ("House number suffix"); + case CIVICADDR_LMK: + return ("Landmark"); + case CIVICADDR_LOC: + return ("Additional location information"); + case CIVICADDR_NAM: + return ("Name/occupant"); + case CIVICADDR_PC: + return ("Postal Code/ZIP"); + case CIVICADDR_BLD: + return ("Building"); + case CIVICADDR_UNIT: + return ("Unit/apt/suite"); + case CIVICADDR_FLR: + return ("Floor"); + case CIVICADDR_ROOM: + return ("Room number"); + case CIVICADDR_TYPE: + return ("Place type"); + case CIVICADDR_PCN: + return ("Postal community name"); + case CIVICADDR_POBOX: + return ("Post office box"); + case CIVICADDR_ADDL: + return ("Additional code"); + case CIVICADDR_SEAT: + return ("Seat/desk"); + case CIVICADDR_ROAD: + return ("Primary road or street"); + case CIVICADDR_RSEC: + return ("Road section"); + case CIVICADDR_RBRA: + return ("Road branch"); + case CIVICADDR_RSBR: + return ("Road sub-branch"); + case CIVICADDR_SPRE: + return ("Street name pre-modifier"); + case CIVICADDR_SPOST: + return ("Street name post-modifier"); + case CIVICADDR_SCRIPT: + return ("Script"); + default: + return ("Unknown"); + } +} + +static void +show_hex(const uint8_t *data, int len, const char *name) +{ + char buffer[16 * 3 + 1]; + int nlen; + int i; + char sep; + + nlen = strlen(name); + sep = '='; + while (len > 0) { + for (i = 0; i < 16 && i < len; i++) + (void) snprintf(buffer + 3 * i, 4, " %02x", *data++); + (void) snprintf(get_line(0, 0), get_line_remain(), "%*s %c%s", + nlen, name, sep, buffer); + name = ""; + sep = ' '; + len -= i; + } +} + +static void +show_ascii(const uint8_t *data, int len, const char *name) +{ + char buffer[64], *bp; + int nlen; + int i; + char sep; + + nlen = strlen(name); + sep = '='; + while (len > 0) { + bp = buffer; + for (i = 0; i < sizeof (buffer) - 4 && len > 0; len--) { + if (!isascii(*data) || !isprint(*data)) + bp += snprintf(bp, 5, "\\%03o", *data++); + else + *bp++; + } + *bp = '\0'; + (void) snprintf(get_line(0, 0), get_line_remain(), + "%*s %c \"%s\"", nlen, name, sep, buffer); + sep = ' '; + name = ""; + } +} + +static void +show_address(const char *addrname, const void *aptr) +{ + char *hname; + char addrstr[INET6_ADDRSTRLEN]; + in6_addr_t addr; + + (void) memcpy(&addr, aptr, sizeof (in6_addr_t)); + (void) inet_ntop(AF_INET6, &addr, addrstr, sizeof (addrstr)); + hname = addrtoname(AF_INET6, &addr); + if (strcmp(hname, addrstr) == 0) { + (void) snprintf(get_line(0, 0), get_line_remain(), "%s = %s", + addrname, addrstr); + } else { + (void) snprintf(get_line(0, 0), get_line_remain(), + "%s = %s (%s)", addrname, addrstr, hname); + } +} + +static void +nest_options(const uint8_t *data, uint_t olen, char *prefix, char *title) +{ + char *str, *oldnest, *oldprefix; + + if (olen <= 0) + return; + oldprefix = prot_prefix; + oldnest = prot_nest_prefix; + str = malloc(strlen(prot_nest_prefix) + strlen(prot_prefix) + 1); + if (str == NULL) { + prot_nest_prefix = prot_prefix; + } else { + (void) sprintf(str, "%s%s", prot_nest_prefix, prot_prefix); + prot_nest_prefix = str; + } + show_header(prefix, title, 0); + show_options(data, olen); + free(str); + prot_prefix = oldprefix; + prot_nest_prefix = oldnest; +} + +static void +show_options(const uint8_t *data, int len) +{ + dhcpv6_option_t d6o; + uint_t olen, retlen; + uint16_t val16; + uint16_t type; + uint32_t val32; + const uint8_t *ostart; + char *str, *sp; + char *oldnest; + + /* + * Be very careful with negative numbers; ANSI signed/unsigned + * comparison doesn't work as expected. + */ + while (len >= (signed)sizeof (d6o)) { + (void) memcpy(&d6o, data, sizeof (d6o)); + d6o.d6o_code = ntohs(d6o.d6o_code); + d6o.d6o_len = olen = ntohs(d6o.d6o_len); + (void) snprintf(get_line(0, 0), get_line_remain(), + "Option Code = %u (%s)", d6o.d6o_code, + option_to_str(d6o.d6o_code)); + ostart = data += sizeof (d6o); + len -= sizeof (d6o); + if (olen > len) { + (void) strlcpy(get_line(0, 0), "Option truncated", + get_line_remain()); + olen = len; + } + switch (d6o.d6o_code) { + case DHCPV6_OPT_CLIENTID: + case DHCPV6_OPT_SERVERID: + if (olen < sizeof (val16)) + break; + (void) memcpy(&val16, data, sizeof (val16)); + data += sizeof (val16); + olen -= sizeof (val16); + type = ntohs(val16); + (void) snprintf(get_line(0, 0), get_line_remain(), + " DUID Type = %u (%s)", type, + duidtype_to_str(type)); + if (type == DHCPV6_DUID_LLT || type == DHCPV6_DUID_LL) { + if (olen < sizeof (val16)) + break; + (void) memcpy(&val16, data, sizeof (val16)); + data += sizeof (val16); + olen -= sizeof (val16); + val16 = ntohs(val16); + (void) snprintf(get_line(0, 0), + get_line_remain(), + " Hardware Type = %u (%s)", val16, + arp_htype(type)); + } + if (type == DHCPV6_DUID_LLT) { + time_t timevalue; + + if (olen < sizeof (val32)) + break; + (void) memcpy(&val32, data, sizeof (val32)); + data += sizeof (val32); + olen -= sizeof (val32); + timevalue = ntohl(val32) + DUID_TIME_BASE; + (void) snprintf(get_line(0, 0), + get_line_remain(), + " Time = %lu (%.24s)", ntohl(val32), + ctime(&timevalue)); + } + if (type == DHCPV6_DUID_EN) { + if (olen < sizeof (val32)) + break; + (void) memcpy(&val32, data, sizeof (val32)); + data += sizeof (val32); + olen -= sizeof (val32); + val32 = ntohl(val32); + (void) snprintf(get_line(0, 0), + get_line_remain(), + " Enterprise Number = %lu (%s)", val32, + entr_to_str(val32)); + } + if (olen == 0) + break; + if ((str = malloc(olen * 3)) == NULL) + pr_err("interpret_dhcpv6: no mem"); + sp = str + snprintf(str, 3, "%02x", *data++); + while (--olen > 0) { + *sp++ = (type == DHCPV6_DUID_LLT || + type == DHCPV6_DUID_LL) ? ':' : ' '; + sp = sp + snprintf(sp, 3, "%02x", *data++); + } + (void) snprintf(get_line(0, 0), get_line_remain(), + (type == DHCPV6_DUID_LLT || + type == DHCPV6_DUID_LL) ? + " Link Layer Address = %s" : + " Identifier = %s", str); + free(str); + break; + case DHCPV6_OPT_IA_NA: + case DHCPV6_OPT_IA_PD: { + dhcpv6_ia_na_t d6in; + + if (olen < sizeof (d6in) - sizeof (d6o)) + break; + (void) memcpy(&d6in, data - sizeof (d6o), + sizeof (d6in)); + data += sizeof (d6in) - sizeof (d6o); + olen -= sizeof (d6in) - sizeof (d6o); + (void) snprintf(get_line(0, 0), get_line_remain(), + " IAID = %u", ntohl(d6in.d6in_iaid)); + (void) snprintf(get_line(0, 0), get_line_remain(), + " T1 (renew) = %u seconds", ntohl(d6in.d6in_t1)); + (void) snprintf(get_line(0, 0), get_line_remain(), + " T2 (rebind) = %u seconds", ntohl(d6in.d6in_t2)); + nest_options(data, olen, "IA: ", + "Identity Association"); + break; + } + case DHCPV6_OPT_IA_TA: { + dhcpv6_ia_ta_t d6it; + + if (olen < sizeof (d6it) - sizeof (d6o)) + break; + (void) memcpy(&d6it, data - sizeof (d6o), + sizeof (d6it)); + data += sizeof (d6it) - sizeof (d6o); + olen -= sizeof (d6it) - sizeof (d6o); + (void) snprintf(get_line(0, 0), get_line_remain(), + " IAID = %u", ntohl(d6it.d6it_iaid)); + nest_options(data, olen, "IA: ", + "Identity Association"); + break; + } + case DHCPV6_OPT_IAADDR: { + dhcpv6_iaaddr_t d6ia; + + if (olen < sizeof (d6ia) - sizeof (d6o)) + break; + (void) memcpy(&d6ia, data - sizeof (d6o), + sizeof (d6ia)); + data += sizeof (d6ia) - sizeof (d6o); + olen -= sizeof (d6ia) - sizeof (d6o); + show_address(" Address", &d6ia.d6ia_addr); + (void) snprintf(get_line(0, 0), get_line_remain(), + " Preferred lifetime = %u seconds", + ntohl(d6ia.d6ia_preflife)); + (void) snprintf(get_line(0, 0), get_line_remain(), + " Valid lifetime = %u seconds", + ntohl(d6ia.d6ia_vallife)); + nest_options(data, olen, "ADDR: ", "Address"); + break; + } + case DHCPV6_OPT_ORO: + while (olen >= sizeof (val16)) { + (void) memcpy(&val16, data, sizeof (val16)); + val16 = ntohs(val16); + (void) snprintf(get_line(0, 0), + get_line_remain(), + " Requested Option Code = %u (%s)", val16, + option_to_str(val16)); + data += sizeof (val16); + olen -= sizeof (val16); + } + break; + case DHCPV6_OPT_PREFERENCE: + if (olen > 0) { + (void) snprintf(get_line(0, 0), + get_line_remain(), + *data == 255 ? + " Preference = %u (immediate)" : + " Preference = %u", *data); + } + break; + case DHCPV6_OPT_ELAPSED_TIME: + if (olen == sizeof (val16)) { + (void) memcpy(&val16, data, sizeof (val16)); + val16 = ntohs(val16); + (void) snprintf(get_line(0, 0), + get_line_remain(), + " Elapsed Time = %u.%02u seconds", + val16 / 100, val16 % 100); + } + break; + case DHCPV6_OPT_RELAY_MSG: + if (olen > 0) { + oldnest = prot_nest_prefix; + prot_nest_prefix = prot_prefix; + retlen = interpret_dhcpv6(F_DTAIL, data, olen); + prot_prefix = prot_nest_prefix; + prot_nest_prefix = oldnest; + } + break; + case DHCPV6_OPT_AUTH: { + dhcpv6_auth_t d6a; + + if (olen < DHCPV6_AUTH_SIZE - sizeof (d6o)) + break; + (void) memcpy(&d6a, data - sizeof (d6o), + DHCPV6_AUTH_SIZE); + data += DHCPV6_AUTH_SIZE - sizeof (d6o); + olen += DHCPV6_AUTH_SIZE - sizeof (d6o); + (void) snprintf(get_line(0, 0), get_line_remain(), + " Protocol = %u (%s)", d6a.d6a_proto, + authproto_to_str(d6a.d6a_proto)); + (void) snprintf(get_line(0, 0), get_line_remain(), + " Algorithm = %u (%s)", d6a.d6a_alg, + authalg_to_str(d6a.d6a_proto, d6a.d6a_alg)); + (void) snprintf(get_line(0, 0), get_line_remain(), + " Replay Detection Method = %u (%s)", d6a.d6a_rdm, + authrdm_to_str(d6a.d6a_rdm)); + show_hex(d6a.d6a_replay, sizeof (d6a.d6a_replay), + " RDM Data"); + if (olen > 0) + show_hex(data, olen, " Auth Info"); + break; + } + case DHCPV6_OPT_UNICAST: + if (olen >= sizeof (in6_addr_t)) + show_address(" Server Address", data); + break; + case DHCPV6_OPT_STATUS_CODE: + if (olen < sizeof (val16)) + break; + (void) memcpy(&val16, data, sizeof (val16)); + val16 = ntohs(val16); + (void) snprintf(get_line(0, 0), get_line_remain(), + " Status Code = %u (%s)", val16, + status_to_str(val16)); + data += sizeof (val16); + olen -= sizeof (val16); + if (olen > 0) + (void) snprintf(get_line(0, 0), + get_line_remain(), " Text = \"%.*s\"", + olen, data); + break; + case DHCPV6_OPT_VENDOR_CLASS: + if (olen < sizeof (val32)) + break; + (void) memcpy(&val32, data, sizeof (val32)); + data += sizeof (val32); + olen -= sizeof (val32); + val32 = ntohl(val32); + (void) snprintf(get_line(0, 0), get_line_remain(), + " Enterprise Number = %lu (%s)", val32, + entr_to_str(val32)); + /* FALLTHROUGH */ + case DHCPV6_OPT_USER_CLASS: + while (olen >= sizeof (val16)) { + (void) memcpy(&val16, data, sizeof (val16)); + data += sizeof (val16); + olen -= sizeof (val16); + val16 = ntohs(val16); + if (val16 > olen) { + (void) strlcpy(get_line(0, 0), + " Truncated class", + get_line_remain()); + val16 = olen; + } + show_hex(data, olen, " Class"); + data += val16; + olen -= val16; + } + break; + case DHCPV6_OPT_VENDOR_OPT: { + dhcpv6_option_t sd6o; + + if (olen < sizeof (val32)) + break; + (void) memcpy(&val32, data, sizeof (val32)); + data += sizeof (val32); + olen -= sizeof (val32); + val32 = ntohl(val32); + (void) snprintf(get_line(0, 0), get_line_remain(), + " Enterprise Number = %lu (%s)", val32, + entr_to_str(val32)); + while (olen >= sizeof (sd6o)) { + (void) memcpy(&sd6o, data, sizeof (sd6o)); + sd6o.d6o_code = ntohs(sd6o.d6o_code); + sd6o.d6o_len = ntohs(sd6o.d6o_len); + (void) snprintf(get_line(0, 0), + get_line_remain(), + " Vendor Option Code = %u", d6o.d6o_code); + data += sizeof (d6o); + olen -= sizeof (d6o); + if (sd6o.d6o_len > olen) { + (void) strlcpy(get_line(0, 0), + " Vendor Option truncated", + get_line_remain()); + sd6o.d6o_len = olen; + } + if (sd6o.d6o_len > 0) { + show_hex(data, sd6o.d6o_len, + " Data"); + data += sd6o.d6o_len; + olen -= sd6o.d6o_len; + } + } + break; + } + case DHCPV6_OPT_REMOTE_ID: + if (olen < sizeof (val32)) + break; + (void) memcpy(&val32, data, sizeof (val32)); + data += sizeof (val32); + olen -= sizeof (val32); + val32 = ntohl(val32); + (void) snprintf(get_line(0, 0), get_line_remain(), + " Enterprise Number = %lu (%s)", val32, + entr_to_str(val32)); + /* FALLTHROUGH */ + case DHCPV6_OPT_INTERFACE_ID: + case DHCPV6_OPT_SUBSCRIBER: + if (olen > 0) + show_hex(data, olen, " ID"); + break; + case DHCPV6_OPT_RECONF_MSG: + if (olen > 0) { + (void) snprintf(get_line(0, 0), + get_line_remain(), + " Message Type = %u (%s)", *data, + reconf_to_str(*data)); + } + break; + case DHCPV6_OPT_SIP_NAMES: + case DHCPV6_OPT_DNS_SEARCH: + case DHCPV6_OPT_NIS_DOMAIN: + case DHCPV6_OPT_NISP_DOMAIN: + case DHCPV6_OPT_BCMCS_SRV_D: { + dhcp_symbol_t *symp; + char *sp2; + + symp = inittab_getbycode( + ITAB_CAT_STANDARD | ITAB_CAT_V6, ITAB_CONS_SNOOP, + d6o.d6o_code); + if (symp != NULL) { + str = inittab_decode(symp, data, olen, B_TRUE); + if (str != NULL) { + sp = str; + do { + sp2 = strchr(sp, ' '); + if (sp2 != NULL) + *sp2++ = '\0'; + (void) snprintf(get_line(0, 0), + get_line_remain(), + " Name = %s", sp); + } while ((sp = sp2) != NULL); + free(str); + } + free(symp); + } + break; + } + case DHCPV6_OPT_SIP_ADDR: + case DHCPV6_OPT_DNS_ADDR: + case DHCPV6_OPT_NIS_SERVERS: + case DHCPV6_OPT_NISP_SERVERS: + case DHCPV6_OPT_SNTP_SERVERS: + case DHCPV6_OPT_BCMCS_SRV_A: + while (olen >= sizeof (in6_addr_t)) { + show_address(" Address", data); + data += sizeof (in6_addr_t); + olen -= sizeof (in6_addr_t); + } + break; + case DHCPV6_OPT_IAPREFIX: { + dhcpv6_iaprefix_t d6ip; + + if (olen < DHCPV6_IAPREFIX_SIZE - sizeof (d6o)) + break; + (void) memcpy(&d6ip, data - sizeof (d6o), + DHCPV6_IAPREFIX_SIZE); + data += DHCPV6_IAPREFIX_SIZE - sizeof (d6o); + olen -= DHCPV6_IAPREFIX_SIZE - sizeof (d6o); + show_address(" Prefix", d6ip.d6ip_addr); + (void) snprintf(get_line(0, 0), get_line_remain(), + " Preferred lifetime = %u seconds", + ntohl(d6ip.d6ip_preflife)); + (void) snprintf(get_line(0, 0), get_line_remain(), + " Valid lifetime = %u seconds", + ntohl(d6ip.d6ip_vallife)); + (void) snprintf(get_line(0, 0), get_line_remain(), + " Prefix length = %u", d6ip.d6ip_preflen); + nest_options(data, olen, "ADDR: ", "Address"); + break; + } + case DHCPV6_OPT_INFO_REFTIME: + if (olen < sizeof (val32)) + break; + (void) memcpy(&val32, data, sizeof (val32)); + (void) snprintf(get_line(0, 0), get_line_remain(), + " Refresh Time = %lu seconds", ntohl(val32)); + break; + case DHCPV6_OPT_GEOCONF_CVC: { + dhcpv6_civic_t d6c; + int solen; + + if (olen < DHCPV6_CIVIC_SIZE - sizeof (d6o)) + break; + (void) memcpy(&d6c, data - sizeof (d6o), + DHCPV6_CIVIC_SIZE); + data += DHCPV6_CIVIC_SIZE - sizeof (d6o); + olen -= DHCPV6_CIVIC_SIZE - sizeof (d6o); + (void) snprintf(get_line(0, 0), get_line_remain(), + " What Location = %u (%s)", d6c.d6c_what, + cwhat_to_str(d6c.d6c_what)); + (void) snprintf(get_line(0, 0), get_line_remain(), + " Country Code = %.*s", sizeof (d6c.d6c_cc), + d6c.d6c_cc); + while (olen >= 2) { + (void) snprintf(get_line(0, 0), + get_line_remain(), + " CA Element = %u (%s)", *data, + catype_to_str(*data)); + solen = data[1]; + data += 2; + olen -= 2; + if (solen > olen) { + (void) strlcpy(get_line(0, 0), + " CA Element truncated", + get_line_remain()); + solen = olen; + } + if (solen > 0) { + show_ascii(data, solen, " CA Data"); + data += solen; + olen -= solen; + } + } + break; + } + case DHCPV6_OPT_CLIENT_FQDN: { + dhcp_symbol_t *symp; + + if (olen == 0) + break; + (void) snprintf(get_line(0, 0), get_line_remain(), + " Flags = %02x", *data); + (void) snprintf(get_line(0, 0), get_line_remain(), + " %s", getflag(*data, DHCPV6_FQDNF_S, + "Perform AAAA RR updates", "No AAAA RR updates")); + (void) snprintf(get_line(0, 0), get_line_remain(), + " %s", getflag(*data, DHCPV6_FQDNF_O, + "Server override updates", + "No server override updates")); + (void) snprintf(get_line(0, 0), get_line_remain(), + " %s", getflag(*data, DHCPV6_FQDNF_N, + "Server performs no updates", + "Server performs updates")); + symp = inittab_getbycode( + ITAB_CAT_STANDARD | ITAB_CAT_V6, ITAB_CONS_SNOOP, + d6o.d6o_code); + if (symp != NULL) { + str = inittab_decode(symp, data, olen, B_TRUE); + if (str != NULL) { + (void) snprintf(get_line(0, 0), + get_line_remain(), + " FQDN = %s", str); + free(str); + } + free(symp); + } + break; + } + } + data = ostart + d6o.d6o_len; + len -= d6o.d6o_len; + } + if (len != 0) { + (void) strlcpy(get_line(0, 0), "Option entry truncated", + get_line_remain()); + } +} diff --git a/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_filter.c b/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_filter.c index 54b669fa44..e4ed050dbd 100644 --- a/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_filter.c +++ b/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_filter.c @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -2041,7 +2041,7 @@ checkstack(int numargs) static void primary() { - int m, s; + int m, m2, s; for (;;) { if (tokentype == FIELD) { @@ -2219,22 +2219,47 @@ primary() } if (EQ("bootp") || EQ("dhcp")) { + ethertype_match(ETHERTYPE_IP); + emitop(OP_BRFL); + m = chain(0); emitop(OP_OFFSET_LINK); - emitop(OP_LOAD_CONST); - emitval(9); - emitop(OP_LOAD_OCTET); - emitop(OP_LOAD_CONST); - emitval(IPPROTO_UDP); + compare_value(9, 1, IPPROTO_UDP); + emitop(OP_OFFSET_POP); + emitop(OP_BRFL); + m = chain(m); emitop(OP_OFFSET_IP); compare_value(0, 4, - (IPPORT_BOOTPS << 16 | IPPORT_BOOTPC)); + (IPPORT_BOOTPS << 16) | IPPORT_BOOTPC); emitop(OP_BRTR); - m = chain(0); + m2 = chain(0); compare_value(0, 4, - (IPPORT_BOOTPC << 16 | IPPORT_BOOTPS)); + (IPPORT_BOOTPC << 16) | IPPORT_BOOTPS); + resolve_chain(m2); + emitop(OP_OFFSET_POP); resolve_chain(m); + opstack++; + dir = ANY; + next(); + break; + } + + if (EQ("dhcp6")) { + ethertype_match(ETHERTYPE_IPV6); + emitop(OP_BRFL); + m = chain(0); + emitop(OP_OFFSET_LINK); + compare_value(6, 1, IPPROTO_UDP); emitop(OP_OFFSET_POP); + emitop(OP_BRFL); + m = chain(m); + emitop(OP_OFFSET_IP); + compare_value(2, 2, IPPORT_DHCPV6S); + emitop(OP_BRTR); + m2 = chain(0); + compare_value(2, 2, IPPORT_DHCPV6C); + resolve_chain(m2); emitop(OP_OFFSET_POP); + resolve_chain(m); opstack++; dir = ANY; next(); diff --git a/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_pf.c b/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_pf.c index 20b88e2c24..a7e25a2fbc 100644 --- a/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_pf.c +++ b/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_pf.c @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -1265,7 +1265,8 @@ pf_primary() EQ("length") || EQ("less") || EQ("greater") || EQ("port") || EQ("srcport") || EQ("dstport") || EQ("rpc") || EQ("gateway") || EQ("nofrag") || - EQ("bootp") || EQ("dhcp") || EQ("slp") || EQ("ldap")) { + EQ("bootp") || EQ("dhcp") || EQ("dhcp6") || + EQ("slp") || EQ("ldap")) { break; } diff --git a/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_rport.c b/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_rport.c index 9722ee24d5..f79202abab 100644 --- a/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_rport.c +++ b/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_rport.c @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -51,32 +51,34 @@ static const struct porttable pt_udp[] = { { IPPORT_ECHO, "ECHO" }, { IPPORT_DISCARD, "DISCARD" }, { IPPORT_DAYTIME, "DAYTIME" }, - { 19, "CHARGEN" }, + { IPPORT_CHARGEN, "CHARGEN" }, { IPPORT_TIMESERVER, "TIME" }, { IPPORT_NAMESERVER, "NAME" }, - { 53, "DNS" }, + { IPPORT_DOMAIN, "DNS" }, { IPPORT_BOOTPS, "BOOTPS" }, { IPPORT_BOOTPC, "BOOTPC" }, { IPPORT_TFTP, "TFTP" }, { IPPORT_FINGER, "FINGER" }, /* { 111, "PORTMAP" }, Just Sun RPC */ - { 123, "NTP" }, - { 137, "NBNS" }, - { 138, "NBDG" }, - { 389, "LDAP" }, - { 427, "SLP" }, + { IPPORT_NTP, "NTP" }, + { IPPORT_NETBIOS_NS, "NBNS" }, + { IPPORT_NETBIOS_DGM, "NBDG" }, + { IPPORT_LDAP, "LDAP" }, + { IPPORT_SLP, "SLP" }, /* Mobile IP defines a set of new control messages sent over UDP port 434 */ - { 434, "Mobile IP" }, + { IPPORT_MIP, "Mobile IP" }, { IPPORT_BIFFUDP, "BIFF" }, { IPPORT_WHOSERVER, "WHO" }, - { 514, "SYSLOG" }, - { 517, "TALK" }, + { IPPORT_SYSLOG, "SYSLOG" }, + { IPPORT_TALK, "TALK" }, { IPPORT_ROUTESERVER, "RIP" }, - { 521, "RIPng" }, + { IPPORT_RIPNG, "RIPng" }, + { IPPORT_DHCPV6C, "DHCPv6C" }, + { IPPORT_DHCPV6S, "DHCPv6S" }, { 550, "NEW-RWHO" }, { 560, "RMONITOR" }, { 561, "MONITOR" }, - { 1080, "SOCKS" }, + { IPPORT_SOCKS, "SOCKS" }, { 0, NULL } }; @@ -87,7 +89,7 @@ static struct porttable pt_tcp[] = { { IPPORT_SYSTAT, "SYSTAT" }, { IPPORT_DAYTIME, "DAYTIME" }, { IPPORT_NETSTAT, "NETSTAT" }, - { 19, "CHARGEN" }, + { IPPORT_CHARGEN, "CHARGEN" }, { 20, "FTP-DATA" }, { IPPORT_FTP, "FTP" }, { IPPORT_TELNET, "TELNET" }, @@ -96,11 +98,11 @@ static struct porttable pt_tcp[] = { { 39, "RLP" }, { IPPORT_NAMESERVER, "NAMESERVER" }, { IPPORT_WHOIS, "NICNAME" }, - { 53, "DNS" }, + { IPPORT_DOMAIN, "DNS" }, { 70, "GOPHER" }, { IPPORT_RJE, "RJE" }, { IPPORT_FINGER, "FINGER" }, - { 80, "HTTP" }, + { IPPORT_HTTP, "HTTP" }, { IPPORT_TTYLINK, "LINK" }, { IPPORT_SUPDUP, "SUPDUP" }, { 101, "HOSTNAME" }, @@ -113,27 +115,27 @@ static struct porttable pt_tcp[] = { { 113, "AUTH" }, { 117, "UUCP-PATH" }, { 119, "NNTP" }, - { 123, "NTP" }, - { 139, "NBT" }, + { IPPORT_NTP, "NTP" }, + { IPPORT_NETBIOS_SSN, "NBT" }, { 143, "IMAP" }, { 144, "NeWS" }, - { 389, "LDAP" }, - { 427, "SLP" }, + { IPPORT_LDAP, "LDAP" }, + { IPPORT_SLP, "SLP" }, { 443, "HTTPS" }, { 445, "SMB" }, { IPPORT_EXECSERVER, "EXEC" }, { IPPORT_LOGINSERVER, "RLOGIN" }, { IPPORT_CMDSERVER, "RSHELL" }, - { 515, "PRINTER" }, + { IPPORT_PRINTER, "PRINTER" }, { 530, "COURIER" }, { 540, "UUCP" }, { 600, "PCSERVER" }, - { 1080, "SOCKS" }, + { IPPORT_SOCKS, "SOCKS" }, { 1524, "INGRESLOCK" }, { 2904, "M2UA" }, { 2905, "M3UA" }, { 6000, "XWIN" }, - { 8080, "HTTP (proxy)" }, + { IPPORT_HTTP_ALT, "HTTP (proxy)" }, { 9900, "IUA" }, { 0, NULL }, }; @@ -352,12 +354,13 @@ interpret_reserved(int flags, int proto, in_port_t src, in_port_t dst, which = dst; } - if ((dst == 53 || src == 53) && proto != IPPROTO_TCP) { + if ((dst == IPPORT_DOMAIN || src == IPPORT_DOMAIN) && + proto != IPPROTO_TCP) { interpret_dns(flags, proto, (uchar_t *)data, dlen); return (1); } - if (dst == 514 && proto != IPPROTO_TCP) { + if (dst == IPPORT_SYSLOG && proto != IPPROTO_TCP) { /* * TCP port 514 is rshell. UDP port 514 is syslog. */ @@ -372,26 +375,30 @@ interpret_reserved(int flags, int proto, in_port_t src, in_port_t dst, (void) interpret_dhcp(flags, (struct dhcp *)data, dlen); return (1); + case IPPORT_DHCPV6S: + case IPPORT_DHCPV6C: + (void) interpret_dhcpv6(flags, (uint8_t *)data, dlen); + return (1); case IPPORT_TFTP: (void) interpret_tftp(flags, (struct tftphdr *)data, dlen); return (1); - case 80: - case 8080: + case IPPORT_HTTP: + case IPPORT_HTTP_ALT: (void) interpret_http(flags, data, dlen); return (1); - case 123: + case IPPORT_NTP: (void) interpret_ntp(flags, (struct ntpdata *)data, dlen); return (1); - case 137: + case IPPORT_NETBIOS_NS: interpret_netbios_ns(flags, (uchar_t *)data, dlen); return (1); - case 138: + case IPPORT_NETBIOS_DGM: interpret_netbios_datagram(flags, (uchar_t *)data, dlen); return (1); - case 139: + case IPPORT_NETBIOS_SSN: case 445: /* * SMB on port 445 is a subset of NetBIOS SMB @@ -400,23 +407,23 @@ interpret_reserved(int flags, int proto, in_port_t src, in_port_t dst, */ interpret_netbios_ses(flags, (uchar_t *)data, dlen); return (1); - case 389: + case IPPORT_LDAP: interpret_ldap(flags, data, dlen, src, dst); return (1); - case 427: + case IPPORT_SLP: interpret_slp(flags, data, dlen); return (1); - case 434: + case IPPORT_MIP: interpret_mip_cntrlmsg(flags, (uchar_t *)data, dlen); return (1); case IPPORT_ROUTESERVER: (void) interpret_rip(flags, (struct rip *)data, dlen); return (1); - case 521: + case IPPORT_RIPNG: (void) interpret_rip6(flags, (struct rip6 *)data, dlen); return (1); - case 1080: + case IPPORT_SOCKS: if (dir == 'C') (void) interpret_socks_call(flags, data, dlen); else diff --git a/usr/src/common/net/dhcp/dhcp_impl.h b/usr/src/common/net/dhcp/dhcp_impl.h index 6ee39108d0..8d9dca577b 100644 --- a/usr/src/common/net/dhcp/dhcp_impl.h +++ b/usr/src/common/net/dhcp/dhcp_impl.h @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 1999-2003 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -42,6 +41,7 @@ extern "C" { #include <netinet/in.h> #include <netinet/udp.h> #include <netinet/dhcp.h> +#include <netinet/dhcp6.h> #include <dhcp_symbol_common.h> #include <sys/sunos_dhcp_class.h> @@ -94,19 +94,30 @@ typedef struct { uint8_t value[1]; } DHCP_OPT; +typedef union sockaddr46_s { + struct sockaddr_in v4; + struct sockaddr_in6 v6; +} sockaddr46_t; + /* * Generic DHCP packet list. Ensure that _REENTRANT bracketed code stays at * bottom of this definition - the client doesn't include it. Scan.c in * libdhcp isn't aware of it either... + * + * The PKT * pointer here actually points to a dhcpv6_message_t if the packet + * is DHCPv6. We assume that PKT * the same or stricter alignment + * requirements, and that the unused elements are not a significant burden. */ #define MAX_PKT_LIST 5 /* maximum list size */ typedef struct dhcp_list { + struct dhcp_list *next; /* keep first and in this */ + struct dhcp_list *prev; /* order for insque/remque */ + PKT *pkt; /* client packet */ uint_t len; /* packet len */ int rfc1048; /* RFC1048 options - boolean */ - struct dhcp_list *prev; - struct dhcp_list *next; uint8_t offset; /* BOOTP packet offset */ + uint8_t isv6; /* DHCPv6 packet - boolean */ /* * standard/site options */ @@ -119,11 +130,19 @@ typedef struct dhcp_list { struct in_addr off_ip; /* Address OFFERed */ + uint_t ifindex; /* received ifindex (if any) */ + sockaddr46_t pktfrom; /* source (peer) address on input */ + sockaddr46_t pktto; /* destination (local) address */ + } PKT_LIST; extern int dhcp_options_scan(PKT_LIST *, boolean_t); extern boolean_t dhcp_getinfo_pl(PKT_LIST *, uchar_t, uint16_t, uint16_t, void *, size_t *); +extern dhcpv6_option_t *dhcpv6_find_option(const void *, size_t, + const dhcpv6_option_t *, uint16_t, uint_t *); +extern dhcpv6_option_t *dhcpv6_pkt_option(const PKT_LIST *, + const dhcpv6_option_t *, uint16_t, uint_t *); #ifdef __cplusplus } diff --git a/usr/src/common/net/dhcp/dhcp_symbol_common.h b/usr/src/common/net/dhcp/dhcp_symbol_common.h index dff5d836f1..ed07453a5a 100644 --- a/usr/src/common/net/dhcp/dhcp_symbol_common.h +++ b/usr/src/common/net/dhcp/dhcp_symbol_common.h @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2003 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -55,8 +54,6 @@ typedef enum { DSYM_INTERNAL = 5 /* Solaris DHCP internal option */ } dsym_category_t; -#define DSYM_CATEGORY_NUM DSYM_INTERNAL + 1 /* DSYM_BAD_CAT excluded */ - /* * Symbol type ids and strings */ @@ -75,11 +72,13 @@ typedef enum { DSYM_SNUMBER8 = 10, /* An 8-bit signed integer */ DSYM_SNUMBER16 = 11, /* A 16-bit signed integer */ DSYM_SNUMBER32 = 12, /* A 32-bit signed integer */ - DSYM_SNUMBER64 = 13 /* A 64-bit signed integer */ + DSYM_SNUMBER64 = 13, /* A 64-bit signed integer */ + DSYM_UNUMBER24 = 14, /* A 24-bit unsigned integer */ + DSYM_IPV6 = 15, /* An IPv6 address */ + DSYM_DUID = 16, /* A DHCP Unique Identifier */ + DSYM_DOMAIN = 17 /* An RFC 1035-encoded domain name */ } dsym_cdtype_t; -#define DSYM_CDTYPE_NUM DSYM_SNUMBER64 + 1 /* DSYM_BAD_TYPE excluded */ - #ifdef __cplusplus } #endif diff --git a/usr/src/common/net/dhcp/scan.c b/usr/src/common/net/dhcp/scan.c index cd669d9d7a..9bd4eccbde 100644 --- a/usr/src/common/net/dhcp/scan.c +++ b/usr/src/common/net/dhcp/scan.c @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 1996-2001, 2003 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Routines used to extract/insert DHCP options. Must be kept MT SAFE, @@ -175,3 +174,66 @@ dhcp_options_scan(PKT_LIST *pl, boolean_t scan_vendor) } return (0); } + +/* + * Locate a DHCPv6 option or suboption within a buffer. DHCPv6 uses nested + * options within options, and this function is designed to work with both + * primary options and the suboptions contained within. + * + * The 'oldopt' is a previous option pointer, and is typically used to iterate + * over options of the same code number. The 'codenum' is in host byte order + * for simplicity. 'retlenp' may be NULL, and if present gets the _entire_ + * option length (including header). + * + * Warning: the returned pointer has no particular alignment because DHCPv6 + * defines options without alignment. The caller must deal with unaligned + * pointers carefully. + */ +dhcpv6_option_t * +dhcpv6_find_option(const void *buffer, size_t buflen, + const dhcpv6_option_t *oldopt, uint16_t codenum, uint_t *retlenp) +{ + const uchar_t *bp; + dhcpv6_option_t d6o; + uint_t olen; + + codenum = htons(codenum); + bp = buffer; + while (buflen >= sizeof (dhcpv6_option_t)) { + (void) memcpy(&d6o, bp, sizeof (d6o)); + olen = ntohs(d6o.d6o_len) + sizeof (d6o); + if (olen > buflen) + break; + if (d6o.d6o_code != codenum || + (oldopt != NULL && bp <= (const uchar_t *)oldopt)) { + bp += olen; + buflen -= olen; + continue; + } + if (retlenp != NULL) + *retlenp = olen; + /* LINTED: alignment */ + return ((dhcpv6_option_t *)bp); + } + return (NULL); +} + +/* + * Locate a DHCPv6 option within the top level of a PKT_LIST entry. DHCPv6 + * uses nested options within options, and this function returns only the + * primary options. Use dhcpv6_find_option to traverse suboptions. + * + * See dhcpv6_find_option for usage details and warnings. + */ +dhcpv6_option_t * +dhcpv6_pkt_option(const PKT_LIST *plp, const dhcpv6_option_t *oldopt, + uint16_t codenum, uint_t *retlenp) +{ + const dhcpv6_message_t *d6m; + + if (plp == NULL || plp->pkt == NULL || plp->len < sizeof (*d6m)) + return (NULL); + d6m = (const dhcpv6_message_t *)plp->pkt; + return (dhcpv6_find_option(d6m + 1, plp->len - sizeof (*d6m), oldopt, + codenum, retlenp)); +} diff --git a/usr/src/head/protocols/ndpd.h b/usr/src/head/protocols/ndpd.h index f2e5d168f4..6cddbfa4e6 100644 --- a/usr/src/head/protocols/ndpd.h +++ b/usr/src/head/protocols/ndpd.h @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -75,7 +75,8 @@ extern struct confvar ifdefaults[]; #define I_TmpPreferredLifetime 14 /* In seconds */ #define I_TmpRegenAdvance 15 /* In seconds */ #define I_TmpMaxDesyncFactor 16 /* In seconds */ -#define I_IFSIZE 17 /* # of variables */ +#define I_StatefulAddrConf 17 +#define I_IFSIZE 18 /* # of variables */ typedef struct ndpd_info_s { uint_t info_type; @@ -137,6 +138,8 @@ typedef struct ndpd_phyint_info_s { #define phyint_TmpRegenAdvance phyint_config[I_TmpRegenAdvance].cf_value #define phyint_TmpMaxDesyncFactor \ phyint_config[I_TmpMaxDesyncFactor].cf_value +#define phyint_StatefulAddrConf \ + phyint_config[I_StatefulAddrConf].cf_value uint_t phyint_num_of_prefixes; uint_t phyint_num_of_routers; } ndpd_phyint_info_t; diff --git a/usr/src/lib/libdhcpagent/Makefile b/usr/src/lib/libdhcpagent/Makefile index dde31ea7b3..0232cfd534 100644 --- a/usr/src/lib/libdhcpagent/Makefile +++ b/usr/src/lib/libdhcpagent/Makefile @@ -19,7 +19,7 @@ # CDDL HEADER END # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" @@ -27,7 +27,7 @@ include ../Makefile.lib -HDRS = dhcp_hostconf.h dhcpagent_ipc.h dhcpagent_util.h +HDRS = dhcp_hostconf.h dhcpagent_ipc.h dhcpagent_util.h dhcp_stable.h HDRDIR = common SUBDIRS = $(MACH) diff --git a/usr/src/lib/libdhcpagent/Makefile.com b/usr/src/lib/libdhcpagent/Makefile.com index ecd15a6496..53625b5eef 100644 --- a/usr/src/lib/libdhcpagent/Makefile.com +++ b/usr/src/lib/libdhcpagent/Makefile.com @@ -19,7 +19,7 @@ # CDDL HEADER END # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" @@ -27,7 +27,7 @@ LIBRARY = libdhcpagent.a VERS = .1 -OBJECTS = dhcp_hostconf.o dhcpagent_ipc.o dhcpagent_util.o +OBJECTS = dhcp_hostconf.o dhcpagent_ipc.o dhcpagent_util.o dhcp_stable.o include ../../Makefile.lib @@ -36,7 +36,7 @@ include ../../Makefile.rootfs LIBS = $(DYNLIB) $(LINTLIB) -LDLIBS += -lc -lsocket -ldhcputil +LDLIBS += -lc -lsocket -ldhcputil -luuid -ldlpi SRCDIR = ../common $(LINTLIB) := SRCS = $(SRCDIR)/$(LINTSRC) diff --git a/usr/src/lib/libdhcpagent/common/dhcp_hostconf.c b/usr/src/lib/libdhcpagent/common/dhcp_hostconf.c index 49a8b09723..49fa6a8f38 100644 --- a/usr/src/lib/libdhcpagent/common/dhcp_hostconf.c +++ b/usr/src/lib/libdhcpagent/common/dhcp_hostconf.c @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -43,23 +42,26 @@ #include "dhcp_hostconf.h" static void relativize_time(DHCP_OPT *, time_t, time_t); +static void relativize_v6(uint32_t *, time_t, time_t); /* * ifname_to_hostconf(): converts an interface name into a hostconf file for * that interface * * input: const char *: the interface name + * boolean_t: B_TRUE if using DHCPv6 * output: char *: the hostconf filename * note: uses an internal static buffer (not threadsafe) */ char * -ifname_to_hostconf(const char *ifname) +ifname_to_hostconf(const char *ifname, boolean_t isv6) { - static char filename[sizeof (DHCP_HOSTCONF_TMPL) + IFNAMSIZ]; + static char filename[sizeof (DHCP_HOSTCONF_TMPL6) + LIFNAMSIZ]; (void) snprintf(filename, sizeof (filename), "%s%s%s", - DHCP_HOSTCONF_PREFIX, ifname, DHCP_HOSTCONF_SUFFIX); + DHCP_HOSTCONF_PREFIX, ifname, + isv6 ? DHCP_HOSTCONF_SUFFIX6 : DHCP_HOSTCONF_SUFFIX); return (filename); } @@ -68,14 +70,15 @@ ifname_to_hostconf(const char *ifname) * remove_hostconf(): removes an interface.dhc file * * input: const char *: the interface name + * boolean_t: B_TRUE if using DHCPv6 * output: int: 0 if the file is removed, -1 if it can't be removed * (errno is set) */ int -remove_hostconf(const char *ifname) +remove_hostconf(const char *ifname, boolean_t isv6) { - return (unlink(ifname_to_hostconf(ifname))); + return (unlink(ifname_to_hostconf(ifname, isv6))); } /* @@ -84,13 +87,15 @@ remove_hostconf(const char *ifname) * input: const char *: the interface name * PKT_LIST **: a pointer to a PKT_LIST * to store the info in * uint_t: the length of the list of PKT_LISTs - * output: int: 0 if the file is read and loaded into the PKT_LIST * + * boolean_t: B_TRUE if using DHCPv6 + * output: int: >0 if the file is read and loaded into the PKT_LIST * * successfully, -1 otherwise (errno is set) * note: the PKT and PKT_LISTs are dynamically allocated here */ int -read_hostconf(const char *ifname, PKT_LIST **plpp, uint_t plplen) +read_hostconf(const char *ifname, PKT_LIST **plpp, uint_t plplen, + boolean_t isv6) { PKT_LIST *plp = NULL; PKT *pkt = NULL; @@ -101,23 +106,23 @@ read_hostconf(const char *ifname, PKT_LIST **plpp, uint_t plplen) int pcnt = 0; int retval; - fd = open(ifname_to_hostconf(ifname), O_RDONLY); + fd = open(ifname_to_hostconf(ifname, isv6), O_RDONLY); if (fd == -1) return (-1); if (read(fd, &magic, sizeof (magic)) != sizeof (magic)) goto failure; - if (magic != DHCP_HOSTCONF_MAGIC) + if (magic != (isv6 ? DHCP_HOSTCONF_MAGIC6 : DHCP_HOSTCONF_MAGIC)) goto failure; if (read(fd, &orig_time, sizeof (orig_time)) != sizeof (orig_time)) goto failure; /* - * read the packet back in from disk, and run it through - * dhcp_options_scan(). note that we use calloc() since - * dhcp_options_scan() relies on the packet being zeroed. + * read the packet back in from disk, and for v4, run it through + * dhcp_options_scan(). note that we use calloc() because + * dhcp_options_scan() relies on the structure being zeroed. */ for (pcnt = 0; pcnt < plplen; pcnt++) { @@ -151,7 +156,7 @@ read_hostconf(const char *ifname, PKT_LIST **plpp, uint_t plplen) plpp[pcnt] = plp; - if (dhcp_options_scan(plp, B_TRUE) != 0) + if (!isv6 && dhcp_options_scan(plp, B_TRUE) != 0) goto failure; /* @@ -162,26 +167,129 @@ read_hostconf(const char *ifname, PKT_LIST **plpp, uint_t plplen) if (pcnt == 0) continue; - /* - * make sure the lease is still valid. - */ + if (isv6) { + dhcpv6_option_t d6o; + dhcpv6_ia_na_t d6in; + dhcpv6_iaaddr_t d6ia; + uchar_t *opts, *optmax, *subomax; - if (plp->opts[CD_LEASE_TIME] != NULL && - plp->opts[CD_LEASE_TIME]->len == sizeof (lease_t)) { + /* + * Loop over contents of the packet to find the address + * options. + */ + opts = (uchar_t *)pkt + sizeof (dhcpv6_message_t); + optmax = (uchar_t *)pkt + plp->len; + while (opts + sizeof (d6o) <= optmax) { + + /* + * Extract option header and make sure option + * is intact. + */ + (void) memcpy(&d6o, opts, sizeof (d6o)); + d6o.d6o_code = ntohs(d6o.d6o_code); + d6o.d6o_len = ntohs(d6o.d6o_len); + subomax = opts + sizeof (d6o) + d6o.d6o_len; + if (subomax > optmax) + break; + + /* + * If this isn't an option that contains + * address or prefix leases, then skip over it. + */ + if (d6o.d6o_code != DHCPV6_OPT_IA_NA && + d6o.d6o_code != DHCPV6_OPT_IA_TA && + d6o.d6o_code != DHCPV6_OPT_IA_PD) { + opts = subomax; + continue; + } + + /* + * Handle the option first. + */ + if (d6o.d6o_code == DHCPV6_OPT_IA_TA) { + /* no timers in this structure */ + opts += sizeof (dhcpv6_ia_ta_t); + } else { + /* both na and pd */ + if (opts + sizeof (d6in) > subomax) { + opts = subomax; + continue; + } + (void) memcpy(&d6in, opts, + sizeof (d6in)); + relativize_v6(&d6in.d6in_t1, orig_time, + current_time); + relativize_v6(&d6in.d6in_t2, orig_time, + current_time); + (void) memcpy(opts, &d6in, + sizeof (d6in)); + opts += sizeof (d6in); + } + + /* + * Now handle each suboption (address) inside. + */ + while (opts + sizeof (d6o) <= subomax) { + /* + * Verify the suboption header first. + */ + (void) memcpy(&d6o, opts, + sizeof (d6o)); + d6o.d6o_code = ntohs(d6o.d6o_code); + d6o.d6o_len = ntohs(d6o.d6o_len); + if (opts + sizeof (d6o) + d6o.d6o_len > + subomax) + break; + if (d6o.d6o_code != DHCPV6_OPT_IAADDR) { + opts += sizeof (d6o) + + d6o.d6o_len; + continue; + } + + /* + * Now process the contents. + */ + if (opts + sizeof (d6ia) > subomax) + break; + (void) memcpy(&d6ia, opts, + sizeof (d6ia)); + relativize_v6(&d6ia.d6ia_preflife, + orig_time, current_time); + relativize_v6(&d6ia.d6ia_vallife, + orig_time, current_time); + (void) memcpy(opts, &d6ia, + sizeof (d6ia)); + opts += sizeof (d6o) + d6o.d6o_len; + } + opts = subomax; + } + } else { - (void) memcpy(&lease, plp->opts[CD_LEASE_TIME]->value, - sizeof (lease_t)); + /* + * make sure the IPv4 DHCP lease is still valid. + */ - lease = ntohl(lease); - if ((lease != DHCP_PERM) && - (orig_time + lease) <= current_time) - goto failure; + if (plp->opts[CD_LEASE_TIME] != NULL && + plp->opts[CD_LEASE_TIME]->len == + sizeof (lease_t)) { + + (void) memcpy(&lease, + plp->opts[CD_LEASE_TIME]->value, + sizeof (lease_t)); + + lease = ntohl(lease); + if ((lease != DHCP_PERM) && + (orig_time + lease) <= current_time) + goto failure; + } + + relativize_time(plp->opts[CD_T1_TIME], orig_time, + current_time); + relativize_time(plp->opts[CD_T2_TIME], orig_time, + current_time); + relativize_time(plp->opts[CD_LEASE_TIME], orig_time, + current_time); } - - relativize_time(plp->opts[CD_T1_TIME], orig_time, current_time); - relativize_time(plp->opts[CD_T2_TIME], orig_time, current_time); - relativize_time(plp->opts[CD_LEASE_TIME], orig_time, - current_time); } (void) close(fd); @@ -203,9 +311,10 @@ failure: * * input: const char *: the interface name * PKT_LIST **: a list of pointers to PKT_LIST to write - * int: length of the list of PKT_LIST pointers + * uint_t: length of the list of PKT_LIST pointers * time_t: a starting time to treat the relative lease times * in the first packet as relative to + * boolean_t: B_TRUE if using DHCPv6 * output: int: 0 if the file is written successfully, -1 otherwise * (errno is set) */ @@ -215,16 +324,18 @@ write_hostconf( const char *ifname, PKT_LIST *pl[], uint_t pllen, - time_t relative_to) + time_t relative_to, + boolean_t isv6) { int fd; struct iovec iov[IOV_MAX]; int retval; - uint32_t magic = DHCP_HOSTCONF_MAGIC; + uint32_t magic; ssize_t explen = 0; /* Expected length of write */ int i, iovlen = 0; - fd = open(ifname_to_hostconf(ifname), O_WRONLY|O_CREAT|O_TRUNC, 0600); + fd = open(ifname_to_hostconf(ifname, isv6), O_WRONLY|O_CREAT|O_TRUNC, + 0600); if (fd == -1) return (-1); @@ -235,6 +346,7 @@ write_hostconf( * read_hostconf() to recalculate the lease times for the first packet. */ + magic = isv6 ? DHCP_HOSTCONF_MAGIC6 : DHCP_HOSTCONF_MAGIC; iov[iovlen].iov_base = (caddr_t)&magic; explen += iov[iovlen++].iov_len = sizeof (magic); iov[iovlen].iov_base = (caddr_t)&relative_to; @@ -280,3 +392,27 @@ relativize_time(DHCP_OPT *option, time_t orig_time, time_t current_time) (void) memcpy(option->value, &pkt_time, option->len); } + +/* + * relativize_v6(): re-relativizes a time in a DHCPv6 option + * + * input: uint32_t *: the time value to convert + * time_t: the time the leases in the packet are currently relative to + * time_t: the current time which leases will become relative to + * output: void + */ + +static void +relativize_v6(uint32_t *val, time_t orig_time, time_t current_time) +{ + uint32_t hval; + time_t time_diff = current_time - orig_time; + + hval = ntohl(*val); + if (hval != DHCPV6_INFTIME) { + if (hval < time_diff) + *val = 0; + else + *val = htonl(hval - time_diff); + } +} diff --git a/usr/src/lib/libdhcpagent/common/dhcp_hostconf.h b/usr/src/lib/libdhcpagent/common/dhcp_hostconf.h index c7dadd8583..dc7852e5ce 100644 --- a/usr/src/lib/libdhcpagent/common/dhcp_hostconf.h +++ b/usr/src/lib/libdhcpagent/common/dhcp_hostconf.h @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2003 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -33,6 +32,7 @@ #include <time.h> #include <netinet/in.h> #include <netinet/dhcp.h> +#include <netinet/dhcp6.h> #include <dhcp_impl.h> /* @@ -45,15 +45,19 @@ extern "C" { #endif -#define DHCP_HOSTCONF_MAGIC 0x44484301 /* hex "DHC1" */ +#define DHCP_HOSTCONF_MAGIC 0x44484301 /* hex "DHC\1" */ +#define DHCP_HOSTCONF_MAGIC6 0x44484302 /* hex "DHC\2" */ #define DHCP_HOSTCONF_PREFIX "/etc/dhcp/" #define DHCP_HOSTCONF_SUFFIX ".dhc" +#define DHCP_HOSTCONF_SUFFIX6 ".dh6" #define DHCP_HOSTCONF_TMPL DHCP_HOSTCONF_PREFIX DHCP_HOSTCONF_SUFFIX +#define DHCP_HOSTCONF_TMPL6 DHCP_HOSTCONF_PREFIX DHCP_HOSTCONF_SUFFIX6 -extern char *ifname_to_hostconf(const char *); -extern int remove_hostconf(const char *); -extern int read_hostconf(const char *, PKT_LIST **, uint_t); -extern int write_hostconf(const char *, PKT_LIST **, uint_t, time_t); +extern char *ifname_to_hostconf(const char *, boolean_t); +extern int remove_hostconf(const char *, boolean_t); +extern int read_hostconf(const char *, PKT_LIST **, uint_t, boolean_t); +extern int write_hostconf(const char *, PKT_LIST **, uint_t, time_t, + boolean_t); #ifdef __cplusplus } diff --git a/usr/src/lib/libdhcpagent/common/dhcp_stable.c b/usr/src/lib/libdhcpagent/common/dhcp_stable.c new file mode 100644 index 0000000000..7ae11346f6 --- /dev/null +++ b/usr/src/lib/libdhcpagent/common/dhcp_stable.c @@ -0,0 +1,308 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This module reads and writes the stable identifier values, DUID and IAID. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <limits.h> +#include <fcntl.h> +#include <errno.h> +#include <libdlpi.h> +#include <uuid/uuid.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <net/if.h> +#include <netinet/dhcp6.h> +#include <dhcp_inittab.h> + +#define DUID_FILE "/etc/dhcp/duid" +#define IAID_FILE "/etc/dhcp/iaid" + +struct iaid_ent { + uint32_t ie_iaid; + char ie_name[LIFNAMSIZ]; +}; + +/* + * read_stable_duid(): read the system's stable DUID, if any + * + * input: size_t *: pointer to a size_t to return the DUID length + * output: uchar_t *: the DUID buffer, or NULL on error (and errno is set) + * note: memory returned is from malloc; caller must free. + */ + +uchar_t * +read_stable_duid(size_t *duidlen) +{ + int fd; + ssize_t retv; + struct stat sb; + uchar_t *duid = NULL; + + if ((fd = open(DUID_FILE, O_RDONLY)) == -1) + return (NULL); + if (fstat(fd, &sb) != -1 && S_ISREG(sb.st_mode) && + (duid = malloc(sb.st_size)) != NULL) { + retv = read(fd, duid, sb.st_size); + if (retv == sb.st_size) { + *duidlen = sb.st_size; + } else { + free(duid); + /* + * Make sure that errno always gets set when something + * goes wrong. + */ + if (retv >= 0) + errno = EINVAL; + duid = NULL; + } + } + (void) close(fd); + return (duid); +} + +/* + * write_stable_duid(): write the system's stable DUID. + * + * input: const uchar_t *: pointer to the DUID buffer + * size_t: length of the DUID + * output: int: 0 on success, -1 on error. errno is set on error. + */ + +int +write_stable_duid(const uchar_t *duid, size_t duidlen) +{ + int fd; + ssize_t retv; + + (void) unlink(DUID_FILE); + if ((fd = open(DUID_FILE, O_WRONLY | O_CREAT, 0644)) == -1) + return (-1); + retv = write(fd, duid, duidlen); + if (retv == duidlen) { + return (close(fd)); + } else { + (void) close(fd); + if (retv >= 0) + errno = ENOSPC; + return (-1); + } +} + +/* + * make_stable_duid(): create a new DUID + * + * input: const char *: name of physical interface for reference + * size_t *: pointer to a size_t to return the DUID length + * output: uchar_t *: the DUID buffer, or NULL on error (and errno is set) + * note: memory returned is from malloc; caller must free. + */ + +uchar_t * +make_stable_duid(const char *physintf, size_t *duidlen) +{ + int fd, len; + dl_info_ack_t dl_info; + dlpi_if_attr_t dia; + duid_en_t *den; + + /* + * Try to read the MAC layer address for the physical interface + * provided as a hint. If that works, we can use a DUID-LLT. + */ + + fd = dlpi_if_open(physintf, &dia, B_FALSE); + if (fd != -1 && + dlpi_info(fd, -1, &dl_info, NULL, NULL, NULL, NULL, NULL, + NULL) != -1 && + (len = dl_info.dl_addr_length - abs(dl_info.dl_sap_length)) > 0) { + duid_llt_t *dllt; + uint_t arptype; + + arptype = dlpi_to_arp(dl_info.dl_mac_type); + + if ((dllt = malloc(sizeof (*dllt) + len)) == NULL) { + (void) dlpi_close(fd); + return (NULL); + } + if (arptype != 0 && dlpi_phys_addr(fd, -1, DL_CURR_PHYS_ADDR, + (uint8_t *)(dllt + 1), NULL) == 0) { + time_t now; + + dllt->dllt_dutype = htons(DHCPV6_DUID_LLT); + dllt->dllt_hwtype = htons(arptype); + now = time(NULL) - DUID_TIME_BASE; + dllt->dllt_time = htonl(now); + *duidlen = sizeof (*dllt) + len; + return ((uchar_t *)dllt); + } + free(dllt); + } + if (fd != -1) + (void) dlpi_close(fd); + + /* + * If we weren't able to create a DUID based on the network interface + * in use, then generate one based on a UUID. + */ + den = malloc(sizeof (*den) + UUID_LEN); + if (den != NULL) { + uuid_t uuid; + + den->den_dutype = htons(DHCPV6_DUID_EN); + DHCPV6_SET_ENTNUM(den, DHCPV6_SUN_ENT); + uuid_generate(uuid); + (void) memcpy(den + 1, uuid, UUID_LEN); + *duidlen = sizeof (*den) + UUID_LEN; + } + return ((uchar_t *)den); +} + +/* + * read_stable_iaid(): read a link's stable IAID, if any + * + * input: const char *: interface name + * output: uint32_t: the IAID, or 0 if none + */ + +uint32_t +read_stable_iaid(const char *intf) +{ + int fd; + struct iaid_ent ie; + + if ((fd = open(IAID_FILE, O_RDONLY)) == -1) + return (0); + while (read(fd, &ie, sizeof (ie)) == sizeof (ie)) { + if (strcmp(intf, ie.ie_name) == 0) { + (void) close(fd); + return (ie.ie_iaid); + } + } + (void) close(fd); + return (0); +} + +/* + * write_stable_iaid(): write out a link's stable IAID + * + * input: const char *: interface name + * output: uint32_t: the IAID, or 0 if none + */ + +int +write_stable_iaid(const char *intf, uint32_t iaid) +{ + int fd; + struct iaid_ent ie; + ssize_t retv; + + if ((fd = open(IAID_FILE, O_RDWR | O_CREAT, 0644)) == -1) + return (0); + while (read(fd, &ie, sizeof (ie)) == sizeof (ie)) { + if (strcmp(intf, ie.ie_name) == 0) { + (void) close(fd); + if (iaid == ie.ie_iaid) { + return (0); + } else { + errno = EINVAL; + return (-1); + } + } + } + (void) memset(&ie, 0, sizeof (ie)); + ie.ie_iaid = iaid; + (void) strlcpy(ie.ie_name, intf, sizeof (ie.ie_name)); + retv = write(fd, &ie, sizeof (ie)); + (void) close(fd); + if (retv == sizeof (ie)) { + return (0); + } else { + if (retv >= 0) + errno = ENOSPC; + return (-1); + } +} + +/* + * make_stable_iaid(): create a stable IAID for a link + * + * input: const char *: interface name + * uint32_t: the ifIndex for this link (as a "hint") + * output: uint32_t: the new IAID, never zero + */ + +/* ARGSUSED */ +uint32_t +make_stable_iaid(const char *intf, uint32_t hint) +{ + int fd; + struct iaid_ent ie; + uint32_t maxid, minunused; + boolean_t recheck; + + if ((fd = open(IAID_FILE, O_RDONLY)) == -1) + return (hint); + maxid = 0; + minunused = 1; + /* + * This logic is deliberately unoptimized. The reason is that it runs + * essentially just once per interface for the life of the system. + * Once the IAID is established, there's no reason to generate it + * again, and all we care about here is correctness. Also, IAIDs tend + * to get added in a logical sequence order, so the outer loop should + * not normally run more than twice. + */ + do { + recheck = B_FALSE; + while (read(fd, &ie, sizeof (ie)) == sizeof (ie)) { + if (ie.ie_iaid > maxid) + maxid = ie.ie_iaid; + if (ie.ie_iaid == minunused) { + recheck = B_TRUE; + minunused++; + } + if (ie.ie_iaid == hint) + hint = 0; + } + if (recheck) + (void) lseek(fd, 0, SEEK_SET); + } while (recheck); + (void) close(fd); + if (hint != 0) + return (hint); + else if (maxid != UINT32_MAX) + return (maxid + 1); + else + return (minunused); +} diff --git a/usr/src/lib/libdhcpagent/common/dhcp_stable.h b/usr/src/lib/libdhcpagent/common/dhcp_stable.h new file mode 100644 index 0000000000..3352283eed --- /dev/null +++ b/usr/src/lib/libdhcpagent/common/dhcp_stable.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 (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _DHCP_STABLE_H +#define _DHCP_STABLE_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This module reads and writes the stable identifier values, DUID and IAID. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/types.h> + +extern uchar_t *read_stable_duid(size_t *); +extern int write_stable_duid(const uchar_t *, size_t); +extern uchar_t *make_stable_duid(const char *, size_t *); + +extern uint32_t read_stable_iaid(const char *); +extern int write_stable_iaid(const char *, uint32_t); +extern uint32_t make_stable_iaid(const char *, uint32_t); + +#ifdef __cplusplus +} +#endif + +#endif /* _DHCP_STABLE_H */ diff --git a/usr/src/lib/libdhcpagent/common/dhcpagent_ipc.c b/usr/src/lib/libdhcpagent/common/dhcpagent_ipc.c index 5f17cb458b..8a3ec18060 100644 --- a/usr/src/lib/libdhcpagent/common/dhcpagent_ipc.c +++ b/usr/src/lib/libdhcpagent/common/dhcpagent_ipc.c @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -35,9 +34,11 @@ #include <fcntl.h> #include <errno.h> #include <netinet/in.h> +#include <netinet/tcp.h> #include <net/if.h> #include <sys/sockio.h> #include <sys/fcntl.h> +#include <sys/time.h> #include <stdio.h> /* snprintf */ #include <arpa/inet.h> /* ntohl, ntohs, etc */ @@ -71,7 +72,6 @@ #define BUFMAX 256 -static int dhcp_ipc_rresvport(in_port_t *); static int dhcp_ipc_timed_read(int, void *, unsigned int, int *); static int getinfo_ifnames(const char *, dhcp_optnum_t *, DHCP_OPT **); static char *get_ifnames(int, int); @@ -82,15 +82,15 @@ static char *get_ifnames(int, int); * * input: dhcp_ipc_type_t: the type of ipc request to allocate * const char *: the interface to associate the request with - * void *: the payload to send with the message (NULL if none) + * const void *: the payload to send with the message (NULL if none) * uint32_t: the payload size (0 if none) * dhcp_data_type_t: the description of the type of payload * output: dhcp_ipc_request_t *: the request on success, NULL on failure */ dhcp_ipc_request_t * -dhcp_ipc_alloc_request(dhcp_ipc_type_t type, const char *ifname, void *buffer, - uint32_t buffer_size, dhcp_data_type_t data_type) +dhcp_ipc_alloc_request(dhcp_ipc_type_t type, const char *ifname, + const void *buffer, uint32_t buffer_size, dhcp_data_type_t data_type) { dhcp_ipc_request_t *request = calloc(1, DHCP_IPC_REQUEST_SIZE + buffer_size); @@ -116,15 +116,15 @@ dhcp_ipc_alloc_request(dhcp_ipc_type_t type, const char *ifname, void *buffer, * * input: dhcp_ipc_request_t *: the request the reply is for * int: the return code (0 for success, DHCP_IPC_E_* otherwise) - * void *: the payload to send with the message (NULL if none) + * const void *: the payload to send with the message (NULL if none) * uint32_t: the payload size (0 if none) * dhcp_data_type_t: the description of the type of payload * output: dhcp_ipc_reply_t *: the reply on success, NULL on failure */ dhcp_ipc_reply_t * -dhcp_ipc_alloc_reply(dhcp_ipc_request_t *request, int return_code, void *buffer, - uint32_t buffer_size, dhcp_data_type_t data_type) +dhcp_ipc_alloc_reply(dhcp_ipc_request_t *request, int return_code, + const void *buffer, uint32_t buffer_size, dhcp_data_type_t data_type) { dhcp_ipc_reply_t *reply = calloc(1, DHCP_IPC_REPLY_SIZE + buffer_size); @@ -175,33 +175,36 @@ dhcp_ipc_get_data(dhcp_ipc_reply_t *reply, size_t *size, dhcp_data_type_t *type) * (dynamically allocated) * uint32_t: the minimum length of the packet * int: the # of milliseconds to wait for the message (-1 is forever) - * output: int: 0 on success, DHCP_IPC_E_* otherwise + * output: int: DHCP_IPC_SUCCESS on success, DHCP_IPC_E_* otherwise */ static int dhcp_ipc_recv_msg(int fd, void **msg, uint32_t base_length, int msec) { - ssize_t retval; + int retval; dhcp_ipc_reply_t *ipc_msg; uint32_t length; retval = dhcp_ipc_timed_read(fd, &length, sizeof (uint32_t), &msec); - if (retval != sizeof (uint32_t)) - return (DHCP_IPC_E_READ); + if (retval != DHCP_IPC_SUCCESS) + return (retval); + + if (length == 0) + return (DHCP_IPC_E_PROTO); *msg = malloc(length); if (*msg == NULL) return (DHCP_IPC_E_MEMORY); retval = dhcp_ipc_timed_read(fd, *msg, length, &msec); - if (retval != length) { + if (retval != DHCP_IPC_SUCCESS) { free(*msg); - return (DHCP_IPC_E_READ); + return (retval); } if (length < base_length) { free(*msg); - return (DHCP_IPC_E_READ); + return (DHCP_IPC_E_PROTO); } /* @@ -211,10 +214,10 @@ dhcp_ipc_recv_msg(int fd, void **msg, uint32_t base_length, int msec) ipc_msg = (dhcp_ipc_reply_t *)(*msg); if (ipc_msg->data_length + base_length != length) { free(*msg); - return (DHCP_IPC_E_READ); + return (DHCP_IPC_E_PROTO); } - return (0); + return (DHCP_IPC_SUCCESS); } /* @@ -335,28 +338,30 @@ int dhcp_ipc_make_request(dhcp_ipc_request_t *request, dhcp_ipc_reply_t **reply, int32_t timeout) { - int fd, retval; - struct sockaddr_in sin_peer; - in_port_t source_port = IPPORT_RESERVED - 1; - - (void) memset(&sin_peer, 0, sizeof (sin_peer)); + int fd, on, retval; + struct sockaddr_in sinv; - sin_peer.sin_family = AF_INET; - sin_peer.sin_port = htons(IPPORT_DHCPAGENT); - sin_peer.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - - if ((fd = dhcp_ipc_rresvport(&source_port)) == -1) { + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) + return (DHCP_IPC_E_SOCKET); - /* - * user isn't privileged. just make a socket. - */ + /* + * Bind a privileged port if we have sufficient privilege to do so. + * Continue as non-privileged otherwise. + */ + on = 1; + (void) setsockopt(fd, IPPROTO_TCP, TCP_ANONPRIVBIND, &on, sizeof (on)); - fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd == -1) - return (DHCP_IPC_E_SOCKET); + (void) memset(&sinv, 0, sizeof (sinv)); + sinv.sin_family = AF_INET; + if (bind(fd, (struct sockaddr *)&sinv, sizeof (sinv)) == -1) { + (void) dhcp_ipc_close(fd); + return (DHCP_IPC_E_BIND); } - retval = connect(fd, (struct sockaddr *)&sin_peer, sizeof (sin_peer)); + sinv.sin_port = htons(IPPORT_DHCPAGENT); + sinv.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + retval = connect(fd, (struct sockaddr *)&sinv, sizeof (sinv)); if (retval == -1) { (void) dhcp_ipc_close(fd); return (DHCP_IPC_E_CONNECT); @@ -504,7 +509,7 @@ dhcp_ipc_strerror(int error) /* note: this must be kept in sync with DHCP_IPC_E_* definitions */ const char *syscalls[] = { "<unknown>", "socket", "fcntl", "read", "accept", "close", - "bind", "listen", "malloc", "connect", "writev" + "bind", "listen", "malloc", "connect", "writev", "poll" }; const char *error_string; @@ -525,7 +530,8 @@ dhcp_ipc_strerror(int error) case DHCP_IPC_E_BIND: /* FALLTHRU */ case DHCP_IPC_E_LISTEN: /* FALLTHRU */ case DHCP_IPC_E_CONNECT: /* FALLTHRU */ - case DHCP_IPC_E_WRITEV: + case DHCP_IPC_E_WRITEV: /* FALLTHRU */ + case DHCP_IPC_E_POLL: error_string = strerror(errno); if (error_string == NULL) @@ -605,9 +611,16 @@ dhcp_ipc_strerror(int error) error_string = "no value was found for this option"; break; - case DHCP_IPC_E_NOIFCID: - error_string = "interface does not have a configured DHCP " - "client id"; + case DHCP_IPC_E_RUNNING: + error_string = "DHCP is already running"; + break; + + case DHCP_IPC_E_SRVFAILED: + error_string = "DHCP server refused request"; + break; + + case DHCP_IPC_E_EOF: + error_string = "ipc connection closed"; break; default: @@ -623,6 +636,28 @@ dhcp_ipc_strerror(int error) } /* + * dhcp_ipc_type_to_string(): maps an ipc command code into a human-readable + * string + * + * input: int: the ipc command code to map + * output: const char *: the corresponding human-readable string + */ + +const char * +dhcp_ipc_type_to_string(dhcp_ipc_type_t type) +{ + static const char *typestr[] = { + "drop", "extend", "ping", "release", "start", "status", + "inform", "get_tag" + }; + + if (type < 0 || type >= DHCP_NIPC) + return ("unknown"); + else + return (typestr[(int)type]); +} + +/* * getinfo_ifnames(): checks the value of a specified option on a list of * interface names. * input: const char *: a list of interface names to query (in order) for @@ -879,53 +914,14 @@ dhcp_ipc_getinfo(dhcp_optnum_t *optnum, DHCP_OPT **result, int32_t timeout) } /* - * NOTE: we provide our own version of this function because currently - * (sunos 5.7), if we link against the one in libnsl, we will - * increase the size of our binary by more than 482K due to - * perversions in linking. besides, this one is tighter :-) - */ - -static int -dhcp_ipc_rresvport(in_port_t *start_port) -{ - struct sockaddr_in sin; - int s, saved_errno; - - (void) memset(&sin, 0, sizeof (struct sockaddr_in)); - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = htonl(INADDR_ANY); - - if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) - return (-1); - - errno = EAGAIN; - while (*start_port > IPPORT_RESERVED / 2) { - - sin.sin_port = htons((*start_port)--); - - if (bind(s, (struct sockaddr *)&sin, sizeof (sin)) == 0) - return (s); - - if (errno != EADDRINUSE) { - saved_errno = errno; - break; - } - } - - (void) close(s); - errno = saved_errno; - return (-1); -} - -/* * dhcp_ipc_timed_read(): reads from a descriptor using a maximum timeout * * input: int: the file descriptor to read from * void *: the buffer to read into * unsigned int: the total length of data to read * int *: the number of milliseconds to wait; the number of - * milliseconds left are returned - * output: int: -1 on failure, otherwise the number of bytes read + * milliseconds left are returned (-1 is "forever") + * output: int: DHCP_IPC_SUCCESS on success, DHCP_IPC_E_* otherwise */ static int @@ -934,56 +930,63 @@ dhcp_ipc_timed_read(int fd, void *buffer, unsigned int length, int *msec) unsigned int n_total = 0; ssize_t n_read; struct pollfd pollfd; - struct timeval start, end, elapsed; - - /* make sure that any errors we return are ours */ - errno = 0; + hrtime_t start, end; + int retv; pollfd.fd = fd; pollfd.events = POLLIN; while (n_total < length) { - if (gettimeofday(&start, NULL) == -1) - return (-1); - - switch (poll(&pollfd, 1, *msec)) { + start = gethrtime(); - case 0: + retv = poll(&pollfd, 1, *msec); + if (retv == 0) { + /* This can happen only if *msec is not -1 */ *msec = 0; - return (n_total); - - case -1: - *msec = 0; - return (-1); + return (DHCP_IPC_E_TIMEOUT); + } - default: - if ((pollfd.revents & POLLIN) == 0) - return (-1); + if (*msec != -1) { + end = gethrtime(); + *msec -= (end - start) / (NANOSEC / MILLISEC); + if (*msec < 0) + *msec = 0; + } - if (gettimeofday(&end, NULL) == -1) - return (-1); + if (retv == -1) { + if (errno != EINTR) + return (DHCP_IPC_E_POLL); + else if (*msec == 0) + return (DHCP_IPC_E_TIMEOUT); + continue; + } - elapsed.tv_sec = end.tv_sec - start.tv_sec; - elapsed.tv_usec = end.tv_usec - start.tv_usec; - if (elapsed.tv_usec < 0) { - elapsed.tv_sec--; - elapsed.tv_usec += 1000000; /* one second */ - } + if (!(pollfd.revents & POLLIN)) { + errno = EINVAL; + return (DHCP_IPC_E_POLL); + } - n_read = read(fd, (caddr_t)buffer + n_total, - length - n_total); + n_read = read(fd, (caddr_t)buffer + n_total, length - n_total); - if (n_read == -1) - return (-1); + if (n_read == -1) { + if (errno != EINTR) + return (DHCP_IPC_E_READ); + else if (*msec == 0) + return (DHCP_IPC_E_TIMEOUT); + continue; + } - n_total += n_read; - *msec -= elapsed.tv_sec * 1000 + elapsed.tv_usec / 1000; - if (*msec <= 0 || n_read == 0) - return (n_total); - break; + if (n_read == 0) { + return (n_total == 0 ? DHCP_IPC_E_EOF : + DHCP_IPC_E_PROTO); } + + n_total += n_read; + + if (*msec == 0 && n_total < length) + return (DHCP_IPC_E_TIMEOUT); } - return (n_total); + return (DHCP_IPC_SUCCESS); } diff --git a/usr/src/lib/libdhcpagent/common/dhcpagent_ipc.h b/usr/src/lib/libdhcpagent/common/dhcpagent_ipc.h index b509917dff..91236a85bb 100644 --- a/usr/src/lib/libdhcpagent/common/dhcpagent_ipc.h +++ b/usr/src/lib/libdhcpagent/common/dhcpagent_ipc.h @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -49,6 +49,7 @@ extern "C" { #define DHCP_AGENT_PATH "/sbin/dhcpagent" #define DHCP_IPC_LISTEN_BACKLOG 30 #define IPPORT_DHCPAGENT 4999 +#define DHCP_IPC_MAX_WAIT 15 /* max seconds to wait to start agent */ /* * return values which should be used by programs which talk to the @@ -83,21 +84,25 @@ typedef enum { * * code in dhcpagent relies on the numeric values of these * requests -- but there's no sane reason to change them anyway. + * + * If any commands are changed, added, or removed, see the typestr[] + * array in dhcpagent_ipc.c. */ typedef enum { DHCP_DROP, DHCP_EXTEND, DHCP_PING, DHCP_RELEASE, DHCP_START, DHCP_STATUS, DHCP_INFORM, DHCP_GET_TAG, DHCP_NIPC, /* number of supported requests */ - DHCP_PRIMARY = 0x100 + DHCP_PRIMARY = 0x100, + DHCP_V6 = 0x200 } dhcp_ipc_type_t; /* structure passed with the DHCP_GET_TAG request */ typedef struct { - uchar_t category; - uint16_t code; - uint16_t size; + uint_t category; + uint_t code; + uint_t size; } dhcp_optnum_t; #define DHCP_IPC_CMD(type) ((type) & 0x00ff) @@ -114,15 +119,19 @@ typedef struct { */ enum { + /* System call errors must be kept contiguous */ DHCP_IPC_SUCCESS, DHCP_IPC_E_SOCKET, DHCP_IPC_E_FCNTL, DHCP_IPC_E_READ, DHCP_IPC_E_ACCEPT, DHCP_IPC_E_CLOSE, DHCP_IPC_E_BIND, DHCP_IPC_E_LISTEN, DHCP_IPC_E_MEMORY, - DHCP_IPC_E_CONNECT, DHCP_IPC_E_WRITEV, DHCP_IPC_E_TIMEOUT, + DHCP_IPC_E_CONNECT, DHCP_IPC_E_WRITEV, DHCP_IPC_E_POLL, + + /* All others follow */ + DHCP_IPC_E_TIMEOUT, DHCP_IPC_E_SRVFAILED, DHCP_IPC_E_EOF, DHCP_IPC_E_INVIF, DHCP_IPC_E_INT, DHCP_IPC_E_PERM, DHCP_IPC_E_OUTSTATE, DHCP_IPC_E_PEND, DHCP_IPC_E_BOOTP, DHCP_IPC_E_CMD_UNKNOWN, DHCP_IPC_E_UNKIF, DHCP_IPC_E_PROTO, DHCP_IPC_E_FAILEDIF, DHCP_IPC_E_NOPRIMARY, DHCP_IPC_E_DOWNIF, - DHCP_IPC_E_NOIPIF, DHCP_IPC_E_NOVALUE, DHCP_IPC_E_NOIFCID + DHCP_IPC_E_NOIPIF, DHCP_IPC_E_NOVALUE, DHCP_IPC_E_RUNNING }; /* @@ -133,11 +142,12 @@ enum { extern const char *dhcp_ipc_strerror(int); extern dhcp_ipc_request_t *dhcp_ipc_alloc_request(dhcp_ipc_type_t, const char *, - void *, uint32_t, dhcp_data_type_t); + const void *, uint32_t, dhcp_data_type_t); extern void *dhcp_ipc_get_data(dhcp_ipc_reply_t *, size_t *, dhcp_data_type_t *); extern int dhcp_ipc_make_request(dhcp_ipc_request_t *, dhcp_ipc_reply_t **, int32_t); +extern const char *dhcp_ipc_type_to_string(dhcp_ipc_type_t); /* * high-level public dhcpagent ipc functions @@ -153,8 +163,8 @@ extern int dhcp_ipc_getinfo(dhcp_optnum_t *, DHCP_OPT **, int32_t); extern int dhcp_ipc_init(int *); extern int dhcp_ipc_accept(int, int *, int *); extern int dhcp_ipc_recv_request(int, dhcp_ipc_request_t **, int); -extern dhcp_ipc_reply_t *dhcp_ipc_alloc_reply(dhcp_ipc_request_t *, int, void *, - uint32_t, dhcp_data_type_t); +extern dhcp_ipc_reply_t *dhcp_ipc_alloc_reply(dhcp_ipc_request_t *, int, + const void *, uint32_t, dhcp_data_type_t); extern int dhcp_ipc_send_reply(int, dhcp_ipc_reply_t *); extern int dhcp_ipc_close(int); @@ -174,9 +184,11 @@ typedef enum { RENEWING, /* have lease, but trying to renew */ REBINDING, /* have lease, but trying to rebind */ INFORMATION, /* sent INFORM, received ACK */ - INIT_REBOOT, /* attempting to use cached ACK */ + INIT_REBOOT, /* attempt to use cached ACK/Reply */ ADOPTING, /* attempting to adopt */ INFORM_SENT, /* sent INFORM, awaiting ACK */ + DECLINING, /* sent v6 Decline, awaiting Reply */ + RELEASING, /* sent v6 Release, awaiting Reply */ DHCP_NSTATES /* total number of states */ } DHCPSTATE; @@ -187,6 +199,7 @@ typedef enum { #define DHCP_IF_BOOTP 0x0400 /* interface is using bootp */ #define DHCP_IF_REMOVED 0x0800 /* interface is going away */ #define DHCP_IF_FAILED 0x1000 /* interface configuration problem */ +#define DHCP_IF_V6 0x2000 /* DHCPv6 interface */ /* * structure passed with the DHCP_STATUS replies diff --git a/usr/src/lib/libdhcpagent/common/dhcpagent_util.c b/usr/src/lib/libdhcpagent/common/dhcpagent_util.c index eab4a98edb..61101bb66c 100644 --- a/usr/src/lib/libdhcpagent/common/dhcpagent_util.c +++ b/usr/src/lib/libdhcpagent/common/dhcpagent_util.c @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -67,7 +67,9 @@ dhcp_state_to_string(DHCPSTATE state) "INFORMATION", "INIT_REBOOT", "ADOPTING", - "INFORM_SENT" + "INFORM_SENT", + "DECLINING", + "RELEASING" }; if (state < 0 || state >= DHCP_NSTATES) @@ -242,6 +244,9 @@ dhcp_status_reply_to_string(dhcp_ipc_reply_t *reply) if (status->if_dflags & DHCP_IF_BUSY) (void) strlcat(str, "[BUSY] ", sizeof (str)); + if (status->if_dflags & DHCP_IF_V6) + (void) strlcat(str, "[V6] ", sizeof (str)); + (void) strlcat(str, "\n", sizeof (str)); switch (status->if_state) { diff --git a/usr/src/lib/libdhcpagent/common/llib-ldhcpagent b/usr/src/lib/libdhcpagent/common/llib-ldhcpagent index 7ad9e54848..168da5b06f 100644 --- a/usr/src/lib/libdhcpagent/common/llib-ldhcpagent +++ b/usr/src/lib/libdhcpagent/common/llib-ldhcpagent @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,8 +19,8 @@ * CDDL HEADER END */ /* - * Copyright (c) 1999-2001 by Sun Microsystems, Inc. - * All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" @@ -30,5 +29,6 @@ /* PROTOLIB1 */ #include <dhcp_hostconf.h> +#include <dhcp_stable.h> #include <dhcpagent_ipc.h> #include <dhcpagent_util.h> diff --git a/usr/src/lib/libdhcpagent/common/mapfile-vers b/usr/src/lib/libdhcpagent/common/mapfile-vers index 589e76b4fb..ddd3a0dddf 100644 --- a/usr/src/lib/libdhcpagent/common/mapfile-vers +++ b/usr/src/lib/libdhcpagent/common/mapfile-vers @@ -19,7 +19,7 @@ # CDDL HEADER END # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" @@ -38,6 +38,7 @@ SUNWprivate_1.1 { dhcp_ipc_recv_request; dhcp_ipc_send_reply; dhcp_ipc_strerror; + dhcp_ipc_type_to_string; dhcp_start_agent; dhcp_state_to_string; dhcp_status_hdr_string; @@ -47,6 +48,12 @@ SUNWprivate_1.1 { read_hostconf; remove_hostconf; write_hostconf; + read_stable_duid; + write_stable_duid; + make_stable_duid; + read_stable_iaid; + write_stable_iaid; + make_stable_iaid; local: *; }; diff --git a/usr/src/lib/libdhcputil/Makefile.com b/usr/src/lib/libdhcputil/Makefile.com index e05af3bd3a..342f71f16d 100644 --- a/usr/src/lib/libdhcputil/Makefile.com +++ b/usr/src/lib/libdhcputil/Makefile.com @@ -19,7 +19,7 @@ # CDDL HEADER END # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" @@ -39,7 +39,7 @@ include ../../Makefile.rootfs LIBS = $(DYNLIB) $(LINTLIB) -LDLIBS += -lc -lnsl -lgen -linetutil +LDLIBS += -lc -lnsl -lgen -linetutil -ldlpi SRCDIR = ../common SRCS = $(LOCOBJS:%.o=$(SRCDIR)/%.c) $(COMOBJS:%.o=$(COMDIR)/%.c) diff --git a/usr/src/lib/libdhcputil/common/dhcp_inittab.c b/usr/src/lib/libdhcputil/common/dhcp_inittab.c index 4adca5a652..e16b241f23 100644 --- a/usr/src/lib/libdhcputil/common/dhcp_inittab.c +++ b/usr/src/lib/libdhcputil/common/dhcp_inittab.c @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -37,10 +36,13 @@ #include <libgen.h> #include <sys/isa_defs.h> #include <sys/socket.h> +#include <net/if_arp.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/sysmacros.h> #include <libinetutil.h> +#include <libdlpi.h> +#include <netinet/dhcp6.h> #include "dhcp_symbol.h" #include "dhcp_inittab.h" @@ -61,6 +63,10 @@ static boolean_t parse_entry(char *, char **); /* * forward declaration of our internal inittab_table[]. too bulky to put * up front -- check the end of this file for its definition. + * + * Note: we have only an IPv4 version here. The inittab_verify() function is + * used by the DHCP server and manager. We'll need a new function if the + * server is extended to DHCPv6. */ static dhcp_symbol_t inittab_table[]; @@ -95,6 +101,58 @@ static category_map_entry_t category_map[] = { }; /* + * dlpi_to_arp(): converts DLPI datalink types into ARP datalink types + * + * input: uint_t: the DLPI datalink type + * output: uint_t: the ARP datalink type (0 if no corresponding code) + * + * note: this function does not belong in this library, but it's here until + * dhcpagent is ported over to libdlpi. It should move to libdlpi + * instead. + */ + +uint_t +dlpi_to_arp(uint_t dlpi_type) +{ + switch (dlpi_type) { + + case DL_ETHER: + return (ARPHRD_ETHER); + + case DL_FRAME: + return (ARPHRD_FRAME); + + case DL_ATM: + return (ARPHRD_ATM); + + case DL_IPATM: + return (ARPHRD_IPATM); + + case DL_HDLC: + return (ARPHRD_HDLC); + + case DL_FC: + return (ARPHRD_FC); + + case DL_CSMACD: /* ieee 802 networks */ + case DL_TPB: + case DL_TPR: + case DL_METRO: + case DL_FDDI: + return (ARPHRD_IEEE802); + + case DL_IB: + return (ARPHRD_IB); + + case DL_IPV4: + case DL_IPV6: + return (ARPHRD_TUNNEL); + } + + return (0); +} + +/* * inittab_load(): returns all inittab entries with the specified criteria * * input: uchar_t: the categories the consumer is interested in @@ -103,6 +161,7 @@ static category_map_entry_t category_map[] = { * output: dhcp_symbol_t *: an array of dynamically allocated entries * on success, NULL upon failure */ + dhcp_symbol_t * inittab_load(uchar_t categories, char consumer, size_t *n_entries) { @@ -118,6 +177,7 @@ inittab_load(uchar_t categories, char consumer, size_t *n_entries) * output: dhcp_symbol_t *: a dynamically allocated dhcp_symbol structure * on success, NULL upon failure */ + dhcp_symbol_t * inittab_getbyname(uchar_t categories, char consumer, const char *name) { @@ -133,6 +193,7 @@ inittab_getbyname(uchar_t categories, char consumer, const char *name) * output: dhcp_symbol_t *: a dynamically allocated dhcp_symbol structure * on success, NULL upon failure */ + dhcp_symbol_t * inittab_getbycode(uchar_t categories, char consumer, uint16_t code) { @@ -152,6 +213,7 @@ inittab_getbycode(uchar_t categories, char consumer, uint16_t code) * output: dhcp_symbol_t *: dynamically allocated dhcp_symbol structures * on success, NULL upon failure */ + static dhcp_symbol_t * inittab_lookup(uchar_t categories, char consumer, const char *name, int32_t code, size_t *n_entriesp) @@ -163,18 +225,24 @@ inittab_lookup(uchar_t categories, char consumer, const char *name, char *fields[ITAB_FIELDS]; unsigned long line = 0; size_t i, n_entries = 0; - char *inittab_path; + const char *inittab_path; uchar_t category_code; dsym_cdtype_t type; - inittab_path = getenv("DHCP_INITTAB_PATH"); - if (inittab_path == NULL) - inittab_path = ITAB_INITTAB_PATH; + if (categories & ITAB_CAT_V6) { + inittab_path = getenv("DHCP_INITTAB6_PATH"); + if (inittab_path == NULL) + inittab_path = ITAB_INITTAB6_PATH; + } else { + inittab_path = getenv("DHCP_INITTAB_PATH"); + if (inittab_path == NULL) + inittab_path = ITAB_INITTAB_PATH; + } inittab_fp = fopen(inittab_path, "r"); if (inittab_fp == NULL) { inittab_msg("inittab_lookup: fopen: %s: %s", - ITAB_INITTAB_PATH, strerror(errno)); + inittab_path, strerror(errno)); return (NULL); } @@ -275,6 +343,7 @@ inittab_lookup(uchar_t categories, char consumer, const char *name, entry.ds_classes.dc_names = NULL; (void) strlcpy(entry.ds_name, fields[ITAB_NAME], sizeof (entry.ds_name)); + entry.ds_dhcpv6 = (categories & ITAB_CAT_V6) ? 1 : 0; entries = new_entries; entries[n_entries++] = entry; @@ -301,6 +370,7 @@ inittab_lookup(uchar_t categories, char consumer, const char *name, * pointers into the entry on upon return * output: boolean_t: B_TRUE on success, B_FALSE on failure */ + static boolean_t parse_entry(char *entry, char **fields) { @@ -375,9 +445,12 @@ parse_entry(char *entry, char **fields) * dhcp_symbol_t *: if non-NULL, a place to store the internal * inittab entry upon return * output: int: ITAB_FAILURE, ITAB_SUCCESS, or ITAB_UNKNOWN + * + * notes: IPv4 only */ + int -inittab_verify(dhcp_symbol_t *inittab_ent, dhcp_symbol_t *internal_ent) +inittab_verify(const dhcp_symbol_t *inittab_ent, dhcp_symbol_t *internal_ent) { unsigned int i; @@ -403,29 +476,152 @@ inittab_verify(dhcp_symbol_t *inittab_ent, dhcp_symbol_t *internal_ent) } /* + * get_hw_type(): interpret ",hwtype" in the input string, as part of a DUID. + * The hwtype string is optional, and must be 0-65535 if + * present. + * + * input: char **: pointer to string pointer + * int *: error return value + * output: int: hardware type, or -1 for empty, or -2 for error. + */ + +static int +get_hw_type(char **strp, int *ierrnop) +{ + char *str = *strp; + ulong_t hwtype; + + if (*str++ != ',') { + *ierrnop = ITAB_BAD_NUMBER; + return (-2); + } + if (*str == ',' || *str == '\0') { + *strp = str; + return (-1); + } + hwtype = strtoul(str, strp, 0); + if (errno != 0 || *strp == str || hwtype > 65535) { + *ierrnop = ITAB_BAD_NUMBER; + return (-2); + } else { + return ((int)hwtype); + } +} + +/* + * get_mac_addr(): interpret ",macaddr" in the input string, as part of a DUID. + * The 'macaddr' may be a hex string (in any standard format), + * or the name of a physical interface. If an interface name + * is given, then the interface type is extracted as well. + * + * input: const char *: input string + * int *: error return value + * uint16_t *: hardware type output (network byte order) + * int: hardware type input; -1 for empty + * uchar_t *: output buffer for MAC address + * output: int: length of MAC address, or -1 for error + */ + +static int +get_mac_addr(const char *str, int *ierrnop, uint16_t *hwret, int hwtype, + uchar_t *outbuf) +{ + int fd = -1; + int maclen; + int dig, val; + dlpi_if_attr_t dia; + dl_info_ack_t dl_info; + char chr; + + if (*str != '\0') { + if (*str++ != ',') + goto failed; + if ((fd = dlpi_if_open(str, &dia, B_FALSE)) == -1) { + maclen = 0; + dig = val = 0; + /* + * Allow MAC addresses with separators matching regexp + * (:|-| *). + */ + while ((chr = *str++) != '\0') { + if (isdigit(chr)) { + val = (val << 4) + chr - '0'; + } else if (isxdigit(chr)) { + val = (val << 4) + chr - + (isupper(chr) ? 'A' : 'a') + 10; + } else if (isspace(chr) && dig == 0) { + continue; + } else if (chr == ':' || chr == '-' || + isspace(chr)) { + dig = 1; + } else { + goto failed; + } + if (++dig == 2) { + *outbuf++ = val; + maclen++; + dig = val = 0; + } + } + } else { + if (dlpi_info(fd, -1, &dl_info, NULL, NULL, NULL, + NULL, NULL, NULL) == -1) + goto failed; + maclen = dl_info.dl_addr_length - + abs(dl_info.dl_sap_length); + if (maclen > MAXADDRLEN) + goto failed; + if (dlpi_phys_addr(fd, -1, DL_CURR_PHYS_ADDR, outbuf, + NULL) == -1) + goto failed; + (void) dlpi_close(fd); + if (hwtype == -1) + hwtype = dlpi_to_arp(dl_info.dl_mac_type); + } + } + if (hwtype == -1) + goto failed; + *hwret = htons(hwtype); + return (maclen); + +failed: + if (fd != -1) + (void) dlpi_close(fd); + *ierrnop = ITAB_BAD_NUMBER; + return (-1); +} + +/* * inittab_encode_e(): converts a string representation of a given datatype into * binary; used for encoding ascii values into a form that * can be put in DHCP packets to be sent on the wire. * - * input: dhcp_symbol_t *: the entry describing the value option + * input: const dhcp_symbol_t *: the entry describing the value option * const char *: the value to convert * uint16_t *: set to the length of the binary data returned * boolean_t: if false, return a full DHCP option + * int *: error return value * output: uchar_t *: a dynamically allocated byte array with converted data */ + uchar_t * -inittab_encode_e(dhcp_symbol_t *ie, const char *value, uint16_t *lengthp, +inittab_encode_e(const dhcp_symbol_t *ie, const char *value, uint16_t *lengthp, boolean_t just_payload, int *ierrnop) { - uint16_t length = 0; + int hlen = 0; + uint16_t length; uchar_t n_entries = 0; const char *valuep; char *currp; uchar_t *result = NULL; + uchar_t *optstart; unsigned int i; uint8_t type_size = inittab_type_to_size(ie); boolean_t is_signed; uint_t vallen, reslen; + dhcpv6_option_t *d6o; + int type; + char *cp2; *ierrnop = 0; if (type_size == 0) { @@ -433,60 +629,319 @@ inittab_encode_e(dhcp_symbol_t *ie, const char *value, uint16_t *lengthp, return (NULL); } - if (ie->ds_type == DSYM_ASCII) + switch (ie->ds_type) { + case DSYM_ASCII: n_entries = strlen(value); /* no NUL */ - else if (ie->ds_type == DSYM_OCTET) { + break; + + case DSYM_OCTET: vallen = strlen(value); n_entries = vallen / 2; n_entries += vallen % 2; - } else { + break; + + case DSYM_DOMAIN: + /* + * Maximum (worst-case) encoded length is one byte more than + * the number of characters on input. + */ + n_entries = strlen(value) + 1; + break; + + case DSYM_DUID: + /* Worst case is ":::::" */ + n_entries = strlen(value); + if (n_entries < MAXADDRLEN) + n_entries = MAXADDRLEN; + n_entries += sizeof (duid_llt_t); + break; + + default: /* * figure out the number of entries by counting the spaces * in the value string */ for (valuep = value; valuep++ != NULL; n_entries++) valuep = strchr(valuep, ' '); + break; } /* * if we're gonna return a complete option, then include the * option length and code in the size of the packet we allocate */ - if (just_payload == B_FALSE) - length += 2; + if (!just_payload) + hlen = ie->ds_dhcpv6 ? sizeof (*d6o) : 2; - length += n_entries * type_size; - if (length > 0) - result = malloc(length); + length = n_entries * type_size; + if (hlen + length > 0) + result = malloc(hlen + length); + + if ((optstart = result) != NULL && !just_payload) + optstart += hlen; switch (ie->ds_type) { case DSYM_ASCII: - if (result == NULL) { + if (optstart == NULL) { + *ierrnop = ITAB_NOMEM; + return (NULL); + } + + (void) memcpy(optstart, value, length); + break; + + case DSYM_DOMAIN: + if (optstart == NULL) { *ierrnop = ITAB_NOMEM; return (NULL); } - if (strlen(value) > length) { + /* + * Note that this encoder always presents the trailing 0-octet + * when dealing with a list. This means that you can't have + * non-fully-qualified members anywhere but at the end of a + * list (or as the only member of the list). + */ + valuep = value; + while (*valuep != '\0') { + int dig, val, inchr; + boolean_t escape; + uchar_t *flen; + + /* + * Skip over whitespace that delimits list members. + */ + if (isascii(*valuep) && isspace(*valuep)) { + valuep++; + continue; + } + dig = val = 0; + escape = B_FALSE; + flen = optstart++; + while ((inchr = *valuep) != '\0') { + valuep++; + /* + * Just copy non-ASCII text directly to the + * output string. This simplifies the use of + * other ctype macros below, as, unlike the + * special isascii function, they don't handle + * non-ASCII. + */ + if (!isascii(inchr)) { + escape = B_FALSE; + *optstart++ = inchr; + continue; + } + if (escape) { + /* + * Handle any of \D, \DD, or \DDD for + * a digit escape. + */ + if (isdigit(inchr)) { + val = val * 10 + inchr - '0'; + if (++dig == 3) { + *optstart++ = val; + dig = val = 0; + escape = B_FALSE; + } + continue; + } else if (dig > 0) { + /* + * User terminated \D or \DD + * with non-digit. An error, + * but we can assume he means + * to treat as \00D or \0DD. + */ + *optstart++ = val; + dig = val = 0; + } + /* Fall through and copy character */ + escape = B_FALSE; + } else if (inchr == '\\') { + escape = B_TRUE; + continue; + } else if (inchr == '.') { + /* + * End of component. Write the length + * prefix. If the component is zero + * length (i.e., ".."), the just omit + * it. + */ + *flen = (optstart - flen) - 1; + if (*flen > 0) + flen = optstart++; + continue; + } else if (isspace(inchr)) { + /* + * Unescaped space; end of domain name + * in list. + */ + break; + } + *optstart++ = inchr; + } + /* + * Handle trailing escape sequence. If string ends + * with \, then assume user wants \ at end of encoded + * string. If it ends with \D or \DD, assume \00D or + * \0DD. + */ + if (escape) + *optstart++ = dig > 0 ? val : '\\'; + *flen = (optstart - flen) - 1; + /* + * If user specified FQDN with trailing '.', then above + * will result in zero for the last component length. + * We're done, and optstart already points to the start + * of the next in list. Otherwise, we need to write a + * single zero byte to end the entry, if there are more + * entries that will be decoded. + */ + while (isascii(*valuep) && isspace(*valuep)) + valuep++; + if (*flen > 0 && *valuep != '\0') + *optstart++ = '\0'; + } + length = (optstart - result) - hlen; + break; + + case DSYM_DUID: + if (optstart == NULL) { + *ierrnop = ITAB_NOMEM; + return (NULL); + } + + errno = 0; + type = strtoul(value, &currp, 0); + if (errno != 0 || value == currp || type > 65535 || + (*currp != ',' && *currp != '\0')) { free(result); - *ierrnop = ITAB_BAD_STRING; + *ierrnop = ITAB_BAD_NUMBER; return (NULL); } + switch (type) { + case DHCPV6_DUID_LLT: { + duid_llt_t dllt; + int hwtype; + ulong_t tstamp; + int maclen; + + if ((hwtype = get_hw_type(&currp, ierrnop)) == -2) { + free(result); + return (NULL); + } + if (*currp++ != ',') { + free(result); + *ierrnop = ITAB_BAD_NUMBER; + return (NULL); + } + if (*currp == ',' || *currp == '\0') { + tstamp = time(NULL) - DUID_TIME_BASE; + } else { + tstamp = strtoul(currp, &cp2, 0); + if (errno != 0 || currp == cp2) { + free(result); + *ierrnop = ITAB_BAD_NUMBER; + return (NULL); + } + currp = cp2; + } + maclen = get_mac_addr(currp, ierrnop, + &dllt.dllt_hwtype, hwtype, + optstart + sizeof (dllt)); + if (maclen == -1) { + free(result); + return (NULL); + } + dllt.dllt_dutype = htons(type); + dllt.dllt_time = htonl(tstamp); + (void) memcpy(optstart, &dllt, sizeof (dllt)); + length = maclen + sizeof (dllt); + break; + } + case DHCPV6_DUID_EN: { + duid_en_t den; + ulong_t enterp; - (void) memcpy(result, value, length); + if (*currp++ != ',') { + free(result); + *ierrnop = ITAB_BAD_NUMBER; + return (NULL); + } + enterp = strtoul(currp, &cp2, 0); + DHCPV6_SET_ENTNUM(&den, enterp); + if (errno != 0 || currp == cp2 || + enterp != DHCPV6_GET_ENTNUM(&den) || + (*cp2 != ',' && *cp2 != '\0')) { + free(result); + *ierrnop = ITAB_BAD_NUMBER; + return (NULL); + } + if (*cp2 == ',') + cp2++; + vallen = strlen(cp2); + reslen = (vallen + 1) / 2; + if (hexascii_to_octet(cp2, vallen, + optstart + sizeof (den), &reslen) != 0) { + free(result); + *ierrnop = ITAB_BAD_NUMBER; + return (NULL); + } + den.den_dutype = htons(type); + (void) memcpy(optstart, &den, sizeof (den)); + length = reslen + sizeof (den); + break; + } + case DHCPV6_DUID_LL: { + duid_ll_t dll; + int hwtype; + int maclen; + + if ((hwtype = get_hw_type(&currp, ierrnop)) == -2) { + free(result); + return (NULL); + } + maclen = get_mac_addr(currp, ierrnop, &dll.dll_hwtype, + hwtype, optstart + sizeof (dll)); + if (maclen == -1) { + free(result); + return (NULL); + } + dll.dll_dutype = htons(type); + (void) memcpy(optstart, &dll, sizeof (dll)); + length = maclen + sizeof (dll); + break; + } + default: + if (*currp == ',') + currp++; + vallen = strlen(currp); + reslen = (vallen + 1) / 2; + if (hexascii_to_octet(currp, vallen, optstart + 2, + &reslen) != 0) { + free(result); + *ierrnop = ITAB_BAD_NUMBER; + return (NULL); + } + optstart[0] = type >> 8; + optstart[1] = type; + length = reslen + 2; + break; + } break; case DSYM_OCTET: - if (result == NULL) { + if (optstart == NULL) { *ierrnop = ITAB_BAD_OCTET; return (NULL); } reslen = length; /* Call libinetutil function to decode */ - if (hexascii_to_octet(value, vallen, result, &reslen) != 0) { + if (hexascii_to_octet(value, vallen, optstart, &reslen) != 0) { free(result); *ierrnop = ITAB_BAD_OCTET; return (NULL); @@ -494,8 +949,9 @@ inittab_encode_e(dhcp_symbol_t *ie, const char *value, uint16_t *lengthp, break; case DSYM_IP: + case DSYM_IPV6: - if (result == NULL) { + if (optstart == NULL) { *ierrnop = ITAB_BAD_IPADDR; return (NULL); } @@ -512,8 +968,8 @@ inittab_encode_e(dhcp_symbol_t *ie, const char *value, uint16_t *lengthp, currp = strchr(valuep, ' '); if (currp != NULL) *currp = '\0'; - if (inet_pton(AF_INET, valuep, - &result[i * sizeof (ipaddr_t)]) != 1) { + if (inet_pton(ie->ds_type == DSYM_IP ? AF_INET : + AF_INET6, valuep, optstart) != 1) { *ierrnop = ITAB_BAD_IPADDR; inittab_msg("inittab_encode: bogus ip address"); free(result); @@ -531,6 +987,7 @@ inittab_encode_e(dhcp_symbol_t *ie, const char *value, uint16_t *lengthp, } break; } + optstart += type_size; } break; @@ -539,12 +996,13 @@ inittab_encode_e(dhcp_symbol_t *ie, const char *value, uint16_t *lengthp, case DSYM_SNUMBER8: /* FALLTHRU */ case DSYM_UNUMBER16: /* FALLTHRU */ case DSYM_SNUMBER16: /* FALLTHRU */ + case DSYM_UNUMBER24: /* FALLTHRU */ case DSYM_UNUMBER32: /* FALLTHRU */ case DSYM_SNUMBER32: /* FALLTHRU */ case DSYM_UNUMBER64: /* FALLTHRU */ case DSYM_SNUMBER64: - if (result == NULL) { + if (optstart == NULL) { *ierrnop = ITAB_BAD_NUMBER; return (NULL); } @@ -555,7 +1013,7 @@ inittab_encode_e(dhcp_symbol_t *ie, const char *value, uint16_t *lengthp, ie->ds_type == DSYM_SNUMBER8); if (encode_number(n_entries, type_size, is_signed, 0, value, - result, ierrnop) == B_FALSE) { + optstart, ierrnop) == B_FALSE) { free(result); return (NULL); } @@ -575,18 +1033,23 @@ inittab_encode_e(dhcp_symbol_t *ie, const char *value, uint16_t *lengthp, } /* - * if just_payload is false, then we need to slide the option - * code and length fields in. (length includes them in its - * count, so we have to subtract 2) + * if just_payload is false, then we need to add the option + * code and length fields in. */ - if (just_payload == B_FALSE) { - (void) memmove(result + 2, result, length - 2); - result[0] = ie->ds_code; - result[1] = length - 2; + if (!just_payload) { + if (ie->ds_dhcpv6) { + /* LINTED: alignment */ + d6o = (dhcpv6_option_t *)result; + d6o->d6o_code = htons(ie->ds_code); + d6o->d6o_len = htons(length); + } else { + result[0] = ie->ds_code; + result[1] = length; + } } if (lengthp != NULL) - *lengthp = length; + *lengthp = length + hlen; return (result); } @@ -603,16 +1066,18 @@ inittab_encode_e(dhcp_symbol_t *ie, const char *value, uint16_t *lengthp, * int *: set to extended error code if error occurs. * output: char *: a dynamically allocated string containing the converted data */ + char * -inittab_decode_e(dhcp_symbol_t *ie, uchar_t *payload, uint16_t length, - boolean_t just_payload, int *ierrnop) +inittab_decode_e(const dhcp_symbol_t *ie, const uchar_t *payload, + uint16_t length, boolean_t just_payload, int *ierrnop) { - char *resultp, *end, *result = NULL; - char *currp; - uchar_t n_entries; + char *resultp, *result = NULL; + uint_t n_entries; struct in_addr in_addr; + in6_addr_t in6_addr; uint8_t type_size = inittab_type_to_size(ie); boolean_t is_signed; + int type; *ierrnop = 0; if (type_size == 0) { @@ -620,9 +1085,17 @@ inittab_decode_e(dhcp_symbol_t *ie, uchar_t *payload, uint16_t length, return (NULL); } - if (just_payload == B_FALSE) { - length = payload[1]; - payload += 2; + if (!just_payload) { + if (ie->ds_dhcpv6) { + dhcpv6_option_t d6o; + + (void) memcpy(&d6o, payload, sizeof (d6o)); + length = ntohs(d6o.d6o_len); + payload += sizeof (d6o); + } else { + length = payload[1]; + payload += 2; + } } /* @@ -659,6 +1132,167 @@ inittab_decode_e(dhcp_symbol_t *ie, uchar_t *payload, uint16_t length, result[n_entries] = '\0'; break; + case DSYM_DOMAIN: + + /* + * A valid, decoded RFC 1035 domain string or sequence of + * strings is always the same size as the encoded form, but we + * allow for RFC 1035 \DDD and \\ and \. escaping. + * + * Decoding stops at the end of the input or the first coding + * violation. Coding violations result in discarding the + * offending list entry entirely. Note that we ignore the 255 + * character overall limit on domain names. + */ + if ((result = malloc(4 * length + 1)) == NULL) { + *ierrnop = ITAB_NOMEM; + return (NULL); + } + resultp = result; + while (length > 0) { + char *dstart; + int slen; + + dstart = resultp; + while (length > 0) { + slen = *payload++; + length--; + /* Upper two bits of length must be zero */ + if ((slen & 0xc0) != 0 || slen > length) { + length = 0; + resultp = dstart; + break; + } + if (resultp != dstart) + *resultp++ = '.'; + if (slen == 0) + break; + length -= slen; + while (slen > 0) { + if (!isascii(*payload) || + !isgraph(*payload)) { + (void) snprintf(resultp, 5, + "\\%03d", + *(unsigned char *)payload); + resultp += 4; + payload++; + } else { + if (*payload == '.' || + *payload == '\\') + *resultp++ = '\\'; + *resultp++ = *payload++; + } + slen--; + } + } + if (resultp != dstart && length > 0) + *resultp++ = ' '; + } + *resultp = '\0'; + break; + + case DSYM_DUID: + + /* + * First, determine the type of DUID. We need at least two + * octets worth of data to grab the type code. Once we have + * that, the number of octets required for representation + * depends on the type. + */ + + if (length < 2) { + *ierrnop = ITAB_BAD_GRAN; + return (NULL); + } + type = (payload[0] << 8) + payload[1]; + switch (type) { + case DHCPV6_DUID_LLT: { + duid_llt_t dllt; + + if (length < sizeof (dllt)) { + *ierrnop = ITAB_BAD_GRAN; + return (NULL); + } + (void) memcpy(&dllt, payload, sizeof (dllt)); + payload += sizeof (dllt); + length -= sizeof (dllt); + n_entries = sizeof ("1,65535,4294967295,") + + length * 3; + if ((result = malloc(n_entries)) == NULL) { + *ierrnop = ITAB_NOMEM; + return (NULL); + } + (void) snprintf(result, n_entries, "%d,%u,%u,", type, + ntohs(dllt.dllt_hwtype), ntohl(dllt.dllt_time)); + break; + } + case DHCPV6_DUID_EN: { + duid_en_t den; + + if (length < sizeof (den)) { + *ierrnop = ITAB_BAD_GRAN; + return (NULL); + } + (void) memcpy(&den, payload, sizeof (den)); + payload += sizeof (den); + length -= sizeof (den); + n_entries = sizeof ("2,4294967295,") + length * 2; + if ((result = malloc(n_entries)) == NULL) { + *ierrnop = ITAB_NOMEM; + return (NULL); + } + (void) snprintf(result, n_entries, "%d,%u,", type, + DHCPV6_GET_ENTNUM(&den)); + break; + } + case DHCPV6_DUID_LL: { + duid_ll_t dll; + + if (length < sizeof (dll)) { + *ierrnop = ITAB_BAD_GRAN; + return (NULL); + } + (void) memcpy(&dll, payload, sizeof (dll)); + payload += sizeof (dll); + length -= sizeof (dll); + n_entries = sizeof ("3,65535,") + length * 3; + if ((result = malloc(n_entries)) == NULL) { + *ierrnop = ITAB_NOMEM; + return (NULL); + } + (void) snprintf(result, n_entries, "%d,%u,", type, + ntohs(dll.dll_hwtype)); + break; + } + default: + n_entries = sizeof ("0,") + length * 2; + if ((result = malloc(n_entries)) == NULL) { + *ierrnop = ITAB_NOMEM; + return (NULL); + } + (void) snprintf(result, n_entries, "%d,", type); + break; + } + resultp = result + strlen(result); + n_entries -= strlen(result); + if (type == DHCPV6_DUID_LLT || type == DHCPV6_DUID_LL) { + if (length > 0) { + resultp += snprintf(resultp, 3, "%02X", + *payload++); + length--; + } + while (length-- > 0) { + resultp += snprintf(resultp, 4, ":%02X", + *payload++); + } + } else { + while (length-- > 0) { + resultp += snprintf(resultp, 3, "%02X", + *payload++); + } + } + break; + case DSYM_OCTET: result = malloc(n_entries * (sizeof ("0xNN") + 1)); @@ -667,50 +1301,50 @@ inittab_decode_e(dhcp_symbol_t *ie, uchar_t *payload, uint16_t length, return (NULL); } - for (resultp = result; n_entries != 0; n_entries--) { - currp = resultp; - resultp += sprintf(resultp, "0x%02X ", *payload++); - if (currp == resultp) { - free(result); - *ierrnop = ITAB_BAD_OCTET; - return (NULL); - } + result[0] = '\0'; + resultp = result; + if (n_entries > 0) { + resultp += sprintf(resultp, "0x%02X", *payload++); + n_entries--; } + while (n_entries-- > 0) + resultp += sprintf(resultp, " 0x%02X", *payload++); - resultp[-1] = '\0'; break; case DSYM_IP: - - if ((length / sizeof (ipaddr_t)) % ie->ds_gran != 0) { + case DSYM_IPV6: + if ((length / type_size) % ie->ds_gran != 0) { *ierrnop = ITAB_BAD_GRAN; inittab_msg("inittab_decode: number of entries " "not compatible with option granularity"); return (NULL); } - result = malloc(n_entries * (sizeof ("aaa.bbb.ccc.ddd") + 1)); - end = &result[n_entries * (sizeof ("aaa.bbb.ccc.ddd") + 1)]; + result = malloc(n_entries * (ie->ds_type == DSYM_IP ? + INET_ADDRSTRLEN : INET6_ADDRSTRLEN)); if (result == NULL) { *ierrnop = ITAB_NOMEM; return (NULL); } for (resultp = result; n_entries != 0; n_entries--) { - (void) memcpy(&in_addr.s_addr, payload, - sizeof (ipaddr_t)); - currp = resultp; - resultp += snprintf(resultp, end - resultp, "%s ", - inet_ntoa(in_addr)); - if (currp == resultp) { - free(result); - *ierrnop = ITAB_BAD_IPADDR; - return (NULL); + if (ie->ds_type == DSYM_IP) { + (void) memcpy(&in_addr.s_addr, payload, + sizeof (ipaddr_t)); + (void) strcpy(resultp, inet_ntoa(in_addr)); + } else { + (void) memcpy(&in6_addr, payload, + sizeof (in6_addr)); + (void) inet_ntop(AF_INET6, &in6_addr, resultp, + INET6_ADDRSTRLEN); } - payload += sizeof (ipaddr_t); + resultp += strlen(resultp); + if (n_entries > 1) + *resultp++ = ' '; + payload += type_size; } - - resultp[-1] = '\0'; + *resultp = '\0'; break; case DSYM_NUMBER: /* FALLTHRU */ @@ -761,8 +1395,9 @@ inittab_decode_e(dhcp_symbol_t *ie, uchar_t *payload, uint16_t length, * boolean_t: if false, return a full DHCP option * output: uchar_t *: a dynamically allocated byte array with converted data */ + uchar_t * -inittab_encode(dhcp_symbol_t *ie, const char *value, uint16_t *lengthp, +inittab_encode(const dhcp_symbol_t *ie, const char *value, uint16_t *lengthp, boolean_t just_payload) { int ierrno; @@ -781,8 +1416,9 @@ inittab_encode(dhcp_symbol_t *ie, const char *value, uint16_t *lengthp, * boolean_t: if false, payload is assumed to be a DHCP option * output: char *: a dynamically allocated string containing the converted data */ + char * -inittab_decode(dhcp_symbol_t *ie, uchar_t *payload, uint16_t length, +inittab_decode(const dhcp_symbol_t *ie, const uchar_t *payload, uint16_t length, boolean_t just_payload) { int ierrno; @@ -797,6 +1433,7 @@ inittab_decode(dhcp_symbol_t *ie, uchar_t *payload, uint16_t length, * ...: arguments to the format string * output: void */ + /*PRINTFLIKE1*/ static void inittab_msg(const char *fmt, ...) @@ -852,6 +1489,7 @@ inittab_msg(const char *fmt, ...) * char *: where to decode the numbers to * output: boolean_t: true on successful conversion, false on failure */ + static boolean_t decode_number(uint8_t n_entries, uint8_t size, boolean_t is_signed, uint8_t granularity, const uint8_t *from, char *to, int *ierrnop) @@ -874,24 +1512,31 @@ decode_number(uint8_t n_entries, uint8_t size, boolean_t is_signed, switch (size) { case 1: - to += sprintf(to, is_signed ? "%d " : "%u ", *from); + to += sprintf(to, is_signed ? "%d" : "%u", *from); break; case 2: (void) memcpy(&uint16, from, 2); - to += sprintf(to, is_signed ? "%hd " : "%hu ", + to += sprintf(to, is_signed ? "%hd" : "%hu", ntohs(uint16)); break; + case 3: + uint32 = 0; + (void) memcpy((uchar_t *)&uint32 + 1, from, 3); + to += sprintf(to, is_signed ? "%ld" : "%lu", + ntohl(uint32)); + break; + case 4: (void) memcpy(&uint32, from, 4); - to += sprintf(to, is_signed ? "%ld " : "%lu ", + to += sprintf(to, is_signed ? "%ld" : "%lu", ntohl(uint32)); break; case 8: (void) memcpy(&uint64, from, 8); - to += sprintf(to, is_signed ? "%lld " : "%llu ", + to += sprintf(to, is_signed ? "%lld" : "%llu", dhcp_ntohll(uint64)); break; @@ -901,9 +1546,11 @@ decode_number(uint8_t n_entries, uint8_t size, boolean_t is_signed, size); return (B_FALSE); } + if (n_entries > 0) + *to++ = ' '; } - to[-1] = '\0'; + *to = '\0'; return (B_TRUE); } @@ -920,6 +1567,7 @@ decode_number(uint8_t n_entries, uint8_t size, boolean_t is_signed, * int *: set to extended error code if error occurs. * output: boolean_t: true on successful conversion, false on failure */ + static boolean_t /* ARGSUSED */ encode_number(uint8_t n_entries, uint8_t size, boolean_t is_signed, uint8_t granularity, const char *from, uint8_t *to, int *ierrnop) @@ -939,7 +1587,7 @@ encode_number(uint8_t n_entries, uint8_t size, boolean_t is_signed, } } - for (i = 0; i < n_entries; i++, from++) { + for (i = 0; i < n_entries; i++, from++, to += size) { /* * totally obscure c factoid: it is legal to pass a @@ -954,7 +1602,7 @@ encode_number(uint8_t n_entries, uint8_t size, boolean_t is_signed, switch (size) { case 1: - to[i] = strtoul(from, &endptr, 0); + *to = strtoul(from, &endptr, 0); if (errno != 0 || from == endptr) { goto error; } @@ -965,7 +1613,15 @@ encode_number(uint8_t n_entries, uint8_t size, boolean_t is_signed, if (errno != 0 || from == endptr) { goto error; } - (void) memcpy(to + (i * 2), &uint16, 2); + (void) memcpy(to, &uint16, 2); + break; + + case 3: + uint32 = htonl(strtoul(from, &endptr, 0)); + if (errno != 0 || from == endptr) { + goto error; + } + (void) memcpy(to, (uchar_t *)&uint32 + 1, 3); break; case 4: @@ -973,7 +1629,7 @@ encode_number(uint8_t n_entries, uint8_t size, boolean_t is_signed, if (errno != 0 || from == endptr) { goto error; } - (void) memcpy(to + (i * 4), &uint32, 4); + (void) memcpy(to, &uint32, 4); break; case 8: @@ -981,7 +1637,7 @@ encode_number(uint8_t n_entries, uint8_t size, boolean_t is_signed, if (errno != 0 || from == endptr) { goto error; } - (void) memcpy(to + (i * 8), &uint64, 8); + (void) memcpy(to, &uint64, 8); break; default: @@ -1010,11 +1666,14 @@ error: * input: dhcp_symbol_t *: an entry of the given type * output: uint8_t: the size in bytes of an entry of that type */ + uint8_t -inittab_type_to_size(dhcp_symbol_t *ie) +inittab_type_to_size(const dhcp_symbol_t *ie) { switch (ie->ds_type) { + case DSYM_DUID: + case DSYM_DOMAIN: case DSYM_ASCII: case DSYM_OCTET: case DSYM_SNUMBER8: @@ -1027,6 +1686,10 @@ inittab_type_to_size(dhcp_symbol_t *ie) return (2); + case DSYM_UNUMBER24: + + return (3); + case DSYM_SNUMBER32: case DSYM_UNUMBER32: case DSYM_IP: @@ -1041,6 +1704,10 @@ inittab_type_to_size(dhcp_symbol_t *ie) case DSYM_NUMBER: return (ie->ds_gran); + + case DSYM_IPV6: + + return (sizeof (in6_addr_t)); } return (0); @@ -1053,6 +1720,7 @@ inittab_type_to_size(dhcp_symbol_t *ie) * input: uchar_t: the inittab category code * output: dsym_category_t: the dsym category code */ + static dsym_category_t itabcode_to_dsymcode(uchar_t itabcode) { @@ -1072,6 +1740,7 @@ itabcode_to_dsymcode(uchar_t itabcode) * input: const char *: the category name * output: uchar_t: its internal code (numeric representation) */ + static uchar_t category_to_code(const char *category) { @@ -1090,6 +1759,7 @@ category_to_code(const char *category) * input: uint64_t: the number to convert * output: uint64_t: its value in network byte order */ + static uint64_t dhcp_htonll(uint64_t uint64_hbo) { @@ -1102,6 +1772,7 @@ dhcp_htonll(uint64_t uint64_hbo) * input: uint64_t: the number to convert * output: uint64_t: its value in host byte order */ + static uint64_t dhcp_ntohll(uint64_t uint64_nbo) { diff --git a/usr/src/lib/libdhcputil/common/dhcp_inittab.h b/usr/src/lib/libdhcputil/common/dhcp_inittab.h index ed70ad20b4..030d057667 100644 --- a/usr/src/lib/libdhcputil/common/dhcp_inittab.h +++ b/usr/src/lib/libdhcputil/common/dhcp_inittab.h @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,8 +19,8 @@ * CDDL HEADER END */ /* - * Copyright (c) 1999-2001 by Sun Microsystems, Inc. - * All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. */ #ifndef _DHCP_INITTAB_H @@ -48,6 +47,7 @@ extern "C" { * On-disk inittab attributes and limits. */ #define ITAB_INITTAB_PATH "/etc/dhcp/inittab" +#define ITAB_INITTAB6_PATH "/etc/dhcp/inittab6" #define ITAB_MAX_LINE_LEN 8192 /* bytes */ #define ITAB_MAX_NUMBER_LEN 30 /* digits */ #define ITAB_COMMENT_CHAR '#' @@ -72,7 +72,8 @@ extern "C" { #define ITAB_CAT_INTERNAL 0x04 #define ITAB_CAT_VENDOR 0x08 #define ITAB_CAT_SITE 0x10 -#define ITAB_CAT_COUNT 5 +#define ITAB_CAT_V6 0x20 +#define ITAB_CAT_COUNT 6 /* * Consumer which is using the inittab functions. @@ -96,19 +97,22 @@ extern "C" { #define ITAB_BAD_GRAN (-8) #define ITAB_NOMEM (-9) -extern uint8_t inittab_type_to_size(dhcp_symbol_t *); -extern int inittab_verify(dhcp_symbol_t *, dhcp_symbol_t *); +extern uint8_t inittab_type_to_size(const dhcp_symbol_t *); +extern int inittab_verify(const dhcp_symbol_t *, dhcp_symbol_t *); extern dhcp_symbol_t *inittab_load(uchar_t, char, size_t *); extern dhcp_symbol_t *inittab_getbyname(uchar_t, char, const char *); extern dhcp_symbol_t *inittab_getbycode(uchar_t, char, uint16_t); -extern uchar_t *inittab_encode(dhcp_symbol_t *, const char *, +extern uchar_t *inittab_encode(const dhcp_symbol_t *, const char *, uint16_t *, boolean_t); -extern uchar_t *inittab_encode_e(dhcp_symbol_t *, const char *, +extern uchar_t *inittab_encode_e(const dhcp_symbol_t *, const char *, uint16_t *, boolean_t, int *); -extern char *inittab_decode(dhcp_symbol_t *, uchar_t *, +extern char *inittab_decode(const dhcp_symbol_t *, const uchar_t *, uint16_t, boolean_t); -extern char *inittab_decode_e(dhcp_symbol_t *, uchar_t *, - uint16_t, boolean_t, int *); +extern char *inittab_decode_e(const dhcp_symbol_t *, + const uchar_t *, uint16_t, boolean_t, int *); + +/* temporary; should be in libdlpi */ +extern uint_t dlpi_to_arp(uint_t); #ifdef __cplusplus } diff --git a/usr/src/lib/libdhcputil/common/dhcp_symbol.c b/usr/src/lib/libdhcputil/common/dhcp_symbol.c index d02d3461a7..793b038d41 100644 --- a/usr/src/lib/libdhcputil/common/dhcp_symbol.c +++ b/usr/src/lib/libdhcputil/common/dhcp_symbol.c @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -48,7 +47,7 @@ typedef struct dsym_cat { ushort_t dc_max; /* maximum valid code */ } dsym_cat_t; -static dsym_cat_t cats[DSYM_CATEGORY_NUM] = { +static dsym_cat_t cats[] = { { "Extend", 6, DSYM_EXTEND, B_TRUE, DHCP_LAST_STD + 1, DHCP_SITE_OPT - 1 }, { "Vendor=", 6, DSYM_VENDOR, B_TRUE, DHCP_FIRST_OPT, @@ -72,7 +71,7 @@ typedef struct dsym_type { boolean_t dt_dhcptab; /* valid for dhcptab use? */ } dsym_type_t; -static dsym_type_t types[DSYM_CDTYPE_NUM] = { +static dsym_type_t types[] = { { "ASCII", DSYM_ASCII, B_TRUE }, { "OCTET", DSYM_OCTET, B_TRUE }, { "IP", DSYM_IP, B_TRUE }, @@ -81,12 +80,16 @@ static dsym_type_t types[DSYM_CDTYPE_NUM] = { { "INCLUDE", DSYM_INCLUDE, B_FALSE }, { "UNUMBER8", DSYM_UNUMBER8, B_TRUE }, { "UNUMBER16", DSYM_UNUMBER16, B_TRUE }, + { "UNUMBER24", DSYM_UNUMBER24, B_TRUE }, { "UNUMBER32", DSYM_UNUMBER32, B_TRUE }, { "UNUMBER64", DSYM_UNUMBER64, B_TRUE }, { "SNUMBER8", DSYM_SNUMBER8, B_TRUE }, { "SNUMBER16", DSYM_SNUMBER16, B_TRUE }, { "SNUMBER32", DSYM_SNUMBER32, B_TRUE }, - { "SNUMBER64", DSYM_SNUMBER64, B_TRUE } + { "SNUMBER64", DSYM_SNUMBER64, B_TRUE }, + { "IPV6", DSYM_IPV6, B_TRUE }, + { "DUID", DSYM_DUID, B_TRUE }, + { "DOMAIN", DSYM_DOMAIN, B_TRUE } }; /* @@ -103,6 +106,7 @@ static dsym_type_t types[DSYM_CDTYPE_NUM] = { * input: char **: a pointer to a string to trim of whitespace. * output: none */ + static void dsym_trim(char **str) { @@ -153,6 +157,7 @@ dsym_trim(char **str) * boolean_t: should delimiters be ignored if within quoted string? * output: char *: token, or NULL if no more tokens */ + static char * dsym_get_token(char *str, char *dels, char **lasts, boolean_t quote_support) { @@ -214,6 +219,7 @@ dsym_get_token(char *str, char *dels, char **lasts, boolean_t quote_support) * long *: the return location for the long value * output: DSYM_SUCCESS, DSYM_VALUE_OUT_OF_RANGE or DSYM_SYNTAX_ERROR */ + static dsym_errcode_t dsym_get_long(const char *str, long *val) { @@ -242,6 +248,7 @@ dsym_get_long(const char *str, long *val) * input: dhcp_classes_t *: pointer to structure containing classes to free * output: none */ + void dsym_free_classes(dhcp_classes_t *classes) { @@ -270,6 +277,7 @@ dsym_free_classes(dhcp_classes_t *classes) * output: DSYM_SUCCESS, DSYM_INVALID_CAT, DSYM_EXCEEDS_MAX_CLASS_SIZE, * DSYM_EXCEEDS_CLASS_SIZE, DSYM_SYNTAX_ERROR, or DSYM_NO_MEMORY */ + static dsym_errcode_t dsym_parse_classes(char *ptr, dhcp_classes_t *classes_ret) { @@ -352,6 +360,7 @@ dsym_parse_classes(char *ptr, dhcp_classes_t *classes_ret) * boolean_t: case-sensitive name compare * output: int: DSYM_SUCCESS or DSYM_INVALID_CAT */ + static dsym_errcode_t dsym_get_cat_by_name(const char *cat, dsym_cat_t **entry, boolean_t cs) { @@ -416,6 +425,7 @@ dsym_get_cat_by_name(const char *cat, dsym_cat_t **entry, boolean_t cs) * dsym_category_t *: the return location for the category value * output: int: DSYM_SUCCESS or DSYM_INVALID_CAT */ + static dsym_errcode_t dsym_parse_cat(const char *field, dsym_category_t *cat) { @@ -449,6 +459,7 @@ dsym_parse_cat(const char *field, dsym_category_t *cat) * int: the maximum valid value * output: int: DSYM_SUCCESS, DSYM_SYNTAX_ERROR, or DSYM_VALUE_OUT_OF_RANGE */ + static dsym_errcode_t dsym_parse_intrange(const char *field, int *intval, int min, int max) { @@ -475,6 +486,7 @@ dsym_parse_intrange(const char *field, int *intval, int min, int max) * uint16_t: the symbol code * output: DSYM_SUCCESS, DSYM_INVALID_CAT or DSYM_CODE_OUT_OF_RANGE */ + static dsym_errcode_t dsym_validate_code(dsym_category_t cat, ushort_t code) { @@ -507,6 +519,7 @@ dsym_validate_code(dsym_category_t cat, ushort_t code) * uchar_t: the symbol granularity * output: DSYM_SUCCESS or DSYM_VALUE_OUT_OF_RANGE */ + static dsym_errcode_t dsym_validate_granularity(dsym_cdtype_t type, uchar_t gran) { @@ -530,6 +543,7 @@ dsym_validate_granularity(dsym_cdtype_t type, uchar_t gran) * boolean_t: case-sensitive name compare * output: int: DSYM_SUCCESS or DSYM_INVALID_TYPE */ + static dsym_errcode_t dsym_get_type_by_name(const char *type, dsym_type_t **entry, boolean_t cs) { @@ -561,6 +575,7 @@ dsym_get_type_by_name(const char *type, dsym_type_t **entry, boolean_t cs) * dsym_cdtype_t *: the return location for the type id * output: int: DSYM_SUCCESS or DSYM_INVALID_TYPE */ + static dsym_errcode_t dsym_parse_type(char *field, dsym_cdtype_t *type) { @@ -592,6 +607,7 @@ dsym_parse_type(char *field, dsym_cdtype_t *type) * input: char **: array of fields to free * output: none */ + void dsym_free_fields(char **fields) { @@ -611,6 +627,7 @@ dsym_free_fields(char **fields) * dhcp_symbol_t *: the structure populated by dsym_init_parser() * output: none */ + void dsym_close_parser(char **fields, dhcp_symbol_t *sym) { @@ -630,6 +647,7 @@ dsym_close_parser(char **fields, dhcp_symbol_t *sym) * output: int: DSYM_SUCCESS, DYSM_NO_MEMORY, DSYM_NULL_FIELD or * DSYM_TOO_MANY_FIELDS */ + dsym_errcode_t dsym_init_parser(const char *name, const char *value, char ***fields_ret, dhcp_symbol_t *sym) @@ -710,6 +728,7 @@ dsym_init_parser(const char *name, const char *value, char ***fields_ret, * DSYM_EXCEEDS_MAX_CLASS_SIZE, DSYM_NO_MEMORY, * DSYM_INVALID_FIELD_NUM, DSYM_VALUE_OUT_OF_RANGE */ + dsym_errcode_t dsym_parse_field(int field_num, char **fields, dhcp_symbol_t *sym) { @@ -778,6 +797,7 @@ dsym_parse_field(int field_num, char **fields, dhcp_symbol_t *sym) * DSYM_EXCEEDS_MAX_CLASS_SIZE, DSYM_NO_MEMORY * DSYM_INVALID_FIELD_NUM, DSYM_VALUE_OUT_OF_RANGE */ + dsym_errcode_t dsym_parser(char **fields, dhcp_symbol_t *sym, int *lastField, boolean_t bestEffort) @@ -818,6 +838,7 @@ dsym_parser(char **fields, dhcp_symbol_t *sym, int *lastField, * boolean_t: case-sensitive name compare * output: int: DSYM_SUCCESS or DSYM_INVALID_CAT */ + dsym_errcode_t dsym_get_cat_id(const char *cat, dsym_category_t *id, boolean_t cs) { @@ -843,6 +864,7 @@ dsym_get_cat_id(const char *cat, dsym_category_t *id, boolean_t cs) * boolean_t: case-sensitive name compare * output: int: DSYM_SUCCESS or DSYM_INVALID_CAT */ + dsym_errcode_t dsym_get_code_ranges(const char *cat, ushort_t *min, ushort_t *max, boolean_t cs) @@ -868,6 +890,7 @@ dsym_get_code_ranges(const char *cat, ushort_t *min, ushort_t *max, * boolean_t: case-sensitive name compare * output: int: DSYM_SUCCESS or DSYM_INVALID_TYPE */ + dsym_errcode_t dsym_get_type_id(const char *type, dsym_cdtype_t *id, boolean_t cs) { diff --git a/usr/src/lib/libdhcputil/common/dhcp_symbol.h b/usr/src/lib/libdhcputil/common/dhcp_symbol.h index fcfd65c20f..0e153457e4 100644 --- a/usr/src/lib/libdhcputil/common/dhcp_symbol.h +++ b/usr/src/lib/libdhcputil/common/dhcp_symbol.h @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2001-2003 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -99,6 +98,8 @@ typedef struct dhcp_classes { * This structure is used to define a DHCP symbol. The structure is * used by both the inittab parsing routines and by the dhcptab parsing * routines to define a symbol definition in either of those tables. + * Note that ds_dhcpv6 is defined last so that it needn't be initialized + * as part of the inittab_table[] definition. */ typedef struct dhcp_symbol { dsym_category_t ds_category; /* category */ @@ -108,6 +109,7 @@ typedef struct dhcp_symbol { uchar_t ds_gran; /* granularity */ uchar_t ds_max; /* maximum number */ dhcp_classes_t ds_classes; /* client classes */ + uchar_t ds_dhcpv6; /* dhcpv6 flag */ } dhcp_symbol_t; extern void dsym_free_fields(char **); diff --git a/usr/src/lib/libdhcputil/common/dhcpmsg.c b/usr/src/lib/libdhcputil/common/dhcpmsg.c index 5329af5fbf..9e4aec5ded 100644 --- a/usr/src/lib/libdhcputil/common/dhcpmsg.c +++ b/usr/src/lib/libdhcputil/common/dhcpmsg.c @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -53,7 +52,6 @@ static int err_to_syslog(int); * output: void */ -/*PRINTFLIKE2*/ void dhcpmsg(int errlevel, const char *fmt, ...) { diff --git a/usr/src/lib/libdhcputil/common/dhcpmsg.h b/usr/src/lib/libdhcputil/common/dhcpmsg.h index 822359cd46..2dad24040a 100644 --- a/usr/src/lib/libdhcputil/common/dhcpmsg.h +++ b/usr/src/lib/libdhcputil/common/dhcpmsg.h @@ -2,9 +2,8 @@ * 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. + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. @@ -20,14 +19,14 @@ * CDDL HEADER END */ /* - * Copyright (c) 1999 by Sun Microsystems, Inc. - * All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. */ #ifndef _DHCPMSG_H #define _DHCPMSG_H -#pragma ident "%W% %E% SMI" +#pragma ident "%Z%%M% %I% %E% SMI" #include <sys/types.h> #include <stdarg.h> @@ -63,6 +62,7 @@ enum { MSG_CRIT /* LOG_CRIT */ }; +/* PRINTFLIKE2 */ extern void dhcpmsg(int, const char *, ...); extern void dhcpmsg_init(const char *, boolean_t, boolean_t, int); extern void dhcpmsg_fini(void); diff --git a/usr/src/lib/libdhcputil/common/mapfile-vers b/usr/src/lib/libdhcputil/common/mapfile-vers index 65d36e67d5..851b69b1b7 100644 --- a/usr/src/lib/libdhcputil/common/mapfile-vers +++ b/usr/src/lib/libdhcputil/common/mapfile-vers @@ -19,7 +19,7 @@ # CDDL HEADER END # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" @@ -31,6 +31,9 @@ SUNWprivate_1.2 { global: dhcp_options_scan; + dhcpv6_find_option; + dhcpv6_pkt_option; + dlpi_to_arp; } SUNWprivate_1.1; SUNWprivate_1.1 { diff --git a/usr/src/lib/libxnet/Makefile.com b/usr/src/lib/libxnet/Makefile.com index 93134f8b63..0e905e3ca3 100644 --- a/usr/src/lib/libxnet/Makefile.com +++ b/usr/src/lib/libxnet/Makefile.com @@ -19,7 +19,7 @@ # CDDL HEADER END # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" @@ -41,6 +41,7 @@ LIBS= $(DYNLIB) $(LINTLIB) $(LINTLIB):= SRCS = $(SRCDIR)/$(LINTSRC) +CPPFLAGS += -D__EXTENSIONS__ CFLAGS += $(CCVERBOSE) DYNFLAGS += $(ZLOADFLTR) diff --git a/usr/src/pkgdefs/SUNWcnetr/i.dhcpagent b/usr/src/pkgdefs/SUNWcnetr/i.dhcpagent new file mode 100644 index 0000000000..ccec70759c --- /dev/null +++ b/usr/src/pkgdefs/SUNWcnetr/i.dhcpagent @@ -0,0 +1,108 @@ +#!/bin/sh +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +while read src dest +do + if [ ! -f $dest ] ; then + cp $src $dest + else + tmpdst=/var/run/dhcpagent.dst.$$ + + # Changes are applied separately to accomodate user updates to + # the file. + + # If the target has the old v4 comments, then update them + # to describe v6. + grep '# All parameters can be tuned for ' $dest >/dev/null && + ( grep '# An interface name alone ' $dest >/dev/null || ( + nawk ' + /# All parameters can be tuned for / { flag = 1; } + /^$/ && flag == 1 { +print "#"; +print "# An interface name alone specifies IPv4 DHCP. For DHCPv6, append \""\ + ".v6\"."; +print "# Some examples:"; +print "#"; +print "# hme0.RELEASE_ON_SIGTERM=no specify hme0 v4 behavior"; +print "# hme0.v6.RELEASE_ON_SIGTERM=no specify hme0 v6 behavior"; +print "# RELEASE_ON_SIGTERM=no match all v4 interfaces"; +print "# .v6.RELEASE_ON_SIGTERM=no match all v6 interfaces"; + flag = 2; + } + { print $0; } + ' $dest > $tmpdst && cp $tmpdst $dest + ) ) + + # If the target has the old SIGTERM documentation, update. + if grep ' is sent a SIGTERM, all managed' $dest >/dev/null && + grep 'parameter-value pair, all managed' $dest >/dev/null + then + nawk ' + / is sent a SIGTERM, all managed/ { flag = 1; } + /parameter-value pair, all managed/ && flag == 1 { +print "# By default, when the DHCP agent is sent a SIGTERM (typically when"; +print "# the system is shut down), all managed addresses are dropped rather"; +print "# than released. Dropping an address does not notify the DHCP server"; +print "# that the address is no longer in use, leaving it possibly available"; +print "# for subsequent use by the same client. If DHCP is later restarted"; +print "# on the interface, the client will ask the server if it can continue"; +print "# to use the address. If the server either grants the request, or"; +print "# does not answer (and the lease has not yet expired), then the client"; +print "# will use the original address."; +print "#"; +print "# By uncommenting the following parameter-value pairs, all managed"; +print "# interfaces are released on SIGTERM instead. In that case, the DHCP"; +print "# server is notified that the address is available for use. Further,"; +print "# if DHCP is later restarted on the interface, the client will not"; +print "# request its previous address from the server, nor will it attempt to"; +print "# reuse the previous lease. This behavior is often preferred for"; +print "# roaming systems."; + flag = 2; + next; + } + flag == 1 { next; } + { print $0; } + ' $dest > $tmpdst && cp $tmpdst $dest + fi + + # If the target lacks a v6 PARAM_REQUEST_LIST entry, then + # add it. + fgrep '.v6.PARAM_REQUEST_LIST' $dest >/dev/null || + cat >> $dest <<EOF + +# The default DHCPv6 parameter request list has preference (7), unicast (12), +# DNS addresses (23), DNS search list (24), NIS addresses (27), and +# NIS domain (29). This may be changed by altering the following parameter- +# value pair. The numbers correspond to the values defined in the IANA +# dhcpv6-parameters registry at the time of this writing. +.v6.PARAM_REQUEST_LIST=7,12,23,24,27,29 +EOF + fi +done +exit 0 diff --git a/usr/src/pkgdefs/SUNWcnetr/pkginfo.tmpl b/usr/src/pkgdefs/SUNWcnetr/pkginfo.tmpl index 787fd55508..5a9803a32b 100644 --- a/usr/src/pkgdefs/SUNWcnetr/pkginfo.tmpl +++ b/usr/src/pkgdefs/SUNWcnetr/pkginfo.tmpl @@ -20,7 +20,7 @@ # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" @@ -44,7 +44,7 @@ DESC="core software for network infrastructure configuration" VENDOR="Sun Microsystems, Inc." HOTLINE="Please contact your local service provider" EMAIL="" -CLASSES="none ipsecalgsbase preserve sock2path" +CLASSES="none ipsecalgsbase dhcpagent preserve sock2path" BASEDIR=/ SUNW_PKGVERS="1.0" SUNW_PKG_ALLZONES="true" diff --git a/usr/src/pkgdefs/SUNWcnetr/prototype_com b/usr/src/pkgdefs/SUNWcnetr/prototype_com index dcaa5efc44..68d848bb6e 100644 --- a/usr/src/pkgdefs/SUNWcnetr/prototype_com +++ b/usr/src/pkgdefs/SUNWcnetr/prototype_com @@ -19,7 +19,7 @@ # CDDL HEADER END # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" @@ -42,6 +42,7 @@ i postinstall i i.ipsecalgsbase i i.preserve i i.sock2path +i i.dhcpagent # # source locations relative to the prototype file # @@ -53,7 +54,7 @@ e preserve etc/dladm/aggregation.conf 644 dladm sys e preserve etc/dladm/linkprop.conf 644 dladm sys e preserve etc/dladm/secobj.conf 600 dladm sys d none etc/default 755 root sys -e preserve etc/default/dhcpagent 644 root sys +e dhcpagent etc/default/dhcpagent 644 root sys e preserve etc/default/inetinit 644 root sys e preserve etc/default/ipsec 644 root sys e preserve etc/default/mpathd 644 root sys diff --git a/usr/src/pkgdefs/SUNWcsr/prototype_com b/usr/src/pkgdefs/SUNWcsr/prototype_com index aa2de6746d..e9cbc44fa0 100644 --- a/usr/src/pkgdefs/SUNWcsr/prototype_com +++ b/usr/src/pkgdefs/SUNWcsr/prototype_com @@ -136,6 +136,7 @@ e preserve etc/dfs/dfstab 644 root sys e preserve etc/dgroup.tab 444 root sys d none etc/dhcp 755 root sys e dhcpinittab etc/dhcp/inittab 644 root sys +e dhcpinittab etc/dhcp/inittab6 644 root sys e preserve etc/dumpdates 664 root sys s none etc/ff=../usr/sbin/ff s none etc/fmthard=../usr/sbin/fmthard diff --git a/usr/src/pkgdefs/SUNWhea/prototype_com b/usr/src/pkgdefs/SUNWhea/prototype_com index 9d7181c485..b10c0e5dd6 100644 --- a/usr/src/pkgdefs/SUNWhea/prototype_com +++ b/usr/src/pkgdefs/SUNWhea/prototype_com @@ -20,7 +20,7 @@ # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" @@ -257,6 +257,7 @@ f none usr/include/netdir.h 644 root bin d none usr/include/netinet 755 root bin f none usr/include/netinet/arp.h 644 root bin f none usr/include/netinet/dhcp.h 644 root bin +f none usr/include/netinet/dhcp6.h 644 root bin f none usr/include/netinet/icmp_var.h 644 root bin f none usr/include/netinet/icmp6.h 644 root bin f none usr/include/netinet/if_ether.h 644 root bin diff --git a/usr/src/pkgdefs/etc/exception_list_i386 b/usr/src/pkgdefs/etc/exception_list_i386 index 6fab12f345..c20e9716df 100644 --- a/usr/src/pkgdefs/etc/exception_list_i386 +++ b/usr/src/pkgdefs/etc/exception_list_i386 @@ -19,7 +19,7 @@ # CDDL HEADER END # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" @@ -435,6 +435,7 @@ usr/include/sys/kmdb.h i386 # for customer use, so the files are excluded from packaging. # usr/include/dhcp_hostconf.h i386 +usr/include/dhcp_stable.h i386 usr/include/dhcp_impl.h i386 usr/include/dhcp_inittab.h i386 usr/include/dhcp_symbol_common.h i386 diff --git a/usr/src/pkgdefs/etc/exception_list_sparc b/usr/src/pkgdefs/etc/exception_list_sparc index 1b7af2c88a..ddd59166e4 100644 --- a/usr/src/pkgdefs/etc/exception_list_sparc +++ b/usr/src/pkgdefs/etc/exception_list_sparc @@ -19,7 +19,7 @@ # CDDL HEADER END # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" @@ -441,6 +441,7 @@ usr/include/sys/kmdb.h sparc usr/include/dhcp_hostconf.h sparc usr/include/dhcp_impl.h sparc usr/include/dhcp_inittab.h sparc +usr/include/dhcp_stable.h sparc usr/include/dhcp_symbol_common.h sparc usr/include/dhcpagent_ipc.h sparc usr/include/dhcpagent_util.h sparc diff --git a/usr/src/uts/common/inet/ip/ip6_if.c b/usr/src/uts/common/inet/ip/ip6_if.c index 3283176ad8..5937c0ee26 100644 --- a/usr/src/uts/common/inet/ip/ip6_if.c +++ b/usr/src/uts/common/inet/ip/ip6_if.c @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* @@ -1671,15 +1671,28 @@ ip_addr_scope_v6(const in6_addr_t *addr) /* - * Calculates the xor of a1 and a2, and stores the result in res. + * Returns the length of the common prefix of a1 and a2, as per + * CommonPrefixLen() defined in RFC 3484. */ -static void -ip_addr_xor_v6(const in6_addr_t *a1, const in6_addr_t *a2, in6_addr_t *res) +static int +ip_common_prefix_v6(const in6_addr_t *a1, const in6_addr_t *a2) { int i; + uint32_t a1val, a2val, mask; - for (i = 0; i < 4; i++) - res->s6_addr32[i] = a1->s6_addr32[i] ^ a2->s6_addr32[i]; + for (i = 0; i < 4; i++) { + if ((a1val = a1->s6_addr32[i]) != (a2val = a2->s6_addr32[i])) { + a1val ^= a2val; + i *= 32; + mask = 0x80000000u; + while (!(a1val & mask)) { + mask >>= 1; + i++; + } + return (i); + } + } + return (IPV6_ABITS); } #define IPIF_VALID_IPV6_SOURCE(ipif) \ @@ -1705,10 +1718,15 @@ typedef struct candidate { boolean_t cand_matchedlabel_set; boolean_t cand_istmp; boolean_t cand_istmp_set; - in6_addr_t cand_xor; - boolean_t cand_xor_set; + int cand_common_pref; + boolean_t cand_common_pref_set; + boolean_t cand_pref_eq; + boolean_t cand_pref_eq_set; + int cand_pref_len; + boolean_t cand_pref_len_set; } cand_t; #define cand_srcaddr cand_ipif->ipif_v6lcl_addr +#define cand_mask cand_ipif->ipif_v6net_mask #define cand_flags cand_ipif->ipif_flags #define cand_ill cand_ipif->ipif_ill #define cand_zoneid cand_ipif->ipif_zoneid @@ -1737,6 +1755,11 @@ typedef struct dstinfo { * destination address ordering. The rules defined here implement the IPv6 * source address selection. Destination address ordering is done by * libnsl, and uses a similar set of rules to implement the sorting. + * + * Most of the rules are defined by the RFC and are not typically altered. The + * last rule, number 8, has language that allows for local preferences. In the + * scheme below, this means that new Solaris rules should normally go between + * rule_ifprefix and rule_prefix. */ typedef enum {CAND_AVOID, CAND_TIE, CAND_PREFER} rule_res_t; typedef rule_res_t (*rulef_t)(cand_t *, cand_t *, const dstinfo_t *); @@ -1937,37 +1960,47 @@ rule_temporary(cand_t *bc, cand_t *cc, const dstinfo_t *dstinfo) } /* - * Prefer source addresses with longer matching prefix with the - * destination. Since this is the last rule, it must not produce a tie. - * We do the longest matching prefix calculation and the tie break in one - * calculation by doing an xor of both addresses with the destination, and - * pick the address with the smallest xor value. That way, we're both - * picking the address with the longest matching prefix, and breaking the - * tie if they happen to have both have equal mathing prefixes. + * Prefer source addresses with longer matching prefix with the destination + * under the interface mask. This gets us on the same subnet before applying + * any Solaris-specific rules. */ static rule_res_t -rule_prefix(cand_t *bc, cand_t *cc, const dstinfo_t *dstinfo) +rule_ifprefix(cand_t *bc, cand_t *cc, const dstinfo_t *dstinfo) { - int i; - - if (!bc->cand_xor_set) { - ip_addr_xor_v6(&bc->cand_srcaddr, - dstinfo->dst_addr, &bc->cand_xor); - bc->cand_xor_set = B_TRUE; - } - - ip_addr_xor_v6(&cc->cand_srcaddr, dstinfo->dst_addr, &cc->cand_xor); - cc->cand_xor_set = B_TRUE; - - for (i = 0; i < 4; i++) { - if (bc->cand_xor.s6_addr32[i] != cc->cand_xor.s6_addr32[i]) - break; + if (!bc->cand_pref_eq_set) { + bc->cand_pref_eq = V6_MASK_EQ_2(bc->cand_srcaddr, + bc->cand_mask, *dstinfo->dst_addr); + bc->cand_pref_eq_set = B_TRUE; + } + + cc->cand_pref_eq = V6_MASK_EQ_2(cc->cand_srcaddr, cc->cand_mask, + *dstinfo->dst_addr); + cc->cand_pref_eq_set = B_TRUE; + + if (bc->cand_pref_eq) { + if (cc->cand_pref_eq) { + if (!bc->cand_pref_len_set) { + bc->cand_pref_len = + ip_mask_to_plen_v6(&bc->cand_mask); + bc->cand_pref_len_set = B_TRUE; + } + cc->cand_pref_len = ip_mask_to_plen_v6(&cc->cand_mask); + cc->cand_pref_len_set = B_TRUE; + if (bc->cand_pref_len == cc->cand_pref_len) + return (CAND_TIE); + else if (bc->cand_pref_len > cc->cand_pref_len) + return (CAND_AVOID); + else + return (CAND_PREFER); + } else { + return (CAND_AVOID); + } + } else { + if (cc->cand_pref_eq) + return (CAND_PREFER); + else + return (CAND_TIE); } - - if (cc->cand_xor.s6_addr32[i] < bc->cand_xor.s6_addr32[i]) - return (CAND_PREFER); - else - return (CAND_AVOID); } /* @@ -1988,6 +2021,73 @@ rule_zone_specific(cand_t *bc, cand_t *cc, const dstinfo_t *dstinfo) } /* + * Prefer to use DHCPv6 (first) and static addresses (second) when possible + * instead of statelessly autoconfigured addresses. + * + * This is done after trying all other preferences (and before the final tie + * breaker) so that, if all else is equal, we select addresses configured by + * DHCPv6 over other addresses. We presume that DHCPv6 addresses, unlike + * stateless autoconfigured addresses, are deliberately configured by an + * administrator, and thus are correctly set up in DNS and network packet + * filters. + */ +/* ARGSUSED2 */ +static rule_res_t +rule_addr_type(cand_t *bc, cand_t *cc, const dstinfo_t *dstinfo) +{ +#define ATYPE(x) \ + ((x) & IPIF_DHCPRUNNING) ? 1 : ((x) & IPIF_ADDRCONF) ? 3 : 2 + int bcval = ATYPE(bc->cand_flags); + int ccval = ATYPE(cc->cand_flags); +#undef ATYPE + + if (bcval == ccval) + return (CAND_TIE); + else if (ccval < bcval) + return (CAND_PREFER); + else + return (CAND_AVOID); +} + +/* + * Prefer source addresses with longer matching prefix with the destination. + * We do the longest matching prefix calculation by doing an xor of both + * addresses with the destination, and pick the address with the longest string + * of leading zeros, as per CommonPrefixLen() defined in RFC 3484. + */ +static rule_res_t +rule_prefix(cand_t *bc, cand_t *cc, const dstinfo_t *dstinfo) +{ + if (!bc->cand_common_pref_set) { + bc->cand_common_pref = ip_common_prefix_v6(&bc->cand_srcaddr, + dstinfo->dst_addr); + bc->cand_common_pref_set = B_TRUE; + } + + cc->cand_common_pref = ip_common_prefix_v6(&cc->cand_srcaddr, + dstinfo->dst_addr); + cc->cand_common_pref_set = B_TRUE; + + if (bc->cand_common_pref == cc->cand_common_pref) + return (CAND_TIE); + else if (bc->cand_common_pref > cc->cand_common_pref) + return (CAND_AVOID); + else + return (CAND_PREFER); +} + +/* + * Last rule: we must pick something, so just prefer the current best + * candidate. + */ +/* ARGSUSED */ +static rule_res_t +rule_must_be_last(cand_t *bc, cand_t *cc, const dstinfo_t *dstinfo) +{ + return (CAND_AVOID); +} + +/* * Determine the best source address given a destination address and a * destination ill. If no suitable source address is found, it returns * NULL. If there is a usable address pointed to by the usesrc @@ -2034,8 +2134,8 @@ ipif_select_source_v6(ill_t *dstill, const in6_addr_t *dst, * The list of ordering rules. They are applied in the order they * appear in the list. * - * XXX rule_mipv6 will need to be implemented (the specification's - * rules 4) if a mobile IPv6 node is ever implemented. + * Solaris doesn't currently support Mobile IPv6, so there's no + * rule_mipv6 corresponding to rule 4 in the specification. */ rulef_t rules[] = { rule_isdst, @@ -2045,8 +2145,11 @@ ipif_select_source_v6(ill_t *dstill, const in6_addr_t *dst, rule_interface, rule_label, rule_temporary, - rule_prefix, + rule_ifprefix, /* local rules after this */ rule_zone_specific, + rule_addr_type, + rule_prefix, /* local rules before this */ + rule_must_be_last, /* must always be last */ NULL }; diff --git a/usr/src/uts/common/net/if_arp.h b/usr/src/uts/common/net/if_arp.h index 9103b1d0b5..073ce5cf2a 100644 --- a/usr/src/uts/common/net/if_arp.h +++ b/usr/src/uts/common/net/if_arp.h @@ -1,5 +1,5 @@ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -36,6 +36,12 @@ struct arphdr { ushort_t ar_hrd; /* format of hardware address */ #define ARPHRD_ETHER 1 /* ethernet hardware address */ #define ARPHRD_IEEE802 6 /* IEEE 802 hardware address */ +#define ARPHRD_FRAME 15 /* Frame relay */ +#define ARPHRD_ATM 16 /* ATM */ +#define ARPHRD_HDLC 17 /* HDLC */ +#define ARPHRD_FC 18 /* Fibre Channel RFC 4338 */ +#define ARPHRD_IPATM 19 /* ATM RFC 2225 */ +#define ARPHRD_TUNNEL 31 /* IPsec Tunnel RFC 3456 */ #define ARPHRD_IB 32 /* IPoIB hardware address */ ushort_t ar_pro; /* format of protocol address */ uchar_t ar_hln; /* length of hardware address */ diff --git a/usr/src/uts/common/netinet/Makefile b/usr/src/uts/common/netinet/Makefile index 3e3b86cf50..7e8701c61d 100644 --- a/usr/src/uts/common/netinet/Makefile +++ b/usr/src/uts/common/netinet/Makefile @@ -2,9 +2,8 @@ # 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. +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. @@ -22,7 +21,7 @@ # #ident "%Z%%M% %I% %E% SMI" # -# Copyright 2004 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # uts/common/netinet/Makefile @@ -30,7 +29,7 @@ # include global definitions include ../../../Makefile.master -HDRS= arp.h dhcp.h icmp6.h icmp_var.h if_ether.h igmp.h igmp_var.h \ +HDRS= arp.h dhcp.h dhcp6.h icmp6.h icmp_var.h if_ether.h igmp.h igmp_var.h \ in.h inetutil.h in_pcb.h in_systm.h in_var.h ip.h ip6.h ip_icmp.h \ ip_mroute.h ip_var.h pim.h sctp.h tcp.h tcp_debug.h tcp_fsm.h \ tcp_seq.h tcp_timer.h tcp_var.h tcpip.h udp.h udp_var.h diff --git a/usr/src/uts/common/netinet/arp.h b/usr/src/uts/common/netinet/arp.h index a3bf0e7761..8d8c749eb8 100644 --- a/usr/src/uts/common/netinet/arp.h +++ b/usr/src/uts/common/netinet/arp.h @@ -1,5 +1,5 @@ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -36,6 +36,12 @@ struct arphdr { ushort_t ar_hrd; /* format of hardware address */ #define ARPHRD_ETHER 1 /* ethernet hardware address */ #define ARPHRD_IEEE802 6 /* IEEE 802 hardware address */ +#define ARPHRD_FRAME 15 /* Frame relay */ +#define ARPHRD_ATM 16 /* ATM */ +#define ARPHRD_HDLC 17 /* HDLC */ +#define ARPHRD_FC 18 /* Fibre Channel RFC 4338 */ +#define ARPHRD_IPATM 19 /* ATM RFC 2225 */ +#define ARPHRD_TUNNEL 31 /* IPsec Tunnel RFC 3456 */ #define ARPHRD_IB 32 /* IPoIB hardware address */ ushort_t ar_pro; /* format of protocol address */ uchar_t ar_hln; /* length of hardware address */ diff --git a/usr/src/uts/common/netinet/dhcp6.h b/usr/src/uts/common/netinet/dhcp6.h new file mode 100644 index 0000000000..9a2b7e89ed --- /dev/null +++ b/usr/src/uts/common/netinet/dhcp6.h @@ -0,0 +1,357 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _DHCP6_H +#define _DHCP6_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This header file describes constants and on-the-wire data structures used + * with DHCPv6. + * + * Note that the data structures contained here must be used with caution. The + * DHCPv6 protocol generally does not maintain alignment. + * + * (Users may also need to include other header files to get ntohs/htons + * definitions, if the DHCPV6_{GET,SET} macros are used.) + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/types.h> +#include <netinet/in.h> + +/* + * Message Types + */ +#define DHCPV6_MSG_SOLICIT 1 /* Client sends */ +#define DHCPV6_MSG_ADVERTISE 2 /* Server sends */ +#define DHCPV6_MSG_REQUEST 3 /* Client sends */ +#define DHCPV6_MSG_CONFIRM 4 /* Client sends */ +#define DHCPV6_MSG_RENEW 5 /* Client sends */ +#define DHCPV6_MSG_REBIND 6 /* Client sends */ +#define DHCPV6_MSG_REPLY 7 /* Server sends */ +#define DHCPV6_MSG_RELEASE 8 /* Client sends */ +#define DHCPV6_MSG_DECLINE 9 /* Client sends */ +#define DHCPV6_MSG_RECONFIGURE 10 /* Server sends */ +#define DHCPV6_MSG_INFO_REQ 11 /* Client sends */ +#define DHCPV6_MSG_RELAY_FORW 12 /* Relay agent sends to server */ +#define DHCPV6_MSG_RELAY_REPL 13 /* Server sends to relay agent */ + +/* + * Status Codes + */ +#define DHCPV6_STAT_SUCCESS 0 +#define DHCPV6_STAT_UNSPECFAIL 1 /* Unknown reason */ +#define DHCPV6_STAT_NOADDRS 2 /* Server has no addresses available */ +#define DHCPV6_STAT_NOBINDING 3 /* Client record unavailable */ +#define DHCPV6_STAT_NOTONLINK 4 /* Prefix inappropriate for link */ +#define DHCPV6_STAT_USEMCAST 5 /* Client must use multicast */ +#define DHCPV6_STAT_NOPREFIX 6 /* No prefix available; RFC3633 */ + +/* + * DHCP Unique Identifier (DUID) Types + */ +#define DHCPV6_DUID_LLT 1 /* Link layer address plus time */ +#define DHCPV6_DUID_EN 2 /* Vendor assigned */ +#define DHCPV6_DUID_LL 3 /* Link layer address */ + +/* + * DHCPv6 Option Codes + * Note: options 10 and 35 are not assigned. + */ +#define DHCPV6_OPT_CLIENTID 1 /* Client's DUID */ +#define DHCPV6_OPT_SERVERID 2 /* Server's DUID */ +#define DHCPV6_OPT_IA_NA 3 /* Non-temporary addrs; dhcpv6_ia_na */ +#define DHCPV6_OPT_IA_TA 4 /* Temporary addrs; dhcpv6_ia_ta */ +#define DHCPV6_OPT_IAADDR 5 /* IA Address; dhcpv6_iaaddr */ +#define DHCPV6_OPT_ORO 6 /* Option Request; uint16_t array */ +#define DHCPV6_OPT_PREFERENCE 7 /* Server preference; uint8_t */ +#define DHCPV6_OPT_ELAPSED_TIME 8 /* Client time; uint16_t; centisec */ +#define DHCPV6_OPT_RELAY_MSG 9 /* Relayed client DHCP message */ +#define DHCPV6_OPT_AUTH 11 /* Authentication; dhcpv6_auth */ +#define DHCPV6_OPT_UNICAST 12 /* Client may unicast; in6_addr_t */ +#define DHCPV6_OPT_STATUS_CODE 13 /* Status; uint16_t plus string */ +#define DHCPV6_OPT_RAPID_COMMIT 14 /* Server may do RC; boolean (len 0) */ +#define DHCPV6_OPT_USER_CLASS 15 /* Classes; {uint16_t,uint8_t...}... */ +#define DHCPV6_OPT_VENDOR_CLASS 16 /* Client vendor; uint32_t + list */ +#define DHCPV6_OPT_VENDOR_OPT 17 /* Vendor specific; uint32_t+opts */ +#define DHCPV6_OPT_INTERFACE_ID 18 /* Relay agent interface */ +#define DHCPV6_OPT_RECONF_MSG 19 /* Reconfigure; uint8_t */ +#define DHCPV6_OPT_RECONF_ACC 20 /* Reconfigure accept; boolean */ +#define DHCPV6_OPT_SIP_NAMES 21 /* SIP srv domain names (RFC3319) */ +#define DHCPV6_OPT_SIP_ADDR 22 /* SIP srv IPv6 address (RFC3319) */ +#define DHCPV6_OPT_DNS_ADDR 23 /* DNS Recur. Name Server (RFC3646) */ +#define DHCPV6_OPT_DNS_SEARCH 24 /* Domain Search List (RFC3646) */ +#define DHCPV6_OPT_IA_PD 25 /* Delegate dhcpv6_ia_na (RFC3633) */ +#define DHCPV6_OPT_IAPREFIX 26 /* Prefix dhcpv6_iaprefix (RFC3633) */ +#define DHCPV6_OPT_NIS_SERVERS 27 /* NIS in6_addr_t array (RFC3898) */ +#define DHCPV6_OPT_NISP_SERVERS 28 /* NIS+ in6_addr_t array (RFC3898) */ +#define DHCPV6_OPT_NIS_DOMAIN 29 /* NIS Domain string (RFC3898) */ +#define DHCPV6_OPT_NISP_DOMAIN 30 /* NIS+ Domain string (RFC3898) */ +#define DHCPV6_OPT_SNTP_SERVERS 31 /* SNTP in6_addr_t array (RFC4075) */ +#define DHCPV6_OPT_INFO_REFTIME 32 /* Info refresh uint32_t (RFC4242) */ +#define DHCPV6_OPT_BCMCS_SRV_D 33 /* NUL-term string list (RFC4280) */ +#define DHCPV6_OPT_BCMCS_SRV_A 34 /* in6_addr_t array (RFC4280) */ +#define DHCPV6_OPT_GEOCONF_CVC 36 /* dhcpv6_civic_t plus TLVs */ +#define DHCPV6_OPT_REMOTE_ID 37 /* uint32_t plus opaque */ +#define DHCPV6_OPT_SUBSCRIBER 38 /* opaque; may be NVT ASCII */ +#define DHCPV6_OPT_CLIENT_FQDN 39 /* uint8_t plus domain */ + +/* + * Reconfiguration types; used with DHCPV6_OPT_RECONF_MSG option. + */ +#define DHCPV6_RECONF_RENEW 5 /* Renew now */ +#define DHCPV6_RECONF_INFO 11 /* Request information */ + +/* + * FQDN Flags; used with DHCPV6_OPT_CLIENT_FQDN option. + */ +#define DHCPV6_FQDNF_S 0x01 /* Server should perform AAAA RR updates */ +#define DHCPV6_FQDNF_O 0x02 /* Server override of 'S' bit */ +#define DHCPV6_FQDNF_N 0x04 /* Server should not perform any updates */ + +/* + * Miscellany + */ +#define DHCPV6_INFTIME 0xfffffffful /* Infinity; used for timers */ +#define DHCPV6_FOREVER 0xffff /* Used for elapsed time option */ +#define DHCPV6_SUN_ENT 42 /* Sun Microsystems enterprise ID */ + +/* + * Basic DHCPv6 message header used for server/client communications. The + * options follow this header. + */ +struct dhcpv6_message { + uint8_t d6m_msg_type; + uint8_t d6m_transid_ho; + uint16_t d6m_transid_lo; +}; + +#define DHCPV6_GET_TRANSID(msg) \ + (((msg)->d6m_transid_ho << 16) + ntohs((msg)->d6m_transid_lo)) +#define DHCPV6_SET_TRANSID(msg, id) \ + ((msg)->d6m_transid_ho = (id) >> 16, (msg)->d6m_transid_lo = htons(id)) + +/* + * DHCPv6 relay agent header used only for server/relay communications. The + * options follow this header, and the client message is encapsulated as an + * option. Note that the IPv6 addresses are not on natural word boundaries. + */ +struct dhcpv6_relay { + uint8_t d6r_msg_type; + uint8_t d6r_hop_count; + uint8_t d6r_linkaddr[16]; + uint8_t d6r_peeraddr[16]; +}; + +/* + * DHCPv6 generic option header. Note that options are not aligned on any + * convenient boundary. + */ +struct dhcpv6_option { + uint16_t d6o_code; + uint16_t d6o_len; +}; + +/* + * Option header for IA_NA (Non-temporary addresses) and IA_PD (Prefix + * delegation). Contains IA Address options for IA_NA, IA_PD Prefixes for + * IA_PD. + */ +struct dhcpv6_ia_na { + uint16_t d6in_code; + uint16_t d6in_len; + uint32_t d6in_iaid; /* Unique ID [interface] */ + uint32_t d6in_t1; /* Extend from same server */ + uint32_t d6in_t2; /* Extend from any server */ +}; + +/* + * Option header for IA_TA (Temporary addresses). Contains IA Address options. + */ +struct dhcpv6_ia_ta { + uint16_t d6it_code; + uint16_t d6it_len; + uint32_t d6it_iaid; /* Unique ID [interface] */ +}; + +/* + * Option header for IA Address. Must be used inside of an IA_NA or IA_TA + * option. May contain a Status Code option. + */ +struct dhcpv6_iaaddr { + uint16_t d6ia_code; + uint16_t d6ia_len; + in6_addr_t d6ia_addr; /* IPv6 address */ + uint32_t d6ia_preflife; /* Preferred lifetime */ + uint32_t d6ia_vallife; /* Valid lifetime */ +}; + +/* + * Option header for Authentication. Followed by variable-length + * authentication information field. Warning: padding may be present. Use + * defined size. + */ +struct dhcpv6_auth { + uint16_t d6a_code; + uint16_t d6a_len; + uint8_t d6a_proto; /* Protocol */ + uint8_t d6a_alg; /* Algorithm */ + uint8_t d6a_rdm; /* Replay Detection Method (RDM) */ + uint8_t d6a_replay[8]; /* Information for RDM */ +}; +#define DHCPV6_AUTH_SIZE 15 + +/* dhpv6_auth.d6a_proto values */ +#define DHCPV6_PROTO_DELAYED 2 /* Delayed Authentication mechanism */ +#define DHCPV6_PROTO_RECONFIG 3 /* Reconfigure Key mechanism */ + +/* dhpv6_auth.d6a_alg values */ +#define DHCPV6_ALG_HMAC_MD5 1 /* HMAC-MD5 signature */ + +/* dhpv6_auth.d6a_rdm values */ +#define DHCPV6_RDM_MONOCNT 0 /* Monotonic counter */ + +/* + * Option header for IA_PD Prefix. Must be used inside of an IA_PD option. + * May contain a Status Code option. Warning: padding may be present; use + * defined size. + */ +struct dhcpv6_iaprefix { + uint16_t d6ip_code; + uint16_t d6ip_len; + uint32_t d6ip_preflife; /* Preferred lifetime */ + uint32_t d6ip_vallife; /* Valid lifetime */ + uint8_t d6ip_preflen; /* Prefix length */ + uint8_t d6ip_addr[16]; /* IPv6 prefix */ +}; +#define DHCPV6_IAPREFIX_SIZE 29 + +/* + * Option header for Civic Address information. Followed by single octet TLV + * encoded address elements, using CIVICADDR_* values for type. Warning: + * padding may be present; use defined size. + */ +struct dhcpv6_civic { + uint16_t d6c_code; + uint16_t d6c_len; + uint8_t d6c_what; /* DHCPV6_CWHAT_* value */ + char d6c_cc[2]; /* Country code; ISO 3166 */ +}; +#define DHCPV6_CIVIC_SIZE 7 + +#define DHCPV6_CWHAT_SERVER 0 /* Location of server */ +#define DHCPV6_CWHAT_NETWORK 1 /* Location of network */ +#define DHCPV6_CWHAT_CLIENT 2 /* Location of client */ + +#define CIVICADDR_LANG 0 /* Language; RFC 2277 */ +#define CIVICADDR_A1 1 /* National division (state) */ +#define CIVICADDR_A2 2 /* County */ +#define CIVICADDR_A3 3 /* City */ +#define CIVICADDR_A4 4 /* City division */ +#define CIVICADDR_A5 5 /* Neighborhood */ +#define CIVICADDR_A6 6 /* Street group */ +#define CIVICADDR_PRD 16 /* Leading street direction */ +#define CIVICADDR_POD 17 /* Trailing street suffix */ +#define CIVICADDR_STS 18 /* Street suffix or type */ +#define CIVICADDR_HNO 19 /* House number */ +#define CIVICADDR_HNS 20 /* House number suffix */ +#define CIVICADDR_LMK 21 /* Landmark */ +#define CIVICADDR_LOC 22 /* Additional location information */ +#define CIVICADDR_NAM 23 /* Name/occupant */ +#define CIVICADDR_PC 24 /* Postal Code/ZIP */ +#define CIVICADDR_BLD 25 /* Building */ +#define CIVICADDR_UNIT 26 /* Unit/apt/suite */ +#define CIVICADDR_FLR 27 /* Floor */ +#define CIVICADDR_ROOM 28 /* Room number */ +#define CIVICADDR_TYPE 29 /* Place type */ +#define CIVICADDR_PCN 30 /* Postal community name */ +#define CIVICADDR_POBOX 31 /* Post office box */ +#define CIVICADDR_ADDL 32 /* Additional code */ +#define CIVICADDR_SEAT 33 /* Seat/desk */ +#define CIVICADDR_ROAD 34 /* Primary road or street */ +#define CIVICADDR_RSEC 35 /* Road section */ +#define CIVICADDR_RBRA 36 /* Road branch */ +#define CIVICADDR_RSBR 37 /* Road sub-branch */ +#define CIVICADDR_SPRE 38 /* Street name pre-modifier */ +#define CIVICADDR_SPOST 39 /* Street name post-modifier */ +#define CIVICADDR_SCRIPT 128 /* Script */ + +/* + * DHCP Unique Identifier structures. These represent the fixed portion of the + * unique identifier object, and are followed by the variable-length link layer + * address or identifier. + */ +struct duid_llt { + uint16_t dllt_dutype; + uint16_t dllt_hwtype; + uint32_t dllt_time; +}; + +/* DUID time stamps start on January 1st, 2000 UTC */ +#define DUID_TIME_BASE 946684800ul + +struct duid_en { + uint16_t den_dutype; + uint16_t den_entho; + uint16_t den_entlo; +}; + +#define DHCPV6_GET_ENTNUM(den) \ + ((ntohs((den)->den_entho) << 16) + ntohs((den)->den_entlo)) +#define DHCPV6_SET_ENTNUM(den, val) \ + ((den)->den_entho = htons((val) >> 16), (den)->den_entlo = htons(val)) + +struct duid_ll { + uint16_t dll_dutype; + uint16_t dll_hwtype; +}; + +/* + * Data types + */ +typedef struct dhcpv6_message dhcpv6_message_t; +typedef struct dhcpv6_relay dhcpv6_relay_t; +typedef struct dhcpv6_option dhcpv6_option_t; +typedef struct dhcpv6_ia_na dhcpv6_ia_na_t; +typedef struct dhcpv6_ia_ta dhcpv6_ia_ta_t; +typedef struct dhcpv6_iaaddr dhcpv6_iaaddr_t; +typedef struct dhcpv6_auth dhcpv6_auth_t; +typedef struct dhcpv6_iaprefix dhcpv6_iaprefix_t; +typedef struct dhcpv6_civic dhcpv6_civic_t; +typedef struct duid_llt duid_llt_t; +typedef struct duid_en duid_en_t; +typedef struct duid_ll duid_ll_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _DHCP6_H */ diff --git a/usr/src/uts/common/netinet/in.h b/usr/src/uts/common/netinet/in.h index d1920bd004..4b5f76767b 100644 --- a/usr/src/uts/common/netinet/in.h +++ b/usr/src/uts/common/netinet/in.h @@ -178,6 +178,9 @@ typedef uint16_t sa_family_t; /* * Port/socket numbers: network standard functions + * + * Entries should exist here for each port number compiled into an ON + * component, such as snoop. */ #define IPPORT_ECHO 7 #define IPPORT_DISCARD 9 @@ -191,6 +194,7 @@ typedef uint16_t sa_family_t; #define IPPORT_TIMESERVER 37 #define IPPORT_NAMESERVER 42 #define IPPORT_WHOIS 43 +#define IPPORT_DOMAIN 53 #define IPPORT_MTP 57 /* @@ -201,8 +205,17 @@ typedef uint16_t sa_family_t; #define IPPORT_TFTP 69 #define IPPORT_RJE 77 #define IPPORT_FINGER 79 +#define IPPORT_HTTP 80 +#define IPPORT_HTTP_ALT 8080 #define IPPORT_TTYLINK 87 #define IPPORT_SUPDUP 95 +#define IPPORT_NTP 123 +#define IPPORT_NETBIOS_NS 137 +#define IPPORT_NETBIOS_DGM 138 +#define IPPORT_NETBIOS_SSN 139 +#define IPPORT_LDAP 389 +#define IPPORT_SLP 427 +#define IPPORT_MIP 434 /* * Internet Key Exchange (IKE) ports @@ -216,6 +229,7 @@ typedef uint16_t sa_family_t; #define IPPORT_EXECSERVER 512 #define IPPORT_LOGINSERVER 513 #define IPPORT_CMDSERVER 514 +#define IPPORT_PRINTER 515 #define IPPORT_EFSSERVER 520 /* @@ -223,7 +237,18 @@ typedef uint16_t sa_family_t; */ #define IPPORT_BIFFUDP 512 #define IPPORT_WHOSERVER 513 -#define IPPORT_ROUTESERVER 520 /* 520+1 also used */ +#define IPPORT_SYSLOG 514 +#define IPPORT_TALK 517 +#define IPPORT_ROUTESERVER 520 +#define IPPORT_RIPNG 521 + +/* + * DHCPv6 UDP ports + */ +#define IPPORT_DHCPV6C 546 +#define IPPORT_DHCPV6S 547 + +#define IPPORT_SOCKS 1080 /* * Ports < IPPORT_RESERVED are reserved for |