Class Cares
In: cares.c
Parent: Object

Overview

Ruby/Cares is a C extension to the c-ares library. It performs DNS requests and name resolving asynchronously.

Example

Below follows a simple example. See Cares’ methods documentations for details valid parameters.

 require 'cares'
 require 'socket'

 # Create new Cares instance
 cares = Cares.new(:timeout => 3)

 #
 # Set up three DNS requests.
 #

 cares.gethostbyname('www.rubyforge.org', Socket::AF_INET) do |name, aliases, family, *addrs|
   puts "[-] Cares#gethostbyname:"
   puts "  #{domain}:"
   puts "    canonical name: #{name}"
   puts "    aliases: #{aliases.join(', ')}"
   puts "    addresses: #{addrs.join(', ')}"
   puts
 end

 cares.gethostbyaddr('205.234.109.18', Socket::AF_INET) do |name, *rest|
   puts "[-] Cares#gethostbyaddr:"
   puts "  #{addr}:"
   puts "    name: #{name}"
   puts
 end

 cares.getnameinfo(:addr => '205.234.109.18') do |name, service|
   puts "[-] Cares#getnameinfo:"
   puts "  #{addr}:"
   puts "    name: #{name}"
   puts
 end

 # Wait for responses and yield the blocks passed to each of the
 # methods calls above.
 cares.select_loop

Methods

Classes and Modules

Class Cares::AddressNotFoundError
Class Cares::BadFlagsError
Class Cares::BadNameError
Class Cares::DestructionError
Class Cares::Init
Class Cares::NameInfo
Class Cares::NoMemoryError
Class Cares::NotImplementedError

Public Class methods

Creates a new Cares object. The options hash accepts the following keys:

  • :flags Flags controlling the behaviour of the resolver. See below for a description os the possible flags.
  • :timeout The number of seconds each name server is given to respond to a query on the first try. For further queries, the timeout scales nearly with the provided value. The default is 5 seconds.
  • :tries The number of times the resolver will try to contact each name server before giving up. The default is 4 tries.
  • :ndots The number of dots that must be present in a domain name for it to be queried "as is", prior to querying with the default domain extensions appended. The default is 1, unless set otherwise in resolv.conf or the RES_OPTIONS environment variable.
  • :udp_port The port to use for UDP queries. The default is 53.
  • :tcp_port The port to use for TCP queries. The default is 53.
  • :servers An array of IP addresses to be used as the servers to be contacted, instead of the ones found in resolv.conf or the local name daemon.
  • :domains An array of domains to be searched, instead of the ones specified in resolv.conf or the machine hostname.
  • :lookups The lookups to perform for host queries. It should be a string of the characters b or f, where b indicates a DNS lookup, and f indicates a hosts file lookup.

The :flags option is a bitwise-or of values from the list below:

  • Cares::Init::USEVC Always use TCP queries. Normally, TCP is only used if a UDP query returns a truncated result.
  • Cares::Init::PRIMARY Only query the first server in the server list.
  • Cares::Init::IGNTC If a truncated response to an UDP query is received, do not fall back to TCP; simply continue with the truncated response.
  • Cares::Init::NORECURSE Do not set the "recursion desired" bit on outgoing queries.
  • Cares::Init::STAYOPEN Do not close the communication sockets when the number of active queries drops to zero.
  • Cares::Init::NOSEARCH Do not use the default search domains; only query hostnames as-is or as aliases.
  • Cares::Init::NOALIASES Do not honor the HOSTALIASES environment variable, which specifies a file of hostname translations.
  • Cares::Init::NOCHECKRESP Do not discard responses with the SERVFAIL, NOTIMP, or REFUSED response codes or responses whose questions don’t match the questions in the request.

If a block is given, it’ll be called when a socket used in name resolving has its state changed. The block takes three arguments. The first one is the Socket object, the the other two are boolean values indicating if the socket should listen for read and/or write events, respectively.

[Source]

