1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright (C) 2013 Intel Corporation. All rights reserved.
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
27 #include "dhcp-internal.h"
29 static int option_append(uint8_t options[], size_t size, size_t *offset,
30 uint8_t code, size_t optlen, const void *optval) {
34 if (code != DHCP_OPTION_END)
35 /* always make sure there is space for an END option */
42 if (size < *offset + 1)
45 options[*offset] = code;
50 if (size < *offset + optlen + 2)
53 options[*offset] = code;
54 options[*offset + 1] = optlen;
59 memcpy(&options[*offset + 2], optval, optlen);
62 *offset += optlen + 2;
70 int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset,
72 uint8_t code, size_t optlen, const void *optval) {
73 size_t file_offset = 0, sname_offset =0;
80 file = overload & DHCP_OVERLOAD_FILE;
81 sname = overload & DHCP_OVERLOAD_SNAME;
84 /* still space in the options array */
85 r = option_append(message->options, size, offset, code, optlen, optval);
88 else if (r == -ENOBUFS && (file || sname)) {
89 /* did not fit, but we have more buffers to try
90 close the options array and move the offset to its end */
91 r = option_append(message->options, size, offset, DHCP_OPTION_END, 0, NULL);
100 if (overload & DHCP_OVERLOAD_FILE) {
101 file_offset = *offset - size;
103 if (file_offset < sizeof(message->file)) {
104 /* still space in the 'file' array */
105 r = option_append(message->file, sizeof(message->file), &file_offset, code, optlen, optval);
107 *offset = size + file_offset;
109 } else if (r == -ENOBUFS && sname) {
110 /* did not fit, but we have more buffers to try
111 close the file array and move the offset to its end */
112 r = option_append(message->options, size, offset, DHCP_OPTION_END, 0, NULL);
116 *offset = size + sizeof(message->file);
122 if (overload & DHCP_OVERLOAD_SNAME) {
123 sname_offset = *offset - size - (file ? sizeof(message->file) : 0);
125 if (sname_offset < sizeof(message->sname)) {
126 /* still space in the 'sname' array */
127 r = option_append(message->sname, sizeof(message->sname), &sname_offset, code, optlen, optval);
129 *offset = size + (file ? sizeof(message->file) : 0) + sname_offset;
132 /* no space, or other error, give up */
141 static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload,
142 uint8_t *message_type, dhcp_option_cb_t cb,
147 while (offset < buflen) {
148 switch (options[offset]) {
149 case DHCP_OPTION_PAD:
154 case DHCP_OPTION_END:
157 case DHCP_OPTION_MESSAGE_TYPE:
158 if (buflen < offset + 3)
161 len = options[++offset];
166 *message_type = options[++offset];
174 case DHCP_OPTION_OVERLOAD:
175 if (buflen < offset + 3)
178 len = options[++offset];
183 *overload = options[++offset];
192 if (buflen < offset + 3)
195 code = options[offset];
196 len = options[++offset];
198 if (buflen < ++offset + len)
202 cb(code, len, &options[offset], user_data);
216 int dhcp_option_parse(DHCPMessage *message, size_t len,
217 dhcp_option_cb_t cb, void *user_data) {
218 uint8_t overload = 0;
219 uint8_t message_type = 0;
225 if (len < sizeof(DHCPMessage))
228 len -= sizeof(DHCPMessage);
230 r = parse_options(message->options, len, &overload, &message_type,
235 if (overload & DHCP_OVERLOAD_FILE) {
236 r = parse_options(message->file, sizeof(message->file),
237 NULL, &message_type, cb, user_data);
242 if (overload & DHCP_OVERLOAD_SNAME) {
243 r = parse_options(message->sname, sizeof(message->sname),
244 NULL, &message_type, cb, user_data);