00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019 FILE_LICENCE ( GPL2_OR_LATER );
00020
00021 #include <stdint.h>
00022 #include <stdlib.h>
00023 #include <stdio.h>
00024 #include <errno.h>
00025 #include <string.h>
00026 #include <gpxe/dhcp.h>
00027 #include <gpxe/dhcpopts.h>
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042 static inline char * dhcp_tag_name ( unsigned int tag ) {
00043 static char name[8];
00044
00045 if ( DHCP_IS_ENCAP_OPT ( tag ) ) {
00046 snprintf ( name, sizeof ( name ), "%d.%d",
00047 DHCP_ENCAPSULATOR ( tag ),
00048 DHCP_ENCAPSULATED ( tag ) );
00049 } else {
00050 snprintf ( name, sizeof ( name ), "%d", tag );
00051 }
00052 return name;
00053 }
00054
00055
00056
00057
00058
00059
00060
00061
00062 static inline __attribute__ (( always_inline )) struct dhcp_option *
00063 dhcp_option ( struct dhcp_options *options, unsigned int offset ) {
00064 return ( ( struct dhcp_option * ) ( options->data + offset ) );
00065 }
00066
00067
00068
00069
00070
00071
00072
00073
00074 static inline __attribute__ (( always_inline )) int
00075 dhcp_option_offset ( struct dhcp_options *options,
00076 struct dhcp_option *option ) {
00077 return ( ( ( void * ) option ) - options->data );
00078 }
00079
00080
00081
00082
00083
00084
00085
00086 static unsigned int dhcp_option_len ( struct dhcp_option *option ) {
00087 if ( ( option->tag == DHCP_END ) || ( option->tag == DHCP_PAD ) ) {
00088 return 1;
00089 } else {
00090 return ( option->len + DHCP_OPTION_HEADER_LEN );
00091 }
00092 }
00093
00094
00095
00096
00097
00098
00099
00100
00101
00102
00103
00104
00105
00106
00107
00108
00109
00110
00111
00112
00113
00114 static int find_dhcp_option_with_encap ( struct dhcp_options *options,
00115 unsigned int tag,
00116 int *encap_offset ) {
00117 unsigned int original_tag __attribute__ (( unused )) = tag;
00118 struct dhcp_option *option;
00119 int offset = 0;
00120 ssize_t remaining = options->len;
00121 unsigned int option_len;
00122
00123
00124 if ( tag == DHCP_PAD )
00125 return -ENOENT;
00126
00127
00128 while ( remaining ) {
00129
00130
00131
00132
00133 option = dhcp_option ( options, offset );
00134 option_len = dhcp_option_len ( option );
00135 remaining -= option_len;
00136 if ( remaining < 0 )
00137 break;
00138
00139 if ( option->tag == DHCP_END ) {
00140 if ( tag == DHCP_END )
00141
00142
00143
00144 return offset;
00145 else
00146 break;
00147 }
00148
00149 if ( option->tag == tag ) {
00150 DBGC ( options, "DHCPOPT %p found %s (length %d)\n",
00151 options, dhcp_tag_name ( original_tag ),
00152 option_len );
00153 return offset;
00154 }
00155
00156 if ( DHCP_IS_ENCAP_OPT ( tag ) &&
00157 ( option->tag == DHCP_ENCAPSULATOR ( tag ) ) ) {
00158 if ( encap_offset )
00159 *encap_offset = offset;
00160
00161 tag = DHCP_ENCAPSULATED ( tag );
00162 remaining = option_len;
00163 offset += DHCP_OPTION_HEADER_LEN;
00164 continue;
00165 }
00166 offset += option_len;
00167 }
00168
00169 return -ENOENT;
00170 }
00171
00172
00173
00174
00175
00176
00177
00178
00179
00180
00181
00182
00183 static int resize_dhcp_option ( struct dhcp_options *options,
00184 int offset, int encap_offset,
00185 size_t old_len, size_t new_len,
00186 int can_realloc ) {
00187 struct dhcp_option *encapsulator;
00188 struct dhcp_option *option;
00189 ssize_t delta = ( new_len - old_len );
00190 size_t new_options_len;
00191 size_t new_encapsulator_len;
00192 void *new_data;
00193 void *source;
00194 void *dest;
00195 void *end;
00196
00197
00198 if ( new_len > DHCP_MAX_LEN ) {
00199 DBGC ( options, "DHCPOPT %p overlength option\n", options );
00200 return -ENOSPC;
00201 }
00202 new_options_len = ( options->len + delta );
00203 if ( new_options_len > options->max_len ) {
00204
00205 if ( can_realloc ) {
00206 new_data = realloc ( options->data, new_options_len );
00207 if ( ! new_data ) {
00208 DBGC ( options, "DHCPOPT %p could not "
00209 "reallocate to %zd bytes\n", options,
00210 new_options_len );
00211 return -ENOMEM;
00212 }
00213 options->data = new_data;
00214 options->max_len = new_options_len;
00215 } else {
00216 DBGC ( options, "DHCPOPT %p out of space\n", options );
00217 return -ENOMEM;
00218 }
00219 }
00220 if ( encap_offset >= 0 ) {
00221 encapsulator = dhcp_option ( options, encap_offset );
00222 new_encapsulator_len = ( encapsulator->len + delta );
00223 if ( new_encapsulator_len > DHCP_MAX_LEN ) {
00224 DBGC ( options, "DHCPOPT %p overlength encapsulator\n",
00225 options );
00226 return -ENOSPC;
00227 }
00228 encapsulator->len = new_encapsulator_len;
00229 }
00230 options->len = new_options_len;
00231
00232
00233 option = dhcp_option ( options, offset );
00234 source = ( ( ( void * ) option ) + old_len );
00235 dest = ( ( ( void * ) option ) + new_len );
00236 end = ( options->data + options->max_len );
00237 memmove ( dest, source, ( end - dest ) );
00238
00239 return 0;
00240 }
00241
00242
00243
00244
00245
00246
00247
00248
00249
00250
00251
00252
00253
00254
00255
00256
00257
00258
00259
00260 static int set_dhcp_option ( struct dhcp_options *options, unsigned int tag,
00261 const void *data, size_t len,
00262 int can_realloc ) {
00263 static const uint8_t empty_encapsulator[] = { DHCP_END };
00264 int offset;
00265 int encap_offset = -1;
00266 int creation_offset;
00267 struct dhcp_option *option;
00268 unsigned int encap_tag = DHCP_ENCAPSULATOR ( tag );
00269 size_t old_len = 0;
00270 size_t new_len = ( len ? ( len + DHCP_OPTION_HEADER_LEN ) : 0 );
00271 int rc;
00272
00273
00274 if ( tag == DHCP_PAD )
00275 return -ENOTTY;
00276
00277 creation_offset = find_dhcp_option_with_encap ( options, DHCP_END,
00278 NULL );
00279 if ( creation_offset < 0 )
00280 creation_offset = options->len;
00281
00282 offset = find_dhcp_option_with_encap ( options, tag, &encap_offset );
00283 if ( offset >= 0 ) {
00284 old_len = dhcp_option_len ( dhcp_option ( options, offset ) );
00285 DBGC ( options, "DHCPOPT %p resizing %s from %zd to %zd\n",
00286 options, dhcp_tag_name ( tag ), old_len, new_len );
00287 } else {
00288 DBGC ( options, "DHCPOPT %p creating %s (length %zd)\n",
00289 options, dhcp_tag_name ( tag ), new_len );
00290 }
00291
00292
00293 if ( encap_tag ) {
00294 if ( encap_offset < 0 )
00295 encap_offset = set_dhcp_option ( options, encap_tag,
00296 empty_encapsulator, 1,
00297 can_realloc );
00298 if ( encap_offset < 0 )
00299 return encap_offset;
00300 creation_offset = ( encap_offset + DHCP_OPTION_HEADER_LEN );
00301 }
00302
00303
00304 if ( offset < 0 )
00305 offset = creation_offset;
00306
00307
00308 if ( ( rc = resize_dhcp_option ( options, offset, encap_offset,
00309 old_len, new_len,
00310 can_realloc ) ) != 0 )
00311 return rc;
00312
00313
00314 if ( len ) {
00315 option = dhcp_option ( options, offset );
00316 option->tag = tag;
00317 option->len = len;
00318 memcpy ( &option->data, data, len );
00319 }
00320
00321
00322 if ( encap_offset >= 0 ) {
00323 option = dhcp_option ( options, encap_offset );
00324 if ( option->len <= 1 )
00325 set_dhcp_option ( options, encap_tag, NULL, 0, 0 );
00326 }
00327
00328 return offset;
00329 }
00330
00331
00332
00333
00334
00335
00336
00337
00338
00339
00340 int dhcpopt_store ( struct dhcp_options *options, unsigned int tag,
00341 const void *data, size_t len ) {
00342 int offset;
00343
00344 offset = set_dhcp_option ( options, tag, data, len, 0 );
00345 if ( offset < 0 )
00346 return offset;
00347 return 0;
00348 }
00349
00350
00351
00352
00353
00354
00355
00356
00357
00358
00359 int dhcpopt_extensible_store ( struct dhcp_options *options, unsigned int tag,
00360 const void *data, size_t len ) {
00361 int offset;
00362
00363 offset = set_dhcp_option ( options, tag, data, len, 1 );
00364 if ( offset < 0 )
00365 return offset;
00366 return 0;
00367 }
00368
00369
00370
00371
00372
00373
00374
00375
00376
00377
00378 int dhcpopt_fetch ( struct dhcp_options *options, unsigned int tag,
00379 void *data, size_t len ) {
00380 int offset;
00381 struct dhcp_option *option;
00382 size_t option_len;
00383
00384 offset = find_dhcp_option_with_encap ( options, tag, NULL );
00385 if ( offset < 0 )
00386 return offset;
00387
00388 option = dhcp_option ( options, offset );
00389 option_len = option->len;
00390 if ( len > option_len )
00391 len = option_len;
00392 memcpy ( data, option->data, len );
00393
00394 return option_len;
00395 }
00396
00397
00398
00399
00400
00401
00402
00403
00404
00405 static void dhcpopt_update_len ( struct dhcp_options *options ) {
00406 struct dhcp_option *option;
00407 int offset = 0;
00408 ssize_t remaining = options->max_len;
00409 unsigned int option_len;
00410
00411
00412 options->len = 0;
00413 while ( remaining ) {
00414 option = dhcp_option ( options, offset );
00415 option_len = dhcp_option_len ( option );
00416 remaining -= option_len;
00417 if ( remaining < 0 )
00418 break;
00419 offset += option_len;
00420 if ( option->tag != DHCP_PAD )
00421 options->len = offset;
00422 }
00423 }
00424
00425
00426
00427
00428
00429
00430
00431
00432
00433
00434
00435 void dhcpopt_init ( struct dhcp_options *options, void *data,
00436 size_t max_len ) {
00437
00438
00439 options->data = data;
00440 options->max_len = max_len;
00441
00442
00443 dhcpopt_update_len ( options );
00444
00445 DBGC ( options, "DHCPOPT %p created (data %p len %#zx max_len %#zx)\n",
00446 options, options->data, options->len, options->max_len );
00447 }