2 This file is part of systemd.
4 Copyright 2016 Lennart Poettering
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 #include "hostname-util.h"
23 #include "resolved-etc-hosts.h"
24 #include "resolved-dns-synthesize.h"
25 #include "string-util.h"
27 #include "time-util.h"
29 /* Recheck /etc/hosts at most once every 2s */
30 #define ETC_HOSTS_RECHECK_USEC (2*USEC_PER_SEC)
32 typedef struct EtcHostsItem
{
34 union in_addr_union address
;
39 typedef struct EtcHostsItemByName
{
43 size_t n_items
, n_allocated
;
46 void manager_etc_hosts_flush(Manager
*m
) {
48 EtcHostsItemByName
*bn
;
50 while ((item
= set_steal_first(m
->etc_hosts_by_address
))) {
51 strv_free(item
->names
);
55 while ((bn
= hashmap_steal_first(m
->etc_hosts_by_name
))) {
61 m
->etc_hosts_by_address
= set_free(m
->etc_hosts_by_address
);
62 m
->etc_hosts_by_name
= hashmap_free(m
->etc_hosts_by_name
);
64 m
->etc_hosts_mtime
= USEC_INFINITY
;
67 static void etc_hosts_item_hash_func(const void *p
, struct siphash
*state
) {
68 const EtcHostsItem
*item
= p
;
70 siphash24_compress(&item
->family
, sizeof(item
->family
), state
);
72 if (item
->family
== AF_INET
)
73 siphash24_compress(&item
->address
.in
, sizeof(item
->address
.in
), state
);
74 else if (item
->family
== AF_INET6
)
75 siphash24_compress(&item
->address
.in6
, sizeof(item
->address
.in6
), state
);
78 static int etc_hosts_item_compare_func(const void *a
, const void *b
) {
79 const EtcHostsItem
*x
= a
, *y
= b
;
81 if (x
->family
!= y
->family
)
82 return x
->family
- y
->family
;
84 if (x
->family
== AF_INET
)
85 return memcmp(&x
->address
.in
.s_addr
, &y
->address
.in
.s_addr
, sizeof(struct in_addr
));
87 if (x
->family
== AF_INET6
)
88 return memcmp(&x
->address
.in6
.s6_addr
, &y
->address
.in6
.s6_addr
, sizeof(struct in6_addr
));
90 return trivial_compare_func(a
, b
);
93 static const struct hash_ops etc_hosts_item_ops
= {
94 .hash
= etc_hosts_item_hash_func
,
95 .compare
= etc_hosts_item_compare_func
,
98 static int add_item(Manager
*m
, int family
, const union in_addr_union
*address
, char **names
) {
111 r
= in_addr_is_null(family
, address
);
115 /* This is an 0.0.0.0 or :: item, which we assume means that we shall map the specified hostname to
119 /* If this is a normal address, then, simply add entry mapping it to the specified names */
121 item
= set_get(m
->etc_hosts_by_address
, &key
);
123 r
= strv_extend_strv(&item
->names
, names
, true);
128 r
= set_ensure_allocated(&m
->etc_hosts_by_address
, &etc_hosts_item_ops
);
132 item
= new0(EtcHostsItem
, 1);
136 item
->family
= family
;
137 item
->address
= *address
;
140 r
= set_put(m
->etc_hosts_by_address
, item
);
148 STRV_FOREACH(n
, names
) {
149 EtcHostsItemByName
*bn
;
151 bn
= hashmap_get(m
->etc_hosts_by_name
, *n
);
153 r
= hashmap_ensure_allocated(&m
->etc_hosts_by_name
, &dns_name_hash_ops
);
157 bn
= new0(EtcHostsItemByName
, 1);
161 bn
->name
= strdup(*n
);
167 r
= hashmap_put(m
->etc_hosts_by_name
, bn
->name
, bn
);
176 if (!GREEDY_REALLOC(bn
->items
, bn
->n_allocated
, bn
->n_items
+1))
179 bn
->items
[bn
->n_items
++] = item
;
186 static int parse_line(Manager
*m
, unsigned nr
, const char *line
) {
187 _cleanup_free_
char *address
= NULL
;
188 _cleanup_strv_free_
char **names
= NULL
;
189 union in_addr_union in
;
190 bool suppressed
= false;
196 r
= extract_first_word(&line
, &address
, NULL
, EXTRACT_RELAX
);
198 return log_error_errno(r
, "Couldn't extract address, in line /etc/hosts:%u.", nr
);
200 log_error("Premature end of line, in line /etc/hosts:%u.", nr
);
204 r
= in_addr_from_string_auto(address
, &family
, &in
);
206 return log_error_errno(r
, "Address '%s' is invalid, in line /etc/hosts:%u.", address
, nr
);
209 _cleanup_free_
char *name
= NULL
;
211 r
= extract_first_word(&line
, &name
, NULL
, EXTRACT_RELAX
);
213 return log_error_errno(r
, "Couldn't extract host name, in line /etc/hosts:%u.", nr
);
217 r
= dns_name_is_valid(name
);
219 return log_error_errno(r
, "Hostname %s is not valid, ignoring, in line /etc/hosts:%u.", name
, nr
);
221 if (is_localhost(name
)) {
222 /* Suppress the "localhost" line that is often seen */
227 r
= strv_push(&names
, name
);
234 if (strv_isempty(names
)) {
239 log_error("Line is missing any host names, in line /etc/hosts:%u.", nr
);
243 /* Takes possession of the names strv */
244 r
= add_item(m
, family
, &in
, names
);
252 int manager_etc_hosts_read(Manager
*m
) {
253 _cleanup_fclose_
FILE *f
= NULL
;
260 assert_se(sd_event_now(m
->event
, clock_boottime_or_monotonic(), &ts
) >= 0);
262 /* See if we checked /etc/hosts recently already */
263 if (m
->etc_hosts_last
!= USEC_INFINITY
&& m
->etc_hosts_last
+ ETC_HOSTS_RECHECK_USEC
> ts
)
266 m
->etc_hosts_last
= ts
;
268 if (m
->etc_hosts_mtime
!= USEC_INFINITY
) {
269 if (stat("/etc/hosts", &st
) < 0) {
270 if (errno
== ENOENT
) {
275 return log_error_errno(errno
, "Failed to stat /etc/hosts: %m");
278 /* Did the mtime change? If not, there's no point in re-reading the file. */
279 if (timespec_load(&st
.st_mtim
) == m
->etc_hosts_mtime
)
283 f
= fopen("/etc/hosts", "re");
285 if (errno
== ENOENT
) {
290 return log_error_errno(errno
, "Failed to open /etc/hosts: %m");
293 /* Take the timestamp at the beginning of processing, so that any changes made later are read on the next
295 r
= fstat(fileno(f
), &st
);
297 return log_error_errno(errno
, "Failed to fstat() /etc/hosts: %m");
299 manager_etc_hosts_flush(m
);
301 FOREACH_LINE(line
, f
, return log_error_errno(errno
, "Failed to read /etc/hosts: %m")) {
312 r
= parse_line(m
, nr
, l
);
313 if (r
== -ENOMEM
) /* On OOM we abandon the half-built-up structure. All other errors we ignore and proceed */
317 m
->etc_hosts_mtime
= timespec_load(&st
.st_mtim
);
318 m
->etc_hosts_last
= ts
;
323 manager_etc_hosts_flush(m
);
327 int manager_etc_hosts_lookup(Manager
*m
, DnsQuestion
* q
, DnsAnswer
**answer
) {
328 bool found_a
= false, found_aaaa
= false;
329 EtcHostsItemByName
*bn
;
340 r
= manager_etc_hosts_read(m
);
344 name
= dns_question_first_name(q
);
348 r
= dns_name_address(name
, &k
.family
, &k
.address
);
351 DnsResourceKey
*found_ptr
= NULL
;
353 item
= set_get(m
->etc_hosts_by_address
, &k
);
357 /* We have an address in /etc/hosts that matches the queried name. Let's return successful. Actual data
358 * we'll only return if the request was for PTR. */
360 DNS_QUESTION_FOREACH(t
, q
) {
361 if (!IN_SET(t
->type
, DNS_TYPE_PTR
, DNS_TYPE_ANY
))
363 if (!IN_SET(t
->class, DNS_CLASS_IN
, DNS_CLASS_ANY
))
366 r
= dns_name_equal(DNS_RESOURCE_KEY_NAME(t
), name
);
378 r
= dns_answer_reserve(answer
, strv_length(item
->names
));
382 STRV_FOREACH(n
, item
->names
) {
383 _cleanup_(dns_resource_record_unrefp
) DnsResourceRecord
*rr
= NULL
;
385 rr
= dns_resource_record_new(found_ptr
);
389 rr
->ptr
.name
= strdup(*n
);
393 r
= dns_answer_add(*answer
, rr
, 0, DNS_ANSWER_AUTHENTICATED
);
402 bn
= hashmap_get(m
->etc_hosts_by_name
, name
);
406 r
= dns_answer_reserve(answer
, bn
->n_items
);
410 DNS_QUESTION_FOREACH(t
, q
) {
411 if (!IN_SET(t
->type
, DNS_TYPE_A
, DNS_TYPE_AAAA
, DNS_TYPE_ANY
))
413 if (!IN_SET(t
->class, DNS_CLASS_IN
, DNS_CLASS_ANY
))
416 r
= dns_name_equal(DNS_RESOURCE_KEY_NAME(t
), name
);
422 if (IN_SET(t
->type
, DNS_TYPE_A
, DNS_TYPE_ANY
))
424 if (IN_SET(t
->type
, DNS_TYPE_AAAA
, DNS_TYPE_ANY
))
427 if (found_a
&& found_aaaa
)
431 for (i
= 0; i
< bn
->n_items
; i
++) {
432 _cleanup_(dns_resource_record_unrefp
) DnsResourceRecord
*rr
= NULL
;
434 if ((found_a
&& bn
->items
[i
]->family
!= AF_INET
) &&
435 (found_aaaa
&& bn
->items
[i
]->family
!= AF_INET6
))
438 r
= dns_resource_record_new_address(&rr
, bn
->items
[i
]->family
, &bn
->items
[i
]->address
, bn
->name
);
442 r
= dns_answer_add(*answer
, rr
, 0, DNS_ANSWER_AUTHENTICATED
);