]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | /*** | |
3 | Copyright © 2013 Intel Corporation. All rights reserved. | |
4 | ***/ | |
5 | ||
6 | #include <net/if_arp.h> | |
7 | ||
8 | #include "sd-dhcp-server.h" | |
9 | #include "sd-event.h" | |
10 | ||
11 | #include "dhcp-server-internal.h" | |
12 | #include "hashmap.h" | |
13 | #include "siphash24.h" | |
14 | #include "tests.h" | |
15 | ||
16 | static void test_pool(struct in_addr *address, unsigned size, int ret) { | |
17 | _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; | |
18 | ||
19 | ASSERT_OK(sd_dhcp_server_new(&server, 1)); | |
20 | ||
21 | if (ret >= 0) | |
22 | ASSERT_RETURN_IS_CRITICAL(true, ASSERT_OK_EQ(sd_dhcp_server_configure_pool(server, address, 8, 0, size), ret)); | |
23 | else | |
24 | ASSERT_RETURN_IS_CRITICAL(false, ASSERT_ERROR(sd_dhcp_server_configure_pool(server, address, 8, 0, size), -ret)); | |
25 | } | |
26 | ||
27 | static int test_basic(bool bind_to_interface) { | |
28 | _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; | |
29 | _cleanup_(sd_event_unrefp) sd_event *event = NULL; | |
30 | struct in_addr address_lo = { | |
31 | .s_addr = htobe32(INADDR_LOOPBACK), | |
32 | }; | |
33 | struct in_addr address_any = { | |
34 | .s_addr = htobe32(INADDR_ANY), | |
35 | }; | |
36 | int r; | |
37 | ||
38 | log_debug("/* %s(bind_to_interface=%s) */", __func__, yes_no(bind_to_interface)); | |
39 | ||
40 | ASSERT_OK(sd_event_new(&event)); | |
41 | ||
42 | /* attach to loopback interface */ | |
43 | ASSERT_OK(sd_dhcp_server_new(&server, 1)); | |
44 | ASSERT_NOT_NULL(server); | |
45 | server->bind_to_interface = bind_to_interface; | |
46 | ||
47 | ASSERT_OK(sd_dhcp_server_attach_event(server, event, 0)); | |
48 | ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_attach_event(server, event, 0), EBUSY)); | |
49 | ASSERT_TRUE(sd_dhcp_server_get_event(server) == event); /* ASSERT_EQ() doesn't work here. */ | |
50 | ASSERT_OK(sd_dhcp_server_detach_event(server)); | |
51 | ASSERT_NULL(sd_dhcp_server_get_event(server)); | |
52 | ASSERT_OK(sd_dhcp_server_attach_event(server, NULL, 0)); | |
53 | ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_attach_event(server, NULL, 0), EBUSY)); | |
54 | ||
55 | ASSERT_TRUE(sd_dhcp_server_ref(server) == server); | |
56 | ASSERT_NULL(sd_dhcp_server_unref(server)); | |
57 | ||
58 | ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_start(server), EUNATCH)); | |
59 | ||
60 | ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_configure_pool(server, &address_any, 28, 0, 0), EINVAL)); | |
61 | ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_configure_pool(server, &address_lo, 38, 0, 0), ERANGE)); | |
62 | ASSERT_OK(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0)); | |
63 | ASSERT_OK(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0)); | |
64 | ||
65 | test_pool(&address_any, 1, -EINVAL); | |
66 | test_pool(&address_lo, 1, 0); | |
67 | ||
68 | r = sd_dhcp_server_start(server); | |
69 | /* skip test if running in an environment with no full networking support, CONFIG_PACKET not | |
70 | * compiled in kernel, nor af_packet module available. */ | |
71 | if (r == -EPERM || r == -EAFNOSUPPORT) | |
72 | return r; | |
73 | ASSERT_OK(r); | |
74 | ||
75 | ASSERT_OK(sd_dhcp_server_start(server)); | |
76 | ASSERT_OK(sd_dhcp_server_stop(server)); | |
77 | ASSERT_OK(sd_dhcp_server_stop(server)); | |
78 | ASSERT_OK(sd_dhcp_server_start(server)); | |
79 | ||
80 | return 0; | |
81 | } | |
82 | ||
83 | static void test_message_handler(void) { | |
84 | _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; | |
85 | struct { | |
86 | struct { | |
87 | DHCP_MESSAGE_HEADER_DEFINITION; | |
88 | } _packed_ message; | |
89 | struct { | |
90 | uint8_t code; | |
91 | uint8_t length; | |
92 | uint8_t type; | |
93 | } _packed_ option_type; | |
94 | struct { | |
95 | uint8_t code; | |
96 | uint8_t length; | |
97 | be32_t address; | |
98 | } _packed_ option_requested_ip; | |
99 | struct { | |
100 | uint8_t code; | |
101 | uint8_t length; | |
102 | be32_t address; | |
103 | } _packed_ option_server_id; | |
104 | struct { | |
105 | uint8_t code; | |
106 | uint8_t length; | |
107 | uint8_t id[7]; | |
108 | } _packed_ option_client_id; | |
109 | struct { | |
110 | uint8_t code; | |
111 | uint8_t length; | |
112 | uint8_t hostname[6]; | |
113 | } _packed_ option_hostname; | |
114 | uint8_t end; | |
115 | } _packed_ test = { | |
116 | .message.op = BOOTREQUEST, | |
117 | .message.htype = ARPHRD_ETHER, | |
118 | .message.hlen = ETHER_ADDR_LEN, | |
119 | .message.xid = htobe32(0x12345678), | |
120 | .message.chaddr = { 'A', 'B', 'C', 'D', 'E', 'F' }, | |
121 | .option_type.code = SD_DHCP_OPTION_MESSAGE_TYPE, | |
122 | .option_type.length = 1, | |
123 | .option_type.type = DHCP_DISCOVER, | |
124 | .option_hostname.code = SD_DHCP_OPTION_HOST_NAME, | |
125 | .option_hostname.length = 6, | |
126 | .option_hostname.hostname = { 'T', 'E', 'S', 'T', 'H', 'N' }, | |
127 | .end = SD_DHCP_OPTION_END, | |
128 | }; | |
129 | struct in_addr address_lo = { | |
130 | .s_addr = htobe32(INADDR_LOOPBACK), | |
131 | }; | |
132 | struct in_addr static_lease_address = { | |
133 | .s_addr = htobe32(INADDR_LOOPBACK + 42), | |
134 | }; | |
135 | static uint8_t static_lease_client_id[7] = {0x01, 'A', 'B', 'C', 'D', 'E', 'G' }; | |
136 | int r; | |
137 | ||
138 | log_debug("/* %s */", __func__); | |
139 | ||
140 | ASSERT_OK(sd_dhcp_server_new(&server, 1)); | |
141 | ASSERT_OK(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0)); | |
142 | ASSERT_OK(sd_dhcp_server_set_static_lease(server, &static_lease_address, static_lease_client_id, | |
143 | ELEMENTSOF(static_lease_client_id))); | |
144 | ASSERT_OK(sd_dhcp_server_attach_event(server, NULL, 0)); | |
145 | ASSERT_OK(sd_dhcp_server_start(server)); | |
146 | ||
147 | r = dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL); | |
148 | if (r == -ENETDOWN) | |
149 | return (void) log_tests_skipped("Network is not available"); | |
150 | ASSERT_OK_EQ(r, DHCP_OFFER); | |
151 | ||
152 | test.end = 0; | |
153 | /* TODO, shouldn't this fail? */ | |
154 | ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); | |
155 | test.end = SD_DHCP_OPTION_END; | |
156 | ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); | |
157 | ||
158 | test.option_type.code = 0; | |
159 | test.option_type.length = 0; | |
160 | test.option_type.type = 0; | |
161 | ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), ENOMSG); | |
162 | test.option_type.code = SD_DHCP_OPTION_MESSAGE_TYPE; | |
163 | test.option_type.length = 1; | |
164 | test.option_type.type = DHCP_DISCOVER; | |
165 | ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); | |
166 | ||
167 | test.message.op = 0; | |
168 | ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); | |
169 | test.message.op = BOOTREQUEST; | |
170 | ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); | |
171 | ||
172 | test.message.htype = 0; | |
173 | ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); | |
174 | test.message.htype = ARPHRD_ETHER; | |
175 | ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); | |
176 | ||
177 | test.message.hlen = 0; | |
178 | ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), EBADMSG); | |
179 | test.message.hlen = ETHER_ADDR_LEN; | |
180 | ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); | |
181 | ||
182 | test.option_type.type = DHCP_REQUEST; | |
183 | ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); | |
184 | test.option_requested_ip.code = SD_DHCP_OPTION_REQUESTED_IP_ADDRESS; | |
185 | test.option_requested_ip.length = 4; | |
186 | test.option_requested_ip.address = htobe32(0x12345678); | |
187 | ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_NAK); | |
188 | test.option_server_id.code = SD_DHCP_OPTION_SERVER_IDENTIFIER; | |
189 | test.option_server_id.length = 4; | |
190 | test.option_server_id.address = htobe32(INADDR_LOOPBACK); | |
191 | test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3); | |
192 | ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); | |
193 | ||
194 | test.option_server_id.address = htobe32(0x12345678); | |
195 | test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3); | |
196 | ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); | |
197 | test.option_server_id.address = htobe32(INADDR_LOOPBACK); | |
198 | test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 4); | |
199 | ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); | |
200 | test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3); | |
201 | ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); | |
202 | ||
203 | test.option_client_id.code = SD_DHCP_OPTION_CLIENT_IDENTIFIER; | |
204 | test.option_client_id.length = 7; | |
205 | test.option_client_id.id[0] = 0x01; | |
206 | test.option_client_id.id[1] = 'A'; | |
207 | test.option_client_id.id[2] = 'B'; | |
208 | test.option_client_id.id[3] = 'C'; | |
209 | test.option_client_id.id[4] = 'D'; | |
210 | test.option_client_id.id[5] = 'E'; | |
211 | test.option_client_id.id[6] = 'F'; | |
212 | ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); | |
213 | ||
214 | test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 30); | |
215 | ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); | |
216 | ||
217 | /* request address reserved for static lease (unmatching client ID) */ | |
218 | test.option_client_id.id[6] = 'H'; | |
219 | test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 42); | |
220 | ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); | |
221 | ||
222 | /* request unmatching address */ | |
223 | test.option_client_id.id[6] = 'G'; | |
224 | test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 41); | |
225 | ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); | |
226 | ||
227 | /* request matching address */ | |
228 | test.option_client_id.id[6] = 'G'; | |
229 | test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 42); | |
230 | ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); | |
231 | ||
232 | /* try again */ | |
233 | test.option_client_id.id[6] = 'G'; | |
234 | test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 42); | |
235 | ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); | |
236 | } | |
237 | ||
238 | static uint64_t client_id_hash_helper(sd_dhcp_client_id *id, uint8_t key[HASH_KEY_SIZE]) { | |
239 | struct siphash state; | |
240 | ||
241 | siphash24_init(&state, key); | |
242 | client_id_hash_func(id, &state); | |
243 | ||
244 | return htole64(siphash24_finalize(&state)); | |
245 | } | |
246 | ||
247 | static void test_client_id_hash(void) { | |
248 | sd_dhcp_client_id a = { | |
249 | .size = 4, | |
250 | }, b = { | |
251 | .size = 4, | |
252 | }; | |
253 | uint8_t hash_key[HASH_KEY_SIZE] = { | |
254 | '0', '1', '2', '3', '4', '5', '6', '7', | |
255 | '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', | |
256 | }; | |
257 | ||
258 | log_debug("/* %s */", __func__); | |
259 | ||
260 | memcpy(a.raw, "abcd", 4); | |
261 | memcpy(b.raw, "abcd", 4); | |
262 | ||
263 | ASSERT_EQ(client_id_compare_func(&a, &b), 0); | |
264 | ASSERT_EQ(client_id_hash_helper(&a, hash_key), client_id_hash_helper(&b, hash_key)); | |
265 | a.size = 3; | |
266 | ASSERT_NE(client_id_compare_func(&a, &b), 0); | |
267 | a.size = 4; | |
268 | ASSERT_EQ(client_id_compare_func(&a, &b), 0); | |
269 | ASSERT_EQ(client_id_hash_helper(&a, hash_key), client_id_hash_helper(&b, hash_key)); | |
270 | ||
271 | b.size = 3; | |
272 | ASSERT_NE(client_id_compare_func(&a, &b), 0); | |
273 | b.size = 4; | |
274 | ASSERT_EQ(client_id_compare_func(&a, &b), 0); | |
275 | ASSERT_EQ(client_id_hash_helper(&a, hash_key), client_id_hash_helper(&b, hash_key)); | |
276 | ||
277 | memcpy(b.raw, "abce", 4); | |
278 | ASSERT_NE(client_id_compare_func(&a, &b), 0); | |
279 | } | |
280 | ||
281 | static void test_static_lease(void) { | |
282 | _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; | |
283 | ||
284 | log_debug("/* %s */", __func__); | |
285 | ||
286 | ASSERT_OK(sd_dhcp_server_new(&server, 1)); | |
287 | ||
288 | ASSERT_OK(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x01020304 }, | |
289 | (uint8_t*) &(uint32_t) { 0x01020304 }, sizeof(uint32_t))); | |
290 | /* Duplicated entry. */ | |
291 | ASSERT_ERROR(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x01020304 }, | |
292 | (uint8_t*) &(uint32_t) { 0x01020304 }, sizeof(uint32_t)), EEXIST); | |
293 | /* Address is conflicted. */ | |
294 | ASSERT_ERROR(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x01020304 }, | |
295 | (uint8_t*) &(uint32_t) { 0x01020305 }, sizeof(uint32_t)), EEXIST); | |
296 | /* Client ID is conflicted. */ | |
297 | ASSERT_ERROR(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x01020305 }, | |
298 | (uint8_t*) &(uint32_t) { 0x01020304 }, sizeof(uint32_t)), EEXIST); | |
299 | ||
300 | ASSERT_OK(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x01020305 }, | |
301 | (uint8_t*) &(uint32_t) { 0x01020305 }, sizeof(uint32_t))); | |
302 | /* Remove the previous entry. */ | |
303 | ASSERT_OK(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x00000000 }, | |
304 | (uint8_t*) &(uint32_t) { 0x01020305 }, sizeof(uint32_t))); | |
305 | /* Then, set a different address. */ | |
306 | ASSERT_OK(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x01020306 }, | |
307 | (uint8_t*) &(uint32_t) { 0x01020305 }, sizeof(uint32_t))); | |
308 | /* Remove again. */ | |
309 | ASSERT_OK(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x00000000 }, | |
310 | (uint8_t*) &(uint32_t) { 0x01020305 }, sizeof(uint32_t))); | |
311 | /* Try to remove non-existent entry. */ | |
312 | ASSERT_OK(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x00000000 }, | |
313 | (uint8_t*) &(uint32_t) { 0x01020305 }, sizeof(uint32_t))); | |
314 | /* Try to remove non-existent entry. */ | |
315 | ASSERT_OK(sd_dhcp_server_set_static_lease(server, &(struct in_addr) { .s_addr = 0x00000000 }, | |
316 | (uint8_t*) &(uint32_t) { 0x01020306 }, sizeof(uint32_t))); | |
317 | } | |
318 | ||
319 | int main(int argc, char *argv[]) { | |
320 | int r; | |
321 | ||
322 | test_setup_logging(LOG_DEBUG); | |
323 | ||
324 | test_client_id_hash(); | |
325 | test_static_lease(); | |
326 | ||
327 | r = test_basic(true); | |
328 | if (r < 0) | |
329 | return log_tests_skipped_errno(r, "cannot start dhcp server(bound to interface)"); | |
330 | ||
331 | r = test_basic(false); | |
332 | if (r < 0) | |
333 | return log_tests_skipped_errno(r, "cannot start dhcp server(non-bound to interface)"); | |
334 | ||
335 | test_message_handler(); | |
336 | ||
337 | return 0; | |
338 | } |