В статье рассмотрено понятие «соединение» для TCP и UDP протоколов в ядре операционной системы Linux на примере работы оборудования MikroTik. Дополнительно рассматриваются особенности работы технологии NAT в указанном контексте. Материалы носят в основном теоретический характер и предназначены для людей, тонко настраивающих Firewall, Qos и маршрутизацию, где им придётся непосредственно работать с рассматриваемыми connections.

В этой части статьи подробно описана сущность сетевого соединения глазами ядра маршрутизатора. В практической части закрепим информацию в результате рассмотрения работы прикладного протокола DNS через подсистемы RouterOS. В заключительной части речь пойдёт о диаграмме потока пакетов, при работе с которой важно понимать сущность рассматриваемого сетевого соединения, а также о не документированной в явном виде особенности работы NAT. Материала достаточно много, и чтобы читатель не потерял смысловую нить к концу статьи, она разделена на 3 части: теория, практика и особенность NAT.
Цикл статей не предназначен для новичков и может их только запутать. Полагаю, что читатель хорошо знаком с предметом разговора.
Начнём с того, что разграничим сетевое соединение в ядре операционной системы маршрутизатора и реально существующее клиент-серверное соединение, передающееся насквозь через него. То, соединение, о котором речь пойдёт в статье, существует только в контексте роутера, ровно также, как маркировка трафика. RouterOS построена на базе операционной системы Linux и поэтому имеет высокое сходство с ней. Подсистема обработки сетевых пакетов на оборудовании MikroTik унаследована от ядра Linux (постулат подтверждён обращением в компанию, номер обращения SUP-65046). Это может быть одним из аргументов в сторону изучения работы RouterOS, так как полученные знания легко адаптивны под работу непосредственно с Linux. Так, совсем недавно на Хабре была опубликована статья, посвящённая маршрутизации, в которой достаточно ясно прослеживается указанная взаимосвязь. Научиться настраивать MikroTik с нуля или систематизировать уже имеющиеся знания можно на углубленном курсе по администрированию MikroTik.

Оставим лирическое отступление и вернёмся к предмету разговора. На оборудовании MikroTik, сетевые соединения в Firewall можно увидеть так:

/ip firewall connection print 
Flags: E - expected, S - seen-reply, A - assured, C - confirmed, D - dying, F - fasttrack, s - srcnat, d - dstnat 
 #          PR.. SRC-ADDRESS           DST-ADDRESS           TCP-STATE   TIMEOUT     ORIG-RATE REPL-RATE
 0  SAC  s  tcp  10.0.0.254:1587       13.33.246.32:443      established 23h36m38s        0bps      0bps
 1  SAC  s  udp  10.0.0.254:28397      64.233.165.147:443                48s              0bps      0bps
 2  SAC     tcp  192.168.1.17:44227    194.67.200.124:1200   established 23h59m54s        0bps      0bps
 3  SAC  s  tcp  10.0.0.254:2069       2.16.103.120:80       close       3s               0bps      0bps
 4    C     udp  10.0.0.254:65274      10.0.0.255:20561                  9s            7.2kbps      0bps
 5  SAC  s  udp  10.0.0.254:16641      64.233.165.147:443                48s              0bps      0bps
 6  SAC  s  udp  10.0.0.254:10493      142.251.1.100:443                 1m52s            0bps      0bps
 7  SAC  s  tcp  10.0.0.254:1066       173.194.73.188:5228   established 23h59m37s        0bps      0bps
 8  SAC  s  udp  10.0.0.254:55243      64.233.165.147:443                1m53s            0bps      0bps
 9  SAC  s  udp  10.0.0.254:15947      64.233.165.147:443                2m48s            0bps      0bps
10  SAC  s  udp  10.0.0.254:63933      64.233.165.147:443                2m48s            0bps      0bps
11  S C     udp  192.168.1.17:35622    1.1.1.1:53                        1s               0bps      0bps

В Winbox это будет выглядеть более наглядно, однако окно дополнительной информации не содержит:



Показанный инструмент называется Connection tracking. Он визуализирует сетевые соединения, однако существует теоретический риск, что соединение может существовать и быть не отражено в нём, вследствие короткого времени существования. На самом деле это не так, и далее я представлю объяснение. Именно с этими соединениями и работают правила «золотого сечения», с которых всегда начинается классическая настройка Firewall на MikroTik:

/ip firewall filter
add action=accept chain=input comment="Accept established,related" connection-state=established,related
add action=drop chain=input comment="Drop invalid" connection-state=invalid
add action=accept chain=forward comment="Accept established,related" connection-state=established,related
add action=drop chain=forward comment="Drop invalid" connection-state=invalid

Они обеспечивают прохождение пакетов уже установленных соединений, а также связанных с ними, минуя нижестоящие правила Firewall, чем высвобождают ресурсы центрального процессора маршрутизатора. Подробнее, какие соединения являются new, established, related, untracked и invalid можно почитать в официальной документации. Ещё раз повторю, что они не равны в полной мере существующим сетевым соединениям, проходящим через роутер. Интуитивно это можно объяснить и так, что маршрутизатор ведь ничего не знает о содержании передающихся через него пакетов, он не устанавливает эти соединения, не проходит процедуры рукопожатий и т.д. Его дело их пропускать далее по сети. Это очень грубо и не отражает суть понятия сетевое соединение глазами маршрутизатора. Объясню на примере работы транспортных протоколов TCP и UDP.

Начнём по порядку. Каждое реально существующее TCP соединение имеет уникальный (случайный) Sequence number, который генерируется после трёхстороннего handshakes:



Можно было бы предположить, что каждый Sequence number является уникальным соединением в операционной системе маршрутизатора. Теперь взглянем на заголовок UDP протокола, в котором никакого номера соединения вообще нет:



Однако в роутере существуют и TCP и UDP соединения. Для объяснения происходящего воспользуемся методом аналогии. Откроем в Wireshark дамп обычного сетевого трафика и посмотрим, что содержат заголовки реально существующих пакетов, а не серые картинки с RFC:



Слева представлен заголовок для TCP пакета, справа для UDP пакета. Sequence number уже знакомый параметр, а вот описание Stream index ранее мы не встречали. И как не сложно догадаться, их нет в протоколах. На самом деле Stream index – это параметр, введённый в отображение заголовков пакетов непосредственно программой Wireshark, для удобства работы с трафиком. Он присваивается каждой паре сокетов, начиная с единицы, и инкрементально увеличивается отдельно для каждого типа протоколов. Здесь появляется новый термин – сокет. Под ним понимается совокупность пар: IP адрес отправителя и номер порта отправителя, а также IP адрес получателя и номера порта получателя. Если вы выполните экспорт только конкретных выбранных (выделенных) пакетов из дампа, то, открыв их, обнаружите, что Stream index будет пересчитан заново, начиная с единицы. Когда выбирается меню Follow -> TCP Stream, то как раз Wireshark и отобразит пакеты, имеющие одинаковое значение Stream index.



Примерно такой же механизм реализован в ядре операционной системы маршрутизатора. Однако в Connection tracking увидеть параметр, аналогичный Stream index в Wireshark, не получится:



Далее по тексту статей я буду нарочно использовать термин Stream index применительно к MikroTik, хотя это и не корректно. Получается, что ядро операционной системы маршрутизатора работает с набором виртуальных сокетов. Встаёт вопрос, каково время их жизни? RouterOS позволяет легко управлять этими параметрами, однако не рекомендую менять их значения по умолчанию, если вы не до конца понимаете, с чем развлекаетесь работаете:

/ip firewall connection tracking print 
	enabled: auto
	tcp-syn-sent-timeout: 5s
	tcp-syn-received-timeout: 5s
	tcp-established-timeout: 1d
	tcp-fin-wait-timeout: 10s
	tcp-close-wait-timeout: 10s
	tcp-last-ack-timeout: 10s
	tcp-time-wait-timeout: 10s
	tcp-close-timeout: 10s
	tcp-max-retrans-timeout: 5m
	tcp-unacked-timeout: 5m
	loose-tcp-tracking: yes
	udp-timeout: 10s
	udp-stream-timeout: 3m
	icmp-timeout: 10s
	generic-timeout: 10m
	max-entries: 88016
	total-entries: 9

