Сокеты в ОС Linux

    В данной статье будет рассмотрено понятие сокета в операционной системе Linux: основные структуры данных, как они работают и можно ли управлять состоянием сокета с помощью приложения. В качестве практики будут рассмотрены инструменты netcat и socat.

    Что такое сокет?

    Сокет - это абстракция сетевого взаимодействия в операционной системе Linux. Каждому сокету соответствует пара IP-адрес + номер порта. Это стандартное определение, к которому привыкли все, спасибо вики. Хотя нет, вот здесь лучше описано. Поскольку сокет является только лишь абстракцией, то связка IP-адрес + номер порта - это уже имплементация в ОС. Верное название этой имплементации - "Интернет сокет". Абстракция используется для того, чтобы операционная система могла работать с любым типом канала передачи данных. Именно поэтому в ОС Linux Интернет сокет - это дескриптор, с которым система работает как с файлом. Типов сокетов, конечно же, намного больше. В ядре ОС Linux сокеты представлены тремя основными структурами:

    1. struct socket - представление сокета BSD, того вида сокета, который стал основой для современных "Интернет сокетов";

    2. struct sock - собственная оболочка, которая в Linux называется "INET socket";

    3. struct sk_buff - "хранилище" данных, которые передает или получает сокет;

    Как видно по исходным кодам, все структуры достаточно объемны. Работа с ними возможна при использовании языка программирования или специальных оберток и написания приложения. Для эффективного управления этими структурами нужно знать, какие типы операций над сокетами существуют и когда их применять. Для сокетов существует набор стандартных действий:

    • socket - создание сокета;

    • bind - действие используется на стороне сервера. В стандартных терминах - это открытие порта на прослушивание, используя указанный интерфейс;

    • listen - используется для перевода сокета в прослушивающее состояние. Применяется к серверному сокету;

    • connect - используется для инициализации соединения;

    • accept - используется сервером, создает новое соединение для клиента;

    • send/recv - используется для работы с отправкой/приемом данных;

    • close - разрыв соединения, уничтожение сокета.

    Если о структурах, которые описаны выше, заботится ядро операционной системы, то в случае команд по управлению соединением ответственность берет на себя приложение, которое хочет пересылать данные по сети. Попробуем использовать знания о сокетах для работы с приложениями netcat и socat.

    netcat

    Оригинальная утилита появилась 25 лет назад, больше не поддерживается. На cегодняшний день существуют порты, которые поддерживаются различными дистрибутивами: Debian, Ubuntu, FreeBSD, MacOS. В операционной системе утилиту можно вызвать с помощью команды nc, nc.traditional или ncat в зависимости от ОС. Утилита позволяет "из коробки" работать с сокетами, которые используют в качестве транспорта TCP и UDP протоколы. Примеры сценариев использования, которые, по мнению автора, наиболее интересны:

    • перенаправление входящих/исходящих запросов;

    • трансляция данных на экран в шестнадцатеричном формате.

    Опробуем операции в действии. Задача будет состоять в том, что необходимо отправить TCP данные через netcat в UDP соединение. Для лабораторной будет использоваться следующая топология сети:

    Проведем трансляцию:

    1. Введем команду на открытие порта на машине Destination: nc -ulvvp 7878

    2. Настроим машину Repeater. Так как передача из одного интерфейса этой машины будет происходить по протоколу TCP, а на другой интерфейс будет осуществляться передача по протоколу UDP, то для таких действий необходимо сделать соединитель, который сможет накапливать данные и пересылать их между открытыми портами. На такую роль отлично подходит FIFO файл. Поэтому команда для запуска будет выглядеть так: sudo mkfifo /tmp/repeater #создать FIFO файл
      sudo nc -l -p 4545 > /tmp/repeater | nc -u 10.0.3.5 7878 < /tmp/repeater IP адрес 10.0.3.5 - адрес машины Destination. Символы "|" и "><" представляют собой пайп и редирект данных соответственно. Функция предоставляется оболочкой терминала.

    3. Запускаем соединение из машины Source: nc 10.0.2.4 4545

    В итоге получаем возможность читать данные от машины Source:

    В машине Destination:

    Пример с трансляцией данных в шестнадцатеричном формате можно провести так же, но заменить команду на Destination или добавить еще один пайп на Repeater:

    nc -l -p 4545 -o file

    В результате будет создан файл, в котором можно будет обнаружить передаваемые данные в шестнадцатеричном формате:

    Как видно из тестового сценария использования, netcat не дает контролировать практически ничего, кроме направления данных. Нет ни разграничения доступа к ресурсам, которые пересылаются, ни возможности без дополнительных ухищрений работать с двумя сокетами, ни возможности контролировать действия сокета. Протестируем socat.

    socat

    Инструмент, который до сих пор поддерживается и имеет весьма обширный функционал по склейке каналов для взаимодействия. Разработчиками инструмент именуется как netcat++. Ниже приведем небольшой список того что можно перенаправить через socat:

    • STDIO -> TCP Socket;

    • FILE -> TCP Socket;

    • TCP Socket -> Custom Application;

    • UDP Socket -> Custom Application;

    • Socket -> Socket.

    Для повседневного использования достаточно опций, но если понадобится когда-то работать напрямую с серийным портом или виртуальным терминалом, то socat тоже умеет это делать. Полный перечень опций можно вызвать с помощью команды:

    socat -h

    Помимо редиректов socat также можно использовать как универсальный сервер для расшаривания ресурсов, через него можно как через chroot ограничивать привилегии и доступ к директориям системы.

    Чтобы комфортно пользоваться этим инструментом, нужно запомнить шаблон командной строки, который ожидает socat:

    socat additionalOptions addr1 addr2

    • additionalOptions - опции, которые могут добавлять возможности логирования информации, управления направлением передачи данных;

    • addr1 - источник данных или приемник (влияет использование флага U или u), это может быть сокет, файл, пайп или виртуальный терминал;

    • addr2 - источник данных или приемник (влияет использование флага U или u), это может быть сокет, файл, пайп или виртуальный терминал;

    Попробуем провести трансляцию данных из сокета в сокет. Будем использовать для этого 1 машину. Перед началом эксперимента стоит отметить, что особенностью socat является то, что для его корректной работы нужно обязательно писать 2 адреса. Причем адрес не обязательно должен быть адресом, это может быть и приложение, и стандартный вывод на экран.

    Например, чтобы использовать socat как netcat в качестве TCP сервера, можно запустить вот такую команду:

    socat TCP-LISTEN:4545, STDOUT

    Для коннекта можно использовать netcat:

    nc localhost 4545

    При таком использовании, socat дает возможность пересылать сообщения в обе стороны, но если добавить флаг "-u", то общение будет только от клиента к серверу. Все серверные сообшения пересылаться не будут:

    Настроим более тонко наш сервер, добавив новые опции через запятую после используемого действия:

    socat TCP-LISTEN:4545,reuseaddr,keepalive,fork STDOUT

    Дополнительные параметры распространяются на те действия, которые socat может выполнять по отношению к адресу. Полный список опций можно найти здесь в разделе "SOCKET option group".

    Таким образом socat дает практически полный контроль над состоянием сокетов и расшариваемых ресурсов.


    Статья написана в преддверии старта курса Network engineer. Basic. Всех, кто желает подробнее узнать о курсе и карьерных перспективах, приглашаем записаться на день открытых дверей, который пройдет уже 4 февраля.


    OTUS
    Цифровые навыки от ведущих экспертов

    Похожие публикации

    Комментарии 15

      –1

      Могу ли я через сокат соедить два сервера через третий? Т.е. первый и второй не могут соединиться с друг другом, но оба могут соединиться с третьим.

        0

        С помощью магии программирования вы сможете это сделать?
        Третий сервер будет принимать данные от первого и отправлять их на второй и наоборот.

          +1

          Даже программирования не нужно.
          Пару строк в iptables с пробросами нужных портов и voilà.
          Сокеты, как таковые, тут тоже особо не при чём.

            +1
            Не так просто. Третий имеет серый IP и в одной сети с одним из тех двух. А последний где-то в интернет. Возможно можно сделать какой-то трюк на третьем типа
            socat [server1] | socat [server2]
              0

              Этот трюк делают все reverse proxy servers.

          0
          pwnat уже смотрели?

          Если на трерьем есть ssh то можно прокинуть порт. Например:
          A: 10.0.0.1 — первый
          B: 10.0.1.3 — второй (в другой сети не доступный первому)
          С: 10.0.0.2, 10.0.1.2 — третий видит обе сети

          На А подключаемся к С с пробросом портов
          ssh 10.0.0.2 -L8080:10.0.0.3:80
          И потом лезим с А по 10.0.0.2:8080 и попадаем на 10.0.1.3:80

          ps: предварительно добавив свои ключи (ssh-copy-id), что бы пароли не вводить каждый раз
            0
            Про проброс ssh широко известно и я бы не поинтересовался тем, что легко нагуглить. Тут задача о несколько необычных серверах. A и B это устройства. Устройство А на порт идут специальные двоичные данные. А устройство «В» имеет порт, к которому можно подключиться и записывать туда такие же данные.
            Сервер С — это действительно сервер и там можно запустить почти любое ПО. На нем работает программа, которая подключена к порту устройства А и подключена к порту устройства В и просто читает из одного порта и записывает в другой.
            Мне стало интересно можно ли это же сделать чем-то наподобие socat. Т.е. имеет ли задача решение стандартными средствами linux, без написания скриптов, например, python.
              +1
              Если это сервер то iptables либо redir
              Но видимо можно и socat-ом
              socat TCP4-LISTEN:8080,fork TCP4:10.0.1.3:80
              Ну и через опятьже ssh. Прям на сервере ssh 127.0.0.1 -L8080:10.0.1.3:80
                0
                Спасибо. Заработало так socat -v TCP4:AServer:8080, fork TCP4:BServer:8081
          +1
          Поэтому команда для запуска будет выглядеть так:
          sudo mkfifo /tmp/repeater #создать FIFO файл
          sudo nc -l -p 4545 &lt; /tmp/repeater | nc -u -l 10.0.3.5 7878 > tmp/repeater
          

          Результат:
          user@backup-server:~$ sudo mkfifo /tmp/repeater
          [sudo] password for user: 
          user@backup-server:~$ sudo nc -l -p 4545 &lt; /tmp/repeater | nc -u -l 10.0.3.5 7878 > tmp/repeater
          [1] 913
          -bash: lt: command not found
          -bash: tmp/repeater: No such file or directory
          -bash: /tmp/repeater: Permission denied
          

          Перед публикацией самостоятельно выполнить собственную лабораторную работу не пробовали?
            0

            Очевидно же, что тут вместо &lt; должно быть < — видимо, где-то неверно отработал парсер и заменил символ (возможно даже непосредственно на этапе публикации на сайт), это ошибка не человека а машины (и недосмотр человека, да).

              –1
              А отсутствие дроби перед вторым tmp — это тоже проделки парсера?
              user@backup-server:~$ sudo nc -l -p 4545 < /tmp/repeater | nc -u -l 10.0.3.5 7878 > tmp/repeater    
              -bash: tmp/repeater: No such file or directory
              
                +1

                Сначала отписывал штатным средством об опечатках, потом понял — всё неверно. Правильная команда (можно без sudo — достаточно прав обычного пользователя):


                mkfifo /tmp/repeater
                nc -kl -p 4545 > /tmp/repeater | nc -u 10.0.3.5 7878 < /tmp/repeater

                -k позволит не закрывать сокет, а перенаправления должны быть сделаны наоборот: пишем в канал, а потом из него читаем и отправляем данные на Destination.

            0

            Сразу в первом абзаце логическая ошибка. Либо неточность в заголовке статьи.
            Сокет — это буквально "гнездо", он в чём-то очень похож на пару труб (pipe). Клиенты подключаются к сокету и обмениваются через него данными — один пишет, другой читает (одна труба), и наоборот (вторая). Никаких ip-адресов, портов и т.д. не подразумевается, в linux (и unix) сокет обычно выглядит как специальный файл с самым обычным путём от корня.
            А то, что имеет ip-адрес — это уже особый сетевой сокет. Он в целом похож на unix-сокет, но говоря о "сокетах в linux" я бы ожидал увидеть прежде всего именно unix-сокеты, и уже потом сетевые. Либо ожидал бы, что статья называется "сетевые сокеты в linux", а не так, как сейчас.

              –1

              Именно так. Даже фраза "Каждому сокету соответствует пара IP-адрес + номер порта." будет неверна для raw-сокетов.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое