resolved: add bus API for configuring per-link DNS settings

This is useful for alternative network management solutions (such as NetworkManager) to push DNS configuration data
into resolved.

The calls will fail should networkd already have taken possesion of a link, so that the bus API is only available if
we don't get the data from networkd.
This commit is contained in:
Lennart Poettering 2016-01-19 17:16:12 +01:00
parent 9a81c76848
commit 97e5d693c0
6 changed files with 448 additions and 18 deletions

View File

@ -33,6 +33,11 @@ union in_addr_union {
struct in6_addr in6;
};
struct in_addr_data {
int family;
union in_addr_union address;
};
int in_addr_is_null(int family, const union in_addr_union *u);
int in_addr_is_link_local(int family, const union in_addr_union *u);
int in_addr_is_localhost(int family, const union in_addr_union *u);

View File

@ -78,6 +78,8 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
SD_BUS_ERROR_MAP(BUS_ERROR_DNSSEC_FAILED, EHOSTUNREACH),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_TRUST_ANCHOR, EHOSTUNREACH),
SD_BUS_ERROR_MAP(BUS_ERROR_RR_TYPE_UNSUPPORTED, EOPNOTSUPP),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_LINK, ENXIO),
SD_BUS_ERROR_MAP(BUS_ERROR_LINK_BUSY, EBUSY),
SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_TRANSFER, ENXIO),
SD_BUS_ERROR_MAP(BUS_ERROR_TRANSFER_IN_PROGRESS, EBUSY),

View File

@ -77,6 +77,8 @@
#define BUS_ERROR_DNSSEC_FAILED "org.freedesktop.resolve1.DnssecFailed"
#define BUS_ERROR_NO_TRUST_ANCHOR "org.freedesktop.resolve1.NoTrustAnchor"
#define BUS_ERROR_RR_TYPE_UNSUPPORTED "org.freedesktop.resolve1.ResourceRecordTypeUnsupported"
#define BUS_ERROR_NO_SUCH_LINK "org.freedesktop.resolve1.NoSuchLink"
#define BUS_ERROR_LINK_BUSY "org.freedesktop.resolve1.LinkBusy"
#define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError."
#define BUS_ERROR_NO_SUCH_TRANSFER "org.freedesktop.import1.NoSuchTransfer"

View File

