blob: 484e30f11508d93ecb5e076ead77bc807a7dc99f [file] [log] [blame]
; -*- fundamental -*-
; -----------------------------------------------------------------------
;
; Copyright 2004-2008 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 .bss1
alignb 2
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.
;
; No segment assumptions permitted.
;
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