Add SetServersEX method in DBus interface.

This commit is contained in:
Simon Kelley
2012-09-20 14:17:39 +01:00
parent 2b127a1eab
commit faafb3f7b7
5 changed files with 372 additions and 166 deletions

View File

@@ -19,6 +19,9 @@ version 2.64
Flag DHCP or DHCPv6 in starup logging. Thanks to Flag DHCP or DHCPv6 in starup logging. Thanks to
Vladislav Grishenko for the patch. Vladislav Grishenko for the patch.
Add SetServersEX method in DBus interface. Thanks to Dan
Williams for the patch.
version 2.63 version 2.63
Do duplicate dhcp-host address check in --test mode. Do duplicate dhcp-host address check in --test mode.

View File

@@ -95,6 +95,48 @@ Each call to SetServers completely replaces the set of servers
specified by via the DBus, but it leaves any servers specified via the specified by via the DBus, but it leaves any servers specified via the
command line or /etc/dnsmasq.conf or /etc/resolv.conf alone. command line or /etc/dnsmasq.conf or /etc/resolv.conf alone.
SetServersEx
------------
This function is more flexible and the SetServers function, in that it can
handle address scoping, port numbers, and is easier for clients to use.
Returns nothing. Takes a set of arguments representing the new
upstream DNS servers to be used by dnsmasq. All addresses (both IPv4 and IPv6)
are represented as STRINGS. Each server address may be followed by one or more
STRINGS, which are the domains for which the preceding server should be used.
This function takes an array of STRING arrays, where each inner array represents
a set of DNS servers and domains for which those servers may be used. Each
string represents a list of upstream DNS servers first, and domains second.
Mixing of domains and servers within a the string array is not allowed.
Examples.
[
["1.2.3.4", "foobar.com"],
["1003:1234:abcd::1%eth0", "eng.mycorp.com", "lab.mycorp.com"]
]
is equivalent to
--server=/foobar.com/1.2.3.4 \
--server=/eng.mycorp.com/lab.mycorp.com/1003:1234:abcd::1%eth0
An IPv4 address of 0.0.0.0 is interpreted as "no address, local only",
so
[ ["0.0.0.0", "local.domain"] ]
is equivalent to
--local=/local.domain/
Each call to SetServersEx completely replaces the set of servers
specified by via the DBus, but it leaves any servers specified via the
command line or /etc/dnsmasq.conf or /etc/resolv.conf alone.
2. SIGNALS 2. SIGNALS
---------- ----------

View File

