| #include <stdio.h> |
| #include <string.h> |
| #include <core.h> |
| #include "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 |
| |
| /* |
| * 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)); |
| |
| |
| #define DNS_PORT htons(53) /* Default DNS port */ |
| #define DNS_MAX_SERVERS 4 /* Max no of DNS servers */ |
| |
| uint32_t dns_server[DNS_MAX_SERVERS] = {0, }; |
| |
| |
| /* |
| * Turn a string in _src_ into a DNS "label set" in _dst_; returns the |
| * number of dots encountered. On return, *dst is updated. |
| */ |
| int dns_mangle(char **dst, const char *p) |
| { |
| char *q = *dst; |
| char *count_ptr; |
| char c; |
| int dots = 0; |
| |
| count_ptr = q; |
| *q++ = 0; |
| |
| while (1) { |
| c = *p++; |
| if (c == 0 || c == ':' || c == '/') |
| break; |
| if (c == '.') { |
| dots++; |
| count_ptr = q; |
| *q++ = 0; |
| continue; |
| } |
| |
| *count_ptr += 1; |
| *q++ = c; |
| } |
| |
| if (*count_ptr) |
| *q++ = 0; |
| |
| /* update the strings */ |
| *dst = q; |
| return dots; |
| } |
| |
| |
| /* |
| * Compare two sets of DNS labels, in _s1_ and _s2_; the one in _s2_ |
| * is allowed pointers relative to a packet in buf. |
| * |
| */ |
| static bool dns_compare(const void *s1, const void *s2, const void *buf) |
| { |
| const uint8_t *q = s1; |
| const uint8_t *p = s2; |
| unsigned int c0, c1; |
| |
| while (1) { |
| c0 = p[0]; |
| if (c0 >= 0xc0) { |
| /* Follow pointer */ |
| c1 = p[1]; |
| p = (const uint8_t *)buf + ((c0 - 0xc0) << 8) + c1; |
| } else if (c0) { |
| c0++; /* Include the length byte */ |
| if (memcmp(q, p, c0)) |
| return false; |
| q += c0; |
| p += c0; |
| } else { |
| return *q == 0; |
| } |
| } |
| } |
| |
| /* |
| * Copy a DNS label into a buffer, considering the possibility that we might |
| * have to follow pointers relative to "buf". |
| * Returns a pointer to the first free byte *after* the terminal null. |
| */ |
| static void *dns_copylabel(void *dst, const void *src, const void *buf) |
| { |
| uint8_t *q = dst; |
| const uint8_t *p = src; |
| unsigned int c0, c1; |
| |
| while (1) { |
| c0 = p[0]; |
| if (c0 >= 0xc0) { |
| /* Follow pointer */ |
| c1 = p[1]; |
| p = (const uint8_t *)buf + ((c0 - 0xc0) << 8) + c1; |
| } else if (c0) { |
| c0++; /* Include the length byte */ |
| memcpy(q, p, c0); |
| p += c0; |
| q += c0; |
| } else { |
| *q++ = 0; |
| return q; |
| } |
| } |
| } |
| |
| /* |
| * Skip past a DNS label set in DS:SI |
| */ |
| static char *dns_skiplabel(char *label) |
| { |
| uint8_t c; |
| |
| while (1) { |
| c = *label++; |
| if (c >= 0xc0) |
| return ++label; /* pointer is two bytes */ |
| if (c == 0) |
| return label; |
| label += c; |
| } |
| } |
| |
| extern const uint8_t TimeoutTable[]; |
| extern uint16_t get_port(void); |
| extern void free_port(uint16_t port); |
| |
| /* |
| * 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'; |
| } |
| |
| /* |
| * Actual resolver function |
| * Points to a null-terminated or :-terminated string in _name_ |
| * and returns the ip addr in _ip_ if it exists and can be found. |
| * If _ip_ = 0 on exit, the lookup failed. _name_ will be updated |
| * |
| * XXX: probably need some caching here. |
| */ |
| __export uint32_t dns_resolv(const char *name) |
| { |
| static char __lowmem DNSSendBuf[PKTBUF_SIZE]; |
| static char __lowmem DNSRecvBuf[PKTBUF_SIZE]; |
| char *p; |
| int err; |
| int dots; |
| int same; |
| int rd_len; |
| int ques, reps; /* number of questions and replies */ |
| uint8_t timeout; |
| const uint8_t *timeout_ptr = TimeoutTable; |
| uint32_t oldtime; |
| uint32_t srv; |
| uint32_t *srv_ptr; |
| struct dnshdr *hd1 = (struct dnshdr *)DNSSendBuf; |
| struct dnshdr *hd2 = (struct dnshdr *)DNSRecvBuf; |
| struct dnsquery *query; |
| struct dnsrr *rr; |
| static __lowmem struct s_PXENV_UDP_WRITE udp_write; |
| static __lowmem struct s_PXENV_UDP_READ udp_read; |
| uint16_t local_port; |
| uint32_t result = 0; |
| |
| /* |
| * 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; |
| |
| /* If it is a valid dot quad, just return that value */ |
| if (parse_dotquad(name, &result)) |
| return result; |
| |
| /* Make sure we have at least one valid DNS server */ |
| if (!dns_server[0]) |
| return 0; |
| |
| /* Get a local port number */ |
| local_port = get_port(); |
| |
| /* First, fill the DNS header struct */ |
| hd1->id++; /* New query ID */ |
| hd1->flags = htons(0x0100); /* Recursion requested */ |
| hd1->qdcount = htons(1); /* One question */ |
| hd1->ancount = 0; /* No answers */ |
| hd1->nscount = 0; /* No NS */ |
| hd1->arcount = 0; /* No AR */ |
| |
| p = DNSSendBuf + sizeof(struct dnshdr); |
| dots = dns_mangle(&p, name); /* store the CNAME */ |
| |
| if (!dots) { |
| p--; /* Remove final null */ |
| /* Uncompressed DNS label set so it ends in null */ |
| p = stpcpy(p, LocalDomain); |
| } |
| |
| /* Fill the DNS query packet */ |
| query = (struct dnsquery *)p; |
| query->qtype = htons(TYPE_A); |
| query->qclass = htons(CLASS_IN); |
| p += sizeof(struct dnsquery); |
| |
| /* Now send it to name server */ |
| timeout_ptr = TimeoutTable; |
| timeout = *timeout_ptr++; |
| srv_ptr = dns_server; |
| while (timeout) { |
| srv = *srv_ptr++; |
| if (!srv) { |
| srv_ptr = dns_server; |
| srv = *srv_ptr++; |
| } |
| |
| udp_write.status = 0; |
| udp_write.ip = srv; |
| udp_write.gw = gateway(srv); |
| udp_write.src_port = local_port; |
| udp_write.dst_port = DNS_PORT; |
| udp_write.buffer_size = p - DNSSendBuf; |
| udp_write.buffer = FAR_PTR(DNSSendBuf); |
| err = pxe_call(PXENV_UDP_WRITE, &udp_write); |
| if (err || udp_write.status) |
| continue; |
| |
| oldtime = jiffies(); |
| do { |
| if (jiffies() - oldtime >= timeout) |
| goto again; |
| |
| udp_read.status = 0; |
| udp_read.src_ip = srv; |
| udp_read.dest_ip = IPInfo.myip; |
| udp_read.s_port = DNS_PORT; |
| udp_read.d_port = local_port; |
| udp_read.buffer_size = PKTBUF_SIZE; |
| udp_read.buffer = FAR_PTR(DNSRecvBuf); |
| err = pxe_call(PXENV_UDP_READ, &udp_read); |
| } while (err || udp_read.status || hd2->id != hd1->id); |
| |
| if ((hd2->flags ^ 0x80) & htons(0xf80f)) |
| goto badness; |
| |
| ques = htons(hd2->qdcount); /* Questions */ |
| reps = htons(hd2->ancount); /* Replies */ |
| p = DNSRecvBuf + sizeof(struct dnshdr); |
| while (ques--) { |
| p = dns_skiplabel(p); /* Skip name */ |
| p += 4; /* Skip question trailer */ |
| } |
| |
| /* Parse the replies */ |
| while (reps--) { |
| same = dns_compare(DNSSendBuf + sizeof(struct dnshdr), |
| p, DNSRecvBuf); |
| p = dns_skiplabel(p); |
| rr = (struct dnsrr *)p; |
| rd_len = ntohs(rr->rdlength); |
| if (same && ntohs(rr->class) == CLASS_IN) { |
| switch (ntohs(rr->type)) { |
| case TYPE_A: |
| if (rd_len == 4) { |
| result = *(uint32_t *)rr->rdata; |
| goto done; |
| } |
| break; |
| case TYPE_CNAME: |
| dns_copylabel(DNSSendBuf + sizeof(struct dnshdr), |
| rr->rdata, DNSRecvBuf); |
| /* |
| * We should probably rescan the packet from the top |
| * here, and technically we might have to send a whole |
| * new request here... |
| */ |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* not the one we want, try next */ |
| p += sizeof(struct dnsrr) + rd_len; |
| } |
| |
| badness: |
| /* |
| * |
| ; We got back no data from this server. |
| ; Unfortunately, for a recursive, non-authoritative |
| ; query there is no such thing as an NXDOMAIN reply, |
| ; which technically means we can't draw any |
| ; conclusions. However, in practice that means the |
| ; domain doesn't exist. If this turns out to be a |
| ; problem, we may want to add code to go through all |
| ; the servers before giving up. |
| |
| ; If the DNS server wasn't capable of recursion, and |
| ; isn't capable of giving us an authoritative reply |
| ; (i.e. neither AA or RA set), then at least try a |
| ; different setver... |
| */ |
| if (hd2->flags == htons(0x480)) |
| continue; |
| |
| break; /* failed */ |
| |
| again: |
| continue; |
| } |
| |
| done: |
| free_port(local_port); /* Return port number to the free pool */ |
| |
| return result; |
| } |