Last active
July 25, 2023 19:24
-
-
Save karolba/6c827b1ebe5d54c604d54ae14ed17c6b to your computer and use it in GitHub Desktop.
A forking web-server with directory contents listing in x86_64 assembly
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extern printf | |
extern perror | |
extern exit | |
extern puts | |
extern socket | |
extern bind | |
extern listen | |
extern accept | |
extern close | |
extern fdopen | |
extern fclose | |
extern fprintf | |
extern fscanf | |
extern malloc | |
extern free | |
extern setsockopt | |
extern strcmp | |
extern open | |
extern read | |
extern fwrite | |
%define NULL 0 | |
%define AF_INET 2 | |
%define SOCK_STREAM 1 | |
%define SOL_SOCKET 1 | |
%define SO_REUSEADDR 2 | |
%define O_RDONLY 0 | |
%define O_LARGEFILE 100000q | |
%define O_DIRECTORY 200000q | |
%define RESOLVE_IN_ROOT 0x10 ; for openat2: Make all jumps to "/" and ".." be scoped inside the dirfd (similar to chroot(2)). | |
%define minus_EISDIR -21 | |
%define DT_DIR 4 | |
%define SYS_openat2 437 ; https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_64.tbl#L361 | |
%define SYS_read 0 | |
%define SYS_getdents64 217 | |
%define BUFFER_SIZE 8196 | |
; what to tell listen(3) - how big of a backlog do we want | |
%define LISTEN_BACKLOG_SIZE 4 | |
section .data | |
string_current_directory_for_open: db '.', 0 | |
string_fdopen_read_write_mode: db 'r+', 0 | |
string_socket_creation_fail: db 'socket() failed', 0 | |
string_socket_creation_successfull: db 'socket() succeeded, sockfd = %d', 10, 0 | |
string_setsockopt_successfull: db 'setsockopt() - setting SO_REUSEADDR succeeded', 0 | |
string_setsockopt_fail: db 'setsockopt() - setting SO_REUSEADDR failed', 0 | |
string_bind_fail: db 'bind() failed', 0 | |
string_bind_successfull: db 'bind() succeeded', 0 | |
string_listen_fail: db 'listen() failed', 0 | |
string_listen_successfull: db 'listen() succeeded - waiting for any clients', 0 | |
string_accept_fail: db 'accept() failed', 0 | |
string_accept_successfull: db 'accept() succeeded - new client connected, connfd = %d', 10, 0 | |
string_fdopen_fail: db 'fdopen() failed', 0 | |
string_fscanf_first_http_line: db '%31s %1023s HTTP/1.%c%*[', 13, ']%*[', 10, ']', 0 | |
string_fscanf_first_http_line_fail: db 'Could not parse the HTTP request, closing the connection', 0 | |
string_GET_method: db 'GET', 0 | |
string_400_bad_request_resp: db 'HTTP/1.%c 400 Bad Request', 13, 10 | |
db 'Connection: close', 13, 10 | |
db 'Content-Type: text/plain', 13, 10 | |
db 13, 10 | |
db '400: Bad Request', 13, 10, 0 | |
string_404_not_found_resp: db 'HTTP/1.%c 404 Not Found', 13, 10 | |
db 'Connection: close', 13, 10 | |
db 'Content-Type: text/plain', 13, 10 | |
db 13, 10 | |
db '404: Not Found', 13, 10, 0 | |
string_405_method_not_allowed_resp: db 'HTTP/1.%c 405 Method Not Allowed', 13, 10 | |
db 'Connection: close', 13, 10 | |
db 'Content-Type: text/plain', 13, 10 | |
db 13, 10 | |
db '405: Method Not Allowed', 13, 10 | |
db 'Only the GET method is allowed', 13, 10, 0 | |
string_http_correct_response_prelude: db 'HTTP/1.%c 200 OK', 13, 10 | |
db 'Connection: close', 13, 10 | |
db 'X-Requested-Path: [%s]', 13, 10 | |
db 13, 10, 0 | |
string_http_preamble_directory: db 'HTTP/1.%c 200 OK', 13, 10 | |
db 'Connection: close', 13, 10 | |
db 'Content-Type: text/html', 13, 10 | |
db 13, 10 | |
db '<!doctype html>', 13, 10 | |
db '<html><head>', 13, 10 | |
db ' <meta charset="UTF-8">', 13, 10 | |
db ' <title>Directory listing: %s</title>', 13, 10 | |
db ' <base href="%s/">', 13, 10 | |
db '</head>', 13, 10 | |
db '<body><h3>Directory listing for %s</h3><pre>', 13, 10 | |
db ' type | filename', 13, 10 | |
db '---------+-------------------------------------------------------', 13, 10, 0 | |
string_http_file_in_listing: db '%.8s | <a href="%s">%s</a>', 13, 10, 0 | |
string_http_directory_in_listing: db '%.8s | <a href="%s">%s/</a>', 13, 10, 0 | |
strings_file_types: db ' ???' ; #define DT_UNKNOWN 0 | |
db ' fifo' ; #define DT_FIFO 1 | |
db 'char dev' ; #define DT_CHR 2 | |
db ' ???' | |
db ' dir' ; #define DT_DIR 4 | |
db ' ???' | |
db 'blck dev' ; #define DT_BLK 6 | |
db ' ???' | |
db ' file' ; #define DT_REG 8 | |
db ' ???' | |
db ' symlink' ; #define DT_LNK 10 | |
db ' ???' | |
db ' socket' ; #define DT_SOCK 12 | |
db ' ???' | |
db 'whiteout' ; #define DT_WHT 14 | |
db ' ???' | |
db ' ???' | |
section .text | |
; macros for creating and exiting out of a stack frame | |
%macro create_stack_frame 1 | |
push rbp | |
mov rbp, rsp | |
sub rsp, %1 | |
%endmacro | |
%macro exit_stack_frame_return 0 | |
mov rsp, rbp | |
pop rbp | |
ret | |
%endmacro | |
; opens a file restricting access to the current directory only | |
; using the RESOLVE_IN_ROOT option to the openat2 syscall, which makes | |
; the syscall behave kind of like it was ran from a chroot | |
; | |
; avoids `curl 127.0.0.1:8080/../../../../etc/passwd` from working | |
; | |
; returns either the file descriptor of the file or -1 if the file doesn't | |
; exist / there was any other error | |
; | |
;int safe_open_in_current_dir(char *path) | |
%define target_file_des DWORD [rbp-50q] | |
%define opened_current_dir DWORD [rbp-44q] | |
%define open_how QWORD [rbp-40q] | |
%define open_how.flags QWORD [rbp-40q] | |
%define open_how.mode QWORD [rbp-30q] | |
%define open_how.resolve QWORD [rbp-20q] | |
%define OPEN_HOW_SIZE 24 | |
%define path QWORD [rbp-10q] | |
safe_open_in_current_dir: | |
create_stack_frame 60q | |
; save the argument | |
mov path, rdi | |
; O_DIRECTORY makes this fail if "." somehow wasn't a directory | |
; opened_current_dir = open(".", O_RDONLY | O_DIRECTORY) | |
mov rdi, string_current_directory_for_open | |
mov rsi, O_RDONLY | O_DIRECTORY | |
call open | |
mov opened_current_dir, eax | |
; check open() return value | |
cmp eax, -1 | |
je .open_current_dir_failed | |
; setup data for openat2 | |
mov open_how.flags, O_RDONLY | O_LARGEFILE | |
mov open_how.mode, 0 | |
mov open_how.resolve, RESOLVE_IN_ROOT ; Make all jumps to "/" and ".." be scoped inside the dirfd (similar to chroot(2)). | |
; invoke openat2 via a syscall directly, as the manpage suggests there isn't a glibc wrapper | |
; SYS_openat2(int dirfd, const char *pathname, struct open_how *how, size_t size); | |
mov rax, SYS_openat2 | |
mov edi, opened_current_dir | |
mov rsi, path | |
lea rdx, open_how | |
lea r10, OPEN_HOW_SIZE | |
syscall | |
mov target_file_des, eax | |
; check exit status code from the syscall | |
cmp rax, 0 | |
jl .openat2_failed | |
.close_current_dir_and_end: | |
mov edi, opened_current_dir | |
call close | |
.end: | |
mov eax, target_file_des | |
exit_stack_frame_return | |
.open_current_dir_failed: | |
; return (int64)-1 | |
mov rax, -1 | |
jmp .end | |
.openat2_failed: | |
; something is wrong: we don't really care what as we are just going to return 404 Not Found | |
mov target_file_des, -1 ; return -1 instead of -errno | |
jmp .close_current_dir_and_end | |
; | |
; from `man 2 getdents`: | |
; | |
; struct linux_dirent64 { | |
; ino64_t d_ino; /* 64-bit inode number */ <-- offset 0 | |
; off64_t d_off; /* 64-bit offset to next structure */ <-- offset 8 | |
; unsigned short d_reclen; /* Size of this dirent */ <-- offset 16 | |
; unsigned char d_type; /* File type */ <-- offset 18 | |
; char d_name[]; /* Filename (null-terminated) */ <-- offset 19 | |
; }; | |
%define LINUX_DIRENT64_OFFSET_D_INO 0 | |
%define LINUX_DIRENT64_OFFSET_D_OFF 8 | |
%define LINUX_DIRENT64_OFFSET_D_RECLEN 16 | |
%define LINUX_DIRENT64_OFFSET_D_TYPE 18 | |
%define LINUX_DIRENT64_OFFSET_D_NAME 19 | |
; sends out one line in the directory listing with the given directory | |
;void send_one_entry_in_listing(FILE *conn, struct linux_dirent64 *linux_dirent64_entry) | |
%define format_str QWORD [rbp-50q] | |
%define linux_dirent64_entry QWORD [rbp-40q] | |
%define listing_dir_type_str QWORD [rbp-30q] | |
%define listing_dir_name_str QWORD [rbp-20q] | |
%define conn QWORD [rbp-10q] | |
send_one_entry_in_listing: | |
create_stack_frame 60q | |
; save the arguments | |
mov conn, rdi | |
mov linux_dirent64_entry, rsi | |
; point to the d_name from this linux_dirent64 | |
mov rax, linux_dirent64_entry | |
lea rax, [rax+LINUX_DIRENT64_OFFSET_D_NAME] | |
mov listing_dir_name_str, rax | |
; load d_type from this linux_dirent64 to rcx | |
mov rax, linux_dirent64_entry | |
xor ecx, ecx | |
mov cl, [rax+LINUX_DIRENT64_OFFSET_D_TYPE] | |
; | |
; for safety `and` rcx so the value is max 15 | |
and rcx, 0xf | |
; | |
; load strings_file_types[d_type * 8] into listing_dir_type_str (d_type is in rcx) | |
lea rdx, [strings_file_types + rcx * 8] | |
mov listing_dir_type_str, rdx | |
; | |
; select the right format string based on d_type (rcx) | |
mov format_str, string_http_file_in_listing | |
cmp rcx, DT_DIR | |
jne .after_format_string_set_to_directory | |
mov format_str, string_http_directory_in_listing | |
.after_format_string_set_to_directory: | |
; printf the dir - use either string_http_file_in_listing or string_http_directory_in_listing | |
; as a special case | |
mov rdi, conn | |
mov rsi, format_str | |
mov rdx, listing_dir_type_str | |
mov rcx, listing_dir_name_str | |
mov r8, listing_dir_name_str | |
call fprintf | |
exit_stack_frame_return | |
;%define html_base_string QWORD [rbp-60q] | |
;void serve_directory_GET(FILE *conn, char *path, char http_minor_version, int requested_dirfd) | |
%define http_minor_version_char BYTE [rbp-65q] | |
%define requested_dirfd DWORD [rbp-64q] | |
%define buffer_end QWORD [rbp-60q] | |
%define current_linux_dirent64 QWORD [rbp-50q] | |
%define getdirent64_result_size QWORD [rbp-40q] | |
%define dirent64_buf QWORD [rbp-30q] | |
%define path_str QWORD [rbp-20q] | |
%define conn QWORD [rbp-10q] | |
serve_directory_GET: | |
create_stack_frame 100q ; q = octal | |
; save all function arguments | |
mov conn, rdi | |
mov path_str, rsi | |
mov http_minor_version_char, dl | |
mov requested_dirfd, ecx | |
; alloate a buffer for directory entries from getdirent64 | |
mov rdi, BUFFER_SIZE | |
call malloc | |
mov dirent64_buf, rax | |
; send the HTTP preamble/headers for a directory | |
mov rdi, conn | |
mov rsi, string_http_preamble_directory | |
xor edx, edx | |
mov dl, http_minor_version_char | |
mov rcx, path_str | |
mov r8, path_str | |
mov r9, path_str | |
call fprintf | |
.getdents64_loop: | |
; read the dir | |
mov rax, SYS_getdents64 | |
mov edi, requested_dirfd | |
mov rsi, dirent64_buf | |
mov rdx, BUFFER_SIZE | |
syscall | |
mov getdirent64_result_size, rax | |
; | |
; either an error or end of file | |
cmp rax, 0 | |
jle .end | |
; | |
; compute the end of the buffer by adding getdirent64_result_size (rax) to the start | |
; of the buffer | |
add rax, dirent64_buf | |
mov buffer_end, rax | |
; look at the first linux_dirent64 | |
mov rax, dirent64_buf | |
mov current_linux_dirent64, rax | |
; loop over all received linux_dirent64's | |
.inside_one_buffer_loop: | |
; send the dir | |
mov rdi, conn | |
mov rsi, current_linux_dirent64 | |
call send_one_entry_in_listing | |
; get current_linux_dirent64.d_reclen into rcx | |
mov rax, current_linux_dirent64 | |
xor ecx, ecx | |
mov cx, [rax+LINUX_DIRENT64_OFFSET_D_RECLEN] | |
; | |
; advance the current_linux_dirent64 pointer (now in rax) by d_reclen (rcx) | |
add rax, rcx | |
mov current_linux_dirent64, rax | |
; | |
; check if we aren't out of bounds | |
cmp rax, buffer_end | |
; if we aren't, loop back to handle the next dir | |
jl .inside_one_buffer_loop | |
; there might still data about the directory to be read, loop back | |
jmp .getdents64_loop | |
.end: | |
; free the buffer | |
mov rdi, dirent64_buf | |
call free | |
exit_stack_frame_return | |
;void serve_file_GET(FILE *conn, char *path, char http_minor_version) | |
%define headers_are_already_sent BYTE [rbp-46q] | |
%define http_minor_version_char BYTE [rbp-45q] | |
%define requested_file DWORD [rbp-44q] | |
%define read_result QWORD [rbp-40q] | |
%define buffer QWORD [rbp-30q] | |
%define path_str QWORD [rbp-20q] | |
%define conn QWORD [rbp-10q] | |
serve_file_GET: | |
create_stack_frame 60q | |
; save all function arguments | |
mov conn, rdi | |
mov path_str, rsi | |
mov http_minor_version_char, dl | |
; allocate memory for the buffer used for copying data | |
mov rdi, BUFFER_SIZE | |
call malloc | |
mov buffer, rax | |
; requested_file = safe_open_in_current_dir(path_str); | |
mov rdi, path_str | |
call safe_open_in_current_dir | |
mov requested_file, eax | |
; handle safe_open_in_current_dir errors | |
cmp eax, -1 | |
je .safe_open_in_current_dir_error | |
; mark the headers not yet sent | |
mov headers_are_already_sent, 0 | |
.read_write_loop: | |
; read something from the file | |
; assume the file is a regular file and not a directory, in case it actually is a directory | |
; read() will return EISDIR - handle it then | |
mov rax, SYS_read | |
mov edi, requested_file | |
mov rsi, buffer | |
mov rdx, BUFFER_SIZE | |
syscall ; use a syscall and not a glibc wrapper because it's more important to get the error | |
; code into rax (for EISDIR) here than into errno so perror works | |
mov read_result, rax | |
; handle read errors (both real errors and EISDIR) | |
cmp rax, 0 | |
jl .read_error | |
; send the HTTP preamble/headers if they weren't already sent | |
cmp headers_are_already_sent, 1 | |
je .after_sent_headers | |
; fprintf(conn, ...) | |
mov rdi, conn | |
mov rsi, string_http_correct_response_prelude | |
xor edx, edx | |
mov dl, http_minor_version_char | |
mov rcx, path_str | |
call fprintf | |
; remember the preamble/headers were sent | |
mov headers_are_already_sent, 1 | |
.after_sent_headers: | |
; check for EOF (0 returned from read) | |
cmp read_result, 0 | |
je .cleanup_end | |
; write binary data | |
mov rdi, buffer | |
mov rsi, read_result | |
mov rdx, 1 | |
mov rcx, conn | |
call fwrite | |
; do the whole thing again | |
jmp .read_write_loop | |
; cleanup | |
.cleanup_end: | |
; close the requested file | |
mov edi, requested_file | |
call close | |
.end: | |
; free the buffer | |
mov rdi, buffer | |
call free | |
exit_stack_frame_return | |
.read_error: | |
; check if read errored because the file is actually a directory (minus_EISDIR) | |
; or for some other reason | |
cmp read_result, minus_EISDIR | |
je .this_is_actually_a_directory | |
; on a real read error close the file and return a 404 Not Found | |
mov edi, requested_file | |
call close | |
;;; fallthrough to .safe_open_in_current_dir_error | |
.safe_open_in_current_dir_error: | |
; send 404 Not Found | |
mov rdi, conn | |
mov rsi, string_404_not_found_resp | |
xor edx, edx | |
mov dl, http_minor_version_char | |
call fprintf | |
jmp .end | |
.this_is_actually_a_directory: | |
; read returned -EISDIR - switch to serve_directory_GET() | |
mov rdi, conn | |
mov rsi, path_str | |
xor edx, edx | |
mov dl, http_minor_version_char | |
mov ecx, requested_file | |
call serve_directory_GET | |
; then cleanup and return | |
jmp .cleanup_end | |
;void handle_client(int connfd) | |
%define minor_http_version_char BYTE [rbp-41q] | |
%define path_str QWORD [rbp-40q] | |
%define method_str QWORD [rbp-30q] | |
%define conn_FILE QWORD [rbp-20q] | |
%define connfd DWORD [rbp-10q] | |
handle_client: | |
create_stack_frame 60q | |
; save the function argument | |
mov connfd, edi | |
;; message_buf = malloc(1024); | |
;mov rdi, 1024 | |
;call malloc | |
;mov message_buf, rax | |
; method_str = malloc(32); | |
mov rdi, 32 | |
call malloc | |
mov method_str, rax | |
; path_str = malloc(1024); | |
mov rdi, 1024 | |
call malloc | |
mov path_str, rax | |
; turn the fd into a FILE* - makes it possible to use fscanf/fprintf on the socket | |
; if((conn_FILE = fdopen(connfd, "r+")) == NULL) goto .fdopen_failed; | |
xor rdi, rdi | |
mov edi, connfd | |
mov rsi, string_fdopen_read_write_mode | |
call fdopen | |
mov conn_FILE, rax | |
; error checking: | |
cmp rax, NULL | |
je .fdopen_failed | |
; read the first line which should be in a format of something like "GET / HTTP/1.1\r\n" | |
; fscanf(conn_FILE, '%31s %1023s HTTP/1.%c%*[\r]%*[\n]', method_str, path_str, &minor_http_version_char); | |
mov rdi, conn_FILE | |
mov rsi, string_fscanf_first_http_line | |
mov rdx, method_str | |
mov rcx, path_str | |
lea r8, minor_http_version_char | |
call fscanf | |
; error check fscanf | |
cmp rax, 3 ; three assigned items -> success | |
jne .fscanf_failure | |
; ensure the HTTP method is a GET | |
; if(strcmp(method_str, "GET") != 0) goto .reply_method_not_allowed; | |
mov rdi, method_str | |
mov rsi, string_GET_method | |
call strcmp | |
cmp rax, 0 | |
jne .reply_method_not_allowed | |
; call serve_file_GET(conn_FILE, method_str, minor_http_version_char) for further processing | |
mov rdi, conn_FILE | |
mov rsi, path_str | |
xor edx, edx | |
mov dl, minor_http_version_char | |
call serve_file_GET | |
.close_and_end: | |
; call fclose(conn_FILE) | |
; this also closes the underlying stream from listen() | |
mov rdi, conn_FILE | |
call fclose | |
.end: | |
; free allocated buffers | |
mov rdi, path_str | |
call free | |
mov rdi, method_str | |
call free | |
exit_stack_frame_return | |
;;; handle_client() error handlers: ;;; | |
; failed fdopen() - the file descriptor is not turned into a FILE* stream, so | |
; it should be closed using close() instread of fclose() | |
.fdopen_failed: | |
; print a diagnostic message | |
mov rdi, string_fdopen_fail | |
call perror | |
; close(connfd) | |
; don't quit on errors, because: from `man 3 close`: | |
; Note, however, that a failure return should be used only for | |
; diagnostic purposes (i.e., a warning to the application that | |
; there may still be I/O pending or there may have been failed I/O) | |
; or remedial purposes (e.g., writing the file once more or | |
; creating a backup). | |
xor rdi, rdi | |
mov edi, connfd | |
call close | |
jmp .end | |
; failed parsing the first HTTP line using fscanf | |
; emit a debug message to stdout and close the connection | |
.fscanf_failure: | |
mov rdi, string_fscanf_first_http_line_fail | |
call puts | |
jmp .close_and_end | |
; handle the HTTP method being something other than a GET by returning a 405 Method Not Allowed | |
.reply_method_not_allowed: | |
mov rdi, conn_FILE | |
mov rsi, string_405_method_not_allowed_resp | |
xor rdx, rdx | |
mov dl, minor_http_version_char | |
call fprintf | |
jmp .close_and_end | |
; TODO: (maybe?) error out on versions other than HTTP/1.0 and HTTP/1.1 | |
; exits with 1 | |
fail: | |
and rsp, 0xfffffffffffffff0 ; make sure the stack is aligned | |
mov rdi, 1 | |
call exit | |
socket_creation_failed: | |
mov rdi, string_socket_creation_fail | |
call perror | |
jmp fail | |
setsockopt_failed: | |
mov rdi, string_setsockopt_fail | |
call perror | |
jmp fail | |
bind_failed: | |
mov rdi, string_bind_fail | |
call perror | |
jmp fail | |
listen_failed: | |
mov rdi, string_listen_fail | |
call perror | |
jmp fail | |
accept_failed: | |
mov rdi, string_accept_fail | |
call perror | |
jmp fail | |
; sizes of the sockaddr_in struct | |
; | |
; from /usr/include/netinet/in.h: (musl libc) | |
; | |
; struct sockaddr_in { | |
; sa_family_t sin_family; | |
; in_port_t sin_port; | |
; struct in_addr sin_addr; | |
; uint8_t sin_zero[8]; | |
; }; | |
; | |
; | |
; sin_family: | |
; typedef unsigned short sa_family_t; | |
; -> 2 bytes | |
; -> WORD | |
; | |
; sin_port: | |
; typedef uint16_t in_port_t; | |
; -> 2 bytes | |
; -> WORD | |
; | |
; sin_addr: | |
; struct in_addr sin_addr; | |
; -> struct in_addr { in_addr_t s_addr; }; | |
; -> typedef uint32_t in_addr_t; | |
; -> 4 bytes | |
; -> DWORD | |
; | |
; sin_zero: | |
; uint8_t[8] | |
; -> 8 bytes | |
; -> QWORD | |
global main | |
; int main(int argc, char *argv[]) | |
; int main(int edi, char *rsi[]) | |
; stack variables: | |
%define so_reuseaddr_enable DWORD [rbp-54q] ; use octal here (NNq) so it's easy to decrement by 8 | |
%define serveraddr [rbp-50q] | |
%define serveraddr.sin_family WORD [rbp-50q] | |
%define serveraddr.sin_port WORD [rbp-46q] | |
%define serveraddr.sin_addr DWORD [rbp-44q] | |
%define serveraddr.sin_zero QWORD [rbp-40q] | |
%define SERVERADDR_SIZE 16 | |
%define length QWORD [rbp-30q] | |
%define connfd QWORD [rbp-20q] | |
%define sockfd QWORD [rbp-10q] | |
main: | |
create_stack_frame 60q | |
; sockfd = socket(AF_INET, SOCK_STREAM, 0); | |
; sockfd = socket(2, 1, 0); | |
mov rdi, AF_INET | |
mov rsi, SOCK_STREAM | |
xor edx, edx ; mov rdx, 0 | |
call socket | |
mov sockfd, rax | |
; check socket() return value: -1 means an error | |
cmp eax, -1 | |
je socket_creation_failed | |
; print a message that socket creation was successfull | |
mov rdi, string_socket_creation_successfull | |
mov rsi, sockfd | |
call printf | |
; enable SO_REUSEADDR on the socket to not have to wait for a connection in a TIME_WAIT state | |
; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &so_reuseaddr_enable, sizeof(int)) | |
mov so_reuseaddr_enable, 1 | |
mov rdi, sockfd | |
mov rsi, SOL_SOCKET | |
mov rdx, SO_REUSEADDR | |
lea rcx, so_reuseaddr_enable | |
mov r8, 4 ; sizeof(int) /* == sizeof(so_reuseaddr_enable) */ | |
call setsockopt | |
; error handling: setsockopt should return 0 | |
cmp eax, 0 | |
jne setsockopt_failed | |
; print a message that setsockopt was successfull | |
mov rdi, string_setsockopt_successfull | |
call puts | |
; initialise serveraddr | |
mov serveraddr.sin_family, AF_INET | |
mov serveraddr.sin_port, 0x901f ; htons(8080) = htons(0x1f90) = 0x901f | |
mov serveraddr.sin_addr, 0 ; 0.0.0.0 = 0x00_00_00_00 (the same in network and host order) | |
mov serveraddr.sin_zero, 0 | |
; bind(sockfd, &serveraddr, sizeof serveraddr) | |
mov rdi, sockfd | |
lea rsi, serveraddr | |
mov rdx, SERVERADDR_SIZE | |
call bind | |
; handle bind() failure (when return value is not zero) | |
cmp eax, 0 | |
jne bind_failed | |
; print a message that bind() succeeded | |
mov rdi, string_bind_successfull | |
call puts | |
; listen(sockfd, LISTEN_BACKLOG_SIZE) | |
mov rdi, sockfd | |
lea rsi, LISTEN_BACKLOG_SIZE | |
call listen | |
; handle listen() failure (when return value is not zero) | |
cmp eax, 0 | |
jne listen_failed | |
; print a message that listen() succeeded | |
mov rdi, string_listen_successfull | |
call puts | |
.accept_handle_close_loop: | |
; connfd = accept(sockfd, NULL, NULL) | |
mov rdi, sockfd | |
xor esi, esi ; mov rsi, 0 | |
xor edx, edx ; mov rdx, 0 | |
call accept | |
mov connfd, rax | |
; handle accept() failure | |
cmp eax, -1 | |
je accept_failed | |
; print a message stating accept() succeeded | |
mov rdi, string_accept_successfull | |
mov rsi, connfd | |
call printf | |
; handle client here, in handle_client(connfd) | |
mov rdi, connfd | |
call handle_client | |
; jump back to accept a new client | |
jmp .accept_handle_close_loop | |
; TODO: set SO_REUSEADDR | |
; vim: ft=nasm ts=4 sw=4 expandtab |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment