[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Qemu-devel] [PATCH 8/8] io: introduce a DNS resolver API
From: |
Daniel P. Berrange |
Subject: |
Re: [Qemu-devel] [PATCH 8/8] io: introduce a DNS resolver API |
Date: |
Fri, 6 Jan 2017 12:19:22 +0000 |
User-agent: |
Mutt/1.7.1 (2016-10-04) |
On Thu, Jan 05, 2017 at 04:51:53PM -0600, Eric Blake wrote:
> On 01/05/2017 10:03 AM, Daniel P. Berrange wrote:
> > + * <example>
> > + * <title>Resolving addresses synchronously</title>
> > + * <programlisting>
> > + * int mylisten(SocketAddress *addr, Error **errp) {
> > + * QIODNSResolver *resolver = qio_dns_resolver_get_instance();
> > + * SocketAddress **rawaddrs = NULL;
> > + * size_t nrawaddrs = 0;
> > + * Error *err = NULL;
> > + * QIOChannel **socks = NULL;
> > + * size_t nsocks = 0;
> > + *
> > + * if (qio_dns_resolver_lookup_sync(dns, addr, &nrawaddrs,
> > + * &rawaddrs, &err) < 0) {
> > + * error_propagate(errp, err);
>
> You aren't using the local err here; why not just call
> qio_dns_resolver_lookup_sync(, errp) directly, then you don't need to
> propagate?
Yep
> Is it guaranteed that 'err' is set only when
> qio_dns_resolver_lookup_sync() returns negative, and conversely that
> nrawaddrs is > 0 when it succeeded? It matters later...[3]
getaddrinfo() returns an error code of EAI_NODATA if the dns name
has no corresponding records, so we should be guaranteed > 0 for
nrawaddrs on success.
>
> > + * return -1;
> > + * }
> > + *
> > + * for (i = 0; i < nrawaddrs; i++) {
> > + * QIOChannel *sock = qio_channel_new();
> > + * Error *local_err = NULL;
>
> It looks weird that you are declaring two different local Error*
> variables. But I read further down, and finally figured out why...[1]
Yeah, the peculiarty of getaddrinfo where you should only report an
error if every address failed.
>
> > + * qio_channel_listen_sync(sock, rawaddrs[i], &local_err);
> > + * if (local_err && !err) {
> > + * error_propagate(err, local_err);
>
> Won't compile as written; you need error_propagate(&err, local_err);
>
> error_propagate() is safe to call more than once (first error wins), so
> you could simplify this to 'if (local_err) {'. In fact, if you don't
> rewrite the condition, then on the second error, you end up falling
> through to the else...[2]
>
> > + * } else {
> > + * socks = g_renew(QIOChannelSocket *, socks, nsocks + 1);
>
> [2]...and allocating a slot in spite of the failure, plus leaking
> local_err at the end of the loop. Oops.
Oh, nice spot.
> As long as nsocks is not going to be arbitrarily huge, it shouldn't
> matter that this particular style of array growth is O(n^2) in
> complexity (quadratic complexity is fine on small lists, but for large
> lists, you need to realloc in geometrically-larger increments to keep
> things amortized to linear costs that otherwise explode due to copying
> old-to-new array on each iteration).
Yeah, realistically 99% of the time there will be 2 results (ipv4 and
ipv6). Only in niche cases where there's a host with multiple public
IPs will we have more addressses and even then it'll be less than
10. So the complexity is not an issue here IMHO - the actual dns
network lookup time will dwarf any inefficiency.
> > + * } else {
> > + * error_free(err);
>
> [1]...and this is why you have two error variables. You've chosen to
> explicitly succeed if you get at least one socket open, even in the case
> where resolution returns multiple possible addresses and some of them fail.
Yep, glibc recommended behaviour for listening with getaddrinfo().
> > +/**
> > + * qio_dns_resolver_lookup_sync:
> > + * @resolver: the DNS resolver instance
> > + * @addr: the address to resolve
> > + * @naddr: pointer to hold number of resolved addresses
> > + * @addrs: pointer to hold resolved addresses
> > + * @errp: pointer to NULL initialized error object
> > + *
> > + * This will attempt to resolve the address provided
> > + * in @addr. If resolution succeeds, @addrs will be filled
> > + * with all the resolved addresses. @naddrs will specify
> > + * the number of entries allocated in @addrs. The caller
> > + * is responsible for freeing each entry in @addrs, as
> > + * well as @addrs itself.
>
> Where in your example code above do you free the memory? Or is that a
> leak you need to plug?
Opps, yes, a leak
> Are we guaranteed that naddrs > 0 on success? (point [3] above)
Yes and will document it.
>
> > + *
> > + * DNS resolution will be done synchronously so execution
> > + * of the caller may be blocked for an arbitrary length
> > + * of time.
> > + *
> > + * Returns: 0 if resolution was successful, -1 on error
> > + */
> > +int qio_dns_resolver_lookup_sync(QIODNSResolver *resolver,
> > + SocketAddress *addr,
> > + size_t *naddrs,
> > + SocketAddress ***addrs,
> > + Error **errp);
> > +
> > +/**
> > + * qio_dns_resolver_lookup_sync:
>
> s/sync/async/
>
> > + * @resolver: the DNS resolver instance
> > + * @addr: the address to resolve
> > + * @naddr: pointer to hold number of resolved addresses
> > + * @addrs: pointer to hold resolved addresses
> > + * @errp: pointer to NULL initialized error object
>
> Wrong parameters; naddr/addrs/errp should be replaced with
> func/opaque/notify.
>
> > + *
> > + * This will attempt to resolve the address provided
> > + * in @addr. The callback @func will be invoked when
> > + * resolution has either completed or failed. On
> > + * success, the @func should call the method
> > + * qio_dns_resolver_lookup_result() to obtain the
> > + * results.
> > + *
> > + * DNS resolution will be done asynchronously so execution
> > + * of the caller will not be blocked.
> > + */
> > +void qio_dns_resolver_lookup_async(QIODNSResolver *resolver,
> > + SocketAddress *addr,
> > + QIOTaskFunc func,
> > + gpointer opaque,
> > + GDestroyNotify notify);
> > +
> > +/**
> > + * qio_dns_resolver_lookup_result:
> > + * @resolver: the DNS resolver instance
> > + * @task: the task object to get results for
> > + * @naddr: pointer to hold number of resolved addresses
> > + * @addrs: pointer to hold resolved addresses
> > + *
> > + * This method should be called from the callback passed
> > + * to qio_dns_resolver_lookup_async() in order to obtain
> > + * results. @addrs will be filled with all the resolved
> > + * addresses. @naddrs will specify the number of entries
> > + * allocated in @addrs. The caller is responsible for
> > + * freeing each entry in @addrs, as well as @addrs itself.
>
> Again, the free seems to be missing in the example above.
Yep, will fix.
> > +static int qio_dns_resolver_lookup_sync_inet(QIODNSResolver *resolver,
> > + SocketAddress *addr,
> > + size_t *naddrs,
> > + SocketAddress ***addrs,
> > + Error **errp)
> > +{
> > + struct addrinfo ai, *res, *e;
> > + InetSocketAddress *iaddr = addr->u.inet.data;
> > + char port[33];
> > + char uaddr[INET6_ADDRSTRLEN + 1];
> > + char uport[33];
> > + int rc;
> > + Error *err = NULL;
> > + size_t i;
> > +
> > + *naddrs = 0;
> > + *addrs = NULL;
> > +
> > + memset(&ai, 0, sizeof(ai));
> > + ai.ai_flags = AI_PASSIVE;
> > + if (iaddr->numeric) {
>
> 'iaddr->has_numeric && iaddr->numeric', unless you make sure that all
> possible initialization paths have a sane value of iaddr->numeric==false
> even when iaddr->has_numeric is false (qapi guarantees 0 initialization,
> but I'm not sure if all SocketAddress come from qapi).
Yeah, makes sense to be explicit about it anyway
> > + /* create socket + bind */
> > + for (i = 0, e = res; e != NULL; i++, e = e->ai_next) {
> > + SocketAddress *newaddr = g_new0(SocketAddress, 1);
> > + InetSocketAddress *newiaddr = g_new0(InetSocketAddress, 1);
> > + newaddr->u.inet.data = newiaddr;
> > + newaddr->type = SOCKET_ADDRESS_KIND_INET;
> > +
> > + getnameinfo((struct sockaddr *)e->ai_addr, e->ai_addrlen,
> > + uaddr, INET6_ADDRSTRLEN, uport, 32,
> > + NI_NUMERICHOST | NI_NUMERICSERV);
> > +
> > + *newiaddr = (InetSocketAddress){
> > + .host = g_strdup(uaddr),
> > + .port = g_strdup(uport),
> > + .numeric = true,
>
> Also need .has_numeric = true
>
> > + .has_to = iaddr->has_to,
> > + .to = iaddr->to,
> > + .has_ipv4 = false,
> > + .has_ipv6 = false,
> > + };
> > +
> > + (*addrs)[i] = newaddr;
> > + }
> > + freeaddrinfo(res);
> > + return 0;
> > +}
> > +
> > +
> > +static int qio_dns_resolver_lookup_sync_unix(QIODNSResolver *resolver,
> > + SocketAddress *addr,
> > + size_t *naddrs,
> > + SocketAddress ***addrs,
> > + Error **errp)
> > +{
> > + *naddrs = 1;
> > + *addrs = g_new0(SocketAddress *, 1);
> > + (*addrs)[0] = QAPI_CLONE(SocketAddress, addr);
>
> Cool - I'm glad to see more use of my clone visitor :)
>
> > +
> > + return 0;
> > +}
> > +
> > +
> > +int qio_dns_resolver_lookup_sync(QIODNSResolver *resolver,
> > + SocketAddress *addr,
> > + size_t *naddrs,
> > + SocketAddress ***addrs,
> > + Error **errp)
> > +{
> > + switch (addr->type) {
> > + case SOCKET_ADDRESS_KIND_INET:
> > + return qio_dns_resolver_lookup_sync_inet(resolver,
> > + addr,
> > + naddrs,
> > + addrs,
> > + errp);
> > +
> > + case SOCKET_ADDRESS_KIND_UNIX:
> > + return qio_dns_resolver_lookup_sync_unix(resolver,
> > + addr,
> > + naddrs,
> > + addrs,
> > + errp);
> > +
> > + default:
>
> Do we need to play with Stefan's vsock stuff?
Oh yes, I didn't realize that was merged. It should be just
cloned as we do for unix sock.
Regards,
Daniel
--
|: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org -o- http://virt-manager.org :|
|: http://entangle-photo.org -o- http://search.cpan.org/~danberr/ :|
- Re: [Qemu-devel] [PATCH 4/8] io: add ability to associate an opaque "result" with with a task, (continued)