Ранее по тексту было опровергнуто утверждение, что теоретически некоторые соединения могут быть не отражены в Connection tracking. Как видно выше, значения по умолчанию задают достаточно длинные таймауты, что гарантирует визуализацию всех сетевых соединений, даже случайно пролетающих через маршрутизатор. В результате DOS атаки можно попытаться наоткрывать пустых соединений. Все они попадут в Connection tracking.

▍ Заключение


Теоретический материал подошёл к концу. Представленной информации должно хватить для того, чтобы у знакомого с темой читателя сложилось понимание, что такое сетевое соединение в роутере MikroTik, а также других устройствах, работающих под управлением операционной системе, в основу которой положен Linux. Внутри RouterOS понятие сетевое соединение не идентично клиент-серверному соединению, что необходимо для её работы с пакетами. Это важно понимать и знать, ведь работа с этими соединениями не ограничивается только Firewall-ом, но также присутствует в приоритизации трафика (Qos) и маршрутизации. Защита маршрутизатора от DOS атак на L3 уровне также базируется на правилах обработки сетевых соединений.

MikroTik работает с виртуальными сокетами, под которыми следует понимать совокупность пар IP адрес отправителя и номер порта отправителя, а также IP адрес получателя и номера порта получателя, искусственно введенных для обеспечения функционирования устройства. В этом и заключается сущность сетевых соединений глазами роутера. Чтобы всё основательно встало на свои места, мы разберём практическую задачу, но это уже будет в следующей части.

Часть 1 (вы тут)
Часть 2
Часть 3

