I'm trying to use the evdns client API for the first time (in tinc-1.1),
and I've noticed a few things:

1. There's no threadsafe API (see ticket 1827379). This is not necessary
for tinc but is good to have in general.

2. There's no way to cancel an outstanding resolve. (see ticket 1827307)
this is important for tinc, which can stop a connection attempt for
reasons of its own. I either need this or to allocate/track a resolver
structure on my own so the callback doesn't crash the program.

3. There's a resolve function for each address family, so callers have
to explicitly support IPv6 with extra code. This would not be necessary
if libevent were to provide a getaddrinfo()-style interface.

I propose a new API to address all of these points in a unified way. I
have a starting point below. I only put in a "cooked" interface because
that's what I would like for my current program, but I would not object
to a "raw" interface that shares the same "struct evdns_client" and
"evdns_client_op". (In fact, one would be necessary internally to
implement this side-by-side with the existing API.)

My primary question to reviewers here is source vs. binary compatibility
of future versions. I'm tempted to shoot for binary compatibility, but
I'm not sure if it's worth it given that other libevent APIs ("struct
event", "struct bufferevent") have chosen the other route. ("struct
evhttp_request" seems to be on the fence - the structure is in the
public header, but the comments suggest you should use accessors instead.)

Here are the event_dns.h additions:

/**
 * Create a new DNS resolver client.
 * @param base Operate with the given event_base
 * @param attrs Use the specified initialization attributes.
 *              NULL is currently the only acceptable value.
 *              (To reviewers: this is for future expansion; it
 *              could hold evdns_resolv_conf_parse arguments,
 *              among other things.)
 */
struct evdns_client *evdns_client_new(struct event_base *base,
                                      struct event_resolver_attr
                                          *attrs);

/**
 * Free the DNS resolver client.
 * All outstanding operations will be canceled.
 */
void evdns_client_free(struct evdns_client *);

/**
 * A cancelable DNS client operation.
 * As with "struct event", the caller must keep this structure
 * in-place from evdns_resolve_*() to callback or
 * evdns_request_cancel().
 *
 * (To reviewers: the definition should be fairly immutable for
 * binary compatibility; maybe just a pointer to the
 * "struct request".)
 */
struct evdns_client_op;

/** Cancels a pending DNS resolution. */
int evdns_client_op_cancel(struct evdns_client *,
                           struct evdns_client_op *);

/**
 * Results from evdns's "cooked" client resolver.
 *
 * (To reviewers: this is a structure rather than direct args
 * to allow future expansion without breaking source compatibility.
 * Because the caller never does the allocation, we could even
 * maintain binary compatibility by making this an opaque structure
 * with accessor functions.)
 */
struct evdns_cooked_results {
    /**
     * Time (in seconds) until cache expiry.
     *
     * (To reviewers: I'm considering the expiry directly available
     * as "expiry" and exposing gettime() as event_gettime() for
     * comparison. It seems most callers wanting to use the TTL
     * will have to do this translation in the callback, and
     * perhaps reimplement gettime() for a stable clock.)
     */
    int ttl;

    /**
     * Individual results, which must be freed with
    struct evdns_cooked_result *results;
};

/**
 * A single cooked result.
 *
 * (To reviewers: could also be opaqued for binary compatibility
 * with member accessors or even
 * evdns_cooked_result_{socket,connect,bind,str}().)
 */
struct evdns_cooked_result {
    struct evdns_cooked_result *next;

    /**
     * Socket family (for first argument of socket()).
     * (To reviewers: sa.sa_family may do instead? I'm not sure
     * why getaddrinfo() has a member like this.)
     */
    int sock_family;

    /** Socket type (for second argument of socket()). */
    int sock_type; /**< for socket() */

    /** Socket protocol (for third argument of socket()). */
    int sock_protocol;

    /**
     * Socket length (for third argument of connect() and bind()).
     * Note sa.sa_len is not portable.
     */
    socklen_t salen;

    struct sockaddr sa; /* MUST BE LAST - HIDDEN EXTRA SPACE HERE */
};

/**
 * Free cooked results.
 * @param res As with free() itself, may be NULL.
 */
void evdns_cooked_results_free(struct evdns_cooked_results *res);

/**
 * Cooked results callback.
 * @param err Can be used with evdns_err_to_string
 * @param res If non-null, results. Must be freed with
 *            evdns_cooked_results_free().
 */
void (*evdns_cooked_resolve_cb_t)(int err,
                                  struct evdns_cooked_results *res,
                                  void *cbargs);

/**
 * Default cooked request flags.
 * This might change to something like EVDNS_COOKED_ADDRCONFIG in
 * the future as flags are added. Applications should use this
 * rather than 0 to follow these changes when recompiled against
 * newer versions of libevent.
 */
#define EVDNS_COOKED_DEFAULT 0

/**
 * Obtains "cooked" DNS results (ready-to-eat sockaddrs).
 *
 * (To reviewers: flags could match some of the capabilities of
 * getaddrinfo() plus perhaps SRV and MX lookups. If more
 * flexibility is desired than fit into a flags variable, we could
 * use a structure here as we do for results.)
 *
 * @param op An uninitialized evdns_client_op, to be kept until
 *           callback or evdns_resolve_cancel
 * @param flags Must be EVDNS_COOKED_DEFAULT at present.
 * @param socktype May be 0 (any), SOCK_STREAM, SOCK_DGRAM, etc.
 */
int evdns_client_op_init_cooked(struct evdns_client *,
                                struct evdns_client_op *op,
                                const char *hostname,
                                const char *servname,
                                int flags,
                                int socktype,
                                evdns_cooked_resolve_cb_t cb,
                                void *cbargs);

/**
 * Translate a socket address to a string without resolution.
 * This is intended as debugging output, currently as follows:
 * <pre>
 *     AF_INET:  127.0.0.1:22
 *     AF_INET6: [0:0:0:0:0:0:0:1]:22
 *     other:    family=4
 * </pre>
 * @param buf The buffer to write into.
 *            On platforms with C99 snprintf(), may be null;
 *            buflen must be 0 in this case.
 * @param buflen The length of the buffer.
 * @param required_buflen If non-null, on a platform with C99
 *                        snprintf(), the number of bytes of
 *                        buffer required, including trailing '\0'.
 * @return On success, 0.
 *         On failure, -1 with errno set appropriately.
 */
int evdns_sockaddr_str(const struct sockaddr *,
                       char *buf,
                       size_t buflen,
                       size_t *required_buflen_p);
_______________________________________________
Libevent-users mailing list
Libevent-users@monkey.org
http://monkeymail.org/mailman/listinfo/libevent-users

Reply via email to