437 lines
18 KiB
Plaintext
437 lines
18 KiB
Plaintext
|
This file documents the protocol that the ISC DHCP server and ISC
|
||
|
Object Management clients (clients that use the ISC Object Management
|
||
|
API) speak between one another.
|
||
|
|
||
|
Protocol:
|
||
|
|
||
|
All multi-byte numbers are represented in network byte order.
|
||
|
|
||
|
On startup, each side sends a status message indicating what version
|
||
|
of the protocol they are speaking. The status message looks like
|
||
|
this:
|
||
|
|
||
|
+---------+---------+
|
||
|
| version | hlength |
|
||
|
+---------+---------+
|
||
|
|
||
|
version - a 32-bit fixed-point number with the decimal point between
|
||
|
the third and second decimal digits from the left,
|
||
|
representing the version of the protocol. The current
|
||
|
protocol version is 1.00. If the field were considered as
|
||
|
a 32-bit integer, this would correspond to a value of 100
|
||
|
decimal, or 0x64.
|
||
|
|
||
|
hlength - a 32-bit integer representing the length of the fixed-length
|
||
|
header in subsequent messages. This is normally 56, but
|
||
|
can be changed to a value larger than 56 by either side
|
||
|
without upgrading the revision number.
|
||
|
|
||
|
|
||
|
The startup message is not authenticated. Either side may reject the
|
||
|
other side's startup message as invalid by simply closing the
|
||
|
connection. The only fixed part of the startup message is the
|
||
|
version number - future versions may delete hlength, or add further
|
||
|
startup information.
|
||
|
|
||
|
Following the startup message, all messages have the same format.
|
||
|
Currently, the format includes a fixed-length header (the length in
|
||
|
hlength, above)
|
||
|
|
||
|
+--------+----+--------+----+-----+---------+------------+------------+-----+
|
||
|
| authid | op | handle | id | rid | authlen | msg values | obj values | sig |
|
||
|
+--------+----+--------+----+-----+---------+------------+------------+-----+
|
||
|
|
||
|
The fixed-length header consists of:
|
||
|
|
||
|
authid = a 32-bit authenticator handle.
|
||
|
For an original message (one not in response to some other
|
||
|
message), this will be chosen by the originator. For a
|
||
|
message in response to another message, the authenticator for
|
||
|
that message is used, except if the response is an error
|
||
|
message indicating that the authenticator used was unknown,
|
||
|
in which case the null authenticator is used. Messages that
|
||
|
are generated as the result of a notify registration use the
|
||
|
authenticator used in the original notify registration.
|
||
|
The authenticator itself is generated by having one side of
|
||
|
the connection send an object of type "authenticator" to the
|
||
|
other side with values that indicate what kind of
|
||
|
authentication mechanism to use and what key to use. The two
|
||
|
most likely things here are a Kerberos V principal name or the
|
||
|
name of a shared secret that can be used to calculate an MD5
|
||
|
hash. The mechanism for doing this has yet to be finalized.
|
||
|
If authid is zero, the message is not authenticated.
|
||
|
|
||
|
op = 32-bit opcode, one of:
|
||
|
open = 1
|
||
|
refresh = 2
|
||
|
update = 3
|
||
|
notify = 4
|
||
|
error = 5
|
||
|
delete = 6
|
||
|
handle = 32-bit object handle
|
||
|
A handle on the object being opened, created, refreshed or
|
||
|
updated. If no handle is yet available (e.g., with open and
|
||
|
new), then the value zero is sent.
|
||
|
id = 32-bit transaction id of the message - a monotonically increasing
|
||
|
number that starts with some randomly chosen number at the
|
||
|
beginning of the life of the connection. The value should never
|
||
|
be zero.
|
||
|
rid = 32-bit transaction ID of the message to which this message is a
|
||
|
response, or zero if this message is not in response to a
|
||
|
message from the other side.
|
||
|
|
||
|
authlen = a 32-bit number representing the length of the authenticator
|
||
|
|
||
|
msg values = a series of name+value pairs, specific to this message.
|
||
|
Each name+value pair starts with a 16-bit name length,
|
||
|
followed by that many bytes of name, followed by a 32-bit
|
||
|
value length, followed by that many bytes of value. If the
|
||
|
length is zero, this is a value of the blank string. If the
|
||
|
length is all ones (2^32-1), then there is no value - for an
|
||
|
update, this means the value for this name and the name
|
||
|
itself should be deleted from the object, which may or may
|
||
|
not be possible. The list of name/value pairs ends with a
|
||
|
zero-length name, which is not followed by a value
|
||
|
length/value pair.
|
||
|
|
||
|
obj values = a series of name+value pairs, as above, specific to the
|
||
|
object being created, updated or refreshed.
|
||
|
|
||
|
signature = authlen bytes of data signing the message. The signature
|
||
|
algorithm is a property of the authenticator handle.
|
||
|
|
||
|
Message types:
|
||
|
|
||
|
1: open
|
||
|
relevant input values:
|
||
|
object-type = the name of the type of object
|
||
|
open:create = boolean - create the object if it doesn't yet exist
|
||
|
open:exclusive = boolean - don't open the object if it does exist
|
||
|
open:update = boolean - update the object with included values
|
||
|
if it matches.
|
||
|
the handle should always be the null handle
|
||
|
|
||
|
The input value must also contain key information for the type of
|
||
|
object being searched that uniquely identifies an object, or search
|
||
|
information that matches only one object. Each object has a key
|
||
|
specification (a key is something that uniquely identifies an
|
||
|
object), so see the key specification for that object to see
|
||
|
what to send here. An open message with the create flag set must
|
||
|
specify a key, and not merely matching criteria. Some objects may
|
||
|
allow more than one key, and it may be that the union of those keys
|
||
|
is required to uniquely identify the object, or it may be that any
|
||
|
one such key will uniquely identify the object. The documentation
|
||
|
for the type of object will specify this.
|
||
|
|
||
|
An open message will result in an immediate response message whose
|
||
|
opcode will either be "error" or "update". The error message may
|
||
|
include an error:reason value containing a text string explaining
|
||
|
the error, and will always include an error:code value which will
|
||
|
be the numeric error code for what went wrong. Possible error
|
||
|
codes are:
|
||
|
|
||
|
not found - no such object exists
|
||
|
already exists - object already exists, and exclusive flag was
|
||
|
set.
|
||
|
not unique - more than one object matching the specification
|
||
|
exists.
|
||
|
permission denied - the authenticator ID specified does not
|
||
|
have authorization to access this object,
|
||
|
or if the update flag was specified, to
|
||
|
update the object.
|
||
|
|
||
|
If the response is an update message, the update message will
|
||
|
include the object handle and all of the name/value pairs
|
||
|
associated with that object.
|
||
|
|
||
|
2: refresh
|
||
|
|
||
|
no input values except the handle need be specified. The null
|
||
|
handle may not be specified. If the handle is valid, and the
|
||
|
authenticator ID specified has permission to examine the object,
|
||
|
then an update message will be sent for that object. Otherwise,
|
||
|
one of the following errors will be sent:
|
||
|
|
||
|
invalid handle - the handle does not refer to a known object
|
||
|
permisson denied - the handle refers to an object that the
|
||
|
requestor does not have permission to
|
||
|
examine.
|
||
|
|
||
|
3: update
|
||
|
|
||
|
Requests that the contents of the specified object be updated with
|
||
|
the values included. Values that are not specified are not
|
||
|
updated. The response will be either an error message or an
|
||
|
update-ok message. If rid is nonzero, no response will be
|
||
|
generated, even if there was an error. Possible errors include:
|
||
|
|
||
|
invalid handle - no such object was found
|
||
|
permission denied - the handle refers to an object that the
|
||
|
requestor does not have permission to
|
||
|
modify.
|
||
|
not confirmed - the update could not be committed due to some
|
||
|
kind of resource problem, for example
|
||
|
insufficient memory or a disk failure.
|
||
|
|
||
|
4: notify
|
||
|
|
||
|
Requests that whenever the object with the specified handle is
|
||
|
modified, an update be sent. If there is something wrong with the
|
||
|
request, an error message will be returned immediately.
|
||
|
Otherwise, whenever a change is made to the object, an update
|
||
|
message will be sent containing whatever changes were made (or
|
||
|
possibly all the values associated with the object, depending on
|
||
|
the implementation). Possible errors:
|
||
|
|
||
|
invalid handle
|
||
|
permission denied - the handle refers to an object that the
|
||
|
requestor does not have permission to
|
||
|
examine.
|
||
|
not supported - the object implementation does not support
|
||
|
notifications
|
||
|
|
||
|
5: status
|
||
|
|
||
|
Sends a status code in response to a message. Always sent in
|
||
|
response to a message sent by the other side. There should never
|
||
|
be a response to this message.
|
||
|
|
||
|
6: delete
|
||
|
|
||
|
Deletes the specified object. Response will be either request-ok,
|
||
|
or error. Possible errors include:
|
||
|
|
||
|
invalid handle - no such object was found
|
||
|
permission denied - the handle refers to an object that the
|
||
|
requestor does not have permission to
|
||
|
modify.
|
||
|
not confirmed - the deletion could not be committed due to
|
||
|
some kind of resource problem, for example
|
||
|
insufficient memory or a disk failure.
|
||
|
|
||
|
7: notify-cancel
|
||
|
|
||
|
Like notify, but requests that an existing notification be cancelled.
|
||
|
|
||
|
8: notify-cancelled
|
||
|
|
||
|
Indicates that because of a local change, a notification that had
|
||
|
been registered can no longer be performed. This could be as a
|
||
|
result of the permissions on a object changing, or an object being
|
||
|
deleted. There should never be a response to this message.
|
||
|
|
||
|
internals:
|
||
|
|
||
|
Both client and server use same protocol and infrastructure. There
|
||
|
are many object types, each of which is stored in a registry.
|
||
|
Objects whose type is not recognized can either be handled by the
|
||
|
generic object type, which is registered with the type "*". If no
|
||
|
generic object type is registered, then objects with unknown types are
|
||
|
simply not supported. On the client, there are probably no special
|
||
|
object handlers (although this is by no means forbidden). On the
|
||
|
server, probably everything is a special object.
|
||
|
|
||
|
Each object type has the following methods:
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
dhcpctl_status dhcpctl_connect (dhcpctl_handle *connection,
|
||
|
char *server_name, int port,
|
||
|
dhcpctl_handle *authinfo)
|
||
|
synchronous
|
||
|
returns nonzero status code if it didn't connect, zero otherwise
|
||
|
stores connection handle through connection, which can be used
|
||
|
for subsequent access to the specified server.
|
||
|
server_name is the name of the server, and port is the TCP
|
||
|
port on which it is listening.
|
||
|
authinfo is the handle to an object containing authentication
|
||
|
information.
|
||
|
|
||
|
dhcpctl_status dhcpctl_open_object (dhcpctl_handle h,
|
||
|
dhcpctl_handle connection,
|
||
|
int flags)
|
||
|
asynchronous - just queues the request
|
||
|
returns nonzero status code if open couldn't be queued
|
||
|
returns zero if open was queued
|
||
|
h is a handle to an object created by dhcpctl_new_object
|
||
|
connection is a connection to a DHCP server
|
||
|
flags include:
|
||
|
DHCPCTL_CREATE - if the object doesn't exist, create it
|
||
|
DHCPCTL_UPDATE - update the object on the server using the
|
||
|
attached parameters
|
||
|
DHCPCTL_EXCL - error if the object exists and DHCPCTL_CREATE
|
||
|
was also specified
|
||
|
|
||
|
dhcpctl_status dhcpctl_new_object (dhcpctl_handle *h,
|
||
|
dhcpctl_handle connection,
|
||
|
char *object_type)
|
||
|
synchronous - creates a local handle for a host entry.
|
||
|
returns nonzero status code if the local host entry couldn't
|
||
|
be created
|
||
|
stores handle to host through h if successful, and returns zero.
|
||
|
object_type is a pointer to a NUL-terminated string containing
|
||
|
the ascii name of the type of object being accessed - e.g., "host"
|
||
|
|
||
|
dhcpctl_status dhcpctl_set_callback (dhcpctl_handle h, void *data,
|
||
|
void (*callback) (dhcpctl_handle,
|
||
|
dhcpctl_status, void *))
|
||
|
synchronous, with asynchronous aftereffect
|
||
|
handle is some object upon which some kind of process has been
|
||
|
started - e.g., an open, an update or a refresh.
|
||
|
data is an anonymous pointer containing some information that
|
||
|
the callback will use to figure out what event completed.
|
||
|
return value of 0 means callback was successfully set, a nonzero
|
||
|
status code is returned otherwise.
|
||
|
Upon completion of whatever task is in process, the callback
|
||
|
will be passed the handle to the object, a status code
|
||
|
indicating what happened, and the anonymous pointer passed to
|
||
|
|
||
|
dhcpctl_status dhcpctl_wait_for_completion (dhcpctl_handle h,
|
||
|
dhcpctl_status *s)
|
||
|
synchronous
|
||
|
returns zero if the callback completes, a nonzero status if
|
||
|
there was some problem relating to the wait operation. The
|
||
|
status of the queued request will be stored through s, and
|
||
|
will also be either zero for success or nonzero for some kind
|
||
|
of failure. Never returns until completion or until the
|
||
|
connection to the server is lost. This performs the same
|
||
|
function as dhcpctl_set_callback and the subsequent callback,
|
||
|
for programs that want to do inline execution instead of using
|
||
|
callbacks.
|
||
|
|
||
|
dhcpctl_status dhcpctl_get_value (data_string *result,
|
||
|
dhcpctl_handle h, char *value_name)
|
||
|
synchronous
|
||
|
returns zero if the call succeeded, a nonzero status code if
|
||
|
it didn't.
|
||
|
result is the address of an empty data string (initialized
|
||
|
with bzero or cleared with data_string_forget). On
|
||
|
successful completion, the addressed data string will contain
|
||
|
the value that was fetched.
|
||
|
dhcpctl_handle refers to some dhcpctl item
|
||
|
value_name refers to some value related to that item - e.g.,
|
||
|
for a handle associated with a completed host lookup, value
|
||
|
could be one of "hardware-address", "dhcp-client-identifier",
|
||
|
"known" or "client-hostname".
|
||
|
|
||
|
dhcpctl_status dhcpctl_get_boolean (int *result,
|
||
|
dhcpctl_handle h, char *value_name)
|
||
|
like dhcpctl_get_value, but more convenient for boolean
|
||
|
values, since no data_string needs to be dealt with.
|
||
|
|
||
|
dhcpctl_status dhcpctl_set_value (dhcpctl_handle h, data_string value,
|
||
|
char *value_name)
|
||
|
Sets a value on an object referred to by a dhcpctl_handle.
|
||
|
The opposite of dhcpctl_get_value. Does not update the
|
||
|
server - just sets the value on the handle.
|
||
|
|
||
|
dhcpctl_status dhcpctl_set_string_value (dhcpctl_handle h, char *value,
|
||
|
char *value_name)
|
||
|
Sets a NUL-terminated ASCII value on an object referred to by
|
||
|
a dhcpctl_handle. like dhcpctl_set_value, but saves the
|
||
|
trouble of creating a data_string for a NUL-terminated string.
|
||
|
Does not update the server - just sets the value on the handle.
|
||
|
|
||
|
dhcpctl_status dhcpctl_set_boolean (dhcpctl_handle h, int value,
|
||
|
char *value_name)
|
||
|
Sets a boolean value on an object - like dhcpctl_set_value,
|
||
|
only more convenient for booleans.
|
||
|
|
||
|
dhcpctl_status dhcpctl_object_update (dhcpctl_handle h)
|
||
|
Queues an update on the object referenced by the handle (there
|
||
|
can't be any other work in progress on the handle). An
|
||
|
update means local parameters will be sent to the server.
|
||
|
|
||
|
dhcpctl_status dhcpctl_object_refresh (dhcpctl_handle h)
|
||
|
Queues an update on the object referenced by the handle (there
|
||
|
can't be any other work in progress on the handle). An
|
||
|
update means local parameters will be sent to the server.
|
||
|
|
||
|
dhcpctl_status dhcpctl_object_delete (dhcpctl_handle h)
|
||
|
Queues a delete of the object referenced by the handle (there
|
||
|
can't be any other work in progress on the handle). A
|
||
|
delete means that the object will be permanently deleted on
|
||
|
the remote end, assuming the remote end supports object
|
||
|
persistence.
|
||
|
|
||
|
So a sample program that would update a host declaration would look
|
||
|
something like this:
|
||
|
|
||
|
/* Create a local object into which to store authentication
|
||
|
information. */
|
||
|
if ((status = dhcpctl_new_object (&auth, dhcpctl_null_handle,
|
||
|
"authentication-information")))
|
||
|
dhcpctl_error ("Can't create authentication information: %m");
|
||
|
|
||
|
/* Set up the authenticator with an algorithm type, user name and
|
||
|
password. */
|
||
|
if ((status = dhcpctl_set_string_value (&auth, "mellon", "username")))
|
||
|
dhcpctl_error ("Can't set username: %m", status);
|
||
|
if ((status = dhcpctl_set_string_value (&auth, "three blind mice",
|
||
|
"password")))
|
||
|
dhcpctl_error ("Can't set password: %m", status);
|
||
|
if ((status = dhcpctl_set_string_value (&auth, "md5-hash",
|
||
|
"algorithm")))
|
||
|
dhcpctl_error ("Can't set authentication algorithm: %m.",
|
||
|
status);
|
||
|
|
||
|
/* Connect to the server. */
|
||
|
if ((status = dhcpctl_connect (&c, "dhcp.server.com", 612, &auth)))
|
||
|
|
||
|
dhcpctl_error ("Can't connect to dhcp.server.com: %m",
|
||
|
status);
|
||
|
|
||
|
/* Create a host object. */
|
||
|
if ((status = dhcpctl_new_object (&hp, c, "host")))
|
||
|
dhcpctl_error ("Host create failed: %m", status);
|
||
|
|
||
|
/* Create a data_string to contain the host's client
|
||
|
identifier, and set it. */
|
||
|
if ((status =
|
||
|
data_string_create_from_hex (&client_id,
|
||
|
"1:08:00:2b:34:1a:c3")))
|
||
|
dhcpctl_error ("Can't create client identifier: %m");
|
||
|
if ((status = dhcpctl_set_value (hp, client_id,
|
||
|
"dhcp-client-identifier")))
|
||
|
dhcpctl_error ("Host client identifier set failed.");
|
||
|
/* Set the known flag to 1. */
|
||
|
if ((status = dhcpctl_set_boolean (hp, 1, "known")))
|
||
|
dhcpctl_error ("Host known set failed.");
|
||
|
|
||
|
/* Open an existing host object that matches the client identifier,
|
||
|
and update it from the local context, or if no host entry
|
||
|
yet exists matching the identifier, create one and
|
||
|
initialize it. */
|
||
|
if ((status = dhcpctl_open_object (&hp, c,
|
||
|
DHCPCTL_CREATE | DHCPCTL_UPDATE)))
|
||
|
dhcpctl_error ("Can't open host: %m", status);
|
||
|
|
||
|
/* Wait for the process to complete, check status. */
|
||
|
if ((status = dhcpctl_wait_for_completion (hp, &wait_status)))
|
||
|
dhcpctl_error ("Host create/lookup wait failed: %m", status);
|
||
|
if (waitstatus)
|
||
|
dhcpctl_error ("Host create/lookup failed: %m", status);
|
||
|
|
||
|
The API is a bit complicated, for a couple of reasons. I want to
|
||
|
make it general, so that there aren't a bazillion functions to call,
|
||
|
one for each data type. I want it to be thread-safe, which is why
|
||
|
each function returns a status and the error printer requires a status
|
||
|
code for input. I want it to be possible to make it asynchronous, so
|
||
|
that it can work in tandem with, for example, an X toolkit. If
|
||
|
you're just writing a simple update cgi program, you probably won't
|
||
|
want to bother to use the asynchronous callbacks, and indeed the above
|
||
|
example doesn't.
|
||
|
|
||
|
I glossed over data strings above - basically, they're objects with a
|
||
|
pointer to a reference-counted buffer structure, an offset into that
|
||
|
buffer, and a length. These are used within the DHCP server, so you
|
||
|
can get an idea of how they work - basically, they're a convenient and
|
||
|
efficient way to store a string with a length such that substrings can
|
||
|
easily be taken and such that more than one user at a time can have a
|
||
|
pointer to the string.
|
||
|
|
||
|
I will also probably add locking primitives, so that you can get the
|
||
|
value of something and be sure that some other updator process won't
|
||
|
modify it while you have the lock.
|