]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/network/networkd-dhcp-server.c
Merge pull request #29224 from keszybz/netdev-config-parsing
[thirdparty/systemd.git] / src / network / networkd-dhcp-server.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <netinet/in.h>
4 #include <linux/if_arp.h>
5 #include <linux/if.h>
6
7 #include "sd-dhcp-server.h"
8
9 #include "fd-util.h"
10 #include "fileio.h"
11 #include "networkd-address.h"
12 #include "networkd-dhcp-server-bus.h"
13 #include "networkd-dhcp-server-static-lease.h"
14 #include "networkd-dhcp-server.h"
15 #include "networkd-link.h"
16 #include "networkd-manager.h"
17 #include "networkd-network.h"
18 #include "networkd-queue.h"
19 #include "networkd-route-util.h"
20 #include "parse-util.h"
21 #include "socket-netlink.h"
22 #include "string-table.h"
23 #include "string-util.h"
24 #include "strv.h"
25
26 static bool link_dhcp4_server_enabled(Link *link) {
27 assert(link);
28
29 if (link->flags & IFF_LOOPBACK)
30 return false;
31
32 if (!link->network)
33 return false;
34
35 if (link->iftype == ARPHRD_CAN)
36 return false;
37
38 return link->network->dhcp_server;
39 }
40
41 int network_adjust_dhcp_server(Network *network, Set **addresses) {
42 int r;
43
44 assert(network);
45 assert(addresses);
46
47 if (!network->dhcp_server)
48 return 0;
49
50 if (network->bond) {
51 log_warning("%s: DHCPServer= is enabled for bond slave. Disabling DHCP server.",
52 network->filename);
53 network->dhcp_server = false;
54 return 0;
55 }
56
57 assert(network->dhcp_server_address_prefixlen <= 32);
58
59 if (network->dhcp_server_address_prefixlen == 0) {
60 Address *address;
61
62 /* If the server address is not specified, then find suitable static address. */
63
64 ORDERED_HASHMAP_FOREACH(address, network->addresses_by_section) {
65 assert(!section_is_invalid(address->section));
66
67 if (address->family != AF_INET)
68 continue;
69
70 if (in4_addr_is_localhost(&address->in_addr.in))
71 continue;
72
73 if (in4_addr_is_link_local(&address->in_addr.in))
74 continue;
75
76 if (in4_addr_is_set(&address->in_addr_peer.in))
77 continue;
78
79 /* TODO: check if the prefix length is small enough for the pool. */
80
81 network->dhcp_server_address = address;
82 break;
83 }
84 if (!network->dhcp_server_address) {
85 log_warning("%s: DHCPServer= is enabled, but no suitable static address configured. "
86 "Disabling DHCP server.",
87 network->filename);
88 network->dhcp_server = false;
89 return 0;
90 }
91
92 } else {
93 _cleanup_(address_freep) Address *a = NULL;
94 Address *existing;
95 unsigned line;
96
97 /* TODO: check if the prefix length is small enough for the pool. */
98
99 /* If an address is explicitly specified, then check if the corresponding [Address] section
100 * is configured, and add one if not. */
101
102 existing = set_get(*addresses,
103 &(Address) {
104 .family = AF_INET,
105 .in_addr.in = network->dhcp_server_address_in_addr,
106 .prefixlen = network->dhcp_server_address_prefixlen,
107 });
108 if (existing) {
109 /* Corresponding [Address] section already exists. */
110 network->dhcp_server_address = existing;
111 return 0;
112 }
113
114 r = ordered_hashmap_by_section_find_unused_line(network->addresses_by_section, network->filename, &line);
115 if (r < 0)
116 return log_warning_errno(r, "%s: Failed to find unused line number for DHCP server address: %m",
117 network->filename);
118
119 r = address_new_static(network, network->filename, line, &a);
120 if (r < 0)
121 return log_warning_errno(r, "%s: Failed to add new static address object for DHCP server: %m",
122 network->filename);
123
124 a->family = AF_INET;
125 a->prefixlen = network->dhcp_server_address_prefixlen;
126 a->in_addr.in = network->dhcp_server_address_in_addr;
127 a->requested_as_null = !in4_addr_is_set(&network->dhcp_server_address_in_addr);
128
129 r = address_section_verify(a);
130 if (r < 0)
131 return r;
132
133 r = set_ensure_put(addresses, &address_hash_ops, a);
134 if (r < 0)
135 return log_oom();
136 assert(r > 0);
137
138 network->dhcp_server_address = TAKE_PTR(a);
139 }
140
141 return 0;
142 }
143
144 static int dhcp_server_find_uplink(Link *link, Link **ret) {
145 assert(link);
146
147 if (link->network->dhcp_server_uplink_name)
148 return link_get_by_name(link->manager, link->network->dhcp_server_uplink_name, ret);
149
150 if (link->network->dhcp_server_uplink_index > 0)
151 return link_get_by_index(link->manager, link->network->dhcp_server_uplink_index, ret);
152
153 if (link->network->dhcp_server_uplink_index == UPLINK_INDEX_AUTO) {
154 /* It is not necessary to propagate error in automatic selection. */
155 if (manager_find_uplink(link->manager, AF_INET, link, ret) < 0)
156 *ret = NULL;
157 return 0;
158 }
159
160 *ret = NULL;
161 return 0;
162 }
163
164 static int link_push_uplink_to_dhcp_server(
165 Link *link,
166 sd_dhcp_lease_server_type_t what,
167 sd_dhcp_server *s) {
168
169 _cleanup_free_ struct in_addr *addresses = NULL;
170 bool use_dhcp_lease_data = true;
171 size_t n_addresses = 0;
172
173 assert(link);
174
175 if (!link->network)
176 return 0;
177 assert(link->network);
178
179 log_link_debug(link, "Copying %s from link", dhcp_lease_server_type_to_string(what));
180
181 switch (what) {
182
183 case SD_DHCP_LEASE_DNS:
184 /* For DNS we have a special case. We the data configured explicitly locally along with the
185 * data from the DHCP lease. */
186
187 for (unsigned i = 0; i < link->network->n_dns; i++) {
188 struct in_addr ia;
189
190 /* Only look for IPv4 addresses */
191 if (link->network->dns[i]->family != AF_INET)
192 continue;
193
194 ia = link->network->dns[i]->address.in;
195
196 /* Never propagate obviously borked data */
197 if (in4_addr_is_null(&ia) || in4_addr_is_localhost(&ia))
198 continue;
199
200 if (!GREEDY_REALLOC(addresses, n_addresses + 1))
201 return log_oom();
202
203 addresses[n_addresses++] = ia;
204 }
205
206 use_dhcp_lease_data = link->network->dhcp_use_dns;
207 break;
208
209 case SD_DHCP_LEASE_NTP: {
210 /* For NTP things are similar, but for NTP hostnames can be configured too, which we cannot
211 * propagate via DHCP. Hence let's only propagate those which are IP addresses. */
212
213 STRV_FOREACH(i, link->network->ntp) {
214 union in_addr_union ia;
215
216 if (in_addr_from_string(AF_INET, *i, &ia) < 0)
217 continue;
218
219 /* Never propagate obviously borked data */
220 if (in4_addr_is_null(&ia.in) || in4_addr_is_localhost(&ia.in))
221 continue;
222
223 if (!GREEDY_REALLOC(addresses, n_addresses + 1))
224 return log_oom();
225
226 addresses[n_addresses++] = ia.in;
227 }
228
229 use_dhcp_lease_data = link->network->dhcp_use_ntp;
230 break;
231 }
232
233 case SD_DHCP_LEASE_SIP:
234
235 /* For SIP we don't allow explicit, local configuration, but there's control whether to use the data */
236 use_dhcp_lease_data = link->network->dhcp_use_sip;
237 break;
238
239 case SD_DHCP_LEASE_POP3:
240 case SD_DHCP_LEASE_SMTP:
241 case SD_DHCP_LEASE_LPR:
242 /* For the other server types we currently do not allow local configuration of server data,
243 * since there are typically no local consumers of the data. */
244 break;
245
246 default:
247 assert_not_reached();
248 }
249
250 if (use_dhcp_lease_data && link->dhcp_lease) {
251 const struct in_addr *da;
252
253 int n = sd_dhcp_lease_get_servers(link->dhcp_lease, what, &da);
254 if (n > 0) {
255 if (!GREEDY_REALLOC(addresses, n_addresses + n))
256 return log_oom();
257
258 for (int j = 0; j < n; j++)
259 if (in4_addr_is_non_local(&da[j]))
260 addresses[n_addresses++] = da[j];
261 }
262 }
263
264 if (n_addresses <= 0)
265 return 0;
266
267 return sd_dhcp_server_set_servers(s, what, addresses, n_addresses);
268 }
269
270 static int dhcp4_server_parse_dns_server_string_and_warn(
271 const char *string,
272 struct in_addr **addresses,
273 size_t *n_addresses) {
274
275 for (;;) {
276 _cleanup_free_ char *word = NULL, *server_name = NULL;
277 union in_addr_union address;
278 int family, r, ifindex = 0;
279
280 r = extract_first_word(&string, &word, NULL, 0);
281 if (r < 0)
282 return r;
283 if (r == 0)
284 break;
285
286 r = in_addr_ifindex_name_from_string_auto(word, &family, &address, &ifindex, &server_name);
287 if (r < 0) {
288 log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring: %m", word);
289 continue;
290 }
291
292 /* Only look for IPv4 addresses */
293 if (family != AF_INET)
294 continue;
295
296 /* Never propagate obviously borked data */
297 if (in4_addr_is_null(&address.in) || in4_addr_is_localhost(&address.in))
298 continue;
299
300 if (!GREEDY_REALLOC(*addresses, *n_addresses + 1))
301 return log_oom();
302
303 (*addresses)[(*n_addresses)++] = address.in;
304 }
305
306 return 0;
307 }
308
309 static int dhcp4_server_set_dns_from_resolve_conf(Link *link) {
310 _cleanup_free_ struct in_addr *addresses = NULL;
311 _cleanup_fclose_ FILE *f = NULL;
312 size_t n_addresses = 0;
313 int r;
314
315 f = fopen(PRIVATE_UPLINK_RESOLV_CONF, "re");
316 if (!f) {
317 if (errno == ENOENT)
318 return 0;
319
320 return log_warning_errno(errno, "Failed to open " PRIVATE_UPLINK_RESOLV_CONF ": %m");
321 }
322
323 for (;;) {
324 _cleanup_free_ char *line = NULL;
325 const char *a;
326 char *l;
327
328 r = read_line(f, LONG_LINE_MAX, &line);
329 if (r < 0)
330 return log_error_errno(r, "Failed to read " PRIVATE_UPLINK_RESOLV_CONF ": %m");
331 if (r == 0)
332 break;
333
334 l = strstrip(line);
335 if (IN_SET(*l, '#', ';', 0))
336 continue;
337
338 a = first_word(l, "nameserver");
339 if (!a)
340 continue;
341
342 r = dhcp4_server_parse_dns_server_string_and_warn(a, &addresses, &n_addresses);
343 if (r < 0)
344 log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring.", a);
345 }
346
347 if (n_addresses <= 0)
348 return 0;
349
350 return sd_dhcp_server_set_dns(link->dhcp_server, addresses, n_addresses);
351 }
352
353 static int dhcp4_server_configure(Link *link) {
354 bool acquired_uplink = false;
355 sd_dhcp_option *p;
356 DHCPStaticLease *static_lease;
357 Link *uplink = NULL;
358 Address *address;
359 bool bind_to_interface;
360 int r;
361
362 assert(link);
363 assert(link->network);
364 assert(link->network->dhcp_server_address);
365
366 log_link_debug(link, "Configuring DHCP Server.");
367
368 if (link->dhcp_server)
369 return -EBUSY;
370
371 r = sd_dhcp_server_new(&link->dhcp_server, link->ifindex);
372 if (r < 0)
373 return r;
374
375 r = sd_dhcp_server_attach_event(link->dhcp_server, link->manager->event, 0);
376 if (r < 0)
377 return r;
378
379 r = sd_dhcp_server_set_callback(link->dhcp_server, dhcp_server_callback, link);
380 if (r < 0)
381 return log_link_warning_errno(link, r, "Failed to set callback for DHCPv4 server instance: %m");
382
383 r = address_get(link, link->network->dhcp_server_address, &address);
384 if (r < 0)
385 return log_link_error_errno(link, r, "Failed to find suitable address for DHCPv4 server instance: %m");
386
387 /* use the server address' subnet as the pool */
388 r = sd_dhcp_server_configure_pool(link->dhcp_server, &address->in_addr.in, address->prefixlen,
389 link->network->dhcp_server_pool_offset, link->network->dhcp_server_pool_size);
390 if (r < 0)
391 return log_link_error_errno(link, r, "Failed to configure address pool for DHCPv4 server instance: %m");
392
393 if (link->network->dhcp_server_max_lease_time_usec > 0) {
394 r = sd_dhcp_server_set_max_lease_time(link->dhcp_server, link->network->dhcp_server_max_lease_time_usec);
395 if (r < 0)
396 return log_link_error_errno(link, r, "Failed to set maximum lease time for DHCPv4 server instance: %m");
397 }
398
399 if (link->network->dhcp_server_default_lease_time_usec > 0) {
400 r = sd_dhcp_server_set_default_lease_time(link->dhcp_server, link->network->dhcp_server_default_lease_time_usec);
401 if (r < 0)
402 return log_link_error_errno(link, r, "Failed to set default lease time for DHCPv4 server instance: %m");
403 }
404
405 r = sd_dhcp_server_set_boot_server_address(link->dhcp_server, &link->network->dhcp_server_boot_server_address);
406 if (r < 0)
407 return log_link_warning_errno(link, r, "Failed to set boot server address for DHCPv4 server instance: %m");
408
409 r = sd_dhcp_server_set_boot_server_name(link->dhcp_server, link->network->dhcp_server_boot_server_name);
410 if (r < 0)
411 return log_link_warning_errno(link, r, "Failed to set boot server name for DHCPv4 server instance: %m");
412
413 r = sd_dhcp_server_set_boot_filename(link->dhcp_server, link->network->dhcp_server_boot_filename);
414 if (r < 0)
415 return log_link_warning_errno(link, r, "Failed to set boot filename for DHCPv4 server instance: %m");
416
417 for (sd_dhcp_lease_server_type_t type = 0; type < _SD_DHCP_LEASE_SERVER_TYPE_MAX; type ++) {
418
419 if (!link->network->dhcp_server_emit[type].emit)
420 continue;
421
422 if (link->network->dhcp_server_emit[type].n_addresses > 0)
423 /* Explicitly specified servers to emit */
424 r = sd_dhcp_server_set_servers(
425 link->dhcp_server,
426 type,
427 link->network->dhcp_server_emit[type].addresses,
428 link->network->dhcp_server_emit[type].n_addresses);
429 else {
430 /* Emission is requested, but nothing explicitly configured. Let's find a suitable upling */
431 if (!acquired_uplink) {
432 (void) dhcp_server_find_uplink(link, &uplink);
433 acquired_uplink = true;
434 }
435
436 if (uplink && uplink->network)
437 r = link_push_uplink_to_dhcp_server(uplink, type, link->dhcp_server);
438 else if (type == SD_DHCP_LEASE_DNS)
439 r = dhcp4_server_set_dns_from_resolve_conf(link);
440 else {
441 log_link_debug(link,
442 "Not emitting %s on link, couldn't find suitable uplink.",
443 dhcp_lease_server_type_to_string(type));
444 continue;
445 }
446 }
447
448 if (r < 0)
449 log_link_warning_errno(link, r,
450 "Failed to set %s for DHCP server, ignoring: %m",
451 dhcp_lease_server_type_to_string(type));
452 }
453
454 if (link->network->dhcp_server_emit_router) {
455 r = sd_dhcp_server_set_router(link->dhcp_server, &link->network->dhcp_server_router);
456 if (r < 0)
457 return log_link_error_errno(link, r, "Failed to set router address for DHCP server: %m");
458 }
459
460 r = sd_dhcp_server_set_relay_target(link->dhcp_server, &link->network->dhcp_server_relay_target);
461 if (r < 0)
462 return log_link_error_errno(link, r, "Failed to set relay target for DHCP server: %m");
463
464 bind_to_interface = sd_dhcp_server_is_in_relay_mode(link->dhcp_server) ? false : link->network->dhcp_server_bind_to_interface;
465 r = sd_dhcp_server_set_bind_to_interface(link->dhcp_server, bind_to_interface);
466 if (r < 0)
467 return log_link_error_errno(link, r, "Failed to set interface binding for DHCP server: %m");
468
469 r = sd_dhcp_server_set_relay_agent_information(link->dhcp_server, link->network->dhcp_server_relay_agent_circuit_id, link->network->dhcp_server_relay_agent_remote_id);
470 if (r < 0)
471 return log_link_error_errno(link, r, "Failed to set agent circuit/remote id for DHCP server: %m");
472
473 if (link->network->dhcp_server_emit_timezone) {
474 _cleanup_free_ char *buffer = NULL;
475 const char *tz = NULL;
476
477 if (link->network->dhcp_server_timezone)
478 tz = link->network->dhcp_server_timezone;
479 else {
480 r = get_timezone(&buffer);
481 if (r < 0)
482 log_link_warning_errno(link, r, "Failed to determine timezone, not sending timezone: %m");
483 else
484 tz = buffer;
485 }
486
487 if (tz) {
488 r = sd_dhcp_server_set_timezone(link->dhcp_server, tz);
489 if (r < 0)
490 return log_link_error_errno(link, r, "Failed to set timezone for DHCP server: %m");
491 }
492 }
493
494 ORDERED_HASHMAP_FOREACH(p, link->network->dhcp_server_send_options) {
495 r = sd_dhcp_server_add_option(link->dhcp_server, p);
496 if (r == -EEXIST)
497 continue;
498 if (r < 0)
499 return log_link_error_errno(link, r, "Failed to set DHCPv4 option: %m");
500 }
501
502 ORDERED_HASHMAP_FOREACH(p, link->network->dhcp_server_send_vendor_options) {
503 r = sd_dhcp_server_add_vendor_option(link->dhcp_server, p);
504 if (r == -EEXIST)
505 continue;
506 if (r < 0)
507 return log_link_error_errno(link, r, "Failed to set DHCPv4 option: %m");
508 }
509
510 HASHMAP_FOREACH(static_lease, link->network->dhcp_static_leases_by_section) {
511 r = sd_dhcp_server_set_static_lease(link->dhcp_server, &static_lease->address, static_lease->client_id, static_lease->client_id_size);
512 if (r < 0)
513 return log_link_error_errno(link, r, "Failed to set DHCPv4 static lease for DHCP server: %m");
514 }
515
516 r = sd_dhcp_server_start(link->dhcp_server);
517 if (r < 0)
518 return log_link_error_errno(link, r, "Could not start DHCPv4 server instance: %m");
519
520 log_link_debug(link, "Offering DHCPv4 leases");
521 return 0;
522 }
523
524 static bool dhcp_server_is_ready_to_configure(Link *link) {
525 Link *uplink = NULL;
526 Address *a;
527
528 assert(link);
529 assert(link->network);
530 assert(link->network->dhcp_server_address);
531
532 if (!link_is_ready_to_configure(link, /* allow_unmanaged = */ false))
533 return false;
534
535 if (!link_has_carrier(link))
536 return false;
537
538 if (!link->static_addresses_configured)
539 return false;
540
541 if (address_get(link, link->network->dhcp_server_address, &a) < 0)
542 return false;
543
544 if (!address_is_ready(a))
545 return false;
546
547 if (dhcp_server_find_uplink(link, &uplink) < 0)
548 return false;
549
550 if (uplink && !uplink->network)
551 return false;
552
553 return true;
554 }
555
556 static int dhcp_server_process_request(Request *req, Link *link, void *userdata) {
557 int r;
558
559 assert(link);
560
561 if (!dhcp_server_is_ready_to_configure(link))
562 return 0;
563
564 r = dhcp4_server_configure(link);
565 if (r < 0)
566 return log_link_warning_errno(link, r, "Failed to configure DHCP server: %m");
567
568 return 1;
569 }
570
571 int link_request_dhcp_server(Link *link) {
572 int r;
573
574 assert(link);
575
576 if (!link_dhcp4_server_enabled(link))
577 return 0;
578
579 if (link->dhcp_server)
580 return 0;
581
582 log_link_debug(link, "Requesting DHCP server.");
583 r = link_queue_request(link, REQUEST_TYPE_DHCP_SERVER, dhcp_server_process_request, NULL);
584 if (r < 0)
585 return log_link_warning_errno(link, r, "Failed to request configuration of DHCP server: %m");
586
587 return 0;
588 }
589
590 int config_parse_dhcp_server_relay_agent_suboption(
591 const char *unit,
592 const char *filename,
593 unsigned line,
594 const char *section,
595 unsigned section_line,
596 const char *lvalue,
597 int ltype,
598 const char *rvalue,
599 void *data,
600 void *userdata) {
601
602 char **suboption_value = data;
603 char* p;
604
605 assert(filename);
606 assert(lvalue);
607 assert(rvalue);
608
609 if (isempty(rvalue)) {
610 *suboption_value = mfree(*suboption_value);
611 return 0;
612 }
613
614 p = startswith(rvalue, "string:");
615 if (!p) {
616 log_syntax(unit, LOG_WARNING, filename, line, 0,
617 "Failed to parse %s=%s'. Invalid format, ignoring.", lvalue, rvalue);
618 return 0;
619 }
620 return free_and_strdup(suboption_value, empty_to_null(p));
621 }
622
623 int config_parse_dhcp_server_emit(
624 const char *unit,
625 const char *filename,
626 unsigned line,
627 const char *section,
628 unsigned section_line,
629 const char *lvalue,
630 int ltype,
631 const char *rvalue,
632 void *data,
633 void *userdata) {
634
635 NetworkDHCPServerEmitAddress *emit = ASSERT_PTR(data);
636
637 assert(rvalue);
638
639 if (isempty(rvalue)) {
640 emit->addresses = mfree(emit->addresses);
641 emit->n_addresses = 0;
642 return 0;
643 }
644
645 for (const char *p = rvalue;;) {
646 _cleanup_free_ char *w = NULL;
647 union in_addr_union a;
648 int r;
649
650 r = extract_first_word(&p, &w, NULL, 0);
651 if (r == -ENOMEM)
652 return log_oom();
653 if (r < 0) {
654 log_syntax(unit, LOG_WARNING, filename, line, r,
655 "Failed to extract word, ignoring: %s", rvalue);
656 return 0;
657 }
658 if (r == 0)
659 return 0;
660
661 if (streq(w, "_server_address"))
662 a = IN_ADDR_NULL; /* null address will be converted to the server address. */
663 else {
664 r = in_addr_from_string(AF_INET, w, &a);
665 if (r < 0) {
666 log_syntax(unit, LOG_WARNING, filename, line, r,
667 "Failed to parse %s= address '%s', ignoring: %m", lvalue, w);
668 continue;
669 }
670
671 if (in4_addr_is_null(&a.in)) {
672 log_syntax(unit, LOG_WARNING, filename, line, 0,
673 "Found a null address in %s=, ignoring.", lvalue);
674 continue;
675 }
676 }
677
678 if (!GREEDY_REALLOC(emit->addresses, emit->n_addresses + 1))
679 return log_oom();
680
681 emit->addresses[emit->n_addresses++] = a.in;
682 }
683 }
684
685 int config_parse_dhcp_server_address(
686 const char *unit,
687 const char *filename,
688 unsigned line,
689 const char *section,
690 unsigned section_line,
691 const char *lvalue,
692 int ltype,
693 const char *rvalue,
694 void *data,
695 void *userdata) {
696
697 Network *network = ASSERT_PTR(userdata);
698 union in_addr_union a;
699 unsigned char prefixlen;
700 int r;
701
702 assert(filename);
703 assert(lvalue);
704 assert(rvalue);
705
706 if (isempty(rvalue)) {
707 network->dhcp_server_address_in_addr = (struct in_addr) {};
708 network->dhcp_server_address_prefixlen = 0;
709 return 0;
710 }
711
712 r = in_addr_prefix_from_string(rvalue, AF_INET, &a, &prefixlen);
713 if (r < 0) {
714 log_syntax(unit, LOG_WARNING, filename, line, r,
715 "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue);
716 return 0;
717 }
718 if (in4_addr_is_localhost(&a.in) || in4_addr_is_link_local(&a.in)) {
719 log_syntax(unit, LOG_WARNING, filename, line, 0,
720 "DHCP server address cannot be a localhost or link-local address, "
721 "ignoring assignment: %s", rvalue);
722 return 0;
723 }
724
725 network->dhcp_server_address_in_addr = a.in;
726 network->dhcp_server_address_prefixlen = prefixlen;
727 return 0;
728 }