1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
|
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.
|