#include #include #include #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 pxe_dns(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; }