[DNS using LibEvent: high and low level functions]
Libevent
Fast portable non blocking network programming
Revision history | |||
edition | date | author | remarks |
V1.0 | 2016-11-15 | Zhou Yong | Libevent programming Chinese help document |
The document was created by Nick Mathewson from 2009 to 2012 based on the attribute noncommercial share like license agreement 3.0. The future version will use a less restrictive license
In addition, the source code example of this document is also based on the "3 clause" or "modification" clause of BSD For details, please refer to all terms of BSD documents Latest download address of this document:
english: http://libevent.org/
chinese: http://blog.csdn.net/zhouyongku/article/details/53431750
Please download and run "GI" tclonegit://github.com/nmathewson/libevent- book. Git "get the latest version of the source code described in this document
14. DNS using LibEvent: high and low level functions
LibEvent provides a small number of API s to solve DNS names and to implement simple DNS services
We will start with the high-level mechanism of name query, and then introduce the low-level mechanism and service mechanism
Note that the current DNS client implementation of LibEvent is limited and does not support TCP query, DNSSec or any record type. We hope to fix these problems in the future version of LibEvent, but not the current version
14.1 front page of body: Portable blocking name resolution
libevent provides a portable implementation of the standard getaddrinfo() interface for porting programs that already use blocking name resolution This alternative implementation is useful for programs that need to run on a platform that does not have the getaddrinfo() function or that does not follow the standard as our alternative function
The getaddrinfo() interface is defined in Section 6.1 of RFC 3493 For an overview of how libevent does not meet its consistent implementation, see the "compatibility tips" section below
Interface
struct evutil_addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; size_t ai_addrlen; char* ai_canonname; struct sockaddr* ai_addr; struct evutil_addrinfo* ai_next; }; #define EVUTIL_AI_PASSIVE / * ...* / #define EVUTIL_AI_CANONNAME / * ...* / #define EVUTIL_AI_NUMERICHOST / * ...* / #define EVUTIL_AI_NUMERICSERV / * ...* / #define EVUTIL_AI_V4MAPPED / * ...* / #define EVUTIL_AI_ALL / * ...* / #define EVUTIL_AI_ADDRCONFIG / * ...* / int evutil_getaddrinfo(const char* nodename, const char * servname, const struct evutil_addrinfo* hints, struct evutil_addrinfo ** res); void evutil_freeaddrinfo(struct evutil_addrinfo* ai); const char* evutil_gai_strerror(int err);
evutil_ The getaddrinfo () function attempts to parse the specified nodename and servname according to the rules given by hints and create an evutil_addrinfo structure linked list and store it in * res The function returns 0 on success and non - zero error code on failure
At least one of nodename and servname must be provided If nodename is provided, it is an IPv4 literal address (such as 127.0.0.1), an IPv6 literal address (such as:: 1), or a DNS name (such as www.example.com) If servname is provided, it is the symbolic name of a network service (such as https) or a string containing decimal port number (such as 443) If servname is not specified, the port number in * res will be zero
If nodename is not specified, the address in * res is either localhost (default) or "any" (if evutil_ai_passive is set)
Ai of hints_ The flags field indicates evutil_ How to query getaddrinfo? It can contain 0 or more of the following flags connected by or operation:
-
EVUTIL_AI_PASSIVE: this flag indicates that the address is used for listening instead of connection Generally, there is no difference between the two unless nodename is empty: for connections, an empty nodename represents localhost (127.0.0.1 or:: 1); For listening, an empty nodename means arbitrary (0.0.0.0 or:: 0)
-
EVUTIL_AI_CANONNAME: if this flag is set, the function attempts to open the AI_ Report the standard name in the canonname field
-
EVUTIL_AI_NUMERICHOST: if this flag is set, the function only parses IPv4 and IPv6 addresses of numeric type; If nodename requires a name query, the function returns EVUTIL_EAI_NONAME error
-
EVUTIL_AI_NUMERICSERV: if this flag is set, the function only resolves the service name of numeric type If servname is not empty or decimal integer, the function returns EVUTIL_EAI_NONAME error
-
EVUTIL_AI_V4MAPPED: this flag indicates that if ai_family is AF_INET6, but the IPv6 address cannot be found, the IPv4 address in the result should be returned in the form of v4 mapped IPv6 address Current evutil_getaddrinfo() does not support this flag unless the operating system supports it
-
EVUTIL_AI_ALL: if this flag and evutil are set_ AI_ V4mapped, the IPv4 address should be returned in the form of v4 mapped IPv6 address whether the result contains IPv6 address or not Current evutil_getaddrinfo() does not support this flag unless the operating system supports it
EVUTIL_AI_ADDRCONFIG: if this flag is set, the IPv4 address will be included in the result only if the system has a non local IPv4 address; Only when the system has a non - local IPv6 address, the result contains the IPv6 address
Ai of hints_ The family y field indicates evutil_ Which address should getaddrinfo () return The field value can be AF_INET, indicating that only IPv4 address is requested; It can also be AF_INET6, indicating that only IPv6 address is requested; Or use AF_UNSPEC means to request all available addresses
Ai of hints_ Socktype and AI_ The protocol field tells evutil_ How will getaddrinfo () use the returned address The meaning of these two field values is the same as that of the socktype and protocol parameters passed to the socket() function
When successful, the function creates a new evutil_ The addrinfo structure linked list is stored in * res, and each element of the linked list passes AI_ The next pointer points to the next element Because the linked list is allocated on the heap, you need to call evutil_freeaddrinfo()
If it fails, the function returns a numeric error code:
-
EVUTIL_EAI_ADDRFAMILY: the requested address family has no meaning for nodename
-
EVUTIL_EAI_AGAIN: a recoverable error occurred in name resolution. Please try again later
-
EVUTIL_EAI_FAIL: an unrecoverable error occurred in name resolution: the parser or DNS server may have crashed
-
EVUTIL_ EAI_ Badflags: AI in hints_ Invalid flags field
-
EVUTIL_EAI_FAMILY: AI in hints is not supported_ Family field
-
EVUTIL_EAI_MEMORY: the process of responding to a request ran out of memory
-
EVUTIL_EAI_NODATA: the requested host does not exist
-
EVUTIL_EAI_SERVICE: the requested service does not exist
-
EVUTIL_EAI_SOCKTYPE: the requested socket type is not supported, or the socket type is different from ai_protocol mismatch
-
EVUTIL_EAI_SYSTEM: other system errors occurred in name resolution. Please check errno for more information
-
EVUTIL_EAI_CANCEL: the application requested cancellation before parsing was completed evutil_ The getaddrinfo () function never generates this error, but the evdns described later_ Getaddrinfo () may cause this error Call evutil_gai_strerror() can convert the above error value into a descriptive string
Note that if the operating system defines the addrinfo structure, evutil_addrinfo is simply an alias for the addrinfo structure built into the operating system Similarly, if the operating system defines AI_* Flag, the corresponding EVUTIL_AI_* Flags are simply aliases of local flags; If the operating system defines EAI_* Error, the corresponding EVUTIL_EAI_* It's just an alias for the local error code
Example: resolve the host name and establish a blocked connection
#include <sys/socket.h> #include <sys/types.h> #include <stdio.h> #include <string.h> #include <assert.h> #include <unistd.h> evutil_socket_t get_tcp_socket_for_host(const char* hostname, ev_uint16_t port) { char port_buf[6]; struct evutil_addrinfo hints; struct evutil_addrinfo* answer = NULL; int err; evutil_socket_t sock; /* Convert the port to decimal.*/ evutil_snprintf(port_buf, sizeof(port_buf), "%d", (int)port); /* Build the hints to tell getaddrinfo how to act.*/ memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; /* v4 or v6 is fine.*/ hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; /* We want a TCP socket*/ /* Only return addresses we can use.*/ hints.ai_flags = EVUTIL_AI_ADDRCONFIG; /* Look up the hostname.*/ err = evutil_getaddrinfo(hostname, port_buf, &hints, &answer); if (err != 0) { fprintf(stderr, "Error while resolving '%s': %s", hostname, evutil_gai_strerror(err)); return -1; } /* If there was no error, we should have at least one answer.*/ assert(answer); /* Just use the first answer.*/ sock = socket(answer->ai_family, answer->ai_socktype, answer->ai_protocol); if (sock < 0) return -1; if (connect(sock, answer->ai_addr, answer->ai_addrlen)) { /* Note that we're doing a blocking connect in this function.* If this were nonblocking, we'd need to treat some errors* (like EINTR and EAGAIN) specially.*/ EVUTIL_CLOSESOCKET(sock); return -1; } return sock; }
The above functions and constants are newly added in version 2.0.3-alpha and are declared in event2 / util H medium
14.2 using evdns_getaddrinfo() for non blocking name resolution
The usual getaddrinfo () and evutil above_ The problem with getaddrinfo() is that they are blocked: the calling thread must wait for the function to query the DNS server and wait for a response For libevent, this may not be the expected behavior
For non blocking applications, libevent provides a set of functions to start DNS requests and let libevent wait for the server to respond
Interface
typedef void ( * evdns_getaddrinfo_cb)(int result, struct evutil_addrinfo* res, void * arg); struct evdns_getaddrinfo_request; struct evdns_getaddrinfo_request* evdns_getaddrinfo( struct evdns_base* dns_base, const char* nodename, const char * servname, const struct evutil_addrinfo* hints_in, evdns_getaddrinfo_cb cb, void* arg); void evdns_getaddrinfo_cancel(struct evdns_getaddrinfo_request* req);
Evdns does not block DNS queries, but uses libevent's underlying DNS mechanism for queries_ Getaddrinfo() and evutil_getaddrinfo() is the same Because the function does not always return results immediately, you need to provide an evdns_getaddrinfo_cb type callback function, and an optional user parameter to the callback function
In addition, evdns is called_ Getaddrinfo () also requires an evdns_base pointer evdns_ The base structure is the status and configuration of the libevent DNS parser About how to get evdns_base pointer, see the next section
If it fails or succeeds immediately, the function returns NULL Otherwise, the function returns an evdns_getaddrinfo_request pointer Evdns can be used at any time until parsing is complete_ getaddrinfo_ Cancel () and this pointer to cancel parsing
Note: regardless of evdns_ Whether getaddrinfo() returns NULL and whether evdns is called_ getaddrinfo_ Cancel(), the callback function is always called
evdns_getaddrinfo() internally copies nodename, servname and hints parameters, so it is not necessary to keep these parameters valid during query
Example: using evdns_ Non blocking query for getaddrinfo()
#include <event2/dns.h> #include <event2/util.h> #include <event2/event.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> int n_pending_requests = 0; struct event_base* base = NULL; struct user_data { char* name; /* the name we're resolving*/ int idx; /* its position on the command line*/ }; void callback(int errcode, struct evutil_addrinfo* addr, void * ptr) { struct user_data* data = ptr; const char* name = data->name; if (errcode) { printf("%d. %s -> %s\n", data->idx, name, evutil_gai_strerror(errcode)); } else { struct evutil_addrinfo* ai; printf("%d. %s", data->idx, name); if (addr->ai_canonname) printf(" [%s]", addr->ai_canonname); puts(""); for (ai = addr; ai; ai = ai->ai_next) { char buf[128]; const char* s = NULL; if (ai->ai_family == AF_INET) { struct sockaddr_in* sin = (struct sockaddr_in * )ai->ai_addr; s = evutil_inet_ntop(AF_INET, &sin->sin_addr, buf, 128); } else if (ai->ai_family == AF_INET6) { struct sockaddr_in6* sin6 =(sockaddr_in6 * )ai->ai_addr; s = evutil_inet_ntop(AF_INET6, &sin6->sin6_addr, buf, 128); } if (s) printf(" -> %s\n", s); } evutil_freeaddrinfo(addr); } free(data->name); free(data); if (--n_pending_requests == 0) event_base_loopexit(base, NULL); } /* Take a list of domain names from the command line and resolve them in parallel.*/ int main(int argc, char** argv) { int i; struct evdns_base* dnsbase; if (argc == 1) { puts("No addresses given."); return 0; } base = event_base_new(); if (!base) return 1; dnsbase = evdns_base_new(base, 1); if (!dnsbase) return 2; for (i = 1; i < argc; ++i) { struct evutil_addrinfo hints; struct evdns_getaddrinfo_request* req; struct user_data* user_data; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_flags = EVUTIL_AI_CANONNAME; /* Unless we specify a socktype, we'll get at least two entries for* each address: one for TCP and one for UDP. That's not what we* want.*/ hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; if (!(user_data = malloc(sizeof(struct user_data)))) { perror("malloc"); exit(1); } if (!(user_data->name = strdup(argv[i]))) { perror("strdup"); exit(1); } user_data->idx = i; ++n_pending_requests; req = evdns_getaddrinfo( dnsbase, argv[i], NULL /* no service name given*/, &hints, callback, user_data); if (req == NULL) { printf(" [request for %s returned immediately]\n", argv[i]); /* No need to free user_data or decrement n_pending_requests; that*happened in the callback.*/ } } if (n_pending_requests) event_base_dispatch(base); evdns_base_free(dnsbase, 0); event_base_free(base); return 0; }
These functions are newly added in version 2.0.3-alpha and are declared in event2 / DNS H medium
14.3 creating and configuring evdns_base
Before using evdns for non blocking DNS queries, you need to configure an evdns_base.evdns_base stores a list of nameservers and DNS configuration options to track active and ongoing DNS requests
Interface
struct evdns_base* evdns_base_new(struct event_base * event_base,int initialize); void evdns_base_free(struct evdns_base* base, int fail_requests);
Evdns on success_ base_ New() returns a new evdns_base, NULL on failure If the initialize parameter is true, the function attempts to configure evdns according to the default value of the operating system_ base; Otherwise, the function lets evdns_base is empty, do not configure nameserver and options
You can use evdns_base_free() releases evdns that are no longer in use_ base. If fail_ If the request parameter is true, the function will release evdns_ Before base, let all ongoing requests call its callback function with cancellation error code
14.3.1 initializing evdns using system configuration
If you need more control over evdns_ How to initialize base can be evdns_base_ The initialize parameter of new () passes 0, and then calls the following function.
Interface
#define DNS_OPTION_SEARCH 1 #define DNS_OPTION_NAMESERVERS 2 #define DNS_OPTION_MISC 4 #define DNS_OPTION_HOSTSFILE 8 #define DNS_OPTIONS_ALL 15 int evdns_base_resolv_conf_parse(struct evdns_base * base, int flags, const char* filename); #ifdef WIN32 int evdns_base_config_windows_nameservers(struct evdns_base* ); #define EVDNS_BASE_CONFIG_WINDOWS_NAMESERVERS_IMPLEMENTED #endif
evdns_ base_ resolv_ conf_ The parse() function scans resolv File name in conf format, from which you can read the options indicated by flags (for more information about the resolv.conf file, see the Unix manual)
-
DNS_OPTION_SEARCH: request from resolv The conf file reads the domain and search fields and the ndots option and uses them to determine which domain (if any) to use to search for non fully qualified host names
-
DNS_OPTION_NAMESERVERS: request from resolv Read the nameserver address in conf
-
DNS_OPTION_MISC: request from resolv Read other configuration options from the conf file
-
DNS_OPTION_HOSTSFILE: request to read the host list from the / etc/hosts file
-
DNS_OPTION_ALL: request from resolv Conf file to get as much information as possible
There is no resolv in Windows that can tell you where the nameserver is Conf file, but you can use
evdns_ base_ config_ windows_ The nameservers () function retrieves the parameters from the registry (or NetworkParams, or
Other hidden places) read the name server
resolv.conf file format
resolv.conf is a text file. Each line is either empty, contains comments beginning with # or consists of a tag followed by zero or more parameters The recognizable marks are:
-
Nameserver: must be followed by the IP address of a nameserver As an extension, libevent allows the use of IP:Port or [IPv6]:port syntax to specify non-standard ports for nameservers
-
Domain: local domain name
-
Search: list of names to search when resolving local host names If any local name with less than "ndots" points cannot be resolved correctly, search in these domain names For example, if the value of the "search" field is example COM, "ndots" is 1, then when the user requests to parse "www", the function thinks it is "www.example.com"
-
Options: a space - delimited list of options The option is either an empty string or has the format option: value (if any) The recognized options are:
-
dots:INTEGER: used to configure search. Please refer to "search" above. The default value is 1
-
timeout:FLOAT: the time to wait for the DNS server to respond, in seconds The default value is 5 seconds
-
Max timeouts: int: how many times does the name server response time out before the server is considered down? The default is 3 times
-
Max inflight: int: how many pending DNS requests are allowed? (if you try to make more than this many requests, too many requests will be delayed
Late until a request is answered or timed out The default value is 64
-
attempts:INT: how many times do you retransmit DNS requests before giving up? The default value is 3
-
Randomize case: int: if it is non-zero, evdns will set a random transaction ID for the DNS request issued, and confirm that the response has the same transaction ID
Random transaction ID value This mechanism called "0x20 hack" can prevent simple activation event attacks on DNS to a certain extent this
The default value of the option is 1
-
Bind to: Address: if provided, bind to the given address before sending data to the nameserver For version 2.0.4-alpha, this
The settings apply only to subsequent nameserver entries
-
Initial probe timeout: float: after confirming that the nameserver is down, libevent probes the server at an exponentially reduced frequency to determine whether the server is restored This option configures the first timeout in the sequence (probe interval), in seconds The default value is 10
- Getaddrinfo allow skew: float: evdns when both IPv4 and IPv6 addresses are requested_ Getaddrinfo() packets with separate DNS requests
Don't request two addresses because some servers can't handle both requests in one package After the server responds to an address type, the function waits for a period of time to determine whether another type of address arrives This option configures how long to wait, in seconds The default value is 3 seconds Unrecognized fields and options are ignored
-
14.3.2 configuring evdns manually
If you need to control the behavior of evdns more finely, you can use the following functions:
Interface
int evdns_base_nameserver_sockaddr_add(struct evdns_base* base, const struct sockaddr* sa, ev_socklen_t len, unsigned flags); int evdns_base_nameserver_ip_add(struct evdns_base* base, const char* ip_as_string); int evdns_base_load_hosts(struct evdns_base* base, const char * hosts_fname); void evdns_base_search_clear(struct evdns_base* base); void evdns_base_search_add(struct evdns_base* base, const char * domain); void evdns_base_search_ndots_set(struct evdns_base* base, int ndots); int evdns_base_set_option(struct evdns_base* base, const char * option, const char* val); int evdns_base_count_nameservers(struct evdns_base* base);
evdns_base_ nameserver_ sockaddr_ The add() function sends an address to evdns_base adds a nameserver. The flags parameter is currently ignored. For forward compatibility, 0 should be passed in. The function returns 0 on success and negative on failure. (this function is added in version 2.0.7-rc)
evdns_base_ nameserver_ ip_ The add() function adds a value to evdns_base adds the name server represented by string. The format can be IPv4 address, IPv6 address, IPv4 address with port number (IPv4:Port), or IPv6 address with port number ([IPv6]:Port). The function returns 0 on success and negative on failure.
evdns_base_load_hosts() function from hosts_ Load the host file in the fname file (the format is the same as / etc/hosts). The function returns 0 on success and negative on failure.
evdns_base_search_clear() function from evdns_ Remove all search suffixes (configured through search) from the base; evdns_base_search_add() adds a suffix.
evdns_base_ set_ The option() function sets evdns_ The value of an option in base. Options and values are represented as strings. (before version 2.0.3, there must be a colon after the option name) evdns can be used after parsing a set of configuration files_ base_ count_ Nameservers () to see how many nameservers have been added.
14.3.3 library end configuration
There are functions to set the library level configuration for the evdns module:
Interface
typedef void ( * evdns_debug_log_fn_type)(int is_warning, const char* msg); void evdns_set_log_fn(evdns_debug_log_fn_type fn); void evdns_set_transaction_id_fn(ev_uint16_t ( * fn)(void));
For historical reasons, the evdns subsystem has its own separate log. evdns_set_log_fn() can set a callback function to do something before discarding log messages.
For security reasons, evdns needs a good random number generation source: when 0x20 hack is used, evdns uses this source to obtain hard to guess transaction IDs to randomize queries (please refer to the "randomize case" option). However, the older version of libevent does not have its own secure RNG (random number generator). At this point, you can call evdns_set_transaction_id_fn(), pass in a function that returns a hard to predict two byte unsigned integer to set up a better random number generator for evdns.
In 2.0.4-alpha and subsequent versions, libevent has its own built-in secure RNG,
evdns_set_transaction_id_fn() has no effect.