; -*- fundamental -*- ; ----------------------------------------------------------------------- ; ; Copyright 2004 H. Peter Anvin - All Rights Reserved ; ; This program is free software; you can redistribute it and/or modify ; it under the terms of the GNU General Public License as published by ; the Free Software Foundation, Inc., 53 Temple Place Ste 330, ; Bostom MA 02111-1307, USA; either version 2 of the License, or ; (at your option) any later version; incorporated herein by reference. ; ; ----------------------------------------------------------------------- ; ; dnsresolv.inc ; ; Very simple DNS resolver (assumes recursion-enabled DNS server; ; this should be the normal thing for client-serving DNS servers.) ; DNS_PORT equ htons(53) ; Default DNS port DNS_MAX_PACKET equ 512 ; Defined by protocol ; TFTP uses the range 49152-57343 DNS_LOCAL_PORT equ htons(60053) ; All local DNS queries come from this port # DNS_MAX_SERVERS equ 4 ; Max no of DNS servers section .text ; ; Turn a string in DS:SI into a DNS "label set" in ES:DI. ; On return, DI points to the first byte after the label set, ; and SI to the terminating byte. ; ; On return, DX contains the number of dots encountered. ; dns_mangle: push ax push bx xor dx,dx .isdot: inc dx xor al,al mov bx,di stosb .getbyte: lodsb and al,al jz .endstring cmp al,':' jz .endstring cmp al,'.' je .isdot inc byte [es:bx] stosb jmp .getbyte .endstring: dec si dec dx ; We always counted one high cmp byte [es:bx],0 jz .done xor al,al stosb .done: pop bx pop ax ret ; ; Compare two sets of DNS labels, in DS:SI and ES:DI; the one in SI ; is allowed pointers relative to a packet in DNSRecvBuf. ; ; Assumes DS == ES. ZF = 1 if same; no registers changed. ; (Note: change reference to [di] to [es:di] to remove DS == ES assumption) ; dns_compare: pusha %if 0 .label: lodsb cmp al,0C0h jb .noptr and al,03Fh ; Get pointer value mov ah,al ; ... in network byte order! lodsb mov si,DNSRecvBuf add si,ax jmp .label .noptr: cmp al,[di] jne .done ; Mismatch inc di movzx cx,al ; End label? and cx,cx ; ZF = 1 if match jz .done ; We have a string of bytes that need to match now repe cmpsb je .label .done: %else xor ax,ax %endif popa ret ; ; Skip past a DNS label set in DS:SI. ; dns_skiplabel: push ax xor ax,ax ; AH == 0 .loop: lodsb cmp al,0C0h ; Pointer? jae .ptr and al,al jz .done add si,ax jmp .loop .ptr: inc si ; Pointer is two bytes .done: pop ax ret ; DNS header format struc dnshdr .id: resw 1 .flags: resw 1 .qdcount: resw 1 .ancount: resw 1 .nscount: resw 1 .arcount: resw 1 endstruc ; DNS query struc dnsquery .qtype: resw 1 .qclass: resw 1 endstruc ; DNS RR struc dnsrr .type: resw 1 .class: resw 1 .ttl: resd 1 .rdlength: resw 1 .rdata: equ $ endstruc section .bss alignb 2, db 0 DNSSendBuf resb DNS_MAX_PACKET DNSRecvBuf resb DNS_MAX_PACKET LocalDomain resb 256 ; Max possible length DNSServers resd DNS_MAX_SERVERS section .data pxe_udp_write_pkt_dns: .status: dw 0 ; Status .sip: dd 0 ; Server IP .gip: dd 0 ; Gateway IP .lport: dw DNS_LOCAL_PORT ; Local port .rport: dw DNS_PORT ; Remote port .buffersize: dw 0 ; Size of packet .buffer: dw DNSSendBuf, 0 ; off, seg of buffer pxe_udp_read_pkt_dns: .status: dw 0 ; Status .sip: dd 0 ; Source IP .dip: dd 0 ; Destination (our) IP .rport: dw DNS_PORT ; Remote port .lport: dw DNS_LOCAL_PORT ; Local port .buffersize: dw DNS_MAX_PACKET ; Max packet size .buffer: dw DNSRecvBuf, 0 ; off, seg of buffer LastDNSServer dw DNSServers ; Actual resolver function ; Points to a null-terminated or :-terminated string in DS:SI ; and returns the name in EAX if it exists and can be found. ; If EAX = 0 on exit, the lookup failed. section .text dns_resolv: push ds push es push di push cx push dx push cs pop es ; ES <- CS ; First, build query packet mov di,DNSSendBuf+dnshdr.flags inc word [es:di-2] ; New query ID mov ax,htons(0100h) ; Recursion requested stosw mov ax,htons(1) ; One question stosw xor ax,ax ; No answers, NS or ARs stosw stosw stosw call dns_mangle ; Convert name to DNS labels push cs ; DS <- CS pop ds push si ; Save pointer to after DNS string ; Initialize... mov eax,[MyIP] mov [pxe_udp_read_pkt_dns.dip],eax and dx,dx jnz .fqdn ; If we have dots, assume it's FQDN dec di ; Remove final null mov si,LocalDomain call strcpy ; Uncompressed DNS label set so it ends in null .fqdn: mov ax,htons(1) stosw ; QTYPE = 1 = A stosw ; QCLASS = 1 = IN sub di,DNSSendBuf mov [pxe_udp_write_pkt_dns.buffersize],di ; Now, send it to the nameserver(s) ; Outer loop: exponential backoff ; Inner loop: scan the various DNS servers mov dx,PKT_TIMEOUT mov cx,PKT_RETRY .backoff: mov si,DNSServers .servers: cmp si,[LastDNSServer] jb .moreservers .nomoreservers: add dx,dx ; Exponential backoff loop .backoff xor eax,eax ; Nothing... .done: pop si pop dx pop cx pop di pop es pop ds ret .moreservers: lodsd ; EAX <- next server push si push cx push dx mov word [pxe_udp_write_pkt_dns.status],0 mov [pxe_udp_write_pkt_dns.sip],eax mov [pxe_udp_read_pkt_dns.sip],eax xor eax,[MyIP] and eax,[Netmask] jz .nogw mov eax,[Gateway] .nogw: mov [pxe_udp_write_pkt_dns.gip],eax mov di,pxe_udp_write_pkt_dns mov bx,PXENV_UDP_WRITE call pxenv jc .timeout ; Treat failed transmit as timeout cmp word [pxe_udp_write_pkt_dns.status],0 jne .timeout mov cx,[BIOS_timer] .waitrecv: mov ax,[BIOS_timer] sub ax,cx cmp ax,dx jae .timeout mov word [pxe_udp_read_pkt_dns.status],0 mov word [pxe_udp_read_pkt_dns.buffersize],DNS_MAX_PACKET mov di,pxe_udp_read_pkt_dns mov bx,PXENV_UDP_READ call pxenv and ax,ax jnz .waitrecv cmp [pxe_udp_read_pkt_dns.status],ax jnz .waitrecv ; Got a packet, deal with it... mov si,DNSRecvBuf lodsw cmp ax,[DNSSendBuf] ; ID jne .waitrecv ; Not ours lodsw ; flags xor al,80h ; Query#/Answer bit test ax,htons(0F80Fh) jnz .badness lodsw xchg ah,al ; ntohs mov cx,ax ; Questions echoed lodsw xchg ah,al ; ntohs push ax ; Replies lodsw ; NS records lodsw ; Authority records jcxz .qskipped .skipq: call dns_skiplabel ; Skip name add si,4 ; Skip question trailer loop .skipq .qskipped: pop cx ; Number of replies jcxz .badness .parseanswer: mov di,DNSSendBuf+dnshdr_size call dns_compare pushf call dns_skiplabel mov ax,[si+8] ; RDLENGTH xchg ah,al ; ntohs popf jnz .notsame cmp dword [si],htons(1)*0x10001 ; TYPE = A, CLASS = IN? jne .notsame cmp ax,4 ; RDLENGTH = 4? jne .notsame ; ; We hit paydirt here... ; mov eax,[si+10] .gotresult: add sp,6 ; Drop timeout information jmp .done .notsame: add si,10 add si,ax loop .parseanswer .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... test word [DNSRecvBuf+dnshdr.flags],htons(0480h) jz .timeout xor eax,eax jmp .gotresult .timeout: pop dx pop cx pop si jmp .servers