#define _POSIX_C_SOURCE 200809L #include #include #include #include /* exit */ #include /* strlen, strncmp */ #include /* sockaddr, sockaddr_un, recvfrom, sendto, recv, socketpair */ #include /* recvfrom, sendto, recv, fork, waitpid, socketpair */ #include /* sockaddr_un */ #include /* waitpid */ #include /* close, fork */ /* Vaším úkolem je tentokrát naprogramovat klient protokolu TFTP * (Trivial File Transfer Protocol, RFC 1350). Jedná se o paketově * (datagramově) orientovaný protokol pro přenos souborů. * * Procedura ‹tftp_put› pomocí protokolu TFTP na server uloží dodaný * soubor. Parametry: * * • ‹sock_fd› – popisovač socketu určeného ke komunikaci, * • ‹name› – nulou ukončený název souboru (ukazatel na pole * bajtů), * • ‹data› – ukazatel na data souboru, který má být uložen, * • ‹size› – velikost dat souboru. * * Nahrání souboru probíhá v protokolu TFTP takto: * * 1. klient odešle paket ‹WRQ›, který obsahuje: * * ◦ kód příkazu – bajty ‹0x00 0x02›, * ◦ název souboru ukončený nulovým bajtem, * ◦ řetězec ‹"octet"›, ukončen dalším nulovým bajtem, * * 2. server odpoví paketem ‹ACK› nebo ‹ERROR› (viz níže), * 3. je-li odpověď ‹ACK›, klient pokračuje odesláním paketu typu * ‹DATA›, který obsahuje: * * ◦ kód příkazu – bajty ‹0x00 0x03›, * ◦ dvoubajtové sekvenční číslo (první ‹DATA› paket má číslo 1, * každý další pak o jedna větší než předchozí), * ◦ 0–512 bajtů dat (každý paket krom posledního má 512 bajtů * dat, poslední paket zbytek – může být i nula), * * 4. server potvrdí příjem datového paketu opět paketem ‹ACK›, * který zároveň slouží jako výzva k odeslání dalšího datového * paketu, * 5. odpověď ‹ACK› na poslední ‹DATA› paket signalizuje, že přenos * skončil. * * Pakety, které odesílá server, vypadají takto: * * • ‹ACK› – začíná bajty ‹0x00 0x04›, další dva bajty určují * pořadové číslo paketu, na který navazují (0 pro paket ‹WRQ›, 1 * a výše pro pakety ‹DATA›), * • ‹ERROR› – začíná bajty ‹0x00 0x05›, dále dvoubajtový kód * chyby, řetězec popisující chybu a nulový bajt. * * Všechny dvoubajtové číselné hodnoty jsou odesílány v pořadí vyšší * bajt, nižší bajt (tzv. „big endian“). * * Procedura ‹tftp_put› vrátí: * * • 0 – přenos byl úspěšný, * • kladné číslo – přenos skončil chybou na straně serveru, * návratový kód odpovídá kódu chyby z paketu ‹ERROR›, * • -3 – server ukončil přenos s chybou 0 (paketem ‹ERROR›), * • -2 – neočekávaný paket od serveru (špatný kód příkazu, špatné * sekvenční číslo v paketu typu ‹ACK›, špatná velikost paketu), * • -1 – nastala systémová chyba na straně klienta. */ int tftp_put( int sock_fd, const char *name, const void *data, size_t size ); /* ┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄┄┄┄ následují testy ┄┄┄┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄ */ static void close_or_warn( int fd, const char *name ) { if ( close( fd ) == -1 ) warn( "closing %s", name ); } static int receive_file( int client_fd, const char *name, const char *response, bool should_exit ) { char buf[ 2048 ]; int name_len = strlen( name ); struct sockaddr_un _src_addr; struct sockaddr *src_addr = ( struct sockaddr * ) &_src_addr; socklen_t addr_len = sizeof( _src_addr ); ssize_t received = recvfrom( client_fd, buf, sizeof( buf ), 0, src_addr, &addr_len ); if ( received == -1 ) return -1; assert( received == name_len + 9 ); assert( buf[ 0 ] == 0x00 && buf[ 1 ] == 0x02 ); assert( strncmp( buf + 2, name, name_len + 1 ) == 0 ); assert( strncmp( buf + 3 + name_len, "octet\0", 6 ) == 0 ); if ( send( client_fd, response, 4, 0 ) == -1 ) return -1; if ( should_exit ) return 0; received = recv( client_fd, buf, sizeof( buf ), 0 ); if ( received == -1 ) return -1; assert( received >= 2 ); assert( buf[ 0 ] == 0x00 && buf[ 1 ] == 0x03 ); if ( send( client_fd, "\x00\x04\x00\x01", 4, 0 ) == -1 ) return -1; return 0; } static int fork_server( int fds[ 2 ], const char *name, const char *response, bool should_exit ) { int server_fd = fds[ 0 ], client_fd = fds[ 1 ]; pid_t child_pid = fork(); if ( child_pid == -1 ) err( 2, "fork" ); if ( child_pid > 0 ) return child_pid; close_or_warn( client_fd, "client socket" ); if ( receive_file( server_fd, name, response, should_exit ) == -1 ) err( 2, "receive file" ); close_or_warn( server_fd, "server socket" ); exit( 0 ); } static int reap( pid_t pid ) { int status; if ( waitpid( pid, &status, 0 ) == -1 ) err( 2, "wait" ); if ( WIFEXITED( status ) ) return WEXITSTATUS( status ); else return -1; } int main() { int fds[ 2 ]; if ( socketpair( AF_UNIX, SOCK_DGRAM, 0, fds ) == -1 ) err( 1, "socketpair" ); int server_fd = fds[ 0 ], client_fd = fds[ 1 ]; pid_t server_pid; server_pid = fork_server( fds, "foo.txt", "\x00\x04\x00\x00", false ); assert( tftp_put( client_fd, "foo.txt", "hello", 5 ) == 0 ); assert( reap( server_pid ) == 0 ); server_pid = fork_server( fds, "bar.txt", "\x00\x04\x00\x00", false ); assert( tftp_put( client_fd, "bar.txt", "he\x00he", 5 ) == 0 ); assert( reap( server_pid ) == 0 ); server_pid = fork_server( fds, "baz.txt", "\x00\x04\x00\x08", true ); assert( tftp_put( client_fd, "baz.txt", "world", 5 ) == -2 ); assert( reap( server_pid ) == 0 ); server_pid = fork_server( fds, "qux.txt", "\x00\x05\x00\x00", true ); assert( tftp_put( client_fd, "qux.txt", "hello", 5 ) == -3 ); assert( reap( server_pid ) == 0 ); assert( tftp_put( -1, "quux.txt", "nope", 4 ) == -1 ); close_or_warn( client_fd, "client socket" ); close_or_warn( server_fd, "server socket" ); return 0; }