/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include "alloc-util.h" #include "dns-domain.h" #include "escape.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" #include "network-internal.h" #include "networkd-link.h" #include "networkd-manager-bus.h" #include "networkd-manager.h" #include "networkd-network.h" #include "networkd-state-file.h" #include "ordered-set.h" #include "set.h" #include "strv.h" #include "tmpfile-util.h" static int ordered_set_put_dns_server(OrderedSet **s, int ifindex, struct in_addr_full *dns) { const char *p; int r; assert(s); assert(dns); if (dns->ifindex != 0 && dns->ifindex != ifindex) return 0; p = in_addr_full_to_string(dns); if (!p) return 0; r = ordered_set_put_strdup(s, p); if (r == -EEXIST) return 0; return r; } static int ordered_set_put_dns_servers(OrderedSet **s, int ifindex, struct in_addr_full **dns, unsigned n) { int r, c = 0; assert(s); assert(dns || n == 0); for (unsigned i = 0; i < n; i++) { r = ordered_set_put_dns_server(s, ifindex, dns[i]); if (r < 0) return r; c += r; } return c; } static int ordered_set_put_in4_addr(OrderedSet **s, const struct in_addr *address) { _cleanup_free_ char *p = NULL; int r; assert(s); assert(address); r = in_addr_to_string(AF_INET, (const union in_addr_union*) address, &p); if (r < 0) return r; r = ordered_set_ensure_allocated(s, &string_hash_ops_free); if (r < 0) return r; r = ordered_set_consume(*s, TAKE_PTR(p)); if (r == -EEXIST) return 0; return r; } static int ordered_set_put_in4_addrv( OrderedSet **s, const struct in_addr *addresses, size_t n, bool (*predicate)(const struct in_addr *addr)) { int r, c = 0; assert(s); assert(n == 0 || addresses); for (size_t i = 0; i < n; i++) { if (predicate && !predicate(&addresses[i])) continue; r = ordered_set_put_in4_addr(s, addresses+i); if (r < 0) return r; c += r; } return c; } int manager_save(Manager *m) { _cleanup_ordered_set_free_ OrderedSet *dns = NULL, *ntp = NULL, *sip = NULL, *search_domains = NULL, *route_domains = NULL; const char *operstate_str, *carrier_state_str, *address_state_str, *ipv4_address_state_str, *ipv6_address_state_str, *online_state_str; LinkOperationalState operstate = LINK_OPERSTATE_OFF; LinkCarrierState carrier_state = LINK_CARRIER_STATE_OFF; LinkAddressState ipv4_address_state = LINK_ADDRESS_STATE_OFF, ipv6_address_state = LINK_ADDRESS_STATE_OFF, address_state = LINK_ADDRESS_STATE_OFF; LinkOnlineState online_state; size_t links_offline = 0, links_online = 0; _cleanup_(unlink_and_freep) char *temp_path = NULL; _cleanup_strv_free_ char **p = NULL; _cleanup_fclose_ FILE *f = NULL; Link *link; int r; assert(m); if (isempty(m->state_file)) return 0; /* Do not update state file when running in test mode. */ HASHMAP_FOREACH(link, m->links_by_index) { const struct in_addr *addresses; if (link->flags & IFF_LOOPBACK) continue; operstate = MAX(operstate, link->operstate); carrier_state = MAX(carrier_state, link->carrier_state); address_state = MAX(address_state, link->address_state); ipv4_address_state = MAX(ipv4_address_state, link->ipv4_address_state); ipv6_address_state = MAX(ipv6_address_state, link->ipv6_address_state); if (!link->network) continue; if (link->network->required_for_online) { if (link->online_state == LINK_ONLINE_STATE_OFFLINE) links_offline++; else if (link->online_state == LINK_ONLINE_STATE_ONLINE) links_online++; } /* First add the static configured entries */ if (link->n_dns != UINT_MAX) r = ordered_set_put_dns_servers(&dns, link->ifindex, link->dns, link->n_dns); else r = ordered_set_put_dns_servers(&dns, link->ifindex, link->network->dns, link->network->n_dns); if (r < 0) return r; r = ordered_set_put_strdupv(&ntp, link->ntp ?: link->network->ntp); if (r < 0) return r; r = ordered_set_put_string_set(&search_domains, link->search_domains ?: link->network->search_domains); if (r < 0) return r; r = ordered_set_put_string_set(&route_domains, link->route_domains ?: link->network->route_domains); if (r < 0) return r; if (!link->dhcp_lease) continue; /* Secondly, add the entries acquired via DHCP */ if (link->network->dhcp_use_dns) { r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses); if (r > 0) { r = ordered_set_put_in4_addrv(&dns, addresses, r, in4_addr_is_non_local); if (r < 0) return r; } else if (r < 0 && r != -ENODATA) return r; } if (link->network->dhcp_use_ntp) { r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses); if (r > 0) { r = ordered_set_put_in4_addrv(&ntp, addresses, r, in4_addr_is_non_local); if (r < 0) return r; } else if (r < 0 && r != -ENODATA) return r; } if (link->network->dhcp_use_sip) { r = sd_dhcp_lease_get_sip(link->dhcp_lease, &addresses); if (r > 0) { r = ordered_set_put_in4_addrv(&sip, addresses, r, in4_addr_is_non_local); if (r < 0) return r; } else if (r < 0 && r != -ENODATA) return r; } if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) { OrderedSet **target_domains; const char *domainname; char **domains = NULL; target_domains = link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES ? &search_domains : &route_domains; r = sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname); if (r >= 0) { r = ordered_set_put_strdup(target_domains, domainname); if (r < 0) return r; } else if (r != -ENODATA) return r; r = sd_dhcp_lease_get_search_domains(link->dhcp_lease, &domains); if (r >= 0) { r = ordered_set_put_strdupv(target_domains, domains); if (r < 0) return r; } else if (r != -ENODATA) return r; } } if (carrier_state >= LINK_CARRIER_STATE_ENSLAVED) carrier_state = LINK_CARRIER_STATE_CARRIER; online_state = links_online > 0 ? (links_offline > 0 ? LINK_ONLINE_STATE_PARTIAL : LINK_ONLINE_STATE_ONLINE) : (links_offline > 0 ? LINK_ONLINE_STATE_OFFLINE : _LINK_ONLINE_STATE_INVALID); operstate_str = link_operstate_to_string(operstate); assert(operstate_str); carrier_state_str = link_carrier_state_to_string(carrier_state); assert(carrier_state_str); address_state_str = link_address_state_to_string(address_state); assert(address_state_str); ipv4_address_state_str = link_address_state_to_string(ipv4_address_state); assert(ipv4_address_state_str); ipv6_address_state_str = link_address_state_to_string(ipv6_address_state); assert(ipv6_address_state_str); r = fopen_temporary(m->state_file, &f, &temp_path); if (r < 0) return r; (void) fchmod(fileno(f), 0644); fprintf(f, "# This is private data. Do not parse.\n" "OPER_STATE=%s\n" "CARRIER_STATE=%s\n" "ADDRESS_STATE=%s\n" "IPV4_ADDRESS_STATE=%s\n" "IPV6_ADDRESS_STATE=%s\n", operstate_str, carrier_state_str, address_state_str, ipv4_address_state_str, ipv6_address_state_str); online_state_str = link_online_state_to_string(online_state); if (online_state_str) fprintf(f, "ONLINE_STATE=%s\n", online_state_str); ordered_set_print(f, "DNS=", dns); ordered_set_print(f, "NTP=", ntp); ordered_set_print(f, "SIP=", sip); ordered_set_print(f, "DOMAINS=", search_domains); ordered_set_print(f, "ROUTE_DOMAINS=", route_domains); r = fflush_and_check(f); if (r < 0) return r; r = conservative_rename(temp_path, m->state_file); if (r < 0) return r; temp_path = mfree(temp_path); if (m->operational_state != operstate) { m->operational_state = operstate; if (strv_extend(&p, "OperationalState") < 0) log_oom(); } if (m->carrier_state != carrier_state) { m->carrier_state = carrier_state; if (strv_extend(&p, "CarrierState") < 0) log_oom(); } if (m->address_state != address_state) { m->address_state = address_state; if (strv_extend(&p, "AddressState") < 0) log_oom(); } if (m->ipv4_address_state != ipv4_address_state) { m->ipv4_address_state = ipv4_address_state; if (strv_extend(&p, "IPv4AddressState") < 0) log_oom(); } if (m->ipv6_address_state != ipv6_address_state) { m->ipv6_address_state = ipv6_address_state; if (strv_extend(&p, "IPv6AddressState") < 0) log_oom(); } if (m->online_state != online_state) { m->online_state = online_state; if (strv_extend(&p, "OnlineState") < 0) log_oom(); } if (p) { r = manager_send_changed_strv(m, p); if (r < 0) log_warning_errno(r, "Could not emit changed properties, ignoring: %m"); } m->dirty = false; return 0; } static void print_link_hashmap(FILE *f, const char *prefix, Hashmap* h) { bool space = false; Link *link; assert(f); assert(prefix); if (hashmap_isempty(h)) return; fputs(prefix, f); HASHMAP_FOREACH(link, h) { if (space) fputc(' ', f); fprintf(f, "%i", link->ifindex); space = true; } fputc('\n', f); } static void link_save_dns(Link *link, FILE *f, struct in_addr_full **dns, unsigned n_dns, bool *space) { bool _space = false; if (!space) space = &_space; for (unsigned j = 0; j < n_dns; j++) { const char *str; if (dns[j]->ifindex != 0 && dns[j]->ifindex != link->ifindex) continue; str = in_addr_full_to_string(dns[j]); if (!str) continue; if (*space) fputc(' ', f); fputs(str, f); *space = true; } } static void serialize_addresses( FILE *f, const char *lvalue, bool *space, char **addresses, sd_dhcp_lease *lease, bool conditional, sd_dhcp_lease_server_type_t what, sd_dhcp6_lease *lease6, bool conditional6, int (*lease6_get_addr)(sd_dhcp6_lease*, const struct in6_addr**), int (*lease6_get_fqdn)(sd_dhcp6_lease*, char ***)) { bool _space = false; int r; if (!space) space = &_space; if (lvalue) fprintf(f, "%s=", lvalue); fputstrv(f, addresses, NULL, space); if (lease && conditional) { const struct in_addr *lease_addresses; r = sd_dhcp_lease_get_servers(lease, what, &lease_addresses); if (r > 0) serialize_in_addrs(f, lease_addresses, r, space, in4_addr_is_non_local); } if (lease6 && conditional6 && lease6_get_addr) { const struct in6_addr *in6_addrs; r = lease6_get_addr(lease6, &in6_addrs); if (r > 0) serialize_in6_addrs(f, in6_addrs, r, space); } if (lease6 && conditional6 && lease6_get_fqdn) { char **in6_hosts; r = lease6_get_fqdn(lease6, &in6_hosts); if (r > 0) fputstrv(f, in6_hosts, NULL, space); } if (lvalue) fputc('\n', f); } static void link_save_domains(Link *link, FILE *f, OrderedSet *static_domains, DHCPUseDomains use_domains) { bool space = false; const char *p; assert(link); assert(link->network); assert(f); ORDERED_SET_FOREACH(p, static_domains) fputs_with_space(f, p, NULL, &space); if (use_domains == DHCP_USE_DOMAINS_NO) return; if (link->dhcp_lease && link->network->dhcp_use_domains == use_domains) { const char *domainname; char **domains; if (sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname) >= 0) fputs_with_space(f, domainname, NULL, &space); if (sd_dhcp_lease_get_search_domains(link->dhcp_lease, &domains) >= 0) fputstrv(f, domains, NULL, &space); } if (link->dhcp6_lease && link->network->dhcp6_use_domains == use_domains) { char **domains; if (sd_dhcp6_lease_get_domains(link->dhcp6_lease, &domains) >= 0) fputstrv(f, domains, NULL, &space); } if (link->network->ipv6_accept_ra_use_domains == use_domains) { NDiscDNSSL *dd; SET_FOREACH(dd, link->ndisc_dnssl) fputs_with_space(f, NDISC_DNSSL_DOMAIN(dd), NULL, &space); } } int link_save(Link *link) { const char *admin_state, *oper_state, *carrier_state, *address_state, *ipv4_address_state, *ipv6_address_state; _cleanup_(unlink_and_freep) char *temp_path = NULL; _cleanup_fclose_ FILE *f = NULL; int r; assert(link); assert(link->manager); if (isempty(link->state_file)) return 0; /* Do not update state files when running in test mode. */ if (link->state == LINK_STATE_LINGER) return 0; link_lldp_save(link); admin_state = link_state_to_string(link->state); assert(admin_state); oper_state = link_operstate_to_string(link->operstate); assert(oper_state); carrier_state = link_carrier_state_to_string(link->carrier_state); assert(carrier_state); address_state = link_address_state_to_string(link->address_state); assert(address_state); ipv4_address_state = link_address_state_to_string(link->ipv4_address_state); assert(ipv4_address_state); ipv6_address_state = link_address_state_to_string(link->ipv6_address_state); assert(ipv6_address_state); r = fopen_temporary(link->state_file, &f, &temp_path); if (r < 0) return r; (void) fchmod(fileno(f), 0644); fprintf(f, "# This is private data. Do not parse.\n" "ADMIN_STATE=%s\n" "OPER_STATE=%s\n" "CARRIER_STATE=%s\n" "ADDRESS_STATE=%s\n" "IPV4_ADDRESS_STATE=%s\n" "IPV6_ADDRESS_STATE=%s\n", admin_state, oper_state, carrier_state, address_state, ipv4_address_state, ipv6_address_state); if (link->network) { const char *online_state; bool space = false; online_state = link_online_state_to_string(link->online_state); if (online_state) fprintf(f, "ONLINE_STATE=%s\n", online_state); fprintf(f, "REQUIRED_FOR_ONLINE=%s\n", yes_no(link->network->required_for_online)); LinkOperationalStateRange st = link->network->required_operstate_for_online; fprintf(f, "REQUIRED_OPER_STATE_FOR_ONLINE=%s%s%s\n", strempty(link_operstate_to_string(st.min)), st.max != LINK_OPERSTATE_RANGE_DEFAULT.max ? ":" : "", st.max != LINK_OPERSTATE_RANGE_DEFAULT.max ? strempty(link_operstate_to_string(st.max)) : ""); fprintf(f, "REQUIRED_FAMILY_FOR_ONLINE=%s\n", link_required_address_family_to_string(link->network->required_family_for_online)); fprintf(f, "ACTIVATION_POLICY=%s\n", activation_policy_to_string(link->network->activation_policy)); fprintf(f, "NETWORK_FILE=%s\n", link->network->filename); fputs("NETWORK_FILE_DROPINS=\"", f); STRV_FOREACH(d, link->network->dropins) { _cleanup_free_ char *escaped = NULL; escaped = xescape(*d, ":"); if (!escaped) return -ENOMEM; fputs_with_space(f, escaped, ":", &space); } fputs("\"\n", f); /************************************************************/ fputs("DNS=", f); if (link->n_dns != UINT_MAX) link_save_dns(link, f, link->dns, link->n_dns, NULL); else { space = false; link_save_dns(link, f, link->network->dns, link->network->n_dns, &space); serialize_addresses(f, NULL, &space, NULL, link->dhcp_lease, link->network->dhcp_use_dns, SD_DHCP_LEASE_DNS, link->dhcp6_lease, link->network->dhcp6_use_dns, sd_dhcp6_lease_get_dns, NULL); if (link->network->ipv6_accept_ra_use_dns) { NDiscRDNSS *dd; SET_FOREACH(dd, link->ndisc_rdnss) serialize_in6_addrs(f, &dd->address, 1, &space); } } fputc('\n', f); /************************************************************/ if (link->ntp) { fputs("NTP=", f); fputstrv(f, link->ntp, NULL, NULL); fputc('\n', f); } else serialize_addresses(f, "NTP", NULL, link->network->ntp, link->dhcp_lease, link->network->dhcp_use_ntp, SD_DHCP_LEASE_NTP, link->dhcp6_lease, link->network->dhcp6_use_ntp, sd_dhcp6_lease_get_ntp_addrs, sd_dhcp6_lease_get_ntp_fqdn); serialize_addresses(f, "SIP", NULL, NULL, link->dhcp_lease, link->network->dhcp_use_sip, SD_DHCP_LEASE_SIP, NULL, false, NULL, NULL); /************************************************************/ fputs("DOMAINS=", f); if (link->search_domains) link_save_domains(link, f, link->search_domains, DHCP_USE_DOMAINS_NO); else link_save_domains(link, f, link->network->search_domains, DHCP_USE_DOMAINS_YES); fputc('\n', f); /************************************************************/ fputs("ROUTE_DOMAINS=", f); if (link->route_domains) link_save_domains(link, f, link->route_domains, DHCP_USE_DOMAINS_NO); else link_save_domains(link, f, link->network->route_domains, DHCP_USE_DOMAINS_ROUTE); fputc('\n', f); /************************************************************/ fprintf(f, "LLMNR=%s\n", resolve_support_to_string(link->llmnr >= 0 ? link->llmnr : link->network->llmnr)); /************************************************************/ fprintf(f, "MDNS=%s\n", resolve_support_to_string(link->mdns >= 0 ? link->mdns : link->network->mdns)); /************************************************************/ int dns_default_route = link->dns_default_route >= 0 ? link->dns_default_route : link->network->dns_default_route; if (dns_default_route >= 0) fprintf(f, "DNS_DEFAULT_ROUTE=%s\n", yes_no(dns_default_route)); /************************************************************/ DnsOverTlsMode dns_over_tls_mode = link->dns_over_tls_mode != _DNS_OVER_TLS_MODE_INVALID ? link->dns_over_tls_mode : link->network->dns_over_tls_mode; if (dns_over_tls_mode != _DNS_OVER_TLS_MODE_INVALID) fprintf(f, "DNS_OVER_TLS=%s\n", dns_over_tls_mode_to_string(dns_over_tls_mode)); /************************************************************/ DnssecMode dnssec_mode = link->dnssec_mode != _DNSSEC_MODE_INVALID ? link->dnssec_mode : link->network->dnssec_mode; if (dnssec_mode != _DNSSEC_MODE_INVALID) fprintf(f, "DNSSEC=%s\n", dnssec_mode_to_string(dnssec_mode)); /************************************************************/ Set *nta_anchors = link->dnssec_negative_trust_anchors; if (set_isempty(nta_anchors)) nta_anchors = link->network->dnssec_negative_trust_anchors; if (!set_isempty(nta_anchors)) { const char *n; fputs("DNSSEC_NTA=", f); space = false; SET_FOREACH(n, nta_anchors) fputs_with_space(f, n, NULL, &space); fputc('\n', f); } } print_link_hashmap(f, "CARRIER_BOUND_TO=", link->bound_to_links); print_link_hashmap(f, "CARRIER_BOUND_BY=", link->bound_by_links); if (link->dhcp_lease) { r = dhcp_lease_save(link->dhcp_lease, link->lease_file); if (r < 0) return r; fprintf(f, "DHCP_LEASE=%s\n", link->lease_file); } else (void) unlink(link->lease_file); r = link_serialize_dhcp6_client(link, f); if (r < 0) return r; r = fflush_and_check(f); if (r < 0) return r; r = conservative_rename(temp_path, link->state_file); if (r < 0) return r; temp_path = mfree(temp_path); return 0; } void link_dirty(Link *link) { int r; assert(link); assert(link->manager); /* The serialized state in /run is no longer up-to-date. */ /* Also mark manager dirty as link is dirty */ link->manager->dirty = true; r = set_ensure_put(&link->manager->dirty_links, NULL, link); if (r <= 0) /* Ignore allocation errors and don't take another ref if the link was already dirty */ return; link_ref(link); } void link_clean(Link *link) { assert(link); assert(link->manager); /* The serialized state in /run is up-to-date */ link_unref(set_remove(link->manager->dirty_links, link)); } int link_save_and_clean(Link *link) { int r; r = link_save(link); if (r < 0) return r; link_clean(link); return 0; }