@ -1339,6 +1339,353 @@ static int bus_method_reset_statistics(sd_bus_message *message, void *userdata,
return sd_bus_reply_method_return(message, NULL);
}
static int get_unmanaged_link(Manager *m, int ifindex, Link **ret, sd_bus_error *error) {
Link *l;
assert(m);
if (ifindex <= 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index");
l = hashmap_get(m->links, INT_TO_PTR(ifindex));
if (!l)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_LINK, "Link %i not known", ifindex);
if (l->flags & IFF_LOOPBACK)
return sd_bus_error_setf(error, BUS_ERROR_LINK_BUSY, "Link %s is loopback device.", l->name);
if (l->is_managed)
return sd_bus_error_setf(error, BUS_ERROR_LINK_BUSY, "Link %s is managed.", l->name);
*ret = l;
return 0;
}
static int bus_method_set_link_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_free_ struct in_addr_data *dns = NULL;
size_t allocated = 0, n = 0;
Manager *m = userdata;
int ifindex, r;
unsigned i;
Link *l;
assert(message);
assert(m);
r = sd_bus_message_read(message, "i", &ifindex);
if (r < 0)
return r;
r = get_unmanaged_link(m, ifindex, &l, error);
if (r < 0)
return r;
r = sd_bus_message_enter_container(message, 'a', "(iay)");
if (r < 0)
return r;
for (;;) {
int family;
size_t sz;
const void *d;
assert_cc(sizeof(int) == sizeof(int32_t));
r = sd_bus_message_enter_container(message, 'r', "iay");
if (r < 0)
return r;
if (r == 0)
break;
r = sd_bus_message_read(message, "i", &family);
if (r < 0)
return r;
if (!IN_SET(family, AF_INET, AF_INET6))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family);
r = sd_bus_message_read_array(message, 'y', &d, &sz);
if (r < 0)
return r;
if (sz != FAMILY_ADDRESS_SIZE(family))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid address size");
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
if (!GREEDY_REALLOC(dns, allocated, n+1))
return -ENOMEM;
dns[n].family = family;
memcpy(&dns[n].address, d, sz);
n++;
}
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
dns_server_mark_all(l->dns_servers);
for (i = 0; i < n; i++) {
DnsServer *s;
s = dns_server_find(l->dns_servers, dns[i].family, &dns[i].address);
if (s)
dns_server_move_back_and_unmark(s);
else {
r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, dns[i].family, &dns[i].address);
if (r < 0)
goto clear;
}
}
dns_server_unlink_marked(l->dns_servers);
link_allocate_scopes(l);
return sd_bus_reply_method_return(message, NULL);
clear:
dns_server_unlink_all(l->dns_servers);
return r;
}
static int bus_method_set_link_domains(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_free_ char **domains = NULL;
Manager *m = userdata;
int ifindex, r;
char **i;
Link *l;
assert(message);
assert(m);
r = sd_bus_message_read(message, "i", &ifindex);
if (r < 0)
return r;
r = get_unmanaged_link(m, ifindex, &l, error);
if (r < 0)
return r;
r = sd_bus_message_read_strv(message, &domains);
if (r < 0)
return r;
STRV_FOREACH(i, domains) {
r = dns_name_is_valid(*i);
if (r < 0)
return r;
if (r == 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid search domain %s", *i);
if (dns_name_is_root(*i))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Root domain is not suitable as search domain");
}
dns_search_domain_mark_all(l->search_domains);
STRV_FOREACH(i, domains) {
DnsSearchDomain *d;
r = dns_search_domain_find(l->search_domains, *i, &d);
if (r < 0)
goto clear;
if (r > 0)
dns_search_domain_move_back_and_unmark(d);
else {
r = dns_search_domain_new(l->manager, NULL, DNS_SEARCH_DOMAIN_LINK, l, *i);
if (r < 0)
goto clear;
}
}
dns_search_domain_unlink_marked(l->search_domains);
return sd_bus_reply_method_return(message, NULL);
clear:
dns_search_domain_unlink_all(l->search_domains);
return r;
}
static int bus_method_set_link_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
ResolveSupport mode;
const char *llmnr;
int ifindex, r;
Link *l;
assert(message);
assert(m);
assert_cc(sizeof(int) == sizeof(int32_t));
r = sd_bus_message_read(message, "is", &ifindex, &llmnr);
if (r < 0)
return r;
if (isempty(llmnr))
mode = RESOLVE_SUPPORT_YES;
else {
mode = resolve_support_from_string(llmnr);
if (mode < 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid LLMNR setting: %s", llmnr);
}
r = get_unmanaged_link(m, ifindex, &l, error);
if (r < 0)
return r;
l->llmnr_support = mode;
link_allocate_scopes(l);
link_add_rrs(l, false);
return sd_bus_reply_method_return(message, NULL);
}
static int bus_method_set_link_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
ResolveSupport mode;
const char *mdns;
int ifindex, r;
Link *l;
assert(message);
assert(m);
assert_cc(sizeof(int) == sizeof(int32_t));
r = sd_bus_message_read(message, "is", &ifindex, &mdns);
if (r < 0)
return r;
if (isempty(mdns))
mode = RESOLVE_SUPPORT_NO;
else {
mode = resolve_support_from_string(mdns);
if (mode < 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid MulticastDNS setting: %s", mdns);
}
r = get_unmanaged_link(m, ifindex, &l, error);
if (r < 0)
return r;
l->mdns_support = mode;
link_allocate_scopes(l);
link_add_rrs(l, false);
return sd_bus_reply_method_return(message, NULL);
}
static int bus_method_set_link_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
const char *dnssec;
DnssecMode mode;
int ifindex, r;
Link *l;
assert(message);
assert(m);
assert_cc(sizeof(int) == sizeof(int32_t));
r = sd_bus_message_read(message, "is", &ifindex, &dnssec);
if (r < 0)
return r;
if (isempty(dnssec))
mode = _DNSSEC_MODE_INVALID;
else {
mode = dnssec_mode_from_string(dnssec);
if (mode < 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNSSEC setting: %s", dnssec);
}
r = get_unmanaged_link(m, ifindex, &l, error);
if (r < 0)
return r;
link_set_dnssec_mode(l, mode);
return sd_bus_reply_method_return(message, NULL);
}
static int bus_method_set_link_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_set_free_free_ Set *ns = NULL;
_cleanup_free_ char **ntas = NULL;
Manager *m = userdata;
int ifindex, r;
char **i;
Link *l;
assert(message);
assert(m);
r = sd_bus_message_read(message, "i", &ifindex);
if (r < 0)
return r;
r = get_unmanaged_link(m, ifindex, &l, error);
if (r < 0)
return r;
r = sd_bus_message_read_strv(message, &ntas);
if (r < 0)
return r;
STRV_FOREACH(i, ntas) {
r = dns_name_is_valid(*i);
if (r < 0)
return r;
if (r == 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid search negative trust anchor domain: %s", *i);
}
ns = set_new(&dns_name_hash_ops);
if (!ns)
return -ENOMEM;
STRV_FOREACH(i, ntas) {
r = set_put_strdup(ns, *i);
if (r < 0)
return r;
}
set_free_free(l->dnssec_negative_trust_anchors);
l->dnssec_negative_trust_anchors = ns;
ns = NULL;
return sd_bus_reply_method_return(message, NULL);
}
static int bus_method_revert_link(sd_bus_message *message, void *userdata, sd_bus_error *error) {
Manager *m = userdata;
int ifindex;
Link *l;
int r;
assert(message);
assert(m);
assert_cc(sizeof(int) == sizeof(int32_t));
r = sd_bus_message_read(message, "i", &ifindex);
if (r < 0)
return r;
r = get_unmanaged_link(m, ifindex, &l, error);
if (r < 0)
return r;
link_flush_settings(l);
link_allocate_scopes(l);
link_add_rrs(l, false);
return sd_bus_reply_method_return(message, NULL);
}
static const sd_bus_vtable resolve_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("LLMNRHostname", "s", NULL, offsetof(Manager, llmnr_hostname), 0),
@ -1354,6 +1701,14 @@ static const sd_bus_vtable resolve_vtable[] = {
SD_BUS_METHOD("ResolveRecord", "isqqt", "a(iqqay)t", bus_method_resolve_record, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ResolveService", "isssit", "a(qqqsa(iiay)s)aayssst", bus_method_resolve_service, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ResetStatistics", NULL, NULL, bus_method_reset_statistics, 0),
SD_BUS_METHOD("SetLinkDNS", "ia(iay)", NULL, bus_method_set_link_dns_servers, 0),
SD_BUS_METHOD("SetLinkDomains", "ias", NULL, bus_method_set_link_domains, 0),
SD_BUS_METHOD("SetLinkLLMNR", "is", NULL, bus_method_set_link_llmnr, 0),
SD_BUS_METHOD("SetLinkMulticastDNS", "is", NULL, bus_method_set_link_mdns, 0),
SD_BUS_METHOD("SetLinkDNSSEC", "is", NULL, bus_method_set_link_dnssec, 0),
SD_BUS_METHOD("SetLinkDNSSECNegativeTrustAnchors", "ias", NULL, bus_method_set_link_dnssec_negative_trust_anchors, 0),
SD_BUS_METHOD("RevertLink", "i", NULL, bus_method_revert_link, 0),
SD_BUS_VTABLE_END,
};

View File

@ -63,15 +63,27 @@ int link_new(Manager *m, Link **ret, int ifindex) {
return 0;
}
Link *link_free(Link *l) {
if (!l)
return NULL;
void link_flush_settings(Link *l) {
assert(l);
l->llmnr_support = RESOLVE_SUPPORT_YES;
l->mdns_support = RESOLVE_SUPPORT_NO;
l->dnssec_mode = _DNSSEC_MODE_INVALID;
dns_server_unlink_all(l->dns_servers);
dns_search_domain_unlink_all(l->search_domains);
l->dnssec_negative_trust_anchors = set_free_free(l->dnssec_negative_trust_anchors);
}
Link *link_free(Link *l) {
if (!l)
return NULL;
link_flush_settings(l);
while (l->addresses)
link_address_free(l->addresses);
(void) link_address_free(l->addresses);
if (l->manager)
hashmap_remove(l->manager->links, INT_TO_PTR(l->ifindex));
@ -82,13 +94,11 @@ Link *link_free(Link *l) {
dns_scope_free(l->mdns_ipv4_scope);
dns_scope_free(l->mdns_ipv6_scope);
set_free_free(l->dnssec_negative_trust_anchors);
free(l);
return NULL;
}
static void link_allocate_scopes(Link *l) {
void link_allocate_scopes(Link *l) {
int r;
assert(l);
@ -278,6 +288,26 @@ clear:
return r;
}
void link_set_dnssec_mode(Link *l, DnssecMode mode) {
assert(l);
if (l->dnssec_mode == mode)
return;
if ((l->dnssec_mode == _DNSSEC_MODE_INVALID) ||
(l->dnssec_mode == DNSSEC_NO && mode != DNSSEC_NO) ||
(l->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE && mode == DNSSEC_YES)) {
/* When switching from non-DNSSEC mode to DNSSEC mode, flush the cache. Also when switching from the
* allow-downgrade mode to full DNSSEC mode, flush it too. */
if (l->unicast_scope)
dns_cache_flush(&l->unicast_scope->cache);
}
l->dnssec_mode = mode;
}
static int link_update_dnssec_mode(Link *l) {
_cleanup_free_ char *m = NULL;
DnssecMode mode;
@ -299,16 +329,7 @@ static int link_update_dnssec_mode(Link *l) {
goto clear;
}
if ((l->dnssec_mode == DNSSEC_NO && mode != DNSSEC_NO) ||
(l->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE && mode == DNSSEC_YES)) {
/* When switching from non-DNSSEC mode to DNSSEC mode, flush the cache. Also when switching from the
* allow-downgrade mode to full DNSSEC mode, flush it too. */
if (l->unicast_scope)
dns_cache_flush(&l->unicast_scope->cache);
}
l->dnssec_mode = mode;
link_set_dnssec_mode(l, mode);
return 0;
@ -396,11 +417,45 @@ clear:
return r;
}
int link_update_monitor(Link *l) {
static int link_is_unmanaged(Link *l) {
_cleanup_free_ char *state = NULL;
int r;
assert(l);
r = sd_network_link_get_setup_state(l->ifindex, &state);
if (r == -ENODATA)
return 1;
if (r < 0)
return r;
return STR_IN_SET(state, "pending", "unmanaged");
}
static void link_read_settings(Link *l) {
int r;
assert(l);
/* Read settings from networkd, except when networkd is not managing this interface. */
r = link_is_unmanaged(l);
if (r < 0) {
log_warning_errno(r, "Failed to determine whether interface %s is managed: %m", l->name);
return;
}
if (r > 0) {
/* If this link used to be managed, but is now unmanaged, flush all our settings -- but only once. */
if (l->is_managed)
link_flush_settings(l);
l->is_managed = false;
return;
}
l->is_managed = true;
r = link_update_dns_servers(l);
if (r < 0)
log_warning_errno(r, "Failed to read DNS servers for interface %s, ignoring: %m", l->name);
@ -424,7 +479,12 @@ int link_update_monitor(Link *l) {
r = link_update_search_domains(l);
if (r < 0)
log_warning_errno(r, "Failed to read search domains for interface %s, ignoring: %m", l->name);
}
int link_update_monitor(Link *l) {
assert(l);
link_read_settings(l);
link_allocate_scopes(l);
link_add_rrs(l, false);

View File

@ -78,6 +78,8 @@ struct Link {
DnsScope *mdns_ipv4_scope;
DnsScope *mdns_ipv6_scope;
bool is_managed;
char name[IF_NAMESIZE];
uint32_t mtu;
};
@ -90,6 +92,10 @@ bool link_relevant(Link *l, int family, bool multicast);
LinkAddress* link_find_address(Link *l, int family, const union in_addr_union *in_addr);
void link_add_rrs(Link *l, bool force_remove);
void link_flush_settings(Link *l);
void link_set_dnssec_mode(Link *l, DnssecMode mode);
void link_allocate_scopes(Link *l);
DnsServer* link_set_dns_server(Link *l, DnsServer *s);
DnsServer* link_get_dns_server(Link *l);
void link_next_dns_server(Link *l);