]>
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 */ | |
9cd9fc8f | 24 | ORDERED_HASHMAP_FOREACH(address, link->network->addresses_by_section) |
5dc31db7 ZJS |
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 */ | |
aa651e88 | 30 | SET_FOREACH(address, link->pool_addresses) |
5dc31db7 ZJS |
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 | 63 | /* Only look for IPv4 addresses */ |
e77bd3fd | 64 | if (link->network->dns[i]->family != AF_INET) |
2a71d57f | 65 | continue; |
8fcf1d61 | 66 | |
e77bd3fd | 67 | ia = link->network->dns[i]->address.in; |
2a71d57f LP |
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; | |
8fcf1d61 YW |
231 | int r; |
232 | ||
233 | address = link_find_dhcp_server_address(link); | |
234 | if (!address) | |
c00c3b64 YW |
235 | return log_link_error_errno(link, SYNTHETIC_ERRNO(EBUSY), |
236 | "Failed to find suitable address for DHCPv4 server instance."); | |
8fcf1d61 YW |
237 | |
238 | /* use the server address' subnet as the pool */ | |
239 | r = sd_dhcp_server_configure_pool(link->dhcp_server, &address->in_addr.in, address->prefixlen, | |
240 | link->network->dhcp_server_pool_offset, link->network->dhcp_server_pool_size); | |
241 | if (r < 0) | |
c00c3b64 | 242 | return log_link_error_errno(link, r, "Failed to configure address pool for DHCPv4 server instance: %m"); |
8fcf1d61 YW |
243 | |
244 | /* TODO: | |
245 | r = sd_dhcp_server_set_router(link->dhcp_server, &main_address->in_addr.in); | |
246 | if (r < 0) | |
247 | return r; | |
248 | */ | |
249 | ||
250 | if (link->network->dhcp_server_max_lease_time_usec > 0) { | |
251 | r = sd_dhcp_server_set_max_lease_time(link->dhcp_server, | |
252 | DIV_ROUND_UP(link->network->dhcp_server_max_lease_time_usec, USEC_PER_SEC)); | |
253 | if (r < 0) | |
c00c3b64 | 254 | return log_link_error_errno(link, r, "Failed to set maximum lease time for DHCPv4 server instance: %m"); |
8fcf1d61 YW |
255 | } |
256 | ||
257 | if (link->network->dhcp_server_default_lease_time_usec > 0) { | |
258 | r = sd_dhcp_server_set_default_lease_time(link->dhcp_server, | |
259 | DIV_ROUND_UP(link->network->dhcp_server_default_lease_time_usec, USEC_PER_SEC)); | |
260 | if (r < 0) | |
c00c3b64 | 261 | return log_link_error_errno(link, r, "Failed to set default lease time for DHCPv4 server instance: %m"); |
8fcf1d61 YW |
262 | } |
263 | ||
2a71d57f LP |
264 | for (sd_dhcp_lease_server_type type = 0; type < _SD_DHCP_LEASE_SERVER_TYPE_MAX; type ++) { |
265 | ||
266 | if (!link->network->dhcp_server_emit[type].emit) | |
267 | continue; | |
268 | ||
269 | if (link->network->dhcp_server_emit[type].n_addresses > 0) | |
270 | /* Explicitly specified servers to emit */ | |
271 | r = sd_dhcp_server_set_servers( | |
272 | link->dhcp_server, | |
273 | type, | |
274 | link->network->dhcp_server_emit[type].addresses, | |
275 | link->network->dhcp_server_emit[type].n_addresses); | |
276 | else { | |
277 | /* Emission is requested, but nothing explicitly configured. Let's find a suitable upling */ | |
278 | if (!acquired_uplink) { | |
279 | uplink = manager_find_uplink(link->manager, link); | |
280 | acquired_uplink = true; | |
281 | } | |
282 | ||
283 | if (uplink && uplink->network) | |
284 | r = link_push_uplink_to_dhcp_server(uplink, type, link->dhcp_server); | |
285 | else if (type == SD_DHCP_LEASE_DNS) | |
286 | r = dhcp4_server_set_dns_from_resolve_conf(link); | |
24e6f458 | 287 | else { |
2a71d57f LP |
288 | log_link_debug(link, |
289 | "Not emitting %s on link, couldn't find suitable uplink.", | |
290 | dhcp_lease_server_type_to_string(type)); | |
291 | continue; | |
24e6f458 | 292 | } |
299d578f | 293 | } |
284e8fd0 | 294 | |
2a71d57f LP |
295 | if (r < 0) |
296 | log_link_warning_errno(link, r, | |
297 | "Failed to set %s for DHCP server, ignoring: %m", | |
298 | dhcp_lease_server_type_to_string(type)); | |
299 | } | |
300 | ||
8fcf1d61 YW |
301 | r = sd_dhcp_server_set_emit_router(link->dhcp_server, link->network->dhcp_server_emit_router); |
302 | if (r < 0) | |
a0fa3ef7 | 303 | return log_link_error_errno(link, r, "Failed to set router emission for DHCP server: %m"); |
8fcf1d61 YW |
304 | |
305 | if (link->network->dhcp_server_emit_timezone) { | |
306 | _cleanup_free_ char *buffer = NULL; | |
307 | const char *tz; | |
308 | ||
309 | if (link->network->dhcp_server_timezone) | |
310 | tz = link->network->dhcp_server_timezone; | |
311 | else { | |
312 | r = get_timezone(&buffer); | |
313 | if (r < 0) | |
98b02994 | 314 | return log_link_error_errno(link, r, "Failed to determine timezone: %m"); |
8fcf1d61 YW |
315 | |
316 | tz = buffer; | |
317 | } | |
318 | ||
319 | r = sd_dhcp_server_set_timezone(link->dhcp_server, tz); | |
320 | if (r < 0) | |
c00c3b64 | 321 | return log_link_error_errno(link, r, "Failed to set timezone for DHCP server: %m"); |
8fcf1d61 | 322 | } |
564ca984 | 323 | |
90e74a66 | 324 | ORDERED_HASHMAP_FOREACH(p, link->network->dhcp_server_send_options) { |
461dbb2f | 325 | r = sd_dhcp_server_add_option(link->dhcp_server, p); |
564ca984 SS |
326 | if (r == -EEXIST) |
327 | continue; | |
328 | if (r < 0) | |
c00c3b64 | 329 | return log_link_error_errno(link, r, "Failed to set DHCPv4 option: %m"); |
564ca984 SS |
330 | } |
331 | ||
90e74a66 | 332 | ORDERED_HASHMAP_FOREACH(p, link->network->dhcp_server_send_vendor_options) { |
7354900d DW |
333 | r = sd_dhcp_server_add_vendor_option(link->dhcp_server, p); |
334 | if (r == -EEXIST) | |
335 | continue; | |
336 | if (r < 0) | |
337 | return log_link_error_errno(link, r, "Failed to set DHCPv4 option: %m"); | |
338 | } | |
339 | ||
8fcf1d61 YW |
340 | if (!sd_dhcp_server_is_running(link->dhcp_server)) { |
341 | r = sd_dhcp_server_start(link->dhcp_server); | |
342 | if (r < 0) | |
a0fa3ef7 | 343 | return log_link_error_errno(link, r, "Could not start DHCPv4 server instance: %m"); |
8fcf1d61 YW |
344 | } |
345 | ||
346 | return 0; | |
347 | } | |
348 | ||
2a71d57f | 349 | int config_parse_dhcp_server_emit( |
8fcf1d61 YW |
350 | const char *unit, |
351 | const char *filename, | |
352 | unsigned line, | |
2a71d57f LP |
353 | const char *section, |
354 | unsigned section_line, | |
8fcf1d61 | 355 | const char *lvalue, |
2a71d57f | 356 | int ltype, |
8fcf1d61 | 357 | const char *rvalue, |
2a71d57f LP |
358 | void *data, |
359 | void *userdata) { | |
8fcf1d61 | 360 | |
2a71d57f LP |
361 | NetworkDHCPServerEmitAddress *emit = data; |
362 | ||
363 | assert(emit); | |
8fcf1d61 YW |
364 | assert(rvalue); |
365 | ||
c1997a5b | 366 | for (const char *p = rvalue;;) { |
8fcf1d61 YW |
367 | _cleanup_free_ char *w = NULL; |
368 | union in_addr_union a; | |
c1997a5b | 369 | int r; |
8fcf1d61 YW |
370 | |
371 | r = extract_first_word(&p, &w, NULL, 0); | |
372 | if (r == -ENOMEM) | |
373 | return log_oom(); | |
374 | if (r < 0) { | |
d96edb2c | 375 | log_syntax(unit, LOG_WARNING, filename, line, r, |
8fcf1d61 YW |
376 | "Failed to extract word, ignoring: %s", rvalue); |
377 | return 0; | |
378 | } | |
379 | if (r == 0) | |
c1997a5b | 380 | return 0; |
8fcf1d61 YW |
381 | |
382 | r = in_addr_from_string(AF_INET, w, &a); | |
383 | if (r < 0) { | |
d96edb2c | 384 | log_syntax(unit, LOG_WARNING, filename, line, r, |
c1997a5b | 385 | "Failed to parse %s= address '%s', ignoring: %m", lvalue, w); |
8fcf1d61 YW |
386 | continue; |
387 | } | |
388 | ||
2a71d57f | 389 | struct in_addr *m = reallocarray(emit->addresses, emit->n_addresses + 1, sizeof(struct in_addr)); |
8fcf1d61 YW |
390 | if (!m) |
391 | return log_oom(); | |
392 | ||
2a71d57f LP |
393 | emit->addresses = m; |
394 | emit->addresses[emit->n_addresses++] = a.in; | |
8fcf1d61 | 395 | } |
8fcf1d61 | 396 | } |