Комментарии (44)


  1. amarao
    23.11.2021 12:49

    А что требуется в conntrack в Linux для того, чтобы tcp соединение считалось established? (вопрос с тремя уровнями подводха).


    1. olegtsss Автор
      23.11.2021 14:03

      RFC 793, пункт 2.7 определяет установление TCP соединения. CT будет считать соединение на основании описанных stream index с учётом тайм-аутов из последнего скрина.


      1. amarao
        23.11.2021 14:33
        +2

        Это первый уровень. Если conntrack заметит SYN в одну сторону, а потом SYN+ACK в другую, он добавит соединение как established. Но единственный ли это метод появления established tcp в conntrack? Переходим на второй уровень.


        1. olegtsss Автор
          23.11.2021 18:07

          Спасибо за хороший вопрос, надо подумать.


        1. VlaoMao
          24.11.2021 03:26

          Не гуглил, не смотрел, второе не expected entry какой-нибудь?


          1. amarao
            24.11.2021 12:12

            Подсказка nf_conntrack_tcp_loose


            1. HiroX
              25.11.2021 21:25

              • ip_conntrack_tcp_loose - при "подхватывании" уже установленного соединения сколько пакетов требуется в обоих направлениях для подтверждения; если 0, то установленное соединение не подхватывается вовсе; по умолчанию - 3

              Ну если роутер был перезагружен или таблица соединений сброшена, то уже существующие соединения подхватятся, но при наличии NAT это вряд ли работает.


              1. amarao
                25.11.2021 22:53

                По-умолчанию там 1. Это означает, что ваш stateful firewall смывается банальным ACK-флудом.


                1. olegtsss Автор
                  26.11.2021 08:37

                  RoutersOS для защиты от ACK-флуда имеет функционал в firewall, основанный на количестве соединений (заданного типа) от определенной сети (хоста). Сегодня выйдет вторая часть статьи, в ней будет приведено правило.


              1. olegtsss Автор
                26.11.2021 08:32

                RouterOS имеет значение по умолчанию:
                /ip firewall connection tracking set loose-tcp-tracking=yes
                Как видно, нет возможности задать количество пакетов по-умолчанию. Почему вы считаете, что значение на самом деле является 3?


              1. amarao
                26.11.2021 12:32
                +1

                Это где вы такое нашли? Я аж почти вам поверил. Полез в доки - нет такого. Полез в сырцы - нет такого. Вот что есть:

                    if (new_state == TCP_CONNTRACK_SYN_SENT) {
                        memset(&ct->proto.tcp, 0, sizeof(ct->proto.tcp));
                        /* SYN packet */
                        ct->proto.tcp.seen[0].td_end =
                            segment_seq_plus_len(ntohl(th->seq), skb->len,
                                         dataoff, th);
                        ct->proto.tcp.seen[0].td_maxwin = ntohs(th->window);
                        if (ct->proto.tcp.seen[0].td_maxwin == 0)
                            ct->proto.tcp.seen[0].td_maxwin = 1;
                        ct->proto.tcp.seen[0].td_maxend =
                            ct->proto.tcp.seen[0].td_end;
                
                        tcp_options(skb, dataoff, th, &ct->proto.tcp.seen[0]);
                    } else if (tn->tcp_loose == 0) {
                        /* Don't try to pick up connections. */
                        return false;
                    } else {
                        memset(&ct->proto.tcp, 0, sizeof(ct->proto.tcp));
                        /*
                         * We are in the middle of a connection,
                         * its history is lost for us.
                         * Let's try to use the data from the packet.
                         */
                        ct->proto.tcp.seen[0].td_end =
                            segment_seq_plus_len(ntohl(th->seq), skb->len,
                                         dataoff, th);
                        ct->proto.tcp.seen[0].td_maxwin = ntohs(th->window);
                        if (ct->proto.tcp.seen[0].td_maxwin == 0)
                            ct->proto.tcp.seen[0].td_maxwin = 1;
                        ct->proto.tcp.seen[0].td_maxend =
                            ct->proto.tcp.seen[0].td_end +
                            ct->proto.tcp.seen[0].td_maxwin;
                
                        /* We assume SACK and liberal window checking to handle
                         * window scaling */
                        ct->proto.tcp.seen[0].flags =
                        ct->proto.tcp.seen[1].flags = IP_CT_TCP_FLAG_SACK_PERM |
                                          IP_CT_TCP_FLAG_BE_LIBERAL;
                    }
                

                И где тут счётчик ack'ов?


        1. HiroX
          25.11.2021 21:21

          Еще может быть related (типа GRE туннель в PPTP), но это вроде не established как таковой. Еще может быть если роутер сам инициировал соединение, или к нему было инициировано соединение.


          1. amarao
            25.11.2021 22:52

            Related, хорошо, это второй уровень.


            1. olegtsss Автор
              26.11.2021 08:39

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


              1. olegtsss Автор
                26.11.2021 12:08

                Или речь идёт про udp invalid?


              1. olegtsss Автор
                26.11.2021 12:09

                Это будет старый established. (Я про предыдущий свой комментарий)


              1. amarao
                26.11.2021 12:29

                Третий уровень - это то, что всё, что первые два уровня говорили не важно. прошёл ack - получился established.

                И только самые мудрые знают про отключение этой опции.


                1. olegtsss Автор
                  27.11.2021 11:43

                  RouterOS при получении TCP пакетов с флагами ack воспринимает их, как соединения new. Привожу доказательство.
                  1) Настроим firewall детектировать флаги TCP пакетов:
                  /ip firewall filter add action=passthrough chain=input protocol=tcp tcp-flags=syn
                  /ip firewall filter add action=passthrough chain=input protocol=tcp tcp-flags=ack

                  2) Запускаю syn-flood, вижу, что детектор срабатывает:
                  hping3 --count 1000 --syn -p 80 --fast 192.168.1.1
                  # CHAIN ACTION BYTES PACKETS
                  0 ;;; TEST TCP
                  input passthrough 10 760 269
                  1 ;;; TEST TCP
                  input passthrough 0

                  2) Запускаю ack-flood, вижу, что детектор срабатывает:
                  # CHAIN ACTION BYTES PACKETS
                  0 ;;; TEST TCP
                  input passthrough 400 10
                  1 ;;; TEST TCP
                  input passthrough 1 055 840 26 396

                  3) Прикручиваю к детектору еще один счетчик, в котором при ack-flood будет все по нулям:
                  /ip firewall filter add action=passthrough chain=input connection-state=established protocol=tcp tcp-flags=ack
                  Последний счетчик сработает только для connection-state=new.


                  1. amarao
                    27.11.2021 12:01

                    Я не собираюсь нырять в особенности врапера "routeros". Флаг net.netfilter.nf_conntrack_tcp_loose в линуксах управляет тем, как netfilter/conntrack обрабатывает ack-пакеты. Создатели конкретно этого дистрибутива могли что-то поменять в настройках (молодцы), но конкретные дистрибутивы к ядру относятся постольку-поскольку.


                    1. mayorovp
                      27.11.2021 12:54

                      Так обсуждаемый пост-то про routeros, а не про ядро


                    1. olegtsss Автор
                      27.11.2021 14:50

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


                      1. amarao
                        27.11.2021 15:51

                      1. olegtsss Автор
                        27.11.2021 17:29

                        Вы имеете ввиду код:

                        else if (tn->tcp_loose == 0) {
                        /* Don't try to pick up connections. */
                        return false;

                        В нем указано, что если флаг равен loose, тогда:
                        /*
                        * We are in the middle of a connection,
                        * its history is lost for us.
                        * Let's try to use the data from the packet.
                        */

                        А если флаг !tcp_loose и !tcp_syn, тогда return false.


                      1. amarao
                        27.11.2021 20:19

                        Да, я про это и говорю. Обычное поведение ядра (это был процитирован код netfilter'а в районе conntrack'а). С учётом, что дефолт ядра 1, получается, что любой ACK делает ESTABLISHED в коннтреке.

                        Если кто-то это меняет, молодец, но дефолт именно такой. И грустно становится под ACK-флудом, который смывает другие established.


                      1. olegtsss Автор
                        29.11.2021 18:40

                        Смотрю код ядра (https://github.com/torvalds/linux/blob/master/net/netfilter/nf_conntrack_proto_tcp.c):
                        1) Смотрю функцию tcp_new (строка 754);
                        2) Спотыкаюсь о проверку if (new_state == TCP_CONNTRACK_SYN_SENT);
                        3) Попадаю под условие:
                        else if (tn->tcp_loose == 0) {
                        /* Don't try to pick up connections. */
                        return false;
                        4)Возвращаю 0, следовательно функция tcp_new возвращает ноль
                        5) Спотыкаюсь о проверку if (!nf_ct_is_confirmed(ct) && !tcp_new(ct, skb, dataoff, th))
                        6) Возвращаю return -NF_ACCEPT, т.е. не принимаю соединение.

                        Поправьте где не верно?


  1. kozlyuk
    23.11.2021 13:12

    Статья пытается запутать не только новичков.

    Каждое реально существующее TCP соединение имеет уникальный (случайный) Sequence number, который генерируется после трёхстороннего handshakes.

    Нет, номер последовательности в принципе не может идентифицировать соединение, так как относится к отдельному пакету. И их два. И они не генерируются после рукопожатия, а согласуются в процессе, для того оно и нужно.

    Здесь появляется новый термин – сокет. Под ним понимается совокупность пар: IP адрес отправителя и номер порта отправителя, а также IP адрес получателя и номера порта получателя.

    Во-первых, термин "сокет" имеет смысл только для стороны подключения, но ладно, мы говорим о "виртуальных сокетах", которые не у роутера, а у клиента и сервера. Во-вторых, даже в посте Q&A, на который ссылка (так себе источник определений), говорится о парах сокетов (socket pairs), адреса и порты (endpoint) которых и образуют то, что промужуточный узел считает соединением. Иногда это называют flow.


    1. olegtsss Автор
      23.11.2021 14:05

      По первой части вашего комментария. В статье говорится точно также.


      1. olegtsss Автор
        23.11.2021 14:07

        Про wireshark не согласен. Разрабочики вправе вводить в свои продукты различные механизмы и давай им собственные названия. В статье применена антология, показывающая на общность этих механизмов.


  1. AcidVenom
    23.11.2021 13:35

    В результате DOS атаки можно попытаться наоткрывать пустых соединений. Все они попадут в Connection tracking.

    Начиная с версии 6.36, могут попадать не все.


    1. olegtsss Автор
      23.11.2021 14:08

      Расскажите пожалуйста подробнее.


      1. AcidVenom
        23.11.2021 14:30
        +2

        *) firewall — added pre-connection tracking filter — «raw» table, that allow to protect connection-tracking from unnecessary traffic;

        Именно в этой версии добавили цепочку RAW, которая идет до Conntrack, как раз чтобы ее разгрузить.
        PFC


        1. olegtsss Автор
          23.11.2021 16:04

          Насколько я знаю, обработка RAW Prerouting всегда существовала в Linux. В RouterOS ранее не было интерфейса управления им.


          1. olegtsss Автор
            23.11.2021 16:06

            А по назначению его я с вами согласен .


          1. AcidVenom
            23.11.2021 16:12

            5 лет как есть. В связке с Mangle и адресными листами отрабатывает прекрасно. При IPsec-туннелировании тоже незаменим.


  1. Tarakanator
    23.11.2021 16:59

    Есть практический вопрос.
    пакет от ДНС сервера микротика к компу
    End output rules output: in:(unknown 0) out:LAN-bridge, proto UDP, 192.168.66.1:53->192.168.66.6:51496, len 60
    Почему он не попал в правило?
    ;;; established related chain=output action=accept connection-state=established,related log=no

    С TCP тоже бывает случается

    End output rules output: in:(unknown 0) out:LAN-bridge, proto TCP (SYN,ACK), 192.168.66.1:53->192.168.66.4:3765, len 52

    С коннекшн трекером по умолчанию такая-же фигня. Но на всякий случай прилагаю текущие настройки.

    ip firewall connection tracking print
    enabled: auto
    tcp-syn-sent-timeout: 5s
    tcp-syn-received-timeout: 10s
    tcp-established-timeout: 12h
    tcp-fin-wait-timeout: 20s
    tcp-close-wait-timeout: 10s
    tcp-last-ack-timeout: 30m
    tcp-time-wait-timeout: 10s
    tcp-close-timeout: 10s
    tcp-max-retrans-timeout: 5m
    tcp-unacked-timeout: 10m
    loose-tcp-tracking: yes
    udp-timeout: 20s
    udp-stream-timeout: 3m
    icmp-timeout: 10s
    generic-timeout: 10m
    max-entries: 183768
    total-entries: 870


    1. AcidVenom
      23.11.2021 17:10
      -1

      chain=output только для пакетов, рожденных на роутере.


      1. mayorovp
        23.11.2021 18:37

        пакет от ДНС сервера микротика к компу


  1. Tarakanator
    23.11.2021 17:35

    Это и есть пакет, рождённый на роутере, это ответ dns сервера роутера


    1. olegtsss Автор
      23.11.2021 18:10

      Работа протокола DNS в контексте connections очень подробно рассмотрена во второй части цикла статей. А в третьей будут еще и уточняющие диаграммы. После их прочтения данный вопрос у вас будет снят.


      1. Tarakanator
        23.11.2021 18:28

        Тогда очень жду продолжения.


  1. Kiano
    23.11.2021 21:51

    Если получится, вспомните про fasttrack, хочется почитать подробно в Вашем изложении.


  1. net_racoon
    24.11.2021 09:39

    Кстати, а в ROS еще не добавили возможность посмотреть НАТ таблицу?


    1. turbidit
      24.11.2021 16:20

      Что-то вроде такого не подойдет?

      /ip firewall connection print detail where dstnat=yes || srcnat=yes


      1. olegtsss Автор
        24.11.2021 17:39

        Отличная команда, спасибо за совет. С помощью нее можно увидеть результат работы NAT. Кому интересно, ниже пример.
        /ip firewall connection print detail where dstnat=yes || srcnat=yes
        Флаги: SAC s
        protocol=tcp
        src-address=IP_LAN:60644 dst-address=173.194.222.119:443
        reply-src-address=173.194.222.119:443 reply-dst-address=IP_WAN:60644
        tcp-state=established timeout=много счетчики

        S — seen-reply, A — assured, C — confirmed, s — srcnat