0,0 → 1,1286 |
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 |
stdcall edx, @IMPORT |
ret 4 |
|
;;===========================================================================;; |
;; 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 |
socket 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 |
lea eax, [esp+4] ; pointer to (1) |
jz .timeout |
; 3. Got packet. Call processing function. |
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 |
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], PF_INET |
jnz .ret |
@@: |
; 1e. Valid combinations for ai_socktype/ai_protocol: 0/0 for any or |
; SOCK_STREAM/IPPROTO_TCP, SOCK_DGRAM/IPPROTO_UDP |
; (raw sockets are not yet supported by the kernel) |
xor edx, edx ; assume 0=any if no hints |
jecxz .socket_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 .socket_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 .socket_type_ok |
inc edx ; edx = SOCK_DGRAM |
cmp esi, IPPROTO_UDP |
jz .socket_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 .socket_type_ok |
cmp esi, IPPROTO_UDP |
jz .socket_type_ok |
jmp .ret |
.check_tcp: |
test esi, esi |
jz .socket_type_ok |
cmp esi, IPPROTO_TCP |
jnz .ret |
.socket_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 sockets and INADDR_ANY for passive sockets. |
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 52, 13 |
xchg esi, eax ; put server address to esi |
; 8. Open UDP socket to DNS server, port 53. |
mcall 53, 0, 0, 53 |
cmp eax, -1 ; error? |
jz .ret.dnserr |
xchg ecx, eax ; put socket handle to ecx |
; 9. Send DNS request packet. |
sub edi, esp ; get packet length |
mcall 53, 4, , edi, esp |
cmp eax, -1 |
jz .ret.close |
mov eax, [.reqdata] |
mov [eax+__gai_reqdata.socket], ecx |
push -1 |
pop eax ; return status: more processing required |
jmp .ret.dns |
.ret.close: |
mcall 53, 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.socket] |
mcall 53, 11, , , 512 |
; 3. Ignore events for other sockets (return if no data read) |
test eax, eax |
jz .ret.more_processing_required |
; 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 socket. |
push eax |
mov ecx, [.reqdata] |
mov ecx, [ecx+__gai_reqdata.socket] |
mcall 53, 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], PF_INET |
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_len], sizeof.sockaddr_in |
mov byte [ecx+sockaddr_in.sin_family], PF_INET |
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 socket 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 |
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 socket, so close it and return. |
mov eax, [esp+8] |
mov ecx, [eax+__gai_reqdata.socket] |
mcall 53, 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' |
|
; import from libini |
align 4 |
@IMPORT: |
|
library libini, 'libini.obj' |
import libini, \ |
ini.get_str, 'ini_get_str', \ |
ini.get_int, 'ini_get_int' |
|
|
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 |