]>
Commit | Line | Data |
---|---|---|
011feef8 PF |
1 | /*** |
2 | This file is part of systemd. | |
3 | ||
4 | Copyright (C) 2013 Intel Corporation. All rights reserved. | |
5 | ||
6 | systemd is free software; you can redistribute it and/or modify it | |
7 | under the terms of the GNU Lesser General Public License as published by | |
8 | the Free Software Foundation; either version 2.1 of the License, or | |
9 | (at your option) any later version. | |
10 | ||
11 | systemd is distributed in the hope that it will be useful, but | |
12 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | Lesser General Public License for more details. | |
15 | ||
16 | You should have received a copy of the GNU Lesser General Public License | |
17 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
18 | ***/ | |
19 | ||
20 | #include <stdlib.h> | |
21 | #include <errno.h> | |
22 | #include <string.h> | |
23 | #include <stdio.h> | |
46a66b79 | 24 | #include <net/ethernet.h> |
011feef8 PF |
25 | |
26 | #include "util.h" | |
27 | #include "list.h" | |
28 | ||
29 | #include "dhcp-protocol.h" | |
46a66b79 | 30 | #include "dhcp-internal.h" |
011feef8 PF |
31 | #include "sd-dhcp-client.h" |
32 | ||
46a66b79 PF |
33 | #define DHCP_CLIENT_MIN_OPTIONS_SIZE 312 |
34 | ||
011feef8 PF |
35 | struct sd_dhcp_client { |
36 | DHCPState state; | |
37 | int index; | |
38 | uint8_t *req_opts; | |
39 | size_t req_opts_size; | |
40 | uint32_t last_addr; | |
46a66b79 PF |
41 | struct ether_addr mac_addr; |
42 | uint32_t xid; | |
011feef8 PF |
43 | }; |
44 | ||
45 | static const uint8_t default_req_opts[] = { | |
46 | DHCP_OPTION_SUBNET_MASK, | |
47 | DHCP_OPTION_ROUTER, | |
48 | DHCP_OPTION_HOST_NAME, | |
49 | DHCP_OPTION_DOMAIN_NAME, | |
50 | DHCP_OPTION_DOMAIN_NAME_SERVER, | |
51 | DHCP_OPTION_NTP_SERVER, | |
52 | }; | |
53 | ||
54 | int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option) | |
55 | { | |
56 | size_t i; | |
57 | ||
58 | assert_return(client, -EINVAL); | |
59 | assert_return (client->state == DHCP_STATE_INIT, -EBUSY); | |
60 | ||
61 | switch(option) { | |
62 | case DHCP_OPTION_PAD: | |
63 | case DHCP_OPTION_OVERLOAD: | |
64 | case DHCP_OPTION_MESSAGE_TYPE: | |
65 | case DHCP_OPTION_PARAMETER_REQUEST_LIST: | |
66 | case DHCP_OPTION_END: | |
67 | return -EINVAL; | |
68 | ||
69 | default: | |
70 | break; | |
71 | } | |
72 | ||
73 | for (i = 0; i < client->req_opts_size; i++) | |
74 | if (client->req_opts[i] == option) | |
75 | return -EEXIST; | |
76 | ||
77 | if (!GREEDY_REALLOC(client->req_opts, client->req_opts_size, | |
78 | client->req_opts_size + 1)) | |
79 | return -ENOMEM; | |
80 | ||
81 | client->req_opts[client->req_opts_size - 1] = option; | |
82 | ||
83 | return 0; | |
84 | } | |
85 | ||
86 | int sd_dhcp_client_set_request_address(sd_dhcp_client *client, | |
87 | const struct in_addr *last_addr) | |
88 | { | |
89 | assert_return(client, -EINVAL); | |
90 | assert_return(client->state == DHCP_STATE_INIT, -EBUSY); | |
91 | ||
92 | if (last_addr) | |
93 | client->last_addr = last_addr->s_addr; | |
94 | else | |
95 | client->last_addr = INADDR_ANY; | |
96 | ||
97 | return 0; | |
98 | } | |
99 | ||
100 | int sd_dhcp_client_set_index(sd_dhcp_client *client, int interface_index) | |
101 | { | |
102 | assert_return(client, -EINVAL); | |
103 | assert_return(client->state == DHCP_STATE_INIT, -EBUSY); | |
104 | assert_return(interface_index >= -1, -EINVAL); | |
105 | ||
106 | client->index = interface_index; | |
107 | ||
108 | return 0; | |
109 | } | |
110 | ||
46a66b79 PF |
111 | int sd_dhcp_client_set_mac(sd_dhcp_client *client, |
112 | const struct ether_addr *addr) | |
113 | { | |
114 | assert_return(client, -EINVAL); | |
115 | assert_return(client->state == DHCP_STATE_INIT, -EBUSY); | |
116 | ||
117 | memcpy(&client->mac_addr, addr, ETH_ALEN); | |
118 | ||
119 | return 0; | |
120 | } | |
121 | ||
122 | static int client_packet_init(sd_dhcp_client *client, uint8_t type, | |
123 | DHCPMessage *message, uint8_t **opt, | |
124 | size_t *optlen) | |
125 | { | |
126 | int err; | |
127 | ||
128 | *opt = (uint8_t *)(message + 1); | |
129 | ||
130 | if (*optlen < 4) | |
131 | return -ENOBUFS; | |
132 | *optlen -= 4; | |
133 | ||
134 | message->op = BOOTREQUEST; | |
135 | message->htype = 1; | |
136 | message->hlen = ETHER_ADDR_LEN; | |
137 | message->xid = htobe32(client->xid); | |
138 | ||
139 | memcpy(&message->chaddr, &client->mac_addr, ETH_ALEN); | |
140 | (*opt)[0] = 0x63; | |
141 | (*opt)[1] = 0x82; | |
142 | (*opt)[2] = 0x53; | |
143 | (*opt)[3] = 0x63; | |
144 | ||
145 | *opt += 4; | |
146 | ||
147 | err = dhcp_option_append(opt, optlen, DHCP_OPTION_MESSAGE_TYPE, 1, | |
148 | &type); | |
149 | if (err < 0) | |
150 | return err; | |
151 | ||
152 | /* Some DHCP servers will refuse to issue an DHCP lease if the Cliient | |
153 | Identifier option is not set */ | |
154 | err = dhcp_option_append(opt, optlen, DHCP_OPTION_CLIENT_IDENTIFIER, | |
155 | ETH_ALEN, &client->mac_addr); | |
156 | if (err < 0) | |
157 | return err; | |
158 | ||
159 | if (type == DHCP_DISCOVER || type == DHCP_REQUEST) { | |
160 | err = dhcp_option_append(opt, optlen, | |
161 | DHCP_OPTION_PARAMETER_REQUEST_LIST, | |
162 | client->req_opts_size, | |
163 | client->req_opts); | |
164 | if (err < 0) | |
165 | return err; | |
166 | } | |
167 | ||
168 | return 0; | |
169 | } | |
170 | ||
171 | static uint16_t client_checksum(void *buf, int len) | |
172 | { | |
173 | uint32_t sum; | |
174 | uint16_t *check; | |
175 | int i; | |
176 | uint8_t *odd; | |
177 | ||
178 | sum = 0; | |
179 | check = buf; | |
180 | ||
181 | for (i = 0; i < len / 2 ; i++) | |
182 | sum += check[i]; | |
183 | ||
184 | if (len & 0x01) { | |
185 | odd = buf; | |
186 | sum += odd[len]; | |
187 | } | |
188 | ||
189 | return ~((sum & 0xffff) + (sum >> 16)); | |
190 | } | |
191 | ||
192 | static int client_send_discover(sd_dhcp_client *client) | |
193 | { | |
194 | int err = 0; | |
195 | _cleanup_free_ DHCPPacket *discover; | |
196 | size_t optlen, len; | |
197 | uint8_t *opt; | |
198 | ||
199 | optlen = DHCP_CLIENT_MIN_OPTIONS_SIZE; | |
200 | len = sizeof(DHCPPacket) + optlen; | |
201 | ||
202 | discover = malloc0(len); | |
203 | ||
204 | if (!discover) | |
205 | return -ENOMEM; | |
206 | ||
207 | err = client_packet_init(client, DHCP_DISCOVER, &discover->dhcp, | |
208 | &opt, &optlen); | |
209 | if (err < 0) | |
210 | return err; | |
211 | ||
212 | if (client->last_addr != INADDR_ANY) { | |
213 | err = dhcp_option_append(&opt, &optlen, | |
214 | DHCP_OPTION_REQUESTED_IP_ADDRESS, | |
215 | 4, &client->last_addr); | |
216 | if (err < 0) | |
217 | return err; | |
218 | } | |
219 | ||
220 | err = dhcp_option_append(&opt, &optlen, DHCP_OPTION_END, 0, NULL); | |
221 | if (err < 0) | |
222 | return err; | |
223 | ||
224 | discover->ip.version = IPVERSION; | |
225 | discover->ip.ihl = sizeof(discover->ip) >> 2; | |
226 | discover->ip.tot_len = htobe16(len); | |
227 | ||
228 | discover->ip.protocol = IPPROTO_UDP; | |
229 | discover->ip.saddr = INADDR_ANY; | |
230 | discover->ip.daddr = INADDR_BROADCAST; | |
231 | ||
232 | discover->udp.source = htobe16(DHCP_PORT_CLIENT); | |
233 | discover->udp.dest = htobe16(DHCP_PORT_SERVER); | |
234 | discover->udp.len = htobe16(len - sizeof(discover->ip)); | |
235 | ||
236 | discover->ip.check = discover->udp.len; | |
237 | discover->udp.check = client_checksum(&discover->ip.ttl, | |
238 | len - 8); | |
239 | ||
240 | discover->ip.ttl = IPDEFTTL; | |
241 | discover->ip.check = 0; | |
242 | discover->ip.check = client_checksum(&discover->ip, | |
243 | sizeof(discover->ip)); | |
244 | ||
245 | err = dhcp_network_send_raw_packet(client->index, discover, len); | |
246 | ||
247 | return 0; | |
248 | } | |
249 | ||
250 | int sd_dhcp_client_start(sd_dhcp_client *client) | |
251 | { | |
252 | assert_return(client, -EINVAL); | |
253 | assert_return(client->index >= 0, -EINVAL); | |
254 | assert_return(client->state == DHCP_STATE_INIT || | |
255 | client->state == DHCP_STATE_INIT_REBOOT, -EBUSY); | |
256 | ||
257 | client->xid = random_u(); | |
258 | ||
259 | return client_send_discover(client); | |
260 | } | |
261 | ||
011feef8 PF |
262 | sd_dhcp_client *sd_dhcp_client_new(void) |
263 | { | |
264 | sd_dhcp_client *client; | |
265 | ||
266 | client = new0(sd_dhcp_client, 1); | |
267 | if (!client) | |
268 | return NULL; | |
269 | ||
270 | client->state = DHCP_STATE_INIT; | |
271 | client->index = -1; | |
272 | ||
273 | client->req_opts_size = ELEMENTSOF(default_req_opts); | |
274 | ||
275 | client->req_opts = memdup(default_req_opts, client->req_opts_size); | |
276 | if (!client->req_opts) { | |
277 | free(client); | |
278 | return NULL; | |
279 | } | |
280 | ||
281 | return client; | |
282 | } |