@@ -38,6 +38,9 @@ const char* introspection_xml_template =
" <method name=\"SetServers\">\n" " <method name=\"SetServers\">\n"
" <arg name=\"servers\" direction=\"in\" type=\"av\"/>\n" " <arg name=\"servers\" direction=\"in\" type=\"av\"/>\n"
" </method>\n" " </method>\n"
" <method name=\"SetServersEx\">\n"
" <arg name=\"servers\" direction=\"in\" type=\"aas\"/>\n"
" </method>\n"
" <signal name=\"DhcpLeaseAdded\">\n" " <signal name=\"DhcpLeaseAdded\">\n"
" <arg name=\"ipaddr\" type=\"s\"/>\n" " <arg name=\"ipaddr\" type=\"s\"/>\n"
" <arg name=\"hwaddr\" type=\"s\"/>\n" " <arg name=\"hwaddr\" type=\"s\"/>\n"
@@ -99,19 +102,117 @@ static void remove_watch(DBusWatch *watch, void *data)
w = data; /* no warning */ w = data; /* no warning */
} }
static void dbus_read_servers(DBusMessage *message) static void add_update_server(union mysockaddr *addr,
union mysockaddr *source_addr,
const char *interface,
const char *domain)
{
struct server *serv;
/* See if there is a suitable candidate, and unmark */
for (serv = daemon->servers; serv; serv = serv->next)
if ((serv->flags & SERV_FROM_DBUS) &&
(serv->flags & SERV_MARK))
{
if (domain)
{
if (!(serv->flags & SERV_HAS_DOMAIN) || !hostname_isequal(domain, serv->domain))
continue;
}
else
{
if (serv->flags & SERV_HAS_DOMAIN)
continue;
}
serv->flags &= ~SERV_MARK;
break;
}
if (!serv && (serv = whine_malloc(sizeof (struct server))))
{
/* Not found, create a new one. */
memset(serv, 0, sizeof(struct server));
if (domain && !(serv->domain = whine_malloc(strlen(domain)+1)))
{
free(serv);
serv = NULL;
}
else
{
serv->next = daemon->servers;
daemon->servers = serv;
serv->flags = SERV_FROM_DBUS;
if (domain)
{
strcpy(serv->domain, domain);
serv->flags |= SERV_HAS_DOMAIN;
}
}
}
if (serv)
{
if (interface)
strcpy(serv->interface, interface);
else
serv->interface[0] = 0;
if (source_addr->in.sin_family == AF_INET &&
addr->in.sin_addr.s_addr == 0 &&
serv->domain)
serv->flags |= SERV_NO_ADDR;
else
{
serv->flags &= ~SERV_NO_ADDR;
serv->addr = *addr;
serv->source_addr = *source_addr;
}
}
}
static void mark_dbus(void)
{
struct server *serv;
/* mark everything from DBUS */
for (serv = daemon->servers; serv; serv = serv->next)
if (serv->flags & SERV_FROM_DBUS)
serv->flags |= SERV_MARK;
}
static void cleanup_dbus()
{ {
struct server *serv, *tmp, **up; struct server *serv, *tmp, **up;
/* unlink and free anything still marked. */
for (serv = daemon->servers, up = &daemon->servers; serv; serv = tmp)
{
tmp = serv->next;
if (serv->flags & SERV_MARK)
{
server_gone(serv);
*up = serv->next;
if (serv->domain)
free(serv->domain);
free(serv);
}
else
up = &serv->next;
}
}
static void dbus_read_servers(DBusMessage *message)
{
DBusMessageIter iter; DBusMessageIter iter;
union mysockaddr addr, source_addr; union mysockaddr addr, source_addr;
char *domain; char *domain;
dbus_message_iter_init(message, &iter); dbus_message_iter_init(message, &iter);
/* mark everything from DBUS */ mark_dbus();
for (serv = daemon->servers; serv; serv = serv->next)
if (serv->flags & SERV_FROM_DBUS)
serv->flags |= SERV_MARK;
while (1) while (1)
{ {
@@ -171,6 +272,7 @@ static void dbus_read_servers(DBusMessage *message)
/* At the end */ /* At the end */
break; break;
/* process each domain */
do { do {
if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING) if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING)
{ {
@@ -181,83 +283,118 @@ static void dbus_read_servers(DBusMessage *message)
domain = NULL; domain = NULL;
if (!skip) if (!skip)
{ add_update_server(&addr, &source_addr, NULL, domain);
/* See if this is already there, and unmark */
for (serv = daemon->servers; serv; serv = serv->next)
if ((serv->flags & SERV_FROM_DBUS) &&
(serv->flags & SERV_MARK))
{
if (!(serv->flags & SERV_HAS_DOMAIN) && !domain)
{
serv->flags &= ~SERV_MARK;
break;
}
if ((serv->flags & SERV_HAS_DOMAIN) &&
domain &&
hostname_isequal(domain, serv->domain))
{
serv->flags &= ~SERV_MARK;
break;
}
}
if (!serv && (serv = whine_malloc(sizeof (struct server)))) } while (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING);
{
/* Not found, create a new one. */
memset(serv, 0, sizeof(struct server));
if (domain)
serv->domain = whine_malloc(strlen(domain)+1);
if (domain && !serv->domain)
{
free(serv);
serv = NULL;
}
else
{
serv->next = daemon->servers;
daemon->servers = serv;
serv->flags = SERV_FROM_DBUS;
if (domain)
{
strcpy(serv->domain, domain);
serv->flags |= SERV_HAS_DOMAIN;
}
}
}
if (serv)
{
if (source_addr.in.sin_family == AF_INET &&
addr.in.sin_addr.s_addr == 0 &&
serv->domain)
serv->flags |= SERV_NO_ADDR;
else
{
serv->flags &= ~SERV_NO_ADDR;
serv->addr = addr;
serv->source_addr = source_addr;
}
}
}
} while (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING);
} }
/* unlink and free anything still marked. */ /* unlink and free anything still marked. */
for (serv = daemon->servers, up = &daemon->servers; serv; serv = tmp) cleanup_dbus();
}
static DBusMessage* dbus_read_servers_ex(DBusMessage *message)
{
DBusMessageIter iter, array_iter, string_iter;
DBusMessage *error = NULL;
const char *addr_err;
if (!dbus_message_iter_init(message, &iter))
{ {
tmp = serv->next; return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
if (serv->flags & SERV_MARK) "Failed to initialize dbus message iter");
{
server_gone(serv);
*up = serv->next;
free(serv);
}
else
up = &serv->next;
} }
/* check that the message contains an array of arrays */
if ((dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) ||
(dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_ARRAY))
{
return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
"Expected array of string arrays");
}
mark_dbus();
/* array_iter points to each "as" element in the outer array */
dbus_message_iter_recurse(&iter, &array_iter);
while (dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_INVALID)
{
const char *str = NULL;
union mysockaddr addr, source_addr;
char interface[IF_NAMESIZE];
char *str_addr;
/* check the types of the struct and its elements */
if ((dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_ARRAY) ||
(dbus_message_iter_get_element_type(&array_iter) != DBUS_TYPE_STRING))
{
error = dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
"Expected inner array of strings");
break;
}
/* string_iter points to each "s" element in the inner array */
dbus_message_iter_recurse(&array_iter, &string_iter);
if (dbus_message_iter_get_arg_type(&string_iter) != DBUS_TYPE_STRING)
{
/* no IP address given */
error = dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
"Expected IP address");
break;
}
dbus_message_iter_get_basic(&string_iter, &str);
if (!str || !strlen (str))
{
error = dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
"Empty IP address");
break;
}
memset(&addr, 0, sizeof(addr));
memset(&source_addr, 0, sizeof(source_addr));
memset(&interface, 0, sizeof(interface));
/* dup the string because it gets modified during parsing */
str_addr = strdup(str);
if (!str_addr)
{
error = dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
"Out of memory parsing IP address");
break;
}
/* parse the IP address */
addr_err = parse_server(str_addr, &addr, &source_addr, &interface, NULL);
free(str_addr);
if (addr_err)
{
error = dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS,
"Invalid IP address '%s': %s",
str, addr_err);
break;
}
/* jump past the address to the domain list (if any) */
dbus_message_iter_next (&string_iter);
/* parse domains and add each server/domain pair to the list */
do {
str = NULL;
if (dbus_message_iter_get_arg_type(&string_iter) == DBUS_TYPE_STRING)
dbus_message_iter_get_basic(&string_iter, &str);
dbus_message_iter_next (&string_iter);
add_update_server(&addr, &source_addr, interface, str);
} while (dbus_message_iter_get_arg_type(&string_iter) == DBUS_TYPE_STRING);
/* jump to next element in outer array */
dbus_message_iter_next(&array_iter);
}
cleanup_dbus();
return error;
} }
DBusHandlerResult message_handler(DBusConnection *connection, DBusHandlerResult message_handler(DBusConnection *connection,
@@ -265,11 +402,10 @@ DBusHandlerResult message_handler(DBusConnection *connection,
void *user_data) void *user_data)
{ {
char *method = (char *)dbus_message_get_member(message); char *method = (char *)dbus_message_get_member(message);
DBusMessage *reply = NULL;
if (dbus_message_is_method_call(message, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) if (dbus_message_is_method_call(message, DBUS_INTERFACE_INTROSPECTABLE, "Introspect"))
{ {
DBusMessage *reply;
/* string length: "%s" provides space for termination zero */ /* string length: "%s" provides space for termination zero */
if (!introspection_xml && if (!introspection_xml &&
(introspection_xml = whine_malloc(strlen(introspection_xml_template) + strlen(daemon->dbus_name)))) (introspection_xml = whine_malloc(strlen(introspection_xml_template) + strlen(daemon->dbus_name))))
@@ -278,20 +414,15 @@ DBusHandlerResult message_handler(DBusConnection *connection,
if (introspection_xml) if (introspection_xml)
{ {
reply = dbus_message_new_method_return(message); reply = dbus_message_new_method_return(message);
dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection_xml, DBUS_TYPE_INVALID); dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection_xml, DBUS_TYPE_INVALID);
dbus_connection_send (connection, reply, NULL);
dbus_message_unref (reply);
} }
} }
else if (strcmp(method, "GetVersion") == 0) else if (strcmp(method, "GetVersion") == 0)
{ {
char *v = VERSION; char *v = VERSION;
DBusMessage *reply = dbus_message_new_method_return(message); reply = dbus_message_new_method_return(message);
dbus_message_append_args(reply, DBUS_TYPE_STRING, &v, DBUS_TYPE_INVALID); dbus_message_append_args(reply, DBUS_TYPE_STRING, &v, DBUS_TYPE_INVALID);
dbus_connection_send (connection, reply, NULL);
dbus_message_unref (reply);
} }
else if (strcmp(method, "SetServers") == 0) else if (strcmp(method, "SetServers") == 0)
{ {
@@ -299,6 +430,12 @@ DBusHandlerResult message_handler(DBusConnection *connection,
dbus_read_servers(message); dbus_read_servers(message);
check_servers(); check_servers();
} }
else if (strcmp(method, "SetServersEx") == 0)
{
my_syslog(LOG_INFO, _("setting upstream servers from DBus"));
reply = dbus_read_servers_ex(message);
check_servers();
}
else if (strcmp(method, "ClearCache") == 0) else if (strcmp(method, "ClearCache") == 0)
clear_cache_and_reload(dnsmasq_time()); clear_cache_and_reload(dnsmasq_time());
else else
@@ -306,8 +443,17 @@ DBusHandlerResult message_handler(DBusConnection *connection,
method = user_data; /* no warning */ method = user_data; /* no warning */
return (DBUS_HANDLER_RESULT_HANDLED); /* If no reply or no error, return nothing */
if (!reply)
reply = dbus_message_new_method_return(message);
if (reply)
{
dbus_connection_send (connection, reply, NULL);
dbus_message_unref (reply);
}
return (DBUS_HANDLER_RESULT_HANDLED);
} }

View File

@@ -928,6 +928,8 @@ void reread_dhcp(void);
void set_option_bool(unsigned int opt); void set_option_bool(unsigned int opt);
void reset_option_bool(unsigned int opt); void reset_option_bool(unsigned int opt);
struct hostsfile *expand_filelist(struct hostsfile *list); struct hostsfile *expand_filelist(struct hostsfile *list);
char *parse_server(char *arg, union mysockaddr *addr,
union mysockaddr *source_addr, char *interface, int *flags);
/* forward.c */ /* forward.c */
void reply_query(int fd, int family, time_t now); void reply_query(int fd, int family, time_t now);

View File

@@ -621,6 +621,93 @@ static char *set_prefix(char *arg)
return arg; return arg;
} }
char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_addr, char *interface, int *flags)
{
int source_port = 0, serv_port = NAMESERVER_PORT;
char *portno, *source;
#ifdef HAVE_IPV6
int scope_index = 0;
char *scope_id;
#endif
if ((source = split_chr(arg, '@')) && /* is there a source. */
(portno = split_chr(source, '#')) &&
!atoi_check16(portno, &source_port))
return _("bad port");
if ((portno = split_chr(arg, '#')) && /* is there a port no. */
!atoi_check16(portno, &serv_port))
return _("bad port");
#ifdef HAVE_IPV6
scope_id = split_chr(arg, '%');
#endif
if ((addr->in.sin_addr.s_addr = inet_addr(arg)) != (in_addr_t) -1)
{
addr->in.sin_port = htons(serv_port);
addr->sa.sa_family = source_addr->sa.sa_family = AF_INET;
#ifdef HAVE_SOCKADDR_SA_LEN
source_addr->in.sin_len = addr->in.sin_len = sizeof(struct sockaddr_in);
#endif
source_addr->in.sin_addr.s_addr = INADDR_ANY;
source_addr->in.sin_port = htons(daemon->query_port);
if (source)
{
if (flags)
*flags |= SERV_HAS_SOURCE;
source_addr->in.sin_port = htons(source_port);
if ((source_addr->in.sin_addr.s_addr = inet_addr(source)) == (in_addr_t) -1)
{
#if defined(SO_BINDTODEVICE)
source_addr->in.sin_addr.s_addr = INADDR_ANY;
strncpy(interface, source, IF_NAMESIZE - 1);
#else
return _("interface binding not supported");
#endif
}
}
}
#ifdef HAVE_IPV6
else if (inet_pton(AF_INET6, arg, &addr->in6.sin6_addr) > 0)
{
if (scope_id && (scope_index = if_nametoindex(scope_id)) == 0)
return _("bad interface name");
addr->in6.sin6_port = htons(serv_port);
addr->in6.sin6_scope_id = scope_index;
source_addr->in6.sin6_addr = in6addr_any;
source_addr->in6.sin6_port = htons(daemon->query_port);
source_addr->in6.sin6_scope_id = 0;
addr->sa.sa_family = source_addr->sa.sa_family = AF_INET6;
addr->in6.sin6_flowinfo = source_addr->in6.sin6_flowinfo = 0;
#ifdef HAVE_SOCKADDR_SA_LEN
addr->in6.sin6_len = source_addr->in6.sin6_len = sizeof(addr->in6);
#endif
if (source)
{
if (flags)
*flags |= SERV_HAS_SOURCE;
source_addr->in6.sin6_port = htons(source_port);
if (inet_pton(AF_INET6, source, &source_addr->in6.sin6_addr) == 0)
{
#if defined(SO_BINDTODEVICE)
source_addr->in6.sin6_addr = in6addr_any;
strncpy(interface, source, IF_NAMESIZE - 1);
#else
return _("interface binding not supported");
#endif
}
}
}
#endif
else
return _("bad address");
return NULL;
}
/* This is too insanely large to keep in-line in the switch */ /* This is too insanely large to keep in-line in the switch */
static int parse_dhcp_opt(char *errstr, char *arg, int flags) static int parse_dhcp_opt(char *errstr, char *arg, int flags)
{ {
@@ -1760,84 +1847,9 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
} }
else else
{ {
int source_port = 0, serv_port = NAMESERVER_PORT; char *err = parse_server(arg, &newlist->addr, &newlist->source_addr, newlist->interface, &newlist->flags);
char *portno, *source; if (err)
#ifdef HAVE_IPV6 ret_err(err);
int scope_index = 0;
char *scope_id;
#endif
if ((source = split_chr(arg, '@')) && /* is there a source. */
(portno = split_chr(source, '#')) &&
!atoi_check16(portno, &source_port))
ret_err(_("bad port"));
if ((portno = split_chr(arg, '#')) && /* is there a port no. */
!atoi_check16(portno, &serv_port))
ret_err(_("bad port"));
#ifdef HAVE_IPV6
scope_id = split_chr(arg, '%');
#endif
if ((newlist->addr.in.sin_addr.s_addr = inet_addr(arg)) != (in_addr_t) -1)
{
newlist->addr.in.sin_port = htons(serv_port);
newlist->source_addr.in.sin_port = htons(source_port);
newlist->addr.sa.sa_family = newlist->source_addr.sa.sa_family = AF_INET;
#ifdef HAVE_SOCKADDR_SA_LEN
newlist->source_addr.in.sin_len = newlist->addr.in.sin_len = sizeof(struct sockaddr_in);
#endif
if (source)
{
newlist->flags |= SERV_HAS_SOURCE;
if ((newlist->source_addr.in.sin_addr.s_addr = inet_addr(source)) == (in_addr_t) -1)
{
#if defined(SO_BINDTODEVICE)
newlist->source_addr.in.sin_addr.s_addr = INADDR_ANY;
strncpy(newlist->interface, source, IF_NAMESIZE - 1);
#else
ret_err(_("interface binding not supported"));
#endif
}
}
else
newlist->source_addr.in.sin_addr.s_addr = INADDR_ANY;
}
#ifdef HAVE_IPV6
else if (inet_pton(AF_INET6, arg, &newlist->addr.in6.sin6_addr) > 0)
{
if (scope_id && (scope_index = if_nametoindex(scope_id)) == 0)
ret_err(_("bad interface name"));
newlist->addr.in6.sin6_port = htons(serv_port);
newlist->addr.in6.sin6_scope_id = scope_index;
newlist->source_addr.in6.sin6_port = htons(source_port);
newlist->source_addr.in6.sin6_scope_id = 0;
newlist->addr.sa.sa_family = newlist->source_addr.sa.sa_family = AF_INET6;
newlist->addr.in6.sin6_flowinfo = newlist->source_addr.in6.sin6_flowinfo = 0;
#ifdef HAVE_SOCKADDR_SA_LEN
newlist->addr.in6.sin6_len = newlist->source_addr.in6.sin6_len = sizeof(newlist->addr.in6);
#endif
if (source)
{
newlist->flags |= SERV_HAS_SOURCE;
if (inet_pton(AF_INET6, source, &newlist->source_addr.in6.sin6_addr) == 0)
{
#if defined(SO_BINDTODEVICE)
newlist->source_addr.in6.sin6_addr = in6addr_any;
strncpy(newlist->interface, source, IF_NAMESIZE - 1);
#else
ret_err(_("interface binding not supported"));
#endif
}
}
else
newlist->source_addr.in6.sin6_addr = in6addr_any;
}
#endif
else
ret_err(gen_err);
} }
serv = newlist; serv = newlist;
@@ -1846,6 +1858,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
serv->next->flags = serv->flags; serv->next->flags = serv->flags;
serv->next->addr = serv->addr; serv->next->addr = serv->addr;
serv->next->source_addr = serv->source_addr; serv->next->source_addr = serv->source_addr;
strcpy(serv->next->interface, serv->interface);
serv = serv->next; serv = serv->next;
} }
serv->next = daemon->servers; serv->next = daemon->servers;