/*
 *  call-seq:
 *     Cares.new([options])                                   => cares_obj
 *     Cares.new([options]) { |socket, read, write| block }   => cares_obj
 *
 *  Creates a new <code>Cares</code> object. The <code>options</code> hash
 *  accepts the following keys:
 *
 *  * <code>:flags</code> Flags controlling the behaviour of the resolver.
 *    See below for a description os the possible flags.
 *  * <code>:timeout</code> The number of seconds each name server is given
 *    to respond to a query on the first try. For further queries, the timeout
 *    scales nearly with the provided value. The default is 5 seconds.
 *  * <code>:tries</code> The number of times the resolver will try to
 *    contact each name server before giving up. The default is 4 tries.
 *  * <code>:ndots</code> The number of dots that must be present in a
 *    domain name for it to be queried "as is", prior to querying with the
 *    default domain extensions appended. The default is 1, unless set
 *    otherwise in resolv.conf or the <code>RES_OPTIONS</code> environment
 *    variable.
 *  * <code>:udp_port</code> The port to use for UDP queries. The default is 53.
 *  * <code>:tcp_port</code> The port to use for TCP queries. The default is 53.
 *  * <code>:servers</code>  An array of IP addresses to be used as the servers
 *    to be contacted, instead of the ones found in resolv.conf or the local
 *    name daemon.
 *  * <code>:domains</code> An array of domains to be searched, instead of
 *    the ones specified in resolv.conf or the machine hostname.
 *  * <code>:lookups</code> The lookups to perform for host queries. It
 *    should be a string of the characters <i>b</i> or <i>f</i>, where <i>b</i>
 *    indicates a DNS lookup, and <i>f</i> indicates a hosts file lookup.
 *
 *  The <code>:flags</code> option is a bitwise-or of values from the list
 *  below:
 *
 *  * <code>Cares::Init::USEVC</code> Always use TCP queries. Normally, TCP
 *    is only used if a UDP query returns a truncated result.
 *  * <code>Cares::Init::PRIMARY</code> Only query the first server in the
 *    server list.
 *  * <code>Cares::Init::IGNTC</code> If a truncated response to an UDP query
 *    is received, do not fall back to TCP; simply continue with the truncated
 *    response.
 *  * <code>Cares::Init::NORECURSE</code> Do not set the "recursion desired"
 *    bit on outgoing queries.
 *  * <code>Cares::Init::STAYOPEN</code> Do not close the communication
 *    sockets when the number of active queries drops to zero.
 *  * <code>Cares::Init::NOSEARCH</code> Do not use the default search
 *    domains; only query hostnames as-is or as aliases.
 *  * <code>Cares::Init::NOALIASES</code> Do not honor the
 *    <code>HOSTALIASES</code> environment variable, which specifies a
 *    file of hostname translations.
 *  * <code>Cares::Init::NOCHECKRESP</code> Do not discard responses with the
 *    <code>SERVFAIL</code>, <code>NOTIMP</code>, or <code>REFUSED</code>
 *    response codes or responses whose questions don't match the
 *    questions in the request.
 *
 *  If a block is given, it'll be called when a socket used in name resolving
 *  has its state changed. The block takes three arguments. The first one is
 *  the <code>Socket</code> object, the the other two are boolean values
 *  indicating if the socket should listen for read and/or write events,
 *  respectively.
 */
static VALUE
rb_cares_init(int argc, VALUE *argv, VALUE self)
{
        int    status, optmask;
        ares_channel *chp;
        struct ares_options ao;
        VALUE  opts;

        Data_Get_Struct(self, ares_channel, chp);

        rb_scan_args(argc, argv, "01", &opts);
        if (NIL_P(opts) && !rb_block_given_p()) {
                status = ares_init(chp);
                if (status != ARES_SUCCESS)
                        raise_error(status);
                return(self);
        }

        optmask = set_init_opts(opts, &ao);

        status = ares_init_options(chp, &ao, optmask);
        if (status != ARES_SUCCESS)
                raise_error(status);

        return(self);
}

Public Instance methods

Performs a reverse DNS query on addr. for addresses of family family. The family argument is either Socket::AF_INET or Socket::AF_INET6. The results are passed as arguments to the block:

  • name: addr’s name.
  • aliases: array of aliases.
  • family: address family.
  • *addrs: array containing name’s addresses.

[Source]

