Pass around struct android_net_context for better behaviour

Group network context elements in to a single struct and
add a version of android_getaddrinfofornet() that accepts it.

The introduction of UID-based routing means that the UID is an
integral part of the network context when evaluating connectivity,
sorting addresses, etc.

Also, introduce a distinction between DNS netids/marks and those
expected to be used by the application.  This can be important
when the network an application is using is not the same as the
network on which DNS queries will be issued.

Additionally, de-duplicate the UDP connect logic (collapse both
_test_connect() and _find_src_addr() into just the latter).

Bug: 19470192
Bug: 20733156
Bug: 21832279
Change-Id: If16c2f4744695f507993afdac078ca105eb5d3e4
This commit is contained in:
Erik Kline 2015-06-25 14:27:34 +09:00
parent eeb9f5e416
commit 01e37c9665
3 changed files with 73 additions and 48 deletions

View File

@ -53,10 +53,37 @@ struct addrinfo;
#define __used_in_netd __attribute__((visibility ("default"))) #define __used_in_netd __attribute__((visibility ("default")))
/*
* A struct to capture context relevant to network operations.
*
* Application and DNS netids/marks can differ from one another under certain
* circumstances, notably when a VPN applies to the given uid's traffic but the
* VPN network does not have its own DNS servers explicitly provisioned.
*
* The introduction of per-UID routing means the uid is also an essential part
* of the evaluation context. Its proper uninitialized value is
* NET_CONTEXT_INVALID_UID.
*/
struct android_net_context {
unsigned app_netid;
unsigned app_mark;
unsigned dns_netid;
unsigned dns_mark;
uid_t uid;
} __attribute__((packed));
#define NET_CONTEXT_INVALID_UID ((uid_t)-1)
struct hostent *android_gethostbyaddrfornet(const void *, socklen_t, int, unsigned, unsigned) __used_in_netd; struct hostent *android_gethostbyaddrfornet(const void *, socklen_t, int, unsigned, unsigned) __used_in_netd;
struct hostent *android_gethostbynamefornet(const char *, int, unsigned, unsigned) __used_in_netd; struct hostent *android_gethostbynamefornet(const char *, int, unsigned, unsigned) __used_in_netd;
int android_getaddrinfofornet(const char *, const char *, const struct addrinfo *, unsigned, int android_getaddrinfofornet(const char *, const char *, const struct addrinfo *, unsigned,
unsigned, struct addrinfo **) __used_in_netd; unsigned, struct addrinfo **) __used_in_netd;
/*
* TODO: consider refactoring android_getaddrinfo_proxy() to serve as an
* explore_fqdn() dispatch table method, with the below function only making DNS calls.
*/
int android_getaddrinfofornetcontext(const char *, const char *, const struct addrinfo *,
const struct android_net_context *, struct addrinfo **) __used_in_netd;
/* set name servers for a network */ /* set name servers for a network */
extern void _resolv_set_nameservers_for_net(unsigned netid, extern void _resolv_set_nameservers_for_net(unsigned netid,

View File

@ -218,7 +218,7 @@ struct res_target {
static int str2number(const char *); static int str2number(const char *);
static int explore_fqdn(const struct addrinfo *, const char *, static int explore_fqdn(const struct addrinfo *, const char *,
const char *, struct addrinfo **, unsigned netid, unsigned mark); const char *, struct addrinfo **, const struct android_net_context *);
static int explore_null(const struct addrinfo *, static int explore_null(const struct addrinfo *,
const char *, struct addrinfo **); const char *, struct addrinfo **);
static int explore_numeric(const struct addrinfo *, const char *, static int explore_numeric(const struct addrinfo *, const char *,
@ -244,6 +244,7 @@ static void _endhtent(FILE **);
static struct addrinfo *_gethtent(FILE **, const char *, static struct addrinfo *_gethtent(FILE **, const char *,
const struct addrinfo *); const struct addrinfo *);
static int _files_getaddrinfo(void *, void *, va_list); static int _files_getaddrinfo(void *, void *, va_list);
static int _find_src_addr(const struct sockaddr *, struct sockaddr *, unsigned , uid_t);
static int res_queryN(const char *, struct res_target *, res_state); static int res_queryN(const char *, struct res_target *, res_state);
static int res_searchN(const char *, struct res_target *, res_state); static int res_searchN(const char *, struct res_target *, res_state);
@ -359,29 +360,6 @@ str2number(const char *p)
return -1; return -1;
} }
/*
* Connect a UDP socket to a given unicast address. This will cause no network
* traffic, but will fail fast if the system has no or limited reachability to
* the destination (e.g., no IPv4 address, no IPv6 default route, ...).
*/
static int
_test_connect(int pf, struct sockaddr *addr, size_t addrlen, unsigned mark) {
int s = socket(pf, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
if (s < 0)
return 0;
if (mark != MARK_UNSET && setsockopt(s, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) < 0)
return 0;
int ret;
do {
ret = __connect(s, addr, addrlen);
} while (ret < 0 && errno == EINTR);
int success = (ret == 0);
do {
ret = close(s);
} while (ret < 0 && errno == EINTR);
return success;
}
/* /*
* The following functions determine whether IPv4 or IPv6 connectivity is * The following functions determine whether IPv4 or IPv6 connectivity is
* available in order to implement AI_ADDRCONFIG. * available in order to implement AI_ADDRCONFIG.
@ -392,24 +370,24 @@ _test_connect(int pf, struct sockaddr *addr, size_t addrlen, unsigned mark) {
* so checking for connectivity is the next best thing. * so checking for connectivity is the next best thing.
*/ */
static int static int
_have_ipv6(unsigned mark) { _have_ipv6(unsigned mark, uid_t uid) {
static const struct sockaddr_in6 sin6_test = { static const struct sockaddr_in6 sin6_test = {
.sin6_family = AF_INET6, .sin6_family = AF_INET6,
.sin6_addr.s6_addr = { // 2000:: .sin6_addr.s6_addr = { // 2000::
0x20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} 0x20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
}; };
sockaddr_union addr = { .in6 = sin6_test }; sockaddr_union addr = { .in6 = sin6_test };
return _test_connect(PF_INET6, &addr.generic, sizeof(addr.in6), mark); return _find_src_addr(&addr.generic, NULL, mark, uid) == 1;
} }
static int static int
_have_ipv4(unsigned mark) { _have_ipv4(unsigned mark, uid_t uid) {
static const struct sockaddr_in sin_test = { static const struct sockaddr_in sin_test = {
.sin_family = AF_INET, .sin_family = AF_INET,
.sin_addr.s_addr = __constant_htonl(0x08080808L) // 8.8.8.8 .sin_addr.s_addr = __constant_htonl(0x08080808L) // 8.8.8.8
}; };
sockaddr_union addr = { .in = sin_test }; sockaddr_union addr = { .in = sin_test };
return _test_connect(PF_INET, &addr.generic, sizeof(addr.in), mark); return _find_src_addr(&addr.generic, NULL, mark, uid) == 1;
} }
bool readBE32(FILE* fp, int32_t* result) { bool readBE32(FILE* fp, int32_t* result) {
@ -474,7 +452,7 @@ android_getaddrinfo_proxy(
int result_code = (int)strtol(buf, NULL, 10); int result_code = (int)strtol(buf, NULL, 10);
// verify the code itself // verify the code itself
if (result_code != DnsProxyQueryResult ) { if (result_code != DnsProxyQueryResult) {
fread(buf, 1, sizeof(buf), proxy); fread(buf, 1, sizeof(buf), proxy);
goto exit; goto exit;
} }
@ -588,6 +566,21 @@ getaddrinfo(const char *hostname, const char *servname,
int int
android_getaddrinfofornet(const char *hostname, const char *servname, android_getaddrinfofornet(const char *hostname, const char *servname,
const struct addrinfo *hints, unsigned netid, unsigned mark, struct addrinfo **res) const struct addrinfo *hints, unsigned netid, unsigned mark, struct addrinfo **res)
{
struct android_net_context netcontext = {
.app_netid = netid,
.app_mark = mark,
.dns_netid = netid,
.dns_mark = mark,
.uid = NET_CONTEXT_INVALID_UID,
};
return android_getaddrinfofornetcontext(hostname, servname, hints, &netcontext, res);
}
int
android_getaddrinfofornetcontext(const char *hostname, const char *servname,
const struct addrinfo *hints, const struct android_net_context *netcontext,
struct addrinfo **res)
{ {
struct addrinfo sentinel; struct addrinfo sentinel;
struct addrinfo *cur; struct addrinfo *cur;
@ -601,6 +594,7 @@ android_getaddrinfofornet(const char *hostname, const char *servname,
/* servname is allowed to be NULL */ /* servname is allowed to be NULL */
/* hints is allowed to be NULL */ /* hints is allowed to be NULL */
assert(res != NULL); assert(res != NULL);
assert(netcontext != NULL);
memset(&sentinel, 0, sizeof(sentinel)); memset(&sentinel, 0, sizeof(sentinel));
cur = &sentinel; cur = &sentinel;
pai = &ai; pai = &ai;
@ -731,7 +725,8 @@ android_getaddrinfofornet(const char *hostname, const char *servname,
ERR(EAI_NONAME); ERR(EAI_NONAME);
#if defined(__ANDROID__) #if defined(__ANDROID__)
int gai_error = android_getaddrinfo_proxy(hostname, servname, hints, res, netid); int gai_error = android_getaddrinfo_proxy(
hostname, servname, hints, res, netcontext->app_netid);
if (gai_error != EAI_SYSTEM) { if (gai_error != EAI_SYSTEM) {
return gai_error; return gai_error;
} }
@ -763,8 +758,8 @@ android_getaddrinfofornet(const char *hostname, const char *servname,
if (pai->ai_protocol == ANY && ex->e_protocol != ANY) if (pai->ai_protocol == ANY && ex->e_protocol != ANY)
pai->ai_protocol = ex->e_protocol; pai->ai_protocol = ex->e_protocol;
error = explore_fqdn(pai, hostname, servname, error = explore_fqdn(
&cur->ai_next, netid, mark); pai, hostname, servname, &cur->ai_next, netcontext);
while (cur && cur->ai_next) while (cur && cur->ai_next)
cur = cur->ai_next; cur = cur->ai_next;
@ -797,7 +792,8 @@ android_getaddrinfofornet(const char *hostname, const char *servname,
*/ */
static int static int
explore_fqdn(const struct addrinfo *pai, const char *hostname, explore_fqdn(const struct addrinfo *pai, const char *hostname,
const char *servname, struct addrinfo **res, unsigned netid, unsigned mark) const char *servname, struct addrinfo **res,
const struct android_net_context *netcontext)
{ {
struct addrinfo *result; struct addrinfo *result;
struct addrinfo *cur; struct addrinfo *cur;
@ -823,7 +819,7 @@ explore_fqdn(const struct addrinfo *pai, const char *hostname,
return 0; return 0;
switch (nsdispatch(&result, dtab, NSDB_HOSTS, "getaddrinfo", switch (nsdispatch(&result, dtab, NSDB_HOSTS, "getaddrinfo",
default_dns_files, hostname, pai, netid, mark)) { default_dns_files, hostname, pai, netcontext)) {
case NS_TRYAGAIN: case NS_TRYAGAIN:
error = EAI_AGAIN; error = EAI_AGAIN;
goto free; goto free;
@ -1763,13 +1759,13 @@ _rfc6724_compare(const void *ptr1, const void* ptr2)
* address. src_addr must be large enough to hold a struct sockaddr_in6. * address. src_addr must be large enough to hold a struct sockaddr_in6.
* *
* Returns 1 if a source address was found, 0 if the address is unreachable, * Returns 1 if a source address was found, 0 if the address is unreachable,
* and -1 if a fatal error occurred. If 0 or 1, the contents of src_addr are * and -1 if a fatal error occurred. If 0 or -1, the contents of src_addr are
* undefined. * undefined.
*/ */
/*ARGSUSED*/ /*ARGSUSED*/
static int static int
_find_src_addr(const struct sockaddr *addr, struct sockaddr *src_addr, unsigned mark) _find_src_addr(const struct sockaddr *addr, struct sockaddr *src_addr, unsigned mark, uid_t uid)
{ {
int sock; int sock;
int ret; int ret;
@ -1797,6 +1793,8 @@ _find_src_addr(const struct sockaddr *addr, struct sockaddr *src_addr, unsigned
} }
if (mark != MARK_UNSET && setsockopt(sock, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) < 0) if (mark != MARK_UNSET && setsockopt(sock, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) < 0)
return 0; return 0;
if (uid > 0 && uid != NET_CONTEXT_INVALID_UID && fchown(sock, uid, (gid_t)-1) < 0)
return 0;
do { do {
ret = __connect(sock, addr, len); ret = __connect(sock, addr, len);
} while (ret == -1 && errno == EINTR); } while (ret == -1 && errno == EINTR);
@ -1806,7 +1804,7 @@ _find_src_addr(const struct sockaddr *addr, struct sockaddr *src_addr, unsigned
return 0; return 0;
} }
if (getsockname(sock, src_addr, &len) == -1) { if (src_addr && getsockname(sock, src_addr, &len) == -1) {
close(sock); close(sock);
return -1; return -1;
} }
@ -1821,7 +1819,7 @@ _find_src_addr(const struct sockaddr *addr, struct sockaddr *src_addr, unsigned
/*ARGSUSED*/ /*ARGSUSED*/
static void static void
_rfc6724_sort(struct addrinfo *list_sentinel, unsigned mark) _rfc6724_sort(struct addrinfo *list_sentinel, unsigned mark, uid_t uid)
{ {
struct addrinfo *cur; struct addrinfo *cur;
int nelem = 0, i; int nelem = 0, i;
@ -1848,7 +1846,7 @@ _rfc6724_sort(struct addrinfo *list_sentinel, unsigned mark)
elems[i].ai = cur; elems[i].ai = cur;
elems[i].original_order = i; elems[i].original_order = i;
has_src_addr = _find_src_addr(cur->ai_addr, &elems[i].src_addr.generic, mark); has_src_addr = _find_src_addr(cur->ai_addr, &elems[i].src_addr.generic, mark, uid);
if (has_src_addr == -1) { if (has_src_addr == -1) {
goto error; goto error;
} }
@ -1879,12 +1877,11 @@ _dns_getaddrinfo(void *rv, void *cb_data, va_list ap)
struct addrinfo sentinel, *cur; struct addrinfo sentinel, *cur;
struct res_target q, q2; struct res_target q, q2;
res_state res; res_state res;
unsigned netid, mark; const struct android_net_context *netcontext;
name = va_arg(ap, char *); name = va_arg(ap, char *);
pai = va_arg(ap, const struct addrinfo *); pai = va_arg(ap, const struct addrinfo *);
netid = va_arg(ap, unsigned); netcontext = va_arg(ap, const struct android_net_context *);
mark = va_arg(ap, unsigned);
//fprintf(stderr, "_dns_getaddrinfo() name = '%s'\n", name); //fprintf(stderr, "_dns_getaddrinfo() name = '%s'\n", name);
memset(&q, 0, sizeof(q)); memset(&q, 0, sizeof(q));
@ -1913,8 +1910,8 @@ _dns_getaddrinfo(void *rv, void *cb_data, va_list ap)
q.anslen = sizeof(buf->buf); q.anslen = sizeof(buf->buf);
int query_ipv6 = 1, query_ipv4 = 1; int query_ipv6 = 1, query_ipv4 = 1;
if (pai->ai_flags & AI_ADDRCONFIG) { if (pai->ai_flags & AI_ADDRCONFIG) {
query_ipv6 = _have_ipv6(mark); query_ipv6 = _have_ipv6(netcontext->app_mark, netcontext->uid);
query_ipv4 = _have_ipv4(mark); query_ipv4 = _have_ipv4(netcontext->app_mark, netcontext->uid);
} }
if (query_ipv6) { if (query_ipv6) {
q.qtype = T_AAAA; q.qtype = T_AAAA;
@ -1966,8 +1963,8 @@ _dns_getaddrinfo(void *rv, void *cb_data, va_list ap)
* fully populate the thread private data here, but if we get down there * fully populate the thread private data here, but if we get down there
* and have a cache hit that would be wasted, so we do the rest there on miss * and have a cache hit that would be wasted, so we do the rest there on miss
*/ */
res_setnetid(res, netid); res_setnetid(res, netcontext->dns_netid);
res_setmark(res, mark); res_setmark(res, netcontext->dns_mark);
if (res_searchN(name, &q, res) < 0) { if (res_searchN(name, &q, res) < 0) {
__res_put_state(res); __res_put_state(res);
free(buf); free(buf);
@ -1999,7 +1996,7 @@ _dns_getaddrinfo(void *rv, void *cb_data, va_list ap)
} }
} }
_rfc6724_sort(&sentinel, netid); _rfc6724_sort(&sentinel, netcontext->app_mark, netcontext->uid);
__res_put_state(res); __res_put_state(res);

View File

@ -306,6 +306,7 @@ LIBC {
alphasort; alphasort;
alphasort64; alphasort64;
android_getaddrinfofornet; android_getaddrinfofornet;
android_getaddrinfofornetcontext;
android_gethostbyaddrfornet; android_gethostbyaddrfornet;
android_gethostbynamefornet; android_gethostbynamefornet;
android_set_abort_message; android_set_abort_message;