]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/network/networkd-dhcp-server.c
license: LGPL-2.1+ -> LGPL-2.1-or-later
[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.h"
13 #include "networkd-link.h"
14 #include "networkd-manager.h"
15 #include "networkd-network.h"
16 #include "parse-util.h"
17 #include "socket-netlink.h"
18 #include "string-table.h"
19 #include "string-util.h"
20 #include "strv.h"
21
22 static bool link_dhcp4_server_enabled(Link *link) {
23 assert(link);
24
25 if (link->flags & IFF_LOOPBACK)
26 return false;
27
28 if (!link->network)
29 return false;
30
31 if (link->network->bond)
32 return false;
33
34 if (link->iftype == ARPHRD_CAN)
35 return false;
36
37 return link->network->dhcp_server;
38 }
39
40 static Address* link_find_dhcp_server_address(Link *link) {
41 Address *address;
42
43 assert(link);
44 assert(link->network);
45
46 /* The first statically configured address if there is any */
47 ORDERED_HASHMAP_FOREACH(address, link->network->addresses_by_section)
48 if (address->family == AF_INET &&
49 !in_addr_is_null(address->family, &address->in_addr))
50 return address;
51
52 /* If that didn't work, find a suitable address we got from the pool */
53 SET_FOREACH(address, link->pool_addresses)
54 if (address->family == AF_INET)
55 return address;
56
57 return NULL;
58 }
59
60 static int link_push_uplink_to_dhcp_server(
61 Link *link,
62 sd_dhcp_lease_server_type what,
63 sd_dhcp_server *s) {
64
65 _cleanup_free_ struct in_addr *addresses = NULL;
66 size_t n_addresses = 0, n_allocated = 0;
67 bool use_dhcp_lease_data = true;
68
69 assert(link);
70
71 if (!link->network)
72 return 0;
73 assert(link->network);
74
75 log_link_debug(link, "Copying %s from link", dhcp_lease_server_type_to_string(what));
76
77 switch (what) {
78
79 case SD_DHCP_LEASE_DNS:
80 /* For DNS we have a special case. We the data configured explicitly locally along with the
81 * data from the DHCP lease. */
82
83 for (unsigned i = 0; i < link->network->n_dns; i++) {
84 struct in_addr ia;
85
86 /* Only look for IPv4 addresses */
87 if (link->network->dns[i]->family != AF_INET)
88 continue;
89
90 ia = link->network->dns[i]->address.in;
91
92 /* Never propagate obviously borked data */
93 if (in4_addr_is_null(&ia) || in4_addr_is_localhost(&ia))
94 continue;
95
96 if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + 1))
97 return log_oom();
98
99 addresses[n_addresses++] = ia;
100 }
101
102 use_dhcp_lease_data = link->network->dhcp_use_dns;
103 break;
104
105 case SD_DHCP_LEASE_NTP: {
106 char **i;
107
108 /* For NTP things are similar, but for NTP hostnames can be configured too, which we cannot
109 * propagate via DHCP. Hence let's only propagate those which are IP addresses. */
110
111 STRV_FOREACH(i, link->network->ntp) {
112 union in_addr_union ia;
113
114 if (in_addr_from_string(AF_INET, *i, &ia) < 0)
115 continue;
116
117 /* Never propagate obviously borked data */
118 if (in4_addr_is_null(&ia.in) || in4_addr_is_localhost(&ia.in))
119 continue;
120
121 if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + 1))
122 return log_oom();
123
124 addresses[n_addresses++] = ia.in;
125 }
126
127 use_dhcp_lease_data = link->network->dhcp_use_ntp;
128 break;
129 }
130
131 case SD_DHCP_LEASE_SIP:
132
133 /* For SIP we don't allow explicit, local configuration, but there's control whether to use the data */
134 use_dhcp_lease_data = link->network->dhcp_use_sip;
135 break;
136
137 case SD_DHCP_LEASE_POP3:
138 case SD_DHCP_LEASE_SMTP:
139 case SD_DHCP_LEASE_LPR:
140 /* For the other server types we currently do not allow local configuration of server data,
141 * since there are typically no local consumers of the data. */
142 break;
143
144 default:
145 assert_not_reached("Unexpected server type");
146 }
147
148 if (use_dhcp_lease_data && link->dhcp_lease) {
149 const struct in_addr *da;
150
151 int n = sd_dhcp_lease_get_servers(link->dhcp_lease, what, &da);
152 if (n > 0) {
153 if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + n))
154 return log_oom();
155
156 for (int j = 0; j < n; j++)
157 if (in4_addr_is_non_local(&da[j]))
158 addresses[n_addresses++] = da[j];
159 }
160 }
161
162 if (n_addresses <= 0)
163 return 0;
164
165 return sd_dhcp_server_set_servers(s, what, addresses, n_addresses);
166 }
167
168 static int dhcp4_server_parse_dns_server_string_and_warn(Link *l, const char *string, struct in_addr **addresses, size_t *n_allocated, size_t *n_addresses) {
169 for (;;) {
170 _cleanup_free_ char *word = NULL, *server_name = NULL;
171 union in_addr_union address;
172 int family, r, ifindex = 0;
173
174 r = extract_first_word(&string, &word, NULL, 0);
175 if (r < 0)
176 return r;
177 if (r == 0)
178 break;
179
180 r = in_addr_ifindex_name_from_string_auto(word, &family, &address, &ifindex, &server_name);
181 if (r < 0) {
182 log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring: %m", word);
183 continue;
184 }
185
186 /* Only look for IPv4 addresses */
187 if (family != AF_INET)
188 continue;
189
190 /* Never propagate obviously borked data */
191 if (in4_addr_is_null(&address.in) || in4_addr_is_localhost(&address.in))
192 continue;
193
194 if (!GREEDY_REALLOC(*addresses, *n_allocated, *n_addresses + 1))
195 return log_oom();
196
197 (*addresses)[(*n_addresses)++] = address.in;
198 }
199
200 return 0;
201 }
202
203 static int dhcp4_server_set_dns_from_resolve_conf(Link *link) {
204 _cleanup_free_ struct in_addr *addresses = NULL;
205 size_t n_addresses = 0, n_allocated = 0;
206 _cleanup_fclose_ FILE *f = NULL;
207 int n = 0, r;
208
209 f = fopen(PRIVATE_UPLINK_RESOLV_CONF, "re");
210 if (!f) {
211 if (errno == ENOENT)
212 return 0;
213
214 return log_warning_errno(errno, "Failed to open " PRIVATE_UPLINK_RESOLV_CONF ": %m");
215 }
216
217 for (;;) {
218 _cleanup_free_ char *line = NULL;
219 const char *a;
220 char *l;
221
222 r = read_line(f, LONG_LINE_MAX, &line);
223 if (r < 0)
224 return log_error_errno(r, "Failed to read " PRIVATE_UPLINK_RESOLV_CONF ": %m");
225 if (r == 0)
226 break;
227
228 n++;
229
230 l = strstrip(line);
231 if (IN_SET(*l, '#', ';', 0))
232 continue;
233
234 a = first_word(l, "nameserver");
235 if (!a)
236 continue;
237
238 r = dhcp4_server_parse_dns_server_string_and_warn(link, a, &addresses, &n_allocated, &n_addresses);
239 if (r < 0)
240 log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring.", a);
241 }
242
243 if (n_addresses <= 0)
244 return 0;
245
246 return sd_dhcp_server_set_dns(link->dhcp_server, addresses, n_addresses);
247 }
248
249 int dhcp4_server_configure(Link *link) {
250 bool acquired_uplink = false;
251 sd_dhcp_option *p;
252 Link *uplink = NULL;
253 Address *address;
254 int r;
255
256 assert(link);
257
258 if (!link_dhcp4_server_enabled(link))
259 return 0;
260
261 if (!(link->flags & IFF_UP))
262 return 0;
263
264 if (!link->dhcp_server) {
265 r = sd_dhcp_server_new(&link->dhcp_server, link->ifindex);
266 if (r < 0)
267 return r;
268
269 r = sd_dhcp_server_attach_event(link->dhcp_server, link->manager->event, 0);
270 if (r < 0)
271 return r;
272 }
273
274 address = link_find_dhcp_server_address(link);
275 if (!address)
276 return log_link_error_errno(link, SYNTHETIC_ERRNO(EBUSY),
277 "Failed to find suitable address for DHCPv4 server instance.");
278
279 /* use the server address' subnet as the pool */
280 r = sd_dhcp_server_configure_pool(link->dhcp_server, &address->in_addr.in, address->prefixlen,
281 link->network->dhcp_server_pool_offset, link->network->dhcp_server_pool_size);
282 if (r < 0)
283 return log_link_error_errno(link, r, "Failed to configure address pool for DHCPv4 server instance: %m");
284
285 /* TODO:
286 r = sd_dhcp_server_set_router(link->dhcp_server, &main_address->in_addr.in);
287 if (r < 0)
288 return r;
289 */
290
291 if (link->network->dhcp_server_max_lease_time_usec > 0) {
292 r = sd_dhcp_server_set_max_lease_time(link->dhcp_server,
293 DIV_ROUND_UP(link->network->dhcp_server_max_lease_time_usec, USEC_PER_SEC));
294 if (r < 0)
295 return log_link_error_errno(link, r, "Failed to set maximum lease time for DHCPv4 server instance: %m");
296 }
297
298 if (link->network->dhcp_server_default_lease_time_usec > 0) {
299 r = sd_dhcp_server_set_default_lease_time(link->dhcp_server,
300 DIV_ROUND_UP(link->network->dhcp_server_default_lease_time_usec, USEC_PER_SEC));
301 if (r < 0)
302 return log_link_error_errno(link, r, "Failed to set default lease time for DHCPv4 server instance: %m");
303 }
304
305 for (sd_dhcp_lease_server_type type = 0; type < _SD_DHCP_LEASE_SERVER_TYPE_MAX; type ++) {
306
307 if (!link->network->dhcp_server_emit[type].emit)
308 continue;
309
310 if (link->network->dhcp_server_emit[type].n_addresses > 0)
311 /* Explicitly specified servers to emit */
312 r = sd_dhcp_server_set_servers(
313 link->dhcp_server,
314 type,
315 link->network->dhcp_server_emit[type].addresses,
316 link->network->dhcp_server_emit[type].n_addresses);
317 else {
318 /* Emission is requested, but nothing explicitly configured. Let's find a suitable upling */
319 if (!acquired_uplink) {
320 uplink = manager_find_uplink(link->manager, link);
321 acquired_uplink = true;
322 }
323
324 if (uplink && uplink->network)
325 r = link_push_uplink_to_dhcp_server(uplink, type, link->dhcp_server);
326 else if (type == SD_DHCP_LEASE_DNS)
327 r = dhcp4_server_set_dns_from_resolve_conf(link);
328 else {
329 log_link_debug(link,
330 "Not emitting %s on link, couldn't find suitable uplink.",
331 dhcp_lease_server_type_to_string(type));
332 continue;
333 }
334 }
335
336 if (r < 0)
337 log_link_warning_errno(link, r,
338 "Failed to set %s for DHCP server, ignoring: %m",
339 dhcp_lease_server_type_to_string(type));
340 }
341
342 r = sd_dhcp_server_set_emit_router(link->dhcp_server, link->network->dhcp_server_emit_router);
343 if (r < 0)
344 return log_link_error_errno(link, r, "Failed to set router emission for DHCP server: %m");
345
346 if (link->network->dhcp_server_emit_timezone) {
347 _cleanup_free_ char *buffer = NULL;
348 const char *tz;
349
350 if (link->network->dhcp_server_timezone)
351 tz = link->network->dhcp_server_timezone;
352 else {
353 r = get_timezone(&buffer);
354 if (r < 0)
355 return log_link_error_errno(link, r, "Failed to determine timezone: %m");
356
357 tz = buffer;
358 }
359
360 r = sd_dhcp_server_set_timezone(link->dhcp_server, tz);
361 if (r < 0)
362 return log_link_error_errno(link, r, "Failed to set timezone for DHCP server: %m");
363 }
364
365 ORDERED_HASHMAP_FOREACH(p, link->network->dhcp_server_send_options) {
366 r = sd_dhcp_server_add_option(link->dhcp_server, p);
367 if (r == -EEXIST)
368 continue;
369 if (r < 0)
370 return log_link_error_errno(link, r, "Failed to set DHCPv4 option: %m");
371 }
372
373 ORDERED_HASHMAP_FOREACH(p, link->network->dhcp_server_send_vendor_options) {
374 r = sd_dhcp_server_add_vendor_option(link->dhcp_server, p);
375 if (r == -EEXIST)
376 continue;
377 if (r < 0)
378 return log_link_error_errno(link, r, "Failed to set DHCPv4 option: %m");
379 }
380
381 if (!sd_dhcp_server_is_running(link->dhcp_server)) {
382 r = sd_dhcp_server_start(link->dhcp_server);
383 if (r < 0)
384 return log_link_error_errno(link, r, "Could not start DHCPv4 server instance: %m");
385
386 log_link_debug(link, "Offering DHCPv4 leases");
387 }
388
389 return 0;
390 }
391
392 int config_parse_dhcp_server_emit(
393 const char *unit,
394 const char *filename,
395 unsigned line,
396 const char *section,
397 unsigned section_line,
398 const char *lvalue,
399 int ltype,
400 const char *rvalue,
401 void *data,
402 void *userdata) {
403
404 NetworkDHCPServerEmitAddress *emit = data;
405
406 assert(emit);
407 assert(rvalue);
408
409 for (const char *p = rvalue;;) {
410 _cleanup_free_ char *w = NULL;
411 union in_addr_union a;
412 int r;
413
414 r = extract_first_word(&p, &w, NULL, 0);
415 if (r == -ENOMEM)
416 return log_oom();
417 if (r < 0) {
418 log_syntax(unit, LOG_WARNING, filename, line, r,
419 "Failed to extract word, ignoring: %s", rvalue);
420 return 0;
421 }
422 if (r == 0)
423 return 0;
424
425 r = in_addr_from_string(AF_INET, w, &a);
426 if (r < 0) {
427 log_syntax(unit, LOG_WARNING, filename, line, r,
428 "Failed to parse %s= address '%s', ignoring: %m", lvalue, w);
429 continue;
430 }
431
432 struct in_addr *m = reallocarray(emit->addresses, emit->n_addresses + 1, sizeof(struct in_addr));
433 if (!m)
434 return log_oom();
435
436 emit->addresses = m;
437 emit->addresses[emit->n_addresses++] = a.in;
438 }
439 }