/*
 *  call-seq:
 *     cares.gethostbyaddr(addr, family) { |name, aliases, family, *addrs| block }  => cares
 *
 *  Performs a reverse DNS query on <code>addr</code>. for addresses of family
 *  <code>family</code>. The <code>family</code> argument is either
 *  <code>Socket::AF_INET</code> or <code>Socket::AF_INET6</code>. The results
 *  are passed as arguments to the block:
 *
 *  * <code>name</code>: <code>addr</code>'s name.
 *  * <code>aliases</code>: array of aliases.
 *  * <code>family</code>: address family.
 *  * <code>*addrs</code>: array containing <code>name</code>'s addresses.
 */
static VALUE
rb_cares_gethostbyaddr(VALUE self, VALUE addr, VALUE family)
{
        char   *caddr;
        int     cfamily;
        ares_channel *chp;

        if (!rb_block_given_p())
                rb_raise(rb_eArgError, "gethostbyaddr: block needed");

        Data_Get_Struct(self, ares_channel, chp);
        cfamily = NUM2INT(family);
        caddr = StringValuePtr(addr);

        switch (cfamily) {
        case AF_INET: {
                struct in_addr in;
                if (inet_pton(cfamily, caddr, &in) != 1)
                        rb_sys_fail("gethostbyaddr");
                ares_gethostbyaddr(*chp, &in, sizeof(in), cfamily,
                                   host_callback, (void *)rb_block_proc());
                break;
        }
        case AF_INET6: {
                struct in6_addr in6;
                if (inet_pton(cfamily, caddr, &in6) != 1)
                        rb_sys_fail("gethostbyaddr");
                ares_gethostbyaddr(*chp, &in6, sizeof(in6), cfamily,
                                   host_callback, (void *)rb_block_proc());
                break;
        }
        default:
                rb_raise(cNotImpError, "gethostbyaddr: invalid address family");
        }
        return(self);
}

Performs a DNS lookup on name, for addresses of family family. The family argument is either Socket::AF_INET or Socket::AF_INET6. The results are passed as arguments to the block:

  • cname: name’s canonical name.
  • aliases: array of aliases.
  • family: address family.
  • *addrs: array containing name’s addresses.

[Source]

/*
 *  call-seq:
 *     cares.gethostbyname(name, family) { |cname, aliases, family, *addrs| block }  => cares
 *
 *  Performs a DNS lookup on <code>name</code>, for addresses of family
 *  <code>family</code>. The <code>family</code> argument is either
 *  <code>Socket::AF_INET</code> or <code>Socket::AF_INET6</code>. The results
 *  are passed as arguments to the block:
 *
 *  * <code>cname</code>: <code>name</code>'s canonical name.
 *  * <code>aliases</code>: array of aliases.
 *  * <code>family</code>: address family.
 *  * <code>*addrs</code>: array containing <code>name</code>'s addresses.
 */
static VALUE
rb_cares_gethostbyname(VALUE self, VALUE host, VALUE family)
{
        ares_channel *chp;

        if (!rb_block_given_p())
                rb_raise(rb_eArgError, "gethostbyname: block needed");

        Data_Get_Struct(self, ares_channel, chp);
        ares_gethostbyname(*chp, StringValuePtr(host), NUM2INT(family),
                           host_callback, (void *)rb_block_proc());
        return(self);
}

Performs a protocol-independent reverse lookup on the host and/or service names specified on the nameservice hash. The valid keys are:

  • :addr: IPv4 or IPv6 address.
  • :service: Port number.

The lookup results are passed as parameters to the block.

[Source]

/*
 *  call-seq:
 *     cares.getnameinfo(nameservice) { |name, service| block }    => cares
 *
 *  Performs a protocol-independent reverse lookup on the host and/or service
 *  names specified on the <code>nameservice</code> hash. The valid keys are:
 *
 *  * <code>:addr</code>: IPv4 or IPv6 address.
 *  * <code>:service</code>: Port number.
 *
 *  The lookup results are passed as parameters to the block.
 */
