# How DNS Resolution Works in Coroutines Without Blocking the Scheduler

> Learn how libfiber resolves DNS in coroutines without blocking your scheduler. Discover its non-blocking UDP sockets and explicit yielding for efficient hostname resolution.

- Repository: [iQIYI/libfiber](https://github.com/iqiyi/libfiber)
- Tags: deep-dive
- Published: 2026-03-04

---

**libfiber intercepts standard `getaddrinfo` calls and replaces them with a fiber-aware implementation that uses non-blocking UDP sockets and explicit yielding to resolve hostnames without stalling the scheduler.**

The iqiyi/libfiber library enables thousands of concurrent coroutines to perform network operations without blocking the scheduler. One critical capability is **DNS resolution in coroutines**, which traditionally blocks the calling thread while waiting for network responses. This article explains how libfiber transparently hooks POSIX DNS functions to provide asynchronous, cooperative name resolution.

## Hooking the Standard `getaddrinfo` API

In [`c/src/hook/getaddrinfo.c`](https://github.com/iqiyi/libfiber/blob/main/c/src/hook/getaddrinfo.c), the library provides `acl_fiber_getaddrinfo`, which intercepts standard `getaddrinfo` calls. When user code calls the POSIX API, the request routes through this fiber-aware implementation rather than the blocking system library. The hook forwards the request to an internal resolver that understands the fiber scheduler's event loop.

## Resolver Initialization and Local Caching

Before issuing network requests, the system optimizes for local entries. The `resolver_init_once()` function in [`c/src/dns/resolver.c`](https://github.com/iqiyi/libfiber/blob/main/c/src/dns/resolver.c), guarded by `pthread_once`, parses [`/etc/resolv.conf`](https://github.com/iqiyi/libfiber/blob/main//etc/resolv.conf), `/etc/hosts`, and `/etc/services` into global structures only on first use.

When a lookup occurs, `check_local()` in [`c/src/hook/getaddrinfo.c`](https://github.com/iqiyi/libfiber/blob/main/c/src/hook/getaddrinfo.c) queries the in-memory `/etc/hosts` cache via `find_from_localhost`. If the name exists locally, the function builds a fully populated `struct addrinfo` chain synchronously, returning immediately without any network I/O or fiber yielding.

## Asynchronous UDP Query Construction

For external hostnames, `resolver_getaddrinfo()` in [`c/src/dns/resolver.c`](https://github.com/iqiyi/libfiber/blob/main/c/src/dns/resolver.c) constructs an RFC 1035 compliant query using `rfc1035_build_query`. It transmits this query via `acl_fiber_socket`, a non-blocking UDP socket that returns control immediately without blocking the calling fiber.

## Yielding During Network Waits

After sending the UDP packet, the critical non-blocking mechanism activates. The code calls `read_wait()` from [`c/src/common/read_wait.c`](https://github.com/iqiyi/libfiber/blob/main/c/src/common/read_wait.c). This function invokes `acl_fiber_poll`, which registers the socket file descriptor with the kernel (using `poll`, `epoll`, or `kqueue`) and **yields the current fiber**.

While the fiber remains suspended, the scheduler continues executing other ready fibers. When the socket becomes readable or the `__wait_timeout` (default 5000ms) expires, the scheduler resumes the DNS fiber at the exact point it yielded.

## Response Parsing and CNAME Resolution

Upon resumption, `udp_request` receives the DNS response. The resolver calls `rfc1035_response_unpack` and `rfc1035_to_addrinfo` in [`c/src/dns/resolver.c`](https://github.com/iqiyi/libfiber/blob/main/c/src/dns/resolver.c) to parse the answer section into a linked list of `struct addrinfo`. If the response contains CNAME records, the resolver follows the canonical name recursively (up to five iterations) until resolving the final A or AAAA records. The complete result returns to the original caller through the same POSIX API surface.

## Why the Scheduler Remains Responsive

**Fiber-aware polling**: Unlike blocking system calls that park kernel threads, `acl_fiber_poll` suspends only the current coroutine. The scheduler's run queue stays populated with other fibers ready to execute, maintaining full CPU utilization across all cores.

**Cooperative design**: DNS operations yield only at well-defined wait points (specifically `read_wait`). No kernel thread blocks on the UDP socket, keeping the entire process in user-space and cooperative.

**Timeout handling**: If a DNS server fails to respond within `__wait_timeout` (default 5000 milliseconds), `read_wait` returns `-1`. The resolver either retries the next nameserver from [`/etc/resolv.conf`](https://github.com/iqiyi/libfiber/blob/main//etc/resolv.conf) or returns `EAI_AGAIN`, all without ever blocking the scheduler or consuming a thread per query.

## Practical Implementation Examples

### Transparent DNS with Standard API

The following code requires no modification to use libfiber's non-blocking resolver. Simply link with `-lfiber`:

```c
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>

int main(void)
{
    struct addrinfo *res, hints;
    int rc;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family   = AF_UNSPEC;      // IPv4 or IPv6
    hints.ai_socktype = SOCK_STREAM;    // TCP (default)

    rc = getaddrinfo("www.qiyi.com", "http", &hints, &res);
    if (rc != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rc));
        return 1;
    }

    for (struct addrinfo *p = res; p; p = p->ai_next) {
        char host[NI_MAXHOST];
        getnameinfo(p->ai_addr, p->ai_addrlen,
                    host, sizeof(host), NULL, 0, NI_NUMERICHOST);
        printf(" resolved: %s\n", host);
    }

    freeaddrinfo(res);
    return 0;
}

```

When linked with libfiber, the `getaddrinfo` call automatically routes through `acl_fiber_getaddrinfo`. The DNS lookup runs inside a fiber, and the scheduler stays responsive to other coroutines.

### Concurrent DNS in Explicit Fibers

For explicit fiber creation, spawn multiple coroutines that resolve different hosts simultaneously:

```c
#include "fiber/fiber.h"
#include "fiber/fiber_hook.h"
#include <netdb.h>
#include <stdio.h>

static void dns_task(void *arg)
{
    const char *name = (const char *)arg;
    struct addrinfo *res, hints;
    int rc;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    rc = getaddrinfo(name, NULL, &hints, &res);
    if (rc) {
        printf("[fiber] %s: %s\n", name, gai_strerror(rc));
        return;
    }

    for (struct addrinfo *p = res; p; p = p->ai_next) {
        char ip[NI_MAXHOST];
        getnameinfo(p->ai_addr, p->ai_addrlen,
                    ip, sizeof(ip), NULL, 0, NI_NUMERICHOST);
        printf("[fiber] %s -> %s\n", name, ip);
    }
    freeaddrinfo(res);
}

int main(void)
{
    fiber_create(dns_task, "www.qiyi.com");
    fiber_create(dns_task, "www.baidu.com");
    /* Run the scheduler – it will interleave the two DNS queries without blocking */
    fiber_schedule();
    return 0;
}

```

Each `fiber_create` spawns a coroutine that performs a DNS query. While one fiber waits on `read_wait`, the other continues execution or performs unrelated work, demonstrating true non-blocking behavior.

## Summary

- libfiber hooks `getaddrinfo` in [`c/src/hook/getaddrinfo.c`](https://github.com/iqiyi/libfiber/blob/main/c/src/hook/getaddrinfo.c) to intercept DNS requests transparently via `acl_fiber_getaddrinfo`
- Local lookups short-circuit via the in-memory `/etc/hosts` cache using `check_local()`, avoiding network I/O entirely
- External queries use non-blocking UDP sockets constructed in [`c/src/dns/resolver.c`](https://github.com/iqiyi/libfiber/blob/main/c/src/dns/resolver.c) with `rfc1035_build_query`
- `read_wait()` in [`c/src/common/read_wait.c`](https://github.com/iqiyi/libfiber/blob/main/c/src/common/read_wait.c) yields fibers during network waits via `acl_fiber_poll`, keeping the scheduler unblocked
- The implementation follows RFC 1035 standards with CNAME recursion support up to five levels, returning fully populated `struct addrinfo` chains

## Frequently Asked Questions

### Does libfiber require modifying existing code that uses `getaddrinfo`?

No. When linked with `-lfiber`, existing applications using the standard POSIX `getaddrinfo` automatically route through `acl_fiber_getaddrinfo`. The hook mechanism in [`c/src/hook/getaddrinfo.c`](https://github.com/iqiyi/libfiber/blob/main/c/src/hook/getaddrinfo.c) intercepts calls transparently, so legacy code gains non-blocking behavior without source modification or API changes.

### What happens if the DNS server is slow or unresponsive?

The resolver uses `read_wait()` with a default `__wait_timeout` of 5000 milliseconds as defined in the fiber configuration. If the timeout expires, the fiber resumes and the resolver either attempts the next configured nameserver from [`/etc/resolv.conf`](https://github.com/iqiyi/libfiber/blob/main//etc/resolv.conf) or returns an error code. The scheduler never blocks waiting for slow DNS responses, and no kernel thread is consumed during the wait.

### How does libfiber handle IPv6 and CNAME records?

The resolver fully supports both IPv6 and IPv4 through `AF_UNSPEC` address family hints. When parsing responses in `rfc1035_to_addrinfo` within [`c/src/dns/resolver.c`](https://github.com/iqiyi/libfiber/blob/main/c/src/dns/resolver.c), the code handles CNAME records by following the canonical name recursively (up to five iterations) until reaching the final A or AAAA records. The function returns a complete `struct addrinfo` chain representing the final resolved addresses.

### Is the DNS resolver thread-safe for multiple concurrent fibers?

Yes. The initialization phase uses `pthread_once` in `resolver_init_once()` to safely load configuration files from [`/etc/resolv.conf`](https://github.com/iqiyi/libfiber/blob/main//etc/resolv.conf) and `/etc/hosts`. Subsequent queries use independent UDP sockets per request, and the fiber scheduler handles concurrency through cooperative multitasking. Thousands of fibers can perform DNS resolution simultaneously without blocking each other or the central scheduler.