Skip to content

File syn_dns.c

File List > net > syn_dns.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#if !defined(SYN_USE_DNS) || SYN_USE_DNS

#include "../port/syn_port_system.h"
#include "../util/syn_assert.h"
#include "../util/syn_pack.h"
#include "syn_dns.h"

#include <string.h>

static size_t encode_qname(uint8_t *dest, const char *src)
{
    uint8_t *len_ptr = dest;
    size_t total = 0;
    dest++;
    total++;
    uint8_t label_len = 0;
    while (*src) {
        if (*src == '.') {
            *len_ptr = label_len;
            len_ptr = dest;
            label_len = 0;
            dest++;
            total++;
        } else {
            *dest++ = (uint8_t)*src;
            label_len++;
            total++;
        }
        src++;
    }
    *len_ptr = label_len;
    *dest++ = 0;
    total++;
    return total;
}

static bool parse_qname(const uint8_t *buf, size_t buf_len, size_t *pos)
{
    size_t p = *pos;
    for (;;) {
        if (p >= buf_len)
            return false;
        uint8_t len = buf[p];
        if ((len & 0xC0) == 0xC0) {
            /* Pointer */
            p += 2;
            break;
        } else if (len == 0) {
            p++;
            break;
        } else {
            p += 1 + len;
        }
    }
    *pos = p;
    return true;
}

static SYN_Status parse_response(const uint8_t *buf, size_t rx_len, SYN_SockAddr *addr_out,
                                 uint16_t expected_txid)
{
    if (rx_len < 12)
        return SYN_ERROR;
    uint16_t rx_txid = syn_peek_u16(buf, 0);
    if (rx_txid != expected_txid)
        return SYN_ERROR; /* Bad ID */
    if ((buf[3] & 0x0F) != 0)
        return SYN_ERROR; /* RCODE != 0 (error) */

    uint16_t questions = syn_peek_u16(buf, 4);
    uint16_t answers = syn_peek_u16(buf, 6);

    if (answers == 0)
        return SYN_ERROR;

    /* Skip questions */
    size_t pos = 12;
    for (uint16_t i = 0; i < questions; i++) {
        if (!parse_qname(buf, rx_len, &pos))
            return SYN_ERROR;
        pos += 4; /* skip QTYPE and QCLASS */
    }

    /* Parse answers */
    for (uint16_t i = 0; i < answers; i++) {
        if (!parse_qname(buf, rx_len, &pos))
            return SYN_ERROR;
        if (pos + 10 > rx_len)
            return SYN_ERROR;

        uint16_t type = syn_peek_u16(buf, pos);
        uint16_t rdlen = syn_peek_u16(buf, pos + 8);
        pos += 10;

        if (type == 1 && rdlen == 4) {
            /* IPv4 A Record */
            if (pos + 4 > rx_len)
                return SYN_ERROR;
            memcpy(addr_out->ip, buf + pos, 4);
            addr_out->port = 0;
            return SYN_OK;
        }
        pos += rdlen;
    }

    return SYN_ERROR;
}

SYN_PT_Status syn_dns_resolve_task(SYN_PT *pt, SYN_Task *task)
{
    SYN_DnsResolver *r = (SYN_DnsResolver *)task->user_data;
    SYN_ASSERT(r != NULL);

    PT_BEGIN(pt);

    r->status = SYN_TIMEOUT;
    if (r->dns_server != NULL) {
        r->server_addr = *(r->dns_server);
    } else {
        r->server_addr.ip[0] = 8;
        r->server_addr.ip[1] = 8;
        r->server_addr.ip[2] = 8;
        r->server_addr.ip[3] = 8;
        r->server_addr.port = 53;
    }

    r->sock = syn_port_udp_open(0);
    if (r->sock == SYN_SOCKET_INVALID) {
        r->status = SYN_ERROR;
        PT_EXIT(pt);
    }

    /* Build query */
    r->txid = (uint16_t)(syn_port_get_tick_ms() & 0xFFFF);
    memset(r->buf, 0, 12);
    syn_poke_u16(r->txid, r->buf, 0); /* Transaction ID */
    syn_poke_u16(0x0100, r->buf, 2);  /* Flags: Standard query, recursion desired */
    syn_poke_u16(0x0001, r->buf, 4);  /* Questions = 1 */

    {
        size_t pos = 12;
        pos += encode_qname(r->buf + pos, r->hostname);
        syn_poke_u16(0x0001, r->buf, pos);
        pos += 2; /* QTYPE = A */
        syn_poke_u16(0x0001, r->buf, pos);
        pos += 2; /* QCLASS = IN */
        r->query_len = pos;
    }

    if (syn_port_udp_sendto(r->sock, r->buf, r->query_len, &r->server_addr) != (int)r->query_len) {
        syn_port_sock_close(r->sock);
        r->sock = SYN_SOCKET_INVALID;
        r->status = SYN_ERROR;
        PT_EXIT(pt);
    }

    r->start_ms = syn_port_get_tick_ms();

    for (;;) {
        SYN_SockAddr from;
        int n = syn_port_udp_recvfrom(r->sock, r->buf, sizeof(r->buf), &from, 0);
        if (n > 0) {
            r->status = parse_response(r->buf, (size_t)n, r->addr_out, r->txid);
            if (r->status == SYN_OK) {
                break;
            }
        }

        if ((syn_port_get_tick_ms() - r->start_ms) >= r->timeout_ms) {
            r->status = SYN_TIMEOUT;
            break;
        }

        PT_YIELD(pt);
    }

    syn_port_sock_close(r->sock);
    r->sock = SYN_SOCKET_INVALID;

    PT_END(pt);
}