static VALUE
rb_cares_getnameinfo(VALUE self, VALUE info)
{
        int    cflags;
        socklen_t sslen;
        struct sockaddr_storage ss;
        ares_channel *chp;
        VALUE  vaddr, vport, vflags;

        Data_Get_Struct(self, ares_channel, chp);

        vflags = rb_hash_aref(info, ID2SYM(rb_intern("flags")));
        if (!NIL_P(vflags))
                cflags = NUM2INT(vflags);
        else
                cflags = 0;

        sslen = 0;
        ss.ss_family = AF_INET;

        vaddr = rb_hash_aref(info, ID2SYM(rb_intern("addr")));
        if (!NIL_P(vaddr)) {
                char  *caddr = StringValuePtr(vaddr);
                struct sockaddr_in *sinp;
                struct sockaddr_in6 *sin6p;

                sinp  = (struct sockaddr_in *)&ss;
                sin6p = (struct sockaddr_in6 *)&ss;

                cflags |= ARES_NI_LOOKUPHOST;

                if (inet_pton(AF_INET, caddr, &sinp->sin_addr) == 1) {
                        sslen = sizeof(struct sockaddr_in);
                } else if (inet_pton(AF_INET6, caddr, &sin6p->sin6_addr) == 1) {
                        ss.ss_family = AF_INET6;
                        sslen = sizeof(struct sockaddr_in6);
                } else {
                        rb_raise(cNotImpError,
                                 "getnameinfo: invalid address family");
                }
        }

        vport = rb_hash_aref(info, ID2SYM(rb_intern("port")));
        if (!NIL_P(vport)) {
                u_short       cport = htons(NUM2UINT(vport));
                cflags |= ARES_NI_LOOKUPSERVICE;
                switch (ss.ss_family) {
                case AF_INET:
                        ((struct sockaddr_in *)&ss)->sin_port = cport;
                        sslen = sizeof(struct sockaddr_in);
                        break;
                case AF_INET6:
                        ((struct sockaddr_in6 *)&ss)->sin6_port = cport;
                        sslen = sizeof(struct sockaddr_in6);
                        break;
                }
        }

        ares_getnameinfo(*chp, (struct sockaddr *)&ss, sslen, cflags,
                         nameinfo_callback, (void *)rb_block_proc());
        return(self);
}

Handles the input/output and timeout associated witht the queries made from the name and address lookup methods. The block passed to each of those methods is yielded when the event is processed.

The timeout hash accepts the following keys:

  • :seconds: Timeout in seconds.
  • :useconds: Timeout in microseconds.

[Source]

/*
 *  call-seq:
 *     cares.select_loop(timeout=nil)    => nil
 *
 *  Handles the input/output and timeout associated witht the queries made
 *  from the name and address lookup methods. The block passed to each of
 *  those methods is yielded when the event is processed.
 *
 *  The <code>timeout</code> hash accepts the following keys:
 *
 *  * <code>:seconds</code>:  Timeout in seconds.
 *  * <code>:useconds</code>: Timeout in microseconds.
 */
static VALUE
rb_cares_select_loop(int argc, VALUE *argv, VALUE self)
{
        int    nfds;
        fd_set read, write;
        struct timeval *tvp, tv;
        struct timeval *maxtvp, maxtv;
        ares_channel *chp;
        VALUE  timeout;

        rb_scan_args(argc, argv, "01", &timeout);

        maxtvp = NULL;
        if (!NIL_P(timeout)) {
                VALUE secs, usecs;

                secs = rb_hash_aref(timeout, ID2SYM(rb_intern("seconds")));
                if (!NIL_P(secs))
                        maxtv.tv_sec = NUM2LONG(secs);
                usecs = rb_hash_aref(timeout, ID2SYM(rb_intern("useconds")));
                if (!NIL_P(usecs))
                        maxtv.tv_usec = NUM2LONG(usecs);

                if (!NIL_P(secs) || !NIL_P(usecs))
                        maxtvp = &maxtv;
        }

        Data_Get_Struct(self, ares_channel, chp);

        for (;;) {
                FD_ZERO(&read);
                FD_ZERO(&write);
                nfds = ares_fds(*chp, &read, &write);
                if (nfds == 0)
                        break;
                tvp = ares_timeout(*chp, maxtvp, &tv);
                rb_thread_select(nfds, &read, &write, NULL, tvp);
                ares_process(*chp, &read, &write);
        }
        return(Qnil);
}

[Validate]