kolibrios-fun/programs/develop/libraries/network/network.asm

1302 lines
50 KiB
NASM
Raw Normal View History

format MS COFF
public @EXPORT as 'EXPORTS'
include '../../../struct.inc'
include '../../../proc32.inc'
include '../../../macros.inc'
purge section,mov,add,sub
include '../../../network.inc'
section '.flat' code readable align 16
;;===========================================================================;;
lib_init: ;//////////////////////////////////////////////////////////////////;;
;;---------------------------------------------------------------------------;;
;? Library entry point (called after library load) ;;
;;---------------------------------------------------------------------------;;
;> eax = pointer to memory allocation routine ;;
;> ebx = pointer to memory freeing routine ;;
;> ecx = pointer to memory reallocation routine ;;
;> edx = pointer to library loading routine ;;
;;---------------------------------------------------------------------------;;
;< eax = 1 (fail) / 0 (ok) (library initialization result) ;;
;;===========================================================================;;
mov [mem.alloc], eax
mov [mem.free], ebx
mov [mem.realloc], ecx
mov [dll.load], edx
mov [DNSrequestID], 1
xor eax, eax
ret
;;===========================================================================;;
;; in_addr_t __stdcall inet_addr(__in const char* hostname); ;;
inet_addr: ;;
;;---------------------------------------------------------------------------;;
;? Convert the string from standard IPv4 dotted notation to integer IP addr. ;;
;;---------------------------------------------------------------------------;;
;> first parameter = host name ;;
;;---------------------------------------------------------------------------;;
;< eax = IP address on success / -1 on error ;;
;;===========================================================================;;
; 0. Save used registers for __stdcall.
push ebx esi edi
mov esi, [esp+16] ; esi = hostname
; 1. Check that only allowed symbols are present.
; (hex digits, possibly letters 'x'/'X' and up to 3 dots)
push esi
xor ecx, ecx
.calcdots_loop:
; loop for all characters in string
lodsb
; check for end of string
cmp al, 0
jz .calcdots_loop_done
; check for dot
cmp al, '.'
jz .dot
; check for digit
sub al, '0'
cmp al, 9
jbe .calcdots_loop
; check for hex letter
sub al, 'A' - '0' ; 'A'-'F' -> 0-5, 'a'-'f' -> 20h-25h
and al, not 20h
cmp al, 'F' - 'A'
jbe .calcdots_loop
; check for 'x'/'X'
cmp al, 'X' - 'A'
jz .calcdots_loop
jmp .fail.pop
.dot:
inc ecx
jmp .calcdots_loop
.calcdots_loop_done:
cmp ecx, 4
jae .fail.pop
; 2. The name can be valid dotted name; try to convert, checking limit
pop esi
xor edi, edi ; edi = address
push 0xFFFFFFFF
pop edx ; edx = mask for rest of address
; 2a. Convert name except for last group.
jecxz .ip_convert_2b
.ip_convert_2a:
push ecx
mov ecx, 0xFF ; limit for all groups except for last
call .get_number
pop ecx
jc .fail
cmp byte [esi-1], '.'
jnz .fail
shl edi, 8
shr edx, 8
add edi, eax
loop .ip_convert_2a
; 2b. Convert last group.
.ip_convert_2b:
mov ecx, edx
call .get_number
jc .fail
cmp byte [esi-1], 0
jnz .fail
@@:
shl edi, 8
shr edx, 8
jnz @b
add edi, eax
; 2c. Convert to network byte order.
bswap edi
; 3. Set return value, restore used registers and return.
xchg eax, edi
.ret:
pop edi esi ebx
ret 4
; 4. On error, return -1.
.fail.pop:
pop esi
.fail:
push -1
pop eax
jmp .ret
;;===========================================================================;;
;; Internal auxiliary function for IP parsing. ;;
.get_number: ;;
;;---------------------------------------------------------------------------;;
;? Converts string to number. ;;
;;---------------------------------------------------------------------------;;
;> esi -> string ;;
;> ecx = limit for number ;;
;;---------------------------------------------------------------------------;;
;< eax = number ;;
;< CF set on error (too big number) / cleared on success ;;
;< esi -> end of number representation ;;
;;===========================================================================;;
; 0. Save edx, which is used in caller.
push edx
; 1. Initialize number, zero eax so that lodsb gets full dword.
xor eax, eax
xor edx, edx
; 2. Get used numeral system: 0x = hex, otherwise 0 = octal, otherwise decimal
push 10
pop ebx
lodsb
cmp al, '0'
jnz .convert
push 8
pop ebx
lodsb
cmp al, 'x'
jnz .convert
add ebx, ebx
; 3. Loop while digits are encountered.
.convert:
; 4. Convert digit from text representation to binary value.
or al, 20h ; '0'-'9' -> '0'-'9', 'A'-'F' -> 'a'-'f'
sub al, '0'
cmp al, 9
jbe .digit
sub al, 'a' - '0'
cmp al, 'f' - 'a'
ja .convert_done
add al, 10
.digit:
; 5. Digit must be less than base of numeral system.
cmp eax, ebx
jae .convert_done
; 6. Advance the number.
imul edx, ebx
add edx, eax
cmp edx, ecx
ja .gn_error
; 3b. Continue loop.
lodsb
jmp .convert
.convert_done:
; 7. Invalid character, number converted, return success.
xchg eax, edx
pop edx
clc
ret
.gn_error:
; 8. Too big number, return error.
pop edx
stc
ret
;;===========================================================================;;
;; char* __stdcall inet_ntoa(struct in_addr in); ;;
inet_ntoa: ;;
;;---------------------------------------------------------------------------;;
;? Convert the Internet host address to standard IPv4 dotted notation. ;;
;;---------------------------------------------------------------------------;;
;> first parameter = host address ;;
;;---------------------------------------------------------------------------;;
;< eax = pointer to resulting string (in static buffer) ;;
;;===========================================================================;;
; 0. Save used registers for __stdcall.
push ebx esi edi
mov bl, 0xCD ; constant for div 10
; 1. Write octet 4 times.
mov edi, .buffer
mov edx, [esp+16] ; eax = in
mov al, dl
call .write
mov al, dh
shr edx, 16
call .write
mov al, dl
call .write
mov al, dh
call .write
; 2. Replace final dot with terminating zero.
mov byte [edi-1], 0
; 3. Restore used registers, set result value and return.
pop edi esi ebx
mov eax, .buffer
ret 4
.write:
movzx esi, al
mul bl
add esi, ('.' shl 8) + '0'
shr ah, 3 ; ah = al / 10
movzx ecx, ah
add ecx, ecx
lea ecx, [ecx*5]
sub esi, ecx ; lobyte(esi) = al % 10, hibyte(esi) = '.'
test ah, ah
jz .1digit
cmp ah, 10
jb .2digit
cmp ah, 20
sbb cl, cl
add cl, '2'
mov byte [edi], cl
movzx ecx, cl
lea ecx, [ecx*5]
sub ah, cl
sub ah, cl
add ah, ('0'*11) and 255
mov byte [edi+1], ah
mov word [edi+2], si
add edi, 4
ret
.2digit:
add ah, '0'
mov byte [edi], ah
mov word [edi+1], si
add edi, 3
ret
.1digit:
mov word [edi], si
add edi, 2
ret
struct __gai_reqdata
socketnum dd ?
; external code should not look on rest of this structure,
; it is internal for getaddrinfo_start/process/abort
reqid dw ? ; DNS request ID
socktype db ? ; SOCK_* or 0 for any
db ?
service dd ?
flags dd ?
reserved rb 16
ends
;;===========================================================================;;
;; int __stdcall getaddrinfo(__in const char* hostname, ;;
;; __in const char* servname, ;;
;; __in const struct addrinfo* hints, ;;
;; __out struct addrinfo **res); ;;
getaddrinfo: ;;
;;---------------------------------------------------------------------------;;
;? Get a list of IP addresses and port numbers for given host and service ;;
;;---------------------------------------------------------------------------;;
;> first parameter (optional) = host name ;;
;> second parameter (optional) = service name (decimal number for now) ;;
;> third parameter (optional) = hints for socket type ;;
;> fourth parameter = pointer to result (head of L1-list) ;;
;;---------------------------------------------------------------------------;;
;< eax = 0 on success / one of EAI_ codes on error ;;
;;===========================================================================;;
; 0. Save used registers for __stdcall.
push ebx esi edi
mov edi, [esp+28] ; edi = res
; 1. Create and send DNS packet.
sub esp, sizeof.__gai_reqdata ; reserve stack place (1)
push esp ; fifth parameter = pointer to (1)
push edi ; fourth parameter = res
push dword [esp+32+sizeof.__gai_reqdata] ; third parameter = hints
push dword [esp+32+sizeof.__gai_reqdata] ; second parameter = servname
push dword [esp+32+sizeof.__gai_reqdata] ; first parameter = hostname
call getaddrinfo_start
test eax, eax
jns .ret ; if name resolved without network activity, return
; 2. Wait for DNS reply.
; 2a. Ignore all events except network stack.
mcall 40, EVM_STACK
push eax ; save previous event mask (2)
; 2b. Get upper limit for wait time. Use timeout = 5 seconds.
mcall 26, 9 ; get time stamp
xchg esi, eax ; save time stamp to esi
mov ebx, 500 ; start value for timeout
add esi, ebx
.wait:
; 2c. Wait for event with timeout.
mcall 23 ; wait for event - must be stack event
; 2d. Check for timeout.
test eax, eax
jz .timeout
; 3. Got packet. Call processing function.
lea eax, [esp+4]
push edi ; second parameter: pointer to result
push eax ; first parameter: pointer to reqdata
call getaddrinfo_process
; 4. Test whether wait loop must be continued.
test eax, eax
jns .ret.restore
; 2e. Recalculate timeout value.
mcall 26, 9
mov ebx, esi
sub ebx, eax
; 2f. Check that time is not over; if not, continue wait loop
cmp ebx, 500
jbe .wait
.timeout:
; 5. Timeout: abort and return error
lea eax, [esp+4] ; pointer to (1)
push eax
call getaddrinfo_abort
and dword [edi], 0
push EAI_AGAIN
pop eax
.ret.restore:
; 6. Restore event mask.
pop ebx ; get event mask (2)
push eax ; save return code (3)
mcall 40
pop eax ; restore return code (3)
.ret:
; 7. Restore stack pointer, used registers and return.
add esp, sizeof.__gai_reqdata ; undo (1)
pop edi esi ebx
ret 16
;;===========================================================================;;
;; int __stdcall getaddrinfo_start(__in const char* hostname, ;;
;; __in const char* servname, ;;
;; __in const struct addrinfo* hints, ;;
;; __out struct addrinfo **res, ;;
;; __out struct __gai_reqdata* reqdata); ;;
getaddrinfo_start: ;;
;;---------------------------------------------------------------------------;;
;? Initiator for getaddrinfo, sends DNS request ;;
;;---------------------------------------------------------------------------;;
;> first 4 parameters same as for getaddrinfo ;;
;> last parameter = pointer to buffer for __gai_reqdata, must be passed to ;;
;> getaddrinfo_process as is ;;
;;---------------------------------------------------------------------------;;
;< eax = <0 if wait loop must be entered / 0 on success / EAI_* on error ;;
;;===========================================================================;;
;; Known limitations: ;;
;; 1. No support for TCP connections => ;;
;; 1a. Long replies will be truncated, and not all IP addresses will be got. ;;
;; 2. No support for iterative resolving => ;;
;; 2a. In theory may fail with some servers. ;;
;; 3. Assumes that domain for relative names is always root, ".". ;;
;; 4. Does not support lookup of services by name, ;;
;; only decimal representation is supported. ;;
;; 5. Assumes that IPv4 is always configured, so AI_ADDRCONFIG has no effect.;;
;;===========================================================================;;
; 0. Create stack frame and save used registers for __stdcall.
push ebx esi edi
push ebp
mov ebp, esp
virtual at ebp-8
.recent_restsize dd ? ; this is for memory alloc in ._.generate_data
.recent_page dd ? ; this is for memory alloc in ._.generate_data
rd 5 ; saved regs and return address
.hostname dd ?
.servname dd ?
.hints dd ?
.res dd ?
.reqdata dd ?
end virtual
xor edi, edi
push edi ; init .recent_page
push edi ; init .recent_restsize
; 1. Check that parameters are correct and can be handled by this implementation.
; 1a. If 'res' pointer is given, set result to zero.
mov eax, [.res]
test eax, eax
jz @f
mov [eax], edi
@@:
; 1b. Only AI_SUPPORTED flags are supported for hints->ai_flags.
mov ecx, [.hints]
xor edx, edx
jecxz .nohints
mov edx, [ecx+addrinfo.ai_flags]
.nohints:
mov ebx, [.reqdata]
mov [ebx+__gai_reqdata.flags], edx
push EAI_BADFLAGS
pop eax
test edx, not AI_SUPPORTED
jnz .ret
; 1c. Either hostname or servname must be given. If AI_CANONNAME is set,
; hostname must also be set.
cmp [.hostname], edi
jnz @f
test dl, AI_CANONNAME
jnz .ret
push EAI_NONAME
pop eax
cmp [.servname], edi
jz .ret
@@:
; 1d. Only IPv4 is supported, so hints->ai_family must be either PF_UNSPEC or PF_INET.
push EAI_FAMILY
pop eax
jecxz @f
cmp [ecx+addrinfo.ai_family], edi
jz @f
cmp [ecx+addrinfo.ai_family], AF_INET4
jnz .ret
@@:
; 1e. Valid combinations for ai_socktype/ai_protocol: 0/0 for any or
; SOCK_STREAM/IPPROTO_TCP, SOCK_DGRAM/IPPROTO_UDP
; (raw socketnums are not yet supported by the kernel)
xor edx, edx ; assume 0=any if no hints
jecxz .socketnum_type_ok
mov edx, [ecx+addrinfo.ai_socktype]
mov esi, [ecx+addrinfo.ai_protocol]
; 1f. Test for ai_socktype=0 and ai_protocol=0.
test edx, edx
jnz .check_socktype
test esi, esi
jz .socketnum_type_ok
; 1g. ai_socktype=0, ai_protocol is nonzero.
push EAI_SERVICE
pop eax
inc edx ; edx = SOCK_STREAM
cmp esi, IPPROTO_TCP
jz .socketnum_type_ok
inc edx ; edx = SOCK_DGRAM
cmp esi, IPPROTO_UDP
jz .socketnum_type_ok
.ret:
; Restore saved registers, destroy stack frame and return.
mov esp, ebp
pop ebp
pop edi esi ebx
ret 20
; 1h. ai_socktype is nonzero.
.check_socktype:
push EAI_SOCKTYPE
pop eax
cmp edx, SOCK_STREAM
jz .check_tcp
cmp edx, SOCK_DGRAM
jnz .ret
test esi, esi
jz .socketnum_type_ok
cmp esi, IPPROTO_UDP
jz .socketnum_type_ok
jmp .ret
.check_tcp:
test esi, esi
jz .socketnum_type_ok
cmp esi, IPPROTO_TCP
jnz .ret
.socketnum_type_ok:
mov [ebx+__gai_reqdata.socktype], dl
; 2. Resolve service.
; 2a. If no name is given, remember value -1.
push -1
pop edx
mov esi, [.servname]
test esi, esi
jz .service_resolved
; 2b. Loop for characters of string while digits are encountered.
xor edx, edx
xor eax, eax
.serv_to_number:
lodsb
sub al, '0'
cmp al, 9
ja .serv_to_number_done
; for each digit, set edx = edx*10 + <digit>
lea edx, [edx*5]
lea edx, [edx*2+eax]
; check for correctness: service port must fit in word
cmp edx, 0x10000
jae .service_not_number
jmp .serv_to_number
.serv_to_number_done:
and edx, 0xFFFF ; make sure that port fits
; 2c. If zero character reached, name is resolved;
; otherwise, return error (no support for symbolic names yet)
cmp al, -'0'
jz .service_resolved
.service_not_number:
push EAI_NONAME
pop eax
jmp .ret
.service_resolved:
; 2d. Save result to reqdata.
mov [ebx+__gai_reqdata.service], edx
; 3. Process host name.
mov esi, [.hostname]
; 3a. If hostname is not given,
; use localhost for active socketnums and INADDR_ANY for passive socketnums.
mov eax, 0x0100007F ; 127.0.0.1 in network byte order
test byte [ebx+__gai_reqdata.flags], AI_PASSIVE
jz @f
xor eax, eax
@@:
test esi, esi
jz .hostname_is_ip
; 3b. Check for dotted IPv4 name.
push esi
call inet_addr
cmp eax, -1
jz .resolve_hostname
.hostname_is_ip:
; 3c. hostname is valid representation of IP address, and we have resolved it.
; Generate result, if .res pointer is not NULL.
mov ebx, [.reqdata]
mov esi, [.res]
test esi, esi
jz .no_result
call getaddrinfo._.generate_data
; 3d. Check for memory allocation error.
.3d:
push EAI_MEMORY
pop eax
test esi, esi
jz .ret
; 3e. If AI_CANONNAME is set, copy input name.
test byte [ebx+__gai_reqdata.flags], AI_CANONNAME
jz .no_result
; 3f. Calculate length of name.
push -1
pop ecx
mov edi, [.hostname]
xor eax, eax
repnz scasb
not ecx
; 3g. Check whether it fits on one page with main data.
cmp ecx, [.recent_restsize]
jbe .name_fits
; 3h. If not, allocate new page.
push ecx
add ecx, 4 ; first dword contains number of objects on the page
mcall 68, 12
pop ecx
; 3i. If allocation has failed, free addrinfo and return error.
test eax, eax
jnz .name_allocated
push [.res]
call freeaddrinfo
push EAI_MEMORY
pop eax
jmp .ret
.name_allocated:
; 3j. Otherwise, set edi to allocated memory and continue to 3l.
xchg edi, eax ; put result to edi
push 1
pop eax
stosd ; number of objects on the page = 1
jmp .copy_name
.name_fits:
; 3k. Get pointer to free memory in allocated page.
mov edi, [.recent_page]
mov eax, edi
and eax, not 0xFFF
inc dword [eax] ; increase number of objects
.copy_name:
; 3l. Put pointer to struct addrinfo.
mov eax, [.res]
mov eax, [eax]
mov [eax+addrinfo.ai_canonname], edi
; 3m. Copy name.
rep movsb
.no_result:
; 3n. Return success.
xor eax, eax
jmp .ret
; 4. Host address is not dotted IP. Test whether we are allowed to contact DNS.
; Return error if no.
.resolve_hostname:
push EAI_NONAME
pop eax
mov ebx, [.reqdata]
test byte [ebx+__gai_reqdata.flags], AI_NUMERICHOST
jnz .ret
; Host address is domain name. Contact DNS server.
mov esi, [.hostname]
; 5. Reserve stack place for UDP packet.
; According to RFC1035, maximum UDP packet size in DNS is 512 bytes.
sub esp, 512
; 6. Create DNS request packet.
; 6a. Set pointer to start of buffer.
mov edi, esp
; 6b. Get request ID, write it to buffer.
push 1
pop eax
lock xadd [DNSrequestID], eax ; atomically increment ID, get old value
stosw
mov [ebx+__gai_reqdata.reqid], ax
; 6c. Packed field: QR=0 (query), Opcode=0000 (standard query),
; AA=0 (ignored in requests), TC=0 (no truncation),
; RD=1 (recursion desired)
mov al, 00000001b
stosb
; 6d. Packed field: ignored in requests
mov al, 0
stosb
; 6e. Write questions count = 1 and answers count = 0
; Note that network byte order is big-endian.
mov eax, 0x00000100
stosd
; 6f. Write nameservers count = 0 and additional records count = 0
xor eax, eax
stosd
; 6g. Write request data: name
; According to RFC1035, maximum length of name is 255 bytes.
; For correct names, buffer cannot overflow.
lea ebx, [esi+256] ; ebx = limit for name (including terminating zero)
; translate string "www.yandex.ru" {00} to byte data {03} "www" {06} "yandex" {02} "ru" {00}
.nameloop: ; here we go in the start of each label: before "www", before "yandex", before "ru"
xor ecx, ecx ; ecx = length of current label
inc edi ; skip length, it will be filled later
.labelloop: ; here we go for each symbol of name
lodsb ; get next character
test al, al ; terminating zero?
jz .endname
cmp esi, ebx ; limit exceeded?
jae .wrongname
cmp al, '.' ; end of label?
jz .labelend
stosb ; put next character
inc ecx ; increment label length
jmp .labelloop
.wrongname:
push EAI_NONAME
pop eax
jmp .ret
.labelend:
test ecx, ecx ; null label can be only in the end of name
jz .wrongname
.endname:
cmp ecx, 63
ja .wrongname
; write length to byte [edi-ecx-1]
mov eax, ecx
neg eax
mov byte [edi+eax-1], cl
cmp byte [esi-1], 0 ; that was last label in the name?
jnz .nameloop
; write terminating zero if not yet
mov al, 0
cmp byte [edi-1], al
jz @f
stosb
@@:
; 6h. Write request data:
; query type = A (host address) = 1,
; query class = IN (internet IPv4 address) = 1
; Note that network byte order is big-endian.
mov eax, 0x01000100
stosd
; 7. Get DNS server address.
mcall 76, API_IPv4 + (1 shl 8) + 4 ; protocol IP=0, device number=0, function=get DNS address
cmp eax, -1
je .ret.dnserr
push eax ; save server address to the stack
; 8. Open UDP socketnum to DNS server, port 53.
; 8a. Create new socketnum.
mcall 75, 0, AF_INET4, SOCK_DGRAM, 0
pop esi ; restore server address saved at step 7
cmp eax, -1 ; error?
jz .ret.dnserr
mov ecx, eax ; put socketnum handle to ecx
; 8b. Create sockaddr structure on the stack.
push 0
push 0 ; sin_zero
push esi ; sin_addr
push AF_INET4 + (53 shl 24)
; sin_family and sin_port in network byte order
; 8c. Connect.
mcall 75, 4, , esp, sizeof.sockaddr_in
; 8d. Restore the stack, undo 8b.
add esp, esi
; 8e. Check result.
cmp eax, -1
jz .ret.close
; 9. Send DNS request packet.
sub edi, esp ; get packet length
mov esi, edi
xor edi, edi
mcall 75, 6, , esp
cmp eax, -1
jz .ret.close
mov eax, [.reqdata]
mov [eax+__gai_reqdata.socketnum], ecx
push -1
pop eax ; return status: more processing required
jmp .ret.dns
.ret.close:
mcall 75, 1
.ret.dnserr:
push EAI_AGAIN
pop eax
.ret.dns:
; 6. Restore stack pointer and return.
jmp .ret
;;===========================================================================;;
;; int __stdcall getaddrinfo_process(__in struct __gai_reqdata* reqdata, ;;
;; __out struct addrinfo** res); ;;
getaddrinfo_process: ;;
;;---------------------------------------------------------------------------;;
;? Processes network events from DNS reply ;;
;;---------------------------------------------------------------------------;;
;> first parameter = pointer to struct __gai_reqdata filled by ..._start ;;
;> second parameter = same as for getaddrinfo ;;
;;---------------------------------------------------------------------------;;
;< eax = -1 if more processing required / 0 on success / >0 = error code ;;
;;===========================================================================;;
; 0. Create stack frame.
push ebp
mov ebp, esp
virtual at ebp-.locals_size
.locals_start:
.datagram rb 512
.addrname dd ?
.name dd ?
.res_list_tail dd ?
.cname dd ?
.recent_restsize dd ? ; this is for memory alloc in ._.generate_data
.recent_page dd ? ; this is for memory alloc in ._.generate_data
.locals_size = $ - .locals_start
rd 2
.reqdata dd ?
.res dd ?
end virtual
xor eax, eax
push eax ; initialize .recent_page
push eax ; initialize .recent_restsize
push eax ; initialize .cname
push [.res] ; initialize .res_list_tail
sub esp, .locals_size-16 ; reserve place for other vars
mov edx, esp ; edx -> buffer for datagram
; 1. Save used registers for __stdcall.
push ebx esi edi
mov edi, [.reqdata]
; 2. Read UDP datagram.
mov ecx, [edi+__gai_reqdata.socketnum]
push edi
mcall 75, 7, , , 512, MSG_DONTWAIT
pop edi
; 3. Check for socket errors
cmp eax, -1
jne @f
cmp ebx, EWOULDBLOCK
je .ret.more_processing_required
jmp .ret.no_recovery
@@:
; 4. Sanity check: discard too short packets.
xchg ecx, eax ; save packet length in ecx
cmp ecx, 12
jb .ret.more_processing_required
; 5. Discard packets with ID != request ID.
mov eax, dword [edi+__gai_reqdata.reqid]
cmp ax, [edx]
jnz .ret.more_processing_required
; 6. Sanity check: discard query packets.
test byte [edx+2], 80h
jz .ret.more_processing_required
; 7. Sanity check: must be exactly one query (our).
cmp word [edx+4], 0x0100 ; note network byte order
jnz .ret.more_processing_required
; 8. Check for errors. Return EAI_NONAME for error code 3 and EAI_FAIL for other.
mov al, [edx+3]
and al, 0xF
jz @f
cmp al, 3
jnz .ret.no_recovery
jmp .ret.no_name
@@:
; 9. Locate answers section. Exactly 1 query is present in this packet.
add ecx, edx ; ecx = limit
lea esi, [edx+12]
call .skip_name
lodsd ; skip QTYPE and QCLASS field
cmp esi, ecx
ja .ret.no_recovery
; 10. Loop through all answers.
movzx ebx, word [edx+6] ; get answers count
xchg bl, bh ; network -> Intel byte order
.answers_loop:
dec ebx
js .answers_done
; 10a. Process each record.
mov [.name], esi
; 10b. Skip name field.
call .skip_name
; 10c. Get record information, handle two types for class IN (internet).
lodsd ; get type and class
cmp esi, ecx
ja .ret.no_recovery
cmp eax, 0x01000500 ; type=5, class=1?
jz .got_cname
cmp eax, 0x01000100 ; type=1, class=1?
jnz .answers_loop.next
.got_addr:
; 10d. Process record A, host address.
add esi, 10
cmp esi, ecx
ja .ret.no_recovery
cmp word [esi-6], 0x0400 ; RDATA for A records must be 4 bytes long
jnz .ret.no_recovery
mov eax, [.name]
mov [.addrname], eax
; 10e. Create corresponding record in the answer.
push ebx ecx esi
mov eax, [esi-4] ; IP address
mov esi, [.res_list_tail] ; pointer to result
test esi, esi
jz .no_result ; do not save if .res is NULL
mov ebx, [.reqdata] ; request data
call getaddrinfo._.generate_data
mov [.res_list_tail], esi
pop esi ecx ebx
cmp [.res_list_tail], 0
jnz .answers_loop
; 10f. If generate_data failed (this means memory allocation failure), abort
jmp .ret.no_memory
.no_result:
pop esi ecx ebx
jmp .answers_loop
.got_cname:
; 10g. Process record CNAME, main host name.
lea eax, [esi+6]
mov [.cname], eax
.answers_loop.next:
; 10h. Skip other record fields, advance to next record.
lodsd ; skip TTL
xor eax, eax
lodsw ; get length of RDATA field
xchg al, ah ; network -> Intel byte order
add esi, eax
cmp esi, ecx
ja .ret.no_recovery
jmp .answers_loop
.answers_done:
; 11. Check that there is at least 1 answer.
mov eax, [.res_list_tail]
cmp [.res], eax
jz .ret.no_data
; 12. If canonical name was required, add it now.
mov eax, [.reqdata]
test byte [eax+__gai_reqdata.flags], AI_CANONNAME
jz .no_canon_name
; 12a. If at least one CNAME record is present, use name from last such record.
; Otherwise, use name from one of A records.
mov esi, [.cname]
test esi, esi
jnz .has_cname
mov esi, [.addrname]
.has_cname:
; 12b. Calculate name length.
call .get_name_length
jc .ret.no_recovery
; 12c. Check that the caller really want to get data.
cmp [.res], 0
jz .no_canon_name
; 12d. Allocate memory for name.
call getaddrinfo._.memalloc
test edi, edi
jz .ret.no_memory
; 12e. Make first entry in .res list point to canonical name.
mov eax, [.res]
mov eax, [eax]
mov [eax+addrinfo.ai_canonname], edi
; 12f. Decode name.
call .decode_name
.no_canon_name:
; 13. Set status to success.
xor eax, eax
jmp .ret.close
; Handle errors.
.ret.more_processing_required:
push -1
pop eax
jmp .ret
.ret.no_recovery:
push EAI_FAIL
pop eax
jmp .ret.destroy
.ret.no_memory:
push EAI_MEMORY
pop eax
jmp .ret.destroy
.ret.no_name:
.ret.no_data:
push EAI_NONAME
pop eax
.ret.destroy:
; 14. If an error occured, free memory acquired so far.
push eax
mov esi, [.res]
test esi, esi
jz @f
pushd [esi]
call freeaddrinfo
and dword [esi], 0
@@:
pop eax
.ret.close:
; 15. Close socketnum.
push eax
mov ecx, [.reqdata]
mov ecx, [ecx+__gai_reqdata.socketnum]
mcall 75, 1
pop eax
; 16. Restore used registers, destroy stack frame and return.
.ret:
pop edi esi ebx
mov esp, ebp
pop ebp
ret 8
;;===========================================================================;;
;; Internal auxiliary function for skipping names in DNS packet. ;;
.skip_name: ;;
;;---------------------------------------------------------------------------;;
;? Skips name in DNS packet. ;;
;;---------------------------------------------------------------------------;;
;> esi -> name ;;
;> ecx = end of packet ;;
;;---------------------------------------------------------------------------;;
;< esi -> end of name ;;
;;===========================================================================;;
xor eax, eax
cmp esi, ecx
jae .skip_name.done
lodsb
test al, al
jz .skip_name.done
test al, 0xC0
jnz .skip_name.pointer
add esi, eax
jmp .skip_name
.skip_name.pointer:
inc esi
.skip_name.done:
ret
;;===========================================================================;;
;; Internal auxiliary function for calculating length of name in DNS packet. ;;
.get_name_length: ;;
;;---------------------------------------------------------------------------;;
;? Calculate length of name (including terminating zero) in DNS packet. ;;
;;---------------------------------------------------------------------------;;
;> edx = start of packet ;;
;> esi -> name ;;
;> ecx = end of packet ;;
;;---------------------------------------------------------------------------;;
;< eax = length of name ;;
;< CF set on error / cleared on success ;;
;;===========================================================================;;
xor ebx, ebx ; ebx will hold data length
.get_name_length.zero:
xor eax, eax
.get_name_length.loop:
cmp esi, ecx
jae .get_name_length.fail
lodsb
test al, al
jz .get_name_length.done
test al, 0xC0
jnz .get_name_length.pointer
add esi, eax
inc ebx
add ebx, eax
cmp ebx, 256
jbe .get_name_length.loop
.get_name_length.fail:
stc
ret
.get_name_length.pointer:
and al, 0x3F
mov ah, al
lodsb
lea esi, [edx+eax]
jmp .get_name_length.zero
.get_name_length.done:
test ebx, ebx
jz .get_name_length.fail
xchg eax, ebx
clc
ret
;;===========================================================================;;
;; Internal auxiliary function for decoding DNS name. ;;
.decode_name: ;;
;;---------------------------------------------------------------------------;;
;? Decode name in DNS packet. ;;
;;---------------------------------------------------------------------------;;
;> edx = start of packet ;;
;> esi -> name in packet ;;
;> edi -> buffer for decoded name ;;
;;===========================================================================;;
xor eax, eax
lodsb
test al, al
jz .decode_name.done
test al, 0xC0
jnz .decode_name.pointer
mov ecx, eax
rep movsb
mov al, '.'
stosb
jmp .decode_name
.decode_name.pointer:
and al, 0x3F
mov ah, al
lodsb
lea esi, [edx+eax]
jmp .decode_name
.decode_name.done:
mov byte [edi-1], 0
ret
;;===========================================================================;;
;; Internal auxiliary function for allocating memory for getaddrinfo. ;;
getaddrinfo._.memalloc: ;;
;;---------------------------------------------------------------------------;;
;? Memory allocation. ;;
;;---------------------------------------------------------------------------;;
;> eax = size in bytes, must be less than page size. ;;
;> [ebp-4] = .recent_page = last allocated page ;;
;> [ebp-8] = .recent_restsize = bytes rest in last allocated page ;;
;;---------------------------------------------------------------------------;;
;< edi -> allocated memory / NULL on error ;;
;;===========================================================================;;
; 1. Set edi to result of function.
mov edi, [ebp-4]
; 2. Check whether we need to allocate a new page.
cmp eax, [ebp-8]
jbe .no_new_page
; 2. Allocate new page if need. Reset edi to new result.
push eax ebx
mcall 68, 12, 0x1000
xchg edi, eax ; put result to edi
pop ebx eax
; 3. Check returned value of allocator. Fail if it failed.
test edi, edi
jz .ret
; 4. Update .recent_page and .recent_restsize.
add edi, 4
sub ecx, 4
mov [ebp-4], edi
mov [ebp-8], ecx
.no_new_page:
; 5. Increase number of objects on this page.
push eax
mov eax, edi
and eax, not 0xFFF
inc dword [eax]
pop eax
; 6. Advance last allocated pointer, decrease memory size.
add [ebp-4], eax
sub [ebp-8], eax
; 7. Return.
.ret:
ret
;;===========================================================================;;
;; Internal auxiliary function for freeing memory for freeaddrinfo. ;;
getaddrinfo._.memfree: ;;
;;---------------------------------------------------------------------------;;
;? Free memory. ;;
;;---------------------------------------------------------------------------;;
;> eax = pointer ;;
;;===========================================================================;;
; 1. Get start of page.
mov ecx, eax
and ecx, not 0xFFF
; 2. Decrease number of objects.
dec dword [ecx]
; 3. If it goes to zero, free the page.
jnz @f
push ebx
mcall 68, 13
pop ebx
@@:
; 4. Done.
ret
;;===========================================================================;;
getaddrinfo._.generate_data: ;;
;;---------------------------------------------------------------------------;;
;? Generate item(s) of getaddrinfo result list by one IP address. ;;
;;---------------------------------------------------------------------------;;
;> eax = IP address ;;
;> ebx = request data ;;
;> esi = pointer to result ;;
;> [ebp-4] = .recent_page = last allocated page ;;
;> [ebp-8] = .recent_restsize = bytes rest in last allocated page ;;
;;---------------------------------------------------------------------------;;
;< esi = pointer to next list item for result / NULL on error ;;
;;===========================================================================;;
; 1. If no service is given, append one item with zero port.
; append one item with zero socktype/protocol/port.
cmp [ebx+__gai_reqdata.service], -1
jnz .has_service
call .append_item
; 1a. If neither protocol nor socktype were specified,
; leave zeroes in socktype and protocol.
mov cl, [ebx+__gai_reqdata.socktype]
test cl, cl
jz .no_socktype
; 1b. Otherwise, set socktype and protocol to desired.
call .set_socktype
.no_socktype:
ret
.has_service:
; 2. If TCP is allowed, append item for TCP.
cmp [ebx+__gai_reqdata.socktype], 0
jz .tcp_ok
cmp [ebx+__gai_reqdata.socktype], SOCK_STREAM
jnz .tcp_disallowed
.tcp_ok:
call .append_item
mov cl, SOCK_STREAM
call .set_socktype
call .set_port
.tcp_disallowed:
; 3. If UDP is allowed, append item for UDP.
cmp [ebx+__gai_reqdata.socktype], 0
jz .udp_ok
cmp [ebx+__gai_reqdata.socktype], SOCK_DGRAM
jnz .udp_disallowed
.udp_ok:
call .append_item
mov cl, SOCK_DGRAM
call .set_socktype
call .set_port
.udp_disallowed:
ret
.append_item:
; 1. Allocate memory for struct sockaddr_in and struct addrinfo.
push eax
push sizeof.addrinfo + sizeof.sockaddr_in
pop eax
call getaddrinfo._.memalloc
; 2. Check for memory allocation fail.
test edi, edi
jz .no_memory
; 3. Zero allocated memory.
push (sizeof.addrinfo + sizeof.sockaddr_in) / 4
pop ecx
xor eax, eax
push edi
rep stosd
pop edi
; 4. Fill struct addrinfo.
mov eax, [ebx+__gai_reqdata.flags]
mov [edi+addrinfo.ai_flags], eax
mov byte [edi+addrinfo.ai_family], AF_INET4
mov byte [edi+addrinfo.ai_addrlen], sizeof.sockaddr_in
lea ecx, [edi+sizeof.addrinfo]
mov [edi+addrinfo.ai_addr], ecx
; 5. Fill struct sockaddr_in.
mov byte [ecx+sockaddr_in.sin_family], AF_INET4
pop eax
mov [ecx+sockaddr_in.sin_addr], eax
; 6. Append new item to the list.
mov [esi], edi
lea esi, [edi+addrinfo.ai_next]
; 7. Return.
ret
.no_memory:
pop eax
xor esi, esi
ret
.set_socktype:
; Set ai_socktype and ai_protocol fields by given socketnum type.
mov byte [edi+addrinfo.ai_socktype], cl
dec cl
jnz .set_udp
.set_tcp:
mov byte [edi+addrinfo.ai_protocol], IPPROTO_TCP
ret
.set_udp:
mov byte [edi+addrinfo.ai_protocol], IPPROTO_UDP
ret
.set_port:
; Just copy port from input __gai_reqdata to output addrinfo.
push edx
mov edx, [ebx+__gai_reqdata.service]
xchg dl, dh ; convert to network byte order ;;;;; CHECKME
mov [edi+sizeof.addrinfo+sockaddr_in.sin_port], dx
pop edx
ret
;;===========================================================================;;
;; void __stdcall getaddrinfo_abort(__in struct __gai_reqdata* reqdata); ;;
getaddrinfo_abort: ;;
;;---------------------------------------------------------------------------;;
;? Abort process started by getaddrinfo_start, free all resources. ;;
;;---------------------------------------------------------------------------;;
;> first parameter = pointer to struct __gai_reqdata filled by ..._start ;;
;;===========================================================================;;
; 0. Save used registers for __stdcall.
push ebx
; 1. Allocated resources: only socketnum, so close it and return.
mov eax, [esp+8]
mov ecx, [eax+__gai_reqdata.socketnum]
mcall 75, 1
; 2. Restore used registers and return.
pop ebx
ret 4
;;===========================================================================;;
;; void __stdcall freeaddrinfo(__in struct addrinfo* ai); ;;
freeaddrinfo: ;;
;;---------------------------------------------------------------------------;;
;? Free one or more addrinfo structures returned by getaddrinfo. ;;
;;---------------------------------------------------------------------------;;
;> first parameter = head of list of structures ;;
; (may be arbitrary sublist of original) ;;
;;===========================================================================;;
; 1. Loop for all items in the list.
mov edx, [esp+4] ; eax = ai
.loop:
test edx, edx
jz .done
; 2. Free each item.
; 2a. Free ai_canonname, if allocated.
mov eax, [edx+addrinfo.ai_canonname]
test eax, eax
jz .no_canon_name
call getaddrinfo._.memfree
.no_canon_name:
; 2b. Remember next item
; (after freeing the field ai_next can became unavailable).
pushd [edx+addrinfo.ai_next]
; 2c. Free item itself.
xchg eax, edx
call getaddrinfo._.memfree
; 2d. Restore pointer to next item and continue loop.
pop edx
jmp .loop
.done:
; 3. Done.
ret 4
;;===========================================================================;;
;;///////////////////////////////////////////////////////////////////////////;;
;;===========================================================================;;
;! Exported functions section ;;
;;===========================================================================;;
;;///////////////////////////////////////////////////////////////////////////;;
;;===========================================================================;;
align 4
@EXPORT:
export \
lib_init , 'lib_init' , \
0x00010001 , 'version' , \
inet_addr , 'inet_addr' , \
inet_ntoa , 'inet_ntoa' , \
getaddrinfo , 'getaddrinfo' , \
getaddrinfo_start , 'getaddrinfo_start' , \
getaddrinfo_process , 'getaddrinfo_process' , \
getaddrinfo_abort , 'getaddrinfo_abort' , \
freeaddrinfo , 'freeaddrinfo'
section '.data' data readable writable align 16
; uninitialized data
mem.alloc dd ?
mem.free dd ?
mem.realloc dd ?
dll.load dd ?
DNSrequestID dd ?
inet_ntoa.buffer rb 16 ; static buffer for inet_ntoa