[syslinux] [PATCH 1/1] efi: Implemented a DNS stub resolver for efi
celelibi at gmail.com
celelibi at gmail.com
Wed Sep 9 22:29:04 PDT 2015
From: Sylvain Gault <sylvain.gault at gmail.com>
First working version of the DNS resolver for EFI. It still lacks
timeouts and caching.
Signed-off-by: Sylvain Gault <sylvain.gault at gmail.com>
---
efi/dns.c | 529 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
efi/pxe.c | 16 --
2 files changed, 529 insertions(+), 16 deletions(-)
create mode 100644 efi/dns.c
diff --git a/efi/dns.c b/efi/dns.c
new file mode 100644
index 0000000..542037e
--- /dev/null
+++ b/efi/dns.c
@@ -0,0 +1,529 @@
+#include <stdint.h>
+#include "net.h"
+#include "fs/pxe/pxe.h"
+
+
+/* DNS CLASS values we care about */
+#define CLASS_IN 1
+
+/* DNS TYPE values we care about */
+#define TYPE_A 1
+#define TYPE_CNAME 5
+
+/* Default DNS port */
+#define DNS_PORT 53
+
+#define DNS_MAX_SERVERS 4 /* Max no of DNS servers */
+uint32_t dns_server[DNS_MAX_SERVERS] = {0, };
+
+/* Id of the current DNS query */
+static uint16_t dns_query_id = 0;
+
+enum dns_answer_state {
+ DNS_ANSWER_IP,
+ DNS_ANSWER_CNAME,
+ DNS_ANSWER_MALFORMED
+};
+
+/*
+ * The DNS header structure
+ */
+struct dnshdr {
+ uint16_t id;
+ uint16_t flags;
+ /* number of entries in the question section */
+ uint16_t qdcount;
+ /* number of resource records in the answer section */
+ uint16_t ancount;
+ /* number of name server resource records in the authority records section*/
+ uint16_t nscount;
+ /* number of resource records in the additional records section */
+ uint16_t arcount;
+} __attribute__ ((packed));
+
+/*
+ * The DNS query structure
+ */
+struct dnsquery {
+ uint16_t qtype;
+ uint16_t qclass;
+} __attribute__ ((packed));
+
+
+/*
+ * The DNS Resource recodes structure
+ */
+struct dnsrr {
+ uint16_t type;
+ uint16_t class;
+ uint32_t ttl;
+ uint16_t rdlength; /* The lenght of this rr data */
+ char rdata[];
+} __attribute__ ((packed));
+
+
+/*
+ * Turns the dotted domain name dotted into a sequence of labels ready for a DNS
+ * query.
+ * size is the size of the buffer pointer to by labels.
+ * Returns the total number of written bytes.
+ * Returns 0 if the buffer is too small, even if some data has been written.
+ */
+static size_t dns_mangle(unsigned char *labels, size_t size, const char *dotted)
+{
+ unsigned char *labels_start = labels;
+ unsigned char *len = labels++;
+
+ if (size < 1)
+ return 0;
+
+ size--;
+
+ *len = 0;
+
+ while (*dotted) {
+ if (*dotted == '.') {
+ len = labels;
+ *len = 0;
+ } else {
+ *labels = *dotted;
+ (*len)++;
+ }
+
+ labels++;
+ dotted++;
+ size--;
+
+ if (*dotted && size < 1)
+ return 0;
+ }
+
+ /* Last label length must be 0 */
+ if (*len && size < 1)
+ return 0;
+
+ if (*len)
+ *labels++ = 0;
+
+ return labels - labels_start;
+}
+
+/*
+ * Turns the sequence of labels from a DNS query / answer to a dotted domain
+ * name.
+ * labels is the pointer to the sequence of labels.
+ * buf is the buffer within which the labels are.
+ * buf_size is the size of the buffer.
+ * dotted is the buffer to fill with the dotted name, it is always nul
+ * terminated.
+ * dotted_size is the size of the buffer.
+ * maxptr indicates the maximal number of pointers to follow.
+ * Returns the number of bytes read from the pointer labels. Returns 0 if the
+ * label sequence is malformed.
+ * /!\ The returned value may be small if the domain name is compressed as per
+ * RFC 1035 4.1.4.
+ */
+static size_t dns_demangle(const unsigned char *labels, const unsigned char *buf,
+ size_t buf_size, char *dotted, size_t dotted_size, int maxptr)
+{
+ const unsigned char *labels_start = labels;
+ const unsigned char *len = labels++;
+
+ while (*len) {
+ /* Compressed name */
+ if (*len & 0xc0) {
+ const unsigned char *newlabels;
+ size_t size;
+
+ /* No more pointer following! */
+ if (maxptr == 0)
+ goto err;
+
+ newlabels = buf + ((*len & 0x3f) << 8) + *(len + 1);
+
+ /* Sanity check */
+ if (newlabels < buf || newlabels >= buf + buf_size)
+ goto err;
+
+ size = dns_demangle(newlabels, buf, buf_size, dotted, dotted_size, maxptr - 1);
+ if (size == 0)
+ goto err;
+
+ return 2;
+ }
+
+ /* Just sanity checks */
+ if (labels + *len + 1 >= buf + buf_size)
+ goto err;
+ if (dotted_size < *len + 2U)
+ goto err;
+
+ strncpy(dotted, (const char *)labels, *len);
+
+ dotted += *len;
+ strcpy(dotted, ".");
+ dotted++;
+ dotted_size -= *len + 1;
+
+ labels += *len;
+ len = labels++;
+ }
+
+ return labels - labels_start;
+
+err:
+ return 0;
+}
+
+/*
+ * Compare two domain names in dotted format. The only trick is the last dot
+ * that is optional.
+ * Returns 0 if the names are equivalents, non-zero otherwise.
+ */
+static int dns_compare_names(const char *s1, const char *s2)
+{
+ size_t l1, l2;
+
+ l1 = strlen(s1);
+ l2 = strlen(s2);
+
+ if (l1 == l2 + 1 && s1[l1 - 1] == '.')
+ return strncmp(s1, s2, l2);
+
+ if (l1 + 1 == l2 && s2[l2 - 1] == '.')
+ return strncmp(s1, s2, l1);
+
+ return strcmp(s1, s2);
+}
+
+/*
+ * Fills the buffer with the DNS header + query ready to be sent to a name
+ * server.
+ * size is the size of the buffer.
+ * Returns the exact size of the buffer that has been written.
+ * If the buffer is too small, the returned size is 0, even if some data has
+ * been partially written.
+ */
+static size_t dns_query_prepare(unsigned char *buff, size_t size,
+ const char *name)
+{
+ struct dnshdr *hdr;
+ struct dnsquery *query;
+ size_t lnamesize;
+
+
+ if (size < sizeof(*hdr))
+ return 0;
+
+ /* First, fill the DNS header struct */
+ hdr = (struct dnshdr *)buff;
+ hdr->id = htons(dns_query_id++); /* New query ID */
+ hdr->flags = htons(0x0100); /* Recursion requested */
+ hdr->qdcount = htons(1); /* One question */
+ hdr->ancount = 0; /* No answers */
+ hdr->nscount = 0; /* No NS */
+ hdr->arcount = 0; /* No AR */
+
+ buff += sizeof(*hdr);
+ size -= sizeof(*hdr);
+
+ lnamesize = dns_mangle(buff, size, name);
+ if (lnamesize == 0)
+ return 0;
+
+ buff += lnamesize;
+ size -= lnamesize;
+
+ if (size < sizeof(*query))
+ return 0;
+
+ query = (struct dnsquery *)buff;
+
+ /* Fill the DNS query packet */
+ query->qtype = htons(TYPE_A);
+ query->qclass = htons(CLASS_IN);
+
+ return sizeof(*hdr) + lnamesize + sizeof(*query);
+}
+
+/*
+ * Parse the received buffer and extract the DNS answer that corresponds to the
+ * given name.
+ * buf_size is the data size of the buffer.
+ * name is the string we want the IP address of.
+ * *ip is filled with the answer if any.
+ * cname is filled with the CNAME answer if any.
+ * cname_size is the size if the cname buffer.
+ * DNS_ANSWER_IP is returned if an IP address is found
+ * DNS_ANSWER_CNAME is returned if a CNAME reply is found
+ * DNS_ANSWER_MALFORMED in any other case, including a non-existing domain.
+ */
+static enum dns_answer_state dns_answer_parse(const unsigned char *buf,
+ size_t buf_size, const char *name, uint32_t *ip, char *cname,
+ size_t cname_size)
+{
+ const struct dnshdr *hdr;
+ int qcnt, acnt;
+ const unsigned char *qname;
+ char textname[512];
+
+ if (buf_size < sizeof(*hdr))
+ goto out;
+
+ hdr = (const struct dnshdr *)buf;
+
+ /* Sanity check on the flags */
+ if ((hdr->flags ^ htons(0x8000)) & htons(0xf80f))
+ goto out;
+
+ qcnt = htons(hdr->qdcount);
+ acnt = htons(hdr->ancount);
+ if (qcnt > 1)
+ dprintf("dns_answer_parse: Suspicious DNS response with multiple queries\n");
+
+ /* Skip questions */
+ qname = buf + sizeof(*hdr);
+ while (qcnt--) {
+ size_t namesize;
+
+ if (qname >= buf + buf_size)
+ goto out;
+
+ namesize = dns_demangle(qname, buf, buf_size, textname, sizeof(textname), 10);
+ if (namesize == 0)
+ goto out;
+
+ if (dns_compare_names(textname, name)) {
+ dprintf("dns_answer_parse: Received a query for an unknown domain\n");
+ goto out;
+ }
+
+ qname += namesize + sizeof(struct dnsquery);
+ }
+
+
+ /* Iterate on the answers until a useful one is found. */
+ while (acnt--) {
+ size_t namesize;
+ int same;
+ size_t rdlen;
+ struct dnsrr *rr;
+
+ if (qname >= buf + buf_size)
+ goto out;
+
+ namesize = dns_demangle(qname, buf, buf_size, textname, sizeof(textname), 10);
+ if (namesize == 0)
+ goto out;
+
+ same = !dns_compare_names(textname, name);
+
+ rr = (struct dnsrr *)(qname + namesize);
+
+ if ((unsigned char *)rr >= buf + buf_size)
+ goto out;
+
+ rdlen = ntohs(rr->rdlength);
+
+ if ((unsigned char *)rr + rdlen > buf + buf_size)
+ goto out;
+
+ if (!same) {
+ dprintf("dns_answer_parse: Received an answer for an unknown domain.\n");
+ continue;
+ }
+
+ if (ntohs(rr->class) != CLASS_IN)
+ continue;
+
+ if (ntohs(rr->type) == TYPE_A && rdlen == 4) {
+ *ip = *(uint32_t *)rr->rdata;
+ return DNS_ANSWER_IP;
+ }
+
+ if (ntohs(rr->type) == TYPE_CNAME) {
+ size_t cnamesize;
+
+ cnamesize = dns_demangle((unsigned char *)rr->rdata, buf, buf_size,
+ textname, sizeof(textname), 10);
+ if (cnamesize == 0)
+ goto out;
+
+ strncpy(cname, textname, cname_size);
+ cname[cname_size - 1] = '\0';
+ return DNS_ANSWER_CNAME;
+ }
+
+ qname += namesize + sizeof(*rr) + rdlen;
+ }
+
+ dprintf("dns_answer_parse: Didn't find an interesting answer.\n");
+
+out:
+ /* If we get there, no meaningful reply were received. */
+ return DNS_ANSWER_MALFORMED;
+}
+
+/*
+ * Tells whether the IP address is that of a known DNS server.
+ */
+static int is_dns_server(uint32_t ip)
+{
+ int i;
+ for (i = 0; i < DNS_MAX_SERVERS && dns_server[i]; i++) {
+ if (dns_server[i] == ip)
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Perform the actual DNS query.
+ */
+static int dns_gethostbyname(const char *name, uint32_t *ip)
+{
+ unsigned char sendbuff[PKTBUF_SIZE];
+ unsigned char recvbuff[PKTBUF_SIZE];
+ char cname[512];
+ struct pxe_pvt_inode socket;
+ size_t sendsize;
+ uint16_t recvsize;
+ uint32_t recvip;
+ uint16_t recvport;
+ int dnsidx;
+ int maxredirect = 10;
+ int stop = 0;
+
+
+ memset(&socket, 0, sizeof(socket));
+ *ip = 0;
+
+ if (core_udp_open(&socket))
+ return -1;
+
+ sendsize = dns_query_prepare(sendbuff, sizeof(sendbuff), name);
+
+ /* Send the actual query on the network */
+ dnsidx = 0;
+ while (!stop && dnsidx < DNS_MAX_SERVERS && dns_server[dnsidx]) {
+ enum dns_answer_state state;
+ int err;
+
+ core_udp_connect(&socket, dns_server[dnsidx], DNS_PORT);
+ core_udp_send(&socket, sendbuff, sendsize);
+ recvsize = sizeof(recvbuff);
+
+ do {
+ err = core_udp_recv(&socket, recvbuff, &recvsize, &recvip, &recvport);
+ if (err)
+ dprintf("dns_gethostbyname: Didn't receive anything.\n");
+ if (!is_dns_server(recvip) || recvport != DNS_PORT)
+ dprintf("dns_gethostbyname: Received an unexpected UDP packet\n");
+ } while (err == 0 && !(is_dns_server(recvip) && recvport == DNS_PORT));
+
+ core_udp_disconnect(&socket);
+
+ state = dns_answer_parse(recvbuff, recvsize, name, ip, cname, sizeof(cname));
+
+ switch (state) {
+ case DNS_ANSWER_IP:
+ stop = 1;
+ break;
+
+ case DNS_ANSWER_CNAME:
+ /* Don't follow CNAME redirections forever */
+ maxredirect--;
+ if (maxredirect == 0) {
+ stop = 1;
+ break;
+ }
+
+ /* Ask the same server for the new name */
+ sendsize = dns_query_prepare(sendbuff, sizeof(sendbuff), cname);
+ name = cname;
+ *ip = 0;
+ break;
+
+ case DNS_ANSWER_MALFORMED:
+ /* Just ask another server */
+ *ip = 0;
+ dnsidx++;
+ break;
+ }
+ }
+
+ core_udp_close(&socket);
+
+ return *ip == 0;
+}
+
+/*
+ * parse the ip_str and return the ip address with *res.
+ * return true if the whole string was consumed and the result
+ * was valid.
+ */
+static bool parse_dotquad(const char *ip_str, uint32_t *res)
+{
+ const char *p = ip_str;
+ uint8_t part = 0;
+ uint32_t ip = 0;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ while (is_digit(*p)) {
+ part = part * 10 + *p - '0';
+ p++;
+ }
+ if (i != 3 && *p != '.')
+ return false;
+
+ ip = (ip << 8) | part;
+ part = 0;
+ p++;
+ }
+ p--;
+
+ *res = htonl(ip);
+ return *p == '\0';
+}
+
+__export uint32_t dns_resolv(const char *name)
+{
+ char fullname[512];
+ uint32_t ip;
+ int err;
+
+ /*
+ * Return failure on an empty input... this can happen during
+ * some types of URL parsing, and this is the easiest place to
+ * check for it.
+ */
+ if (!name || !*name)
+ return 0;
+
+ /* IP already in dotted notation */
+ if (parse_dotquad(name, &ip))
+ return ip;
+
+ /* Special case for localhost */
+ if (!strcmp(name, "localhost")) {
+ parse_dotquad("127.0.0.1", &ip);
+ return ip;
+ }
+
+ /* At this point we'll need at least one DNS server */
+ if (dns_server[0] == 0)
+ return 0;
+
+ /* Is it a local (unqualified) domain name? */
+ if (!strchr(name, '.') && LocalDomain[0]) {
+ snprintf(fullname, sizeof(fullname), "%s.%s", name, LocalDomain);
+ name = fullname;
+ }
+
+ err = dns_gethostbyname(name, &ip);
+ if (err)
+ return 0;
+
+ return ip;
+}
diff --git a/efi/pxe.c b/efi/pxe.c
index 4300947..8a33133 100644
--- a/efi/pxe.c
+++ b/efi/pxe.c
@@ -32,22 +32,6 @@ int reset_pxe(void)
return 0;
}
-#define DNS_MAX_SERVERS 4 /* Max no of DNS servers */
-uint32_t dns_server[DNS_MAX_SERVERS] = {0, };
-
-__export uint32_t dns_resolv(const char *name)
-{
- /*
- * Return failure on an empty input... this can happen during
- * some types of URL parsing, and this is the easiest place to
- * check for it.
- */
- if (!name || !*name)
- return 0;
-
- return 0;
-}
-
int pxe_init(bool quiet)
{
EFI_HANDLE *handles;
--
2.5.1
More information about the Syslinux
mailing list