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