Telnet chat server for Linux x86_64

Тема в разделе "WASM.BEGINNERS", создана пользователем Hacker, 16 июл 2023.

  1. Hacker

    Hacker Member

    Публикаций:
    0
    Регистрация:
    9 авг 2018
    Сообщения:
    170
    Адрес:
    Москва
    Просто помогите поправь и оптимизировать уже имеющийся код. Может что-то там недописали. Сервер зависает после нескольких сообщений из telnet GNU, а из PuTTY вообще падает
     
  2. alex_dz

    alex_dz Active Member

    Публикаций:
    0
    Регистрация:
    26 июл 2006
    Сообщения:
    447
    сколько $ в час плотите
    или сумарно на сколько бюджет
     
  3. Hacker

    Hacker Member

    Публикаций:
    0
    Регистрация:
    9 авг 2018
    Сообщения:
    170
    Адрес:
    Москва
    Код (Text):
    1.  
    2. //Powered by antichat
    3. //telnet chat server
    4. //19.09.2023
    5. //gcc tcs.c -o tcs
    6.  
    7. #include <stdio.h>
    8. #include <stdlib.h>
    9. #include <string.h>
    10. #include <sys/socket.h>
    11. #include <netinet/in.h>
    12. #include <arpa/inet.h>
    13. #include <errno.h>
    14. #include <sys/types.h>
    15. #include <time.h>
    16. #include <unistd.h>
    17. #include <assert.h>
    18. #include <fcntl.h>
    19. #include <poll.h>
    20. #include <sys/ioctl.h>
    21.  
    22. #define FDS_ARRAY_CHUNK_SIZE 100
    23. #define MAX_MESSAGE_LEN 1728
    24. struct pollfd *fds;
    25. int fds_len;
    26.  
    27. void extend_fds()//extending fds array
    28. {
    29.     int i;
    30.  
    31.     fds = realloc(fds, (fds_len + FDS_ARRAY_CHUNK_SIZE) * sizeof(struct pollfd));
    32.    
    33.     for (i = 0; i < FDS_ARRAY_CHUNK_SIZE; i++) {
    34.         fds[fds_len + i].fd = -1;
    35.         fds[fds_len + i].events = 0;
    36.     }
    37.  
    38.     fds_len += FDS_ARRAY_CHUNK_SIZE;
    39.    
    40. }
    41.  
    42. void fds_set(int fd)//add fd to fds
    43. {
    44.     int i;
    45.  
    46.     for (i = 0; ; i++) {
    47.        
    48.         if (i == fds_len) extend_fds();
    49.  
    50.         if (fds[i].fd < 0) {
    51.             fds[i].fd = fd;
    52.             fds[i].events = POLLIN;
    53.             fds[i].revents = 0;
    54.             break;
    55.         }
    56.     }
    57. }
    58.  
    59. void fds_clr(int fd) // remove fd from fds
    60. {
    61.     int i;
    62.  
    63.     for (i = 0; i < fds_len; i++) {
    64.         if (fds[i].fd == fd) {
    65.             fds[i].fd = -1;
    66.                 fds[i].events = 0; 
    67.             break;
    68.         }
    69.     }
    70. }
    71. void set_nonblock(int socket)
    72. {    
    73.     int flags;    
    74.     flags = fcntl(socket,F_GETFL,0);    
    75.     assert(flags != -1);    
    76.     fcntl(socket, F_SETFL, flags | O_NONBLOCK);
    77. }
    78. void send_to_all(int servfd, int fd,  char *buff, int len)
    79. {
    80.     int i;
    81.  
    82.     for (i = 0; i < fds_len; i++) {
    83.         if (fds[i].fd > 0 && fd != fds[i].fd && fds[i].fd != servfd) {
    84.             write(fds[i].fd, buff, len);//TODO check return value and mark fd as closed
    85.            
    86.         }
    87.     }
    88. }
    89.  
    90. int telnet_negotiate_linemode(int fd)
    91. {
    92.     char do_linemode[] = {255, 253, 34};
    93.     char on_linemode[] = {255, 250, 34, 1, 1, 255, 240};
    94.     char will_echo[] = {255, 253, 1};
    95.     char wont_echo[] = {255, 252, 1};
    96.     char reply_buff[512];
    97.     int n;
    98.  
    99.    
    100.  
    101.     write(fd, do_linemode, sizeof(do_linemode));
    102.     write(fd, on_linemode, sizeof(on_linemode));
    103.     write(fd, will_echo, sizeof(will_echo));
    104.     write(fd, wont_echo, sizeof(wont_echo));
    105.  
    106.     read(fd, reply_buff, sizeof(reply_buff));
    107.  
    108.     return 0;
    109.  
    110. }
    111.  
    112. void replace_commands_with_spaces(char *buff, int len)
    113. {
    114.     int i;
    115.     unsigned char c;
    116.  
    117.     for (i = 0; i < len && buff[i] != 0; i++) {
    118.         c = buff[i];
    119.         if (c < ' ' || c > 126) {
    120.             c = ' ';//replacing with spaces
    121.         }  
    122.     }
    123. }
    124. int main(int argc, char** argv)
    125. {
    126.     struct sockaddr_in servaddr;
    127.     int servfd, connfd, port, pollret;
    128.     char *host, buff[MAX_MESSAGE_LEN], cleaned_buff[MAX_MESSAGE_LEN];
    129.     int i, msg_len, on, negresult;
    130.    
    131.     fds = NULL;
    132.     fds_len = 0;
    133.     on  = 1;
    134.  
    135.     setbuf(stdout, NULL);  
    136.    
    137.     if (argc != 3) {
    138.         perror("Please provide host and port. Example: telnet-chat 0.0.0.0 4022\r\n");
    139.         exit(1);
    140.     }  
    141.  
    142.     memset(&servaddr, 0, sizeof(servaddr));
    143.     servfd = socket(AF_INET, SOCK_STREAM, 0);
    144.  
    145.     host = argv[1];
    146.     port = atoi(argv[2]);
    147.  
    148.     servaddr.sin_family = AF_INET;
    149.     servaddr.sin_addr.s_addr = inet_addr(host);
    150.     servaddr.sin_port = htons(port);
    151.  
    152.     bind(servfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    153.  
    154.     listen(servfd, 1024);
    155.  
    156.     ioctl(servfd, FIONBIO, (char*)&on);
    157.    
    158.     fds_set(servfd);
    159.  
    160.     while(1) {
    161.         pollret = poll(fds, fds_len, 100);
    162.  
    163.         for (i = 0; i < fds_len; i++) {
    164.             if (fds[i].fd > 0) {
    165.                 if (fds[i].revents & POLLIN) {
    166.                     if (fds[i].fd == servfd) {
    167.                         connfd = accept(servfd, (struct sockaddr*)NULL, NULL);
    168.                         telnet_negotiate_linemode(connfd);
    169.                         fds_set(connfd);
    170.                     } else {
    171.                         msg_len = read(fds[i].fd, buff, MAX_MESSAGE_LEN);
    172.                         replace_commands_with_spaces(buff, msg_len);
    173.                         send_to_all(servfd, fds[i].fd, buff, msg_len);
    174.                     }
    175.                 }
    176.                    
    177.                 if(fds[i].revents & POLLHUP ) {            
    178.                     //closed by peer            
    179.                     close(fds[i].fd);
    180.                     fds[i].fd = -1;
    181.                     fds[i].events = 0;
    182.                 }
    183.             }
    184.         }
    185.        
    186.     }
    187.     return 0;
    188.  
    189. }
    190.  
     
  4. Hacker

    Hacker Member

    Публикаций:
    0
    Регистрация:
    9 авг 2018
    Сообщения:
    170
    Адрес:
    Москва

    --- Сообщение объединено, 27 сен 2023 ---
    У кого какие идеи? Всё вроде работает, но иногда сревис отключается или отваливается хз, замечал такое. и какие странные коннекты на 23 порт из интернета на сервере, но в чати вроде тихо
    --- Сообщение объединено, 27 сен 2023 ---
    http://global.net.ru:8080/temp/tcs
    --- Сообщение объединено, 27 сен 2023 ---
    тебе на hh.ru
     
  5. Hacker

    Hacker Member

    Публикаций:
    0
    Регистрация:
    9 авг 2018
    Сообщения:
    170
    Адрес:
    Москва
    Код (ASM):
    1.  
    2. ;;Telnet Chat Server
    3. ;;Powered by antichat and Kuleshov Alexey
    4. ;;
    5. ;;
    6.  
    7. %include '/home/user/Desktop/nasmx/inc/nasmx.inc'
    8. %include '/home/user/Desktop/nasmx/inc/linux/libc.inc'
    9. %include '/home/user/Desktop/nasmx/inc/linux/syscall.inc'
    10.  
    11. ENTRY main
    12.  
    13. struc sockaddr_in
    14.     .sin_family resw 1
    15.     .sin_port resw 1
    16.     .sin_addr resd 1
    17.     .sin_zero resb 8
    18. endstruc
    19.  
    20. [SECTION .data]
    21.  
    22. tcs_so istruc sockaddr_in
    23.     at sockaddr_in.sin_family, dw 2
    24.     at sockaddr_in.sin_port, dw 0xffff
    25.     at sockaddr_in.sin_addr, dd 0
    26.     at sockaddr_in.sin_zero, dd 0, 0
    27. iend
    28.  
    29. sockaddr_in_size      equ $ - tcs_so
    30.  
    31. [SECTION .bss]
    32.  
    33. [SECTION .text]
    34.  
    35. proc main
    36. locals none
    37.  
    38. syscall socket, 2, 1, 0
    39. mov r13, rax
    40.  
    41. syscall bind, r13, tcs_so, sockaddr_in_size
    42. syscall listen, r13, 4096
    43.  
    44. syscall accept, r13, tcs_so, sockaddr_in_size
    45.  
    46. syscall close, r13
    47.  
    48. xor rax, rax
    49. endproc
    50.  
    начал писать но дальше не понимаю сишный код :dntknw:
     
  6. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.455
    Адрес:
    Россия, Нижний Новгород
    А зачем ты начал переписывать его на ассемблере?
     
  7. Hacker

    Hacker Member

    Публикаций:
    0
    Регистрация:
    9 авг 2018
    Сообщения:
    170
    Адрес:
    Москва
    Потому что считаю что эта программа должна быть на nasm
     
  8. alex_dz

    alex_dz Active Member

    Публикаций:
    0
    Регистрация:
    26 июл 2006
    Сообщения:
    447
    нет, только masm
    только 64 битца!
     
  9. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.455
    Адрес:
    Россия, Нижний Новгород
    А почему ты так считаешь?
     
  10. Hacker

    Hacker Member

    Публикаций:
    0
    Регистрация:
    9 авг 2018
    Сообщения:
    170
    Адрес:
    Москва
    [SECTION .bss]
    ioctlchar: resq 1
    ...
    syscall ioctl, r13, FIONBIO, [ioctlchar]

    Последний параметр это указатель на память с qword переменной?
    Плохо понимаю что делаю конечно, буду ждать владельца кода, может объяснит мне

    Нужно настроить терминалы видимо сокет
    --- Сообщение объединено, 26 окт 2023 ---
    http://explorer.net.ru:8080/temp/tcs.asm.txt
     
    Последнее редактирование: 26 окт 2023
  11. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.455
    Адрес:
    Россия, Нижний Новгород
    Hacker, смотри, этим сисколлом предполагается перевести сокет в неблокирующий режим, чтобы впоследствии слушать его poll’ом.

    FIONBIO - это не стандартизированный способ. В POSIX стандартом является смена через сисколл fcntl(…, O_NONBLOCK, …).
    Чтобы не делать лишний сисколл, ты можешь сразу создать сокет в неблокирующем режиме, задав SOCK_NONBLOCK в параметре type в функции socket().
    Примерно так:
    Код (Text):
    1.  
    2. syscall socket, AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP
    3.  
     
  12. Hacker

    Hacker Member

    Публикаций:
    0
    Регистрация:
    9 авг 2018
    Сообщения:
    170
    Адрес:
    Москва
    Спасибо огромное
    --- Сообщение объединено, 27 окт 2023 ---
    Сокет создали!!! Что дальше? :)
     
  13. TrashGen

    TrashGen ТрещГен

    Публикаций:
    0
    Регистрация:
    15 мар 2011
    Сообщения:
    1.184
    Адрес:
    подполье
    Дальше необходимо вступить и сконпелировать в сокет
     
  14. Hacker

    Hacker Member

    Публикаций:
    0
    Регистрация:
    9 авг 2018
    Сообщения:
    170
    Адрес:
    Москва
    Разкажите о работе poll пожалуйста
     
  15. TrashGen

    TrashGen ТрещГен

    Публикаций:
    0
    Регистрация:
    15 мар 2011
    Сообщения:
    1.184
    Адрес:
    подполье
    Hacker, да запросто расскажем, бро:

    --- Сообщение объединено, 27 окт 2023 ---
    на заставкэ - личинка скамерсанта, ловящаяся на слаткий шекиль за крипт x86 из абщака упаковщиков в последний раз, например, бгг,)
     
  16. Application

    Application Active Member

    Публикаций:
    1
    Регистрация:
    15 окт 2022
    Сообщения:
    110
    От доброты своей душевной, так и быть расскажу:

    https://www.google.com/search?q=poll+socket
    --- Сообщение объединено, 27 окт 2023 ---
     
  17. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.455
    Адрес:
    Россия, Нижний Новгород
    Обычно схема такая:
    1. Создаём сокет через socket().
    2. Назначаем ему порт через bind().
    3. Создаём для сокета очередь входящих пакетов через listen(), говоря системе, что это будет серверный сокет.
    4. Запускаем цикл приёма соединений, крутя в цикле accept().
    5. accept() уходит в ядро и не возвращается, пока не придёт новый клиент. Как только клиент пришёл, ядро создаёт для него дескриптор и возвращает его из accept'a.
    6. Чтобы получить от клиента данные, делаем recv() на полученном от accept'a клиентском дескрипторе.
    7. Получили recv'ом пакет, обработали, отправляем ответ, используя send().

    Если ты всё делаешь в одном потоке, то, пока делаешь пункты 6 и 7, сервер не может принимать новые соединения - ведь он не ждёт на accept'e, а занят чтением из клиентского сокета.
    В блокирующем режиме можно создавать на каждого клиента отдельный поток:
    1. Получили accept'ом клиентский сокет.
    2. Создали поток (например, через pthread_create()), передали в него сокет.
    3. Делаем пункты 6 и 7 в этом потоке.
    4. Основной поток в это время снова встаёт на accept.

    Такая схема неоптимальна, т.к. создание потока - затратная операция. Если к тебе пришло 100 клиентов - надо создать 100 потоков.
    Проблема в блокирующей схеме в том, что поток может одновременно ждать только на чём-то одном - или на accept, или на recv.
    Чтобы решить эту проблему, для многих объектов ядра ввели неблокирующие режимы - те, которые не ждут, пока произойдёт запрашиваемое действие, а возвращаются сразу после вызова - или с успешным результатом, или с ошибкой.
    В дополнение к неблокирующим режимам ввели функции, позволяющие одновременно ждать событий на нескольких объектах.
    Сначала ввели функцию select(), её интерфейс оказался неудачным, и на замену ввели poll(). Он всех устраивает и по сей день, но при очень высоких нагрузках ожидание можно организовать оптимальнее - и так появилось семейство epoll.

    Рассмотрим, как переписать сервер, используя poll. Проблема выше была в том, что мы не можем одновременно ждать новых клиентов и ждать, пока в клиентском сокете появятся данные.
    Это можно решить, используя poll. На вход он принимает массив дескрипторов, события от которых он будет ждать. Как только заданные события произойдут хотя бы в одном дескрипторе - он вернёт управление, и мы сможем понять, какой именно дескриптор "сработал".

    В псевдокоде всё вышеописанное будет выглядеть примерно так:
    Код (C++):
    1.  
    2. // Создаём неблокирующий сокет, использующий протокол TCP:
    3. int server = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    4.  
    5. // Задаём сокету порт 65533:
    6. sockaddr_in addr{};
    7. addr.sin_family = AF_INET;
    8. addr.port = htons(65533); // htons = Host-to-Network byte order
    9. ::bind(server, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
    10.  
    11. // Создаём очередь входящих пакетов:
    12. ::listen(server, 512);
    13.  
    14. //
    15. // Резервируем список объектов для poll'a, на которых будем ждать.
    16. // Чтобы не усложнять динамическим выделением памяти и ресайзом поллсета,
    17. // ограничим количество клиентов - пусть будет 128.
    18. //
    19. constexpr size_t k_maxClients = 128;
    20. pollfd pollset[k_maxClients + 1]{}; // Резервируем место под 128 клиентов + 1 наш серверный сокет.
    21.  
    22. // Кладём наш серверный сокет в поллсет:
    23. pollset[0].fd = server;
    24. pollset[0].events = POLLIN;
    25.  
    26. // Количество объектов в поллсете: сейчас там один объект (серверный сокет):
    27. size_t pollsetSize = 1;
    28.  
    29. // Запускаем цикл приёма новых соединений:
    30. while (true)
    31. {
    32.     // Ждём бесконечно, пока один из объектов в поллсете не просигналит:
    33.     int signaledCount = ::poll(&pollset, pollsetSize, -1);
    34.     if (signaledCount < 0)
    35.     {
    36.         // Что-то пошло не так.
    37.     }
    38.  
    39.     // Пройдёмся по всему поллсету и найдём того, кто просигналил:
    40.     for (int i = 0; i < pollsetSize; ++i)
    41.     {
    42.         const auto& pollsetEntry = pollset[i];
    43.         if ((pollsetEntry.revents & POLLIN) == 0)
    44.         {
    45.             // Этот не просигналил, идём дальше:
    46.             continue;
    47.         }
    48.  
    49.         pollsetEntry.revents = 0; // Сбрасываем маску событий
    50.  
    51.         // Смотрим, какой объект просигналил:
    52.         if (pollsetEntry.fd == server)
    53.         {
    54.             // На сервер пришёл новый клиент, принимаем его:
    55.             sockaddr_in clientAddr{};
    56.             socklen_t addrLen = sizeof(clientAddr);
    57.             int client = ::accept(server, reinterpret_cast<sockaddr*>(&clientAddr), &addrLen);
    58.  
    59.             if (pollsetSize > k_maxClients)
    60.             {
    61.                 // Мы упёрлись в лимит клиентов, отключаем клиента:
    62.                 ::shutdown(client, SHUT_RDWR);
    63.                 ::close(client);
    64.                 continue;
    65.             }
    66.  
    67.             // Добавляем клиента в поллсет, чтобы подождать, пока он пришлёт данные:
    68.             pollset[pollsetSize].fd = client;
    69.             pollset[pollsetSize].events = POLLIN;
    70.             ++pollsetSize;
    71.         }
    72.         else
    73.         {
    74.             // Клиент прислал данные, читаем:
    75.             char buffer[65536]{};
    76.             ssize_t receivedBytes = ::recv(pollsetEntry.fd, &buffer[0], sizeof(buffer), 0);
    77.             // ... Теперь у нас данные в buffer, можно с ними работать ...
    78.         }
    79.     }
    80. }
    81.  
    Здесь мы сначала добавили в поллсет серверный сокет, дождались, пока придёт клиент, добавили в поллсет клиента и ждём уже на двух объектах. Кто-то просигналил - вышли из poll'a, нашли просигналившего (или сервер, или клиент) - обрабатываем. Если просигналил сервер - значит, пришёл новый клиент (добавляем его в поллсет), а если просигналил клиент - значит, он прислал нам данные - а значит, читаем их.

    В примере выше нет обработки ошибок, не самый оптимальный обход поллсета (можно выходить из обхода, если обработали signaledCount дескрипторов), нет обработки отключений клиентов - всё это добавишь сам.
    И это мы только запустили сервер и научили его принимать пакеты. Если тебе нужен telnet - тебе надо будет прочесть несколько объёмных RFC и парсить принятые данные по этому протоколу.
    И как ты всё это собираешься писать на ассемблере (а главное, зачем) - не представляю.
     
  18. alex_dz

    alex_dz Active Member

    Публикаций:
    0
    Регистрация:
    26 июл 2006
    Сообщения:
    447
    занятно и интересно
    а можете покзаать тему поддержки 100К клиентов на одной машинке?
     
  19. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.455
    Адрес:
    Россия, Нижний Новгород
    Да всё то же самое: те же сокеты, тот же полл.
    В коде выше у меня сервер однопоточный - по-хорошему сделать тредпул и отдавать ему обработку клиентов сразу после полла. Вышли из полла, нашли клиента, отдали его в другой поток, а тот уже сделает recv и обработает пакет.
    Если хотим выжать из железа максимум - меняем poll на epoll и юзаем по-настоящему асинхронные функции через AIO, чтобы избежать лишних переключений контекста, но даже без этих извращений, думаю, не составит труда поддержать 100к клиентов.
     
    alex_dz нравится это.
  20. TrashGen

    TrashGen ТрещГен

    Публикаций:
    0
    Регистрация:
    15 мар 2011
    Сообщения:
    1.184
    Адрес:
    подполье
    пейсал помню socks5 двиг, обкурвалсо при этом периодически, и, чото начинал тогда есчо понимать, шо вся это сокетно-сетевая пылета весьма свойственна персонажам, коии её проектируют. ну, типо, упасть в ядро и законнектиццо в поток, коему предполагаюццо инструкцыии. так то.