/* ════════════════════════════════════════════════════════════════════════ */
/*  mDNS Responder Task                                                    */
/* ════════════════════════════════════════════════════════════════════════ */

SYN_Status syn_mdns_init(SYN_Mdns *mdns, const char *hostname, const uint8_t ip[4])
{
    SYN_ASSERT(mdns != NULL);
    SYN_ASSERT(hostname != NULL);
    SYN_ASSERT(ip != NULL);

    memset(mdns, 0, sizeof(*mdns));
    mdns->hostname = hostname;
    memcpy(mdns->ip, ip, 4);

    mdns->sock = syn_port_udp_open(5353);
    if (mdns->sock == SYN_SOCKET_INVALID) {
        return SYN_ERROR;
    }

    if (syn_port_udp_join_multicast(mdns->sock, "224.0.0.251") != SYN_OK) {
        syn_port_sock_close(mdns->sock);
        mdns->sock = SYN_SOCKET_INVALID;
        return SYN_ERROR;
    }

    return SYN_OK;
}

static bool match_qname_local(const uint8_t *buf, size_t buf_len, size_t *pos, const char *hostname)
{
    size_t p = *pos;
    if (p >= buf_len)
        return false;

    /* First label: hostname length + string */
    uint8_t h_len = buf[p++];
    size_t host_len = strlen(hostname);
    if (h_len != host_len)
        return false;
    if (p + host_len > buf_len)
        return false;
    if (memcmp(buf + p, hostname, host_len) != 0)
        return false;
    p += host_len;

    /* Second label: "local" length + string */
    if (p >= buf_len)
        return false;
    uint8_t l_len = buf[p++];
    if (l_len != 5)
        return false;
    if (p + 5 > buf_len)
        return false;
    if (memcmp(buf + p, "local", 5) != 0)
        return false;
    p += 5;

    /* Terminator */
    if (p >= buf_len || buf[p] != 0)
        return false;
    p++;

    *pos = p;
    return true;
}

SYN_PT_Status syn_mdns_task(SYN_PT *pt, SYN_Task *task)
{
    const SYN_Mdns *mdns = (const SYN_Mdns *)task->user_data;
    SYN_ASSERT(mdns != NULL);

    PT_BEGIN(pt);

    for (;;) {
        if (mdns->sock != SYN_SOCKET_INVALID) {
            uint8_t buf[256];
            SYN_SockAddr from;
            /* Non-blocking read (0 ms timeout) */
            int n = syn_port_udp_recvfrom(mdns->sock, buf, sizeof(buf), &from, 0);
            if (n > 12) {
                size_t rx_len = (size_t)n;
                uint16_t flags = syn_peek_u16(buf, 2);
                uint16_t questions = syn_peek_u16(buf, 4);

                /* Parse if it's a query (Flags QR bit = 0) */
                if ((flags & 0x8000) == 0 && questions > 0) {
                    size_t pos = 12;
                    bool matched = false;
                    for (uint16_t i = 0; i < questions; i++) {
                        if (match_qname_local(buf, rx_len, &pos, mdns->hostname)) {
                            matched = true;
                            break;
                        }
                        /* if didn't match, parse to skip */
                        if (!parse_qname(buf, rx_len, &pos))
                            break;
                        pos += 4; /* QTYPE + QCLASS */
                    }

                    if (matched) {
                        /* Build mDNS response packet */
                        uint8_t resp[256];
                        memset(resp, 0, 12);
                        /* Flags: 0x8400 (Authoritative Answer, Response) */
                        syn_poke_u16(0x8400, resp, 2);
                        syn_poke_u16(0x0001, resp, 6); /* Answers = 1 */

                        size_t rpos = 12;
                        rpos += encode_qname(resp + rpos, mdns->hostname);
                        /* Replace trailing 0 in qname with pointer to ".local" */
                        rpos--;
                        resp[rpos++] = 5;
                        memcpy(resp + rpos, "local", 5);
                        rpos += 5;
                        resp[rpos++] = 0;

                        syn_poke_u16(0x0001, resp, rpos);
                        rpos += 2; /* TYPE = A */
                        syn_poke_u16(0x8001, resp, rpos);
                        rpos += 2; /* CLASS = IN + cache flush */
                        syn_poke_u32(120, resp, rpos);
                        rpos += 4; /* TTL = 120 */
                        syn_poke_u16(4, resp, rpos);
                        rpos += 2;                        /* RDLEN = 4 */
                        memcpy(resp + rpos, mdns->ip, 4); /* Address */
                        rpos += 4;

                        /* Send mDNS reply back to multicast group 224.0.0.251:5353 */
                        SYN_SockAddr mcast_dest;
                        mcast_dest.ip[0] = 224;
                        mcast_dest.ip[1] = 0;
                        mcast_dest.ip[2] = 0;
                        mcast_dest.ip[3] = 251;
                        mcast_dest.port = 5353;

                        syn_port_udp_sendto(mdns->sock, resp, rpos, &mcast_dest);
                    }
                }
            }
        }
        PT_DEFER(pt, task);
    }

    PT_END(pt);
}

#endif /* SYN_USE_DNS */