Сети для самых маленьких. Часть 10: TCP

В девятой части UDP добавил к IP только порты и ничего больше: пакет либо дошёл, либо нет. Для большинства приложений этого мало. Файл должен прийти целиком, байты — в том порядке, в котором отправлены, и отправитель не должен заваливать получателя и сеть быстрее, чем они справляются. Всё это делает TCP, Transmission Control Protocol, RFC 793 (сейчас — RFC 9293), и делает поверх того же ненадёжного IP, который теряет, дублирует и переставляет пакеты.

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


Что TCP обещает

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

  • перед передачей данных — рукопожатие, лишний круг до сервера и обратно;
  • на каждый сегмент — подтверждение в обратную сторону;
  • границы сообщений не сохраняются: записали два сообщения, можете прочитать одно, полтора или три — поток есть поток;
  • потеря одного пакета задерживает всё, что пришло после него: получатель не отдаст приложению байт 3000, пока не получит байт 2000;
  • ровно две стороны, никакого broadcast.

Заголовок

Двадцать байт обязательных и до сорока байт опций:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
┌───────────────────────────────┬───────────────────────────────┐
│          Source Port          │       Destination Port        │
├───────────────────────────────┴───────────────────────────────┤
│                        Sequence Number                        │
├───────────────────────────────────────────────────────────────┤
│                    Acknowledgment Number                      │
├───────┬───────┬─┬─┬─┬─┬─┬─┬─┬─┼───────────────────────────────┤
│ Data  │ резерв│C│E│U│A│P│R│S│F│            Window             │
│ Offset│       │W│C│R│C│S│S│Y│I│                               │
│       │       │R│E│G│K│H│T│N│N│                               │
├───────┴───────┴─┴─┴─┴─┴─┴─┴─┴─┼───────────────────────────────┤
│           Checksum            │        Urgent Pointer         │
├───────────────────────────────┴───────────────────────────────┤
│                    Options (0–40 байт)                        │
└───────────────────────────────────────────────────────────────┘
  • Порты — как в UDP.
  • Sequence Number — номер первого байта данных этого сегмента в потоке. В TCP пронумерован каждый байт, не сегмент. Это сердце протокола.
  • Acknowledgment Number — «следующий байт, который я жду»: всё до него получено. Действителен при флаге ACK.
  • Data Offset — длина заголовка в 32-битных словах, как IHL в IP; 5 без опций.
  • Флаги: SYN — установление, синхронизация номеров; ACK — поле подтверждения действительно; FIN — отправитель закончил передавать; RST — сброс, «такого соединения нет»; PSH — отдать данные приложению не дожидаясь буфера; URG — срочные данные, практически не используется; ECE, CWR — сигналы перегрузки ECN из шестой части.
  • Window — сколько байт отправитель этого сегмента готов принять. Это управление потоком, раздел ниже.
  • Checksum — по заголовку, данным и псевдозаголовку с адресами IP, как в UDP, но обязательная.
  • Urgent Pointer — для URG.
  • Опции — договариваются при установлении и работают всё соединение: MSS (максимальный размер сегмента), Window Scale (множитель окна), SACK Permitted и SACK (выборочные подтверждения), Timestamps (метки времени). Каждую встретим ниже.

Полезно держать в голове размер: 20 байт IP, 20 байт TCP, обычно ещё 12 байт опции Timestamps в каждом сегменте, итого 52 байта на сегмент до данных.


Установление: три шага

Соединение начинается с рукопожатия, в котором стороны обмениваются начальными номерами последовательности и опциями:

   клиент                                                   сервер (LISTEN)
     │                                                           │
     ├── SYN, seq = x ──────────────────────────────────────────►│  SYN-SENT → …
     │   MSS 1460, SACK ok, Timestamps, WScale 7                 │  … SYN-RECEIVED
     │                                                           │
     │◄── SYN + ACK, seq = y, ack = x + 1 ───────────────────────┤
     │   MSS 1460, SACK ok, Timestamps, WScale 7                 │
     │                                                           │
     ├── ACK, seq = x + 1, ack = y + 1 ─────────────────────────►│  ESTABLISHED
     │   ESTABLISHED                                             │
  1. Клиент шлёт сегмент с флагом SYN и своим начальным номером x. Номер случайный: чтобы старые сегменты от прошлого соединения между теми же портами не были приняты за новые и чтобы его нельзя было угадать со стороны. В опциях — что клиент умеет.
  2. Сервер отвечает SYN + ACK: свой начальный номер y и подтверждение ack = x + 1. Флаг SYN занимает один номер последовательности, хотя не несёт данных, поэтому плюс один.
  3. Клиент подтверждает: ack = y + 1. Соединение установлено с обеих сторон, и уже в этом третьем сегменте могут быть данные.

Почему три, а не два: каждая сторона должна убедиться, что её SYN дошёл (получить ACK на него), и обе стороны передают. Двух сегментов хватило бы только на одно направление.

Что происходит, если порт закрыт: сервер отвечает RST, и клиент мгновенно получает «Connection refused». Если порт закрыт файрволом молча, ответа нет, и клиент повторяет SYN: через 1, 2, 4, 8, 16 и 32 секунды, в Linux шесть попыток, около двух минут до «Connection timed out». Разница между «refused сразу» и «timeout через две минуты» — первый диагностический признак: первое — хост жив и порт закрыт, второе — пакеты не доходят или ответы фильтруются.

На сервере слушающий сокет держит очередь полуоткрытых соединений (получен SYN, ждём третий сегмент). Её можно переполнить потоком SYN с подменёнными адресами — SYN flood, старейшая атака на TCP. Защита, SYN cookies: сервер не хранит состояние, а кодирует его в своём начальном номере y, и восстанавливает по ack = y + 1, когда и если третий сегмент придёт.


Передача данных

После рукопожатия каждая сторона знает номер следующего байта, который ждёт от другой. Отправитель режет поток на сегменты размером до MSS, который объявлен в SYN: обычно MTU минус 40 байт заголовков, 1460 для Ethernet, 1452 для PPPoE. У каждого сегмента в Sequence Number — номер его первого байта; получатель отвечает сегментом с ACK, в котором номер следующего ожидаемого байта.

   A                                                   B
   ├── seq 1, 1448 байт ──────────────────────────────►│  получил 1…1448
   ├── seq 1449, 1448 байт ───────────────────────────►│  получил 1449…2896
   │◄── ack 2897 ──────────────────────────────────────┤  «жду 2897», подтверждены оба
   ├── seq 2897, 1448 байт ───────────────────────────►│

Подтверждения кумулятивные: ack 2897 означает «всё до 2896 включительно получено», и один ACK может подтвердить несколько сегментов. Получатель не обязан отвечать на каждый: правило delayed ACK — подтверждать каждый второй полный сегмент или по таймеру через 40–200 мс, если второй не пришёл. Это уменьшает число пакетов вдвое, но об одном его побочном эффекте — ниже.

Ответ может ехать на попутном сегменте: если у B есть свои данные для A, он поставит ACK и номер подтверждения в тот же сегмент, что несёт его данные. Поэтому в реальном трафике почти в каждом сегменте стоит флаг ACK, и «чистых» подтверждений без данных мало.

Флаг PSH — подсказка получателю отдать накопленное приложению сейчас, не дожидаясь заполнения буфера; его ставят на последнем сегменте каждой записи приложения. В захватах P. на сегменте означает «здесь кончился write()».

Поток, а не сообщения

Приложение написало в сокет «HELLO» и затем «WORLD». TCP может отправить их одним сегментом «HELLOWORLD», двумя, или «HEL» и «LOWORLD», если первый сегмент был обрезан окном. Получатель прочитает то, что успело прийти к моменту read(). Программа, которая предполагает «один read() — одно сообщение», работает на локальной машине и ломается в интернете, когда сегменты приходят по-другому. Все протоколы поверх TCP размечают сообщения сами: длиной в начале (как в TLS, протоколе шифрования, где каждая запись начинается с типа и длины), разделителем (пустая строка между заголовками и телом в HTTP, протоколе, по которому браузер запрашивает страницы) или и тем и другим.


Надёжность: повторы

IP теряет сегменты. TCP узнаёт об этом двумя способами.

По таймеру. Отправив сегмент, отправитель заводит таймер RTO (Retransmission Timeout). Не дождался подтверждения — отправляет заново. RTO вычисляется из измеренного времени оборота RTT: сглаженное среднее плюс четыре сглаженных отклонения. Для первого сегмента, когда измерений ещё нет, RTO — одна секунда; в Linux он не может быть меньше 200 мс и после каждого повтора удваивается (экспоненциальная отсрочка, как в Ethernet). Опция Timestamps кладёт в каждый сегмент время отправки, а ответ возвращает его, и RTT измеряется по каждому сегменту, а не по одному на окно.

По дублирующим подтверждениям. Получатель, получив сегмент не по порядку (пришёл 2897…4344, а ждал 1449), всё равно отвечает, но с прежним ack 1449 — он по-прежнему ждёт 1449. Отправитель, увидев три одинаковых ACK подряд, не ждёт таймера: сегмент 1449 потерян, его надо повторить. Это fast retransmit, и срабатывает он за один RTT вместо RTO.

Без дополнительной информации отправитель после потери одного сегмента не знает, дошли ли следующие, и может повторить всё окно. Опция SACK (Selective Acknowledgment) решает это: получатель в дублирующем ACK перечисляет блоки, которые получил сверх подтверждённого — «жду 1449, но 2897–5792 у меня есть». Отправитель повторяет ровно дыру. SACK включён везде, где TCP моложе 2000 года.


Управление потоком: окно приёма

У получателя буфер конечен: приложение может читать из сокета медленнее, чем приходят данные. Чтобы отправитель не слал лишнего, получатель в каждом сегменте объявляет окно — сколько байт свободно в буфере. Отправитель держит «в полёте» (отправлено, но не подтверждено) не больше, чем окно. Когда приложение читает из буфера, окно растёт, и в следующем ACK получатель об этом сообщает.

Окно 0 — «стой». Отправитель останавливается и раз в несколько секунд шлёт window probe, однобайтовый сегмент, чтобы получатель ответил актуальным окном. В Wireshark такие паузы подписаны TCP ZeroWindow и TCP Window Full, и это означает: тормозит приложение-получатель, не сеть.

Поле Window — 16 бит, максимум 65 535 байт. В 1981 году этого хватало, но подумайте: при 100 Мбит/с и RTT 50 мс, чтобы линия была занята непрерывно, в полёте должно быть 100 000 000 / 8 × 0,05 = 625 000 байт. С окном 64 КБ отправитель будет простаивать 90 % времени, ожидая ACK. Это произведение полосы на задержку, bandwidth-delay product, и ради него появилась опция Window Scale: множитель 2ⁿ, объявленный в SYN, на который умножается поле Window всё соединение. wscale 7 — 128, окно до 8 МБ; максимум n = 14, окно до гигабайта. В tcpdump после рукопожатия показано поле как есть: win 502 при wscale 7 значит 64 256 байт. Современные стеки подбирают размер буфера на лету под измеренный BDP.


Управление перегрузкой: окно отправителя

Окно приёма защищает получателя. А кто защитит сеть — роутер посередине с очередью, которая переполняется, если все отправители шлют на полной скорости? Роутер ничего не объявляет; он просто выбрасывает лишнее. Отправитель должен сам угадать, сколько сеть пропустит, и единственный сигнал, который у него есть, — потери.

Для этого у отправителя есть второе окно, cwnd (congestion window), и реальный лимит данных в полёте — меньшее из двух: min(rwnd, cwnd). Управляет cwnd алгоритм, и классическая его версия (RFC 5681) такая:

 cwnd
  ▲
  │                        потеря (3 dup ACK)
  │                   ╱│                       ╱│
  │                 ╱  │                     ╱  │
  │               ╱    │     ssthresh      ╱    │
  │             ╱      ├──────────────── ╱      │ ───── …
  │           ╱        │               ╱        │
  │         ╱          │             ╱          │
  │       ╱            │           ╱            │
  │     ╱  медленный   │         ╱              │
  │   ╱    старт       │       ╱ избегание      │
  │ ╱   (×2 за RTT)    │     ╱   (+1 за RTT)    │
  ├────────────────────┴─────┴──────────────────┴───────────► время
  1. Медленный старт. cwnd начинается с 10 сегментов (около 14 КБ) и удваивается каждый RTT: за каждый ACK прибавляется сегмент. Название историческое: это не медленно, это экспонента, но по сравнению с «отправить всё сразу» 1980-х — медленно.
  2. Избегание перегрузки. Достигнув порога ssthresh, рост становится линейным: плюс один сегмент за RTT. Осторожно прощупываем, есть ли ещё место.
  3. Потеря по трём дублирующим ACK — сеть переполнена, но работает. ssthresh = cwnd / 2, cwnd тоже вдвое, продолжаем линейно с половины. Это fast recovery.
  4. Потеря по таймеру — сеть молчит, всё плохо. cwnd = 1 сегмент, медленный старт заново до ssthresh.

Получается пила: скорость растёт до первой потери, падает вдвое, снова растёт. Отсюда три следствия, которые видно в жизни:

  • Короткое соединение никогда не выходит на полную скорость. Страница в 100 КБ закончится, пока cwnd ещё в медленном старте. Поэтому браузеры держат соединения открытыми и переиспользуют их.
  • Одна потеря на тысячу пакетов — это не 0,1 % скорости, а в разы. Каждая потеря режет cwnd пополам, и на длинных линках с большим RTT восстановление занимает секунды.
  • Потери от помех TCP не отличает от потерь от перегрузки. Wi-Fi из третьей части повторяет кадры сам, чтобы TCP не видел потерь и не резал скорость; на спутниковых и мобильных линках то же делают промежуточные устройства.

Классический алгоритм давно заменён. В Linux по умолчанию CUBIC: после потери окно растёт по кубической кривой — сначала быстро к прежнему значению, около него осторожно, а затем снова быстро, если потерь нет; на быстрых линках он восстанавливается гораздо быстрее линейного. BBR от Google идёт дальше и вовсе не ждёт потерь: он измеряет доступную полосу и минимальный RTT и держит в полёте ровно их произведение, не заполняя очереди роутеров. С BBR потери перестают быть сигналом, и он лучше чувствует себя на линках с помехами. Алгоритм выбирается отправителем, sysctl net.ipv4.tcp_congestion_control, и стороны не обязаны использовать один и тот же.

Отдельно о bufferbloat: роутеры с большими буферами не выбрасывают пакеты, а копят их, и TCP, не видя потерь, разгоняется до тех пор, пока очередь не вырастет до секунд задержки. Загрузка файла «кладёт» видеозвонок не из-за полосы, а из-за очереди. Решение — на роутере: активное управление очередью (fq_codel), которое выбрасывает или помечает ECN пакеты раньше, чем очередь разрастётся.


Закрытие: четыре шага

Соединение закрывается в каждую сторону отдельно. Сторона, которой больше нечего сказать, шлёт FIN; другая подтверждает и может продолжать передавать, пока не закончит сама:

   клиент                                                   сервер
   ESTABLISHED                                              ESTABLISHED
     ├── FIN, seq = m ───────────────────────────────────────►│
     │   FIN-WAIT-1                                           │  CLOSE-WAIT
     │◄── ACK, ack = m + 1 ───────────────────────────────────┤  (приложение сервера
     │   FIN-WAIT-2                                           │   узнаёт: read() вернул 0)
     │                                                        │
     │             … сервер может ещё передавать …            │
     │                                                        │
     │◄── FIN, seq = n ───────────────────────────────────────┤  LAST-ACK
     ├── ACK, ack = n + 1 ───────────────────────────────────►│  CLOSED
     │   TIME-WAIT, 60 секунд, затем CLOSED                   │

FIN, как и SYN, занимает один номер. Часто второй и третий сегменты объединяются: сервер отвечает FIN + ACK сразу, и получается три сегмента. Состояние CLOSE-WAIT на сервере — «другая сторона закрыла, жду, когда моё приложение вызовет close()»; много соединений в CLOSE-WAIT — это утечка в приложении, которое не закрывает сокеты.

TIME-WAIT — сторона, закрывшая первой, после последнего ACK ещё минуту (в Linux ровно 60 секунд, по стандарту — два максимальных времени жизни сегмента) держит соединение в памяти. Две причины: если последний ACK потерялся, сервер повторит FIN, и нужно кому-то ответить; и чтобы запоздавшие сегменты старого соединения не попали в новое с теми же портами. Побочный эффект: клиент, открывающий тысячи коротких соединений в секунду к одному серверу, исчерпывает временные порты, потому что каждый занят на минуту. Лечат переиспользованием соединений, а не уменьшением таймера.

RST — аварийное закрытие: «соединения нет, забудь». Его шлёт стек в ответ на сегмент для несуществующего соединения, закрытый порт, и приложение при close() с непрочитанными данными. RST не подтверждается и TIME-WAIT не создаёт. Connection reset by peer в логах — это он.


Мелкие сегменты: Nagle и delayed ACK

Приложение пишет в сокет по байту — например, ssh, удалённый терминал, отправляет каждое нажатие клавиши отдельно. Каждый байт в сегменте с 52 байтами заголовков — 2 % полезной нагрузки. Алгоритм Нейгла велит: пока есть неподтверждённый сегмент, копить мелкие записи и отправить их одним сегментом, когда придёт ACK. Для ssh это терпимо, а вот для протокола «запрос — короткий ответ» он сталкивается с delayed ACK: клиент отправил запрос в два write(), второй кусок ждёт ACK на первый по Нейглу, а сервер не шлёт ACK, ожидая второй сегмент по delayed ACK. Оба ждут, пока таймер delayed ACK не истечёт — 40 мс в Linux. Запрос, который должен занимать миллисекунду, занимает сорок. Симптом известный, лечение — TCP_NODELAY на сокете, что делают почти все современные библиотеки по умолчанию, или одна запись вместо двух.

Keepalive: по умолчанию TCP молчит, пока молчат приложения, и соединение без трафика может жить сутками, даже если другая сторона давно выключена. Опция keepalive заставляет стек слать пустые пробы после двух часов тишины (в Linux tcp_keepalive_time = 7200), девять штук через 75 секунд, и закрывать соединение, если ответа нет. Приложения, которым важно узнать об обрыве раньше, уменьшают таймер или шлют свои пинги.


Одно соединение целиком

Захват запроса по HTTPS от начала до конца, tcpdump -i eth0 -n 'tcp port 443'. В SYN и SYN+ACK tcpdump показывает настоящие начальные номера, а после рукопожатия — номера относительно начальных, иначе читать было бы невозможно.

Полный обмен (адреса сокращены: C = 192.168.1.10.51234, S = 203.0.113.10.443):

C > S: Flags [S],  seq 1000000000, win 64240, options [mss 1460,sackOK,TS val 1 ecr 0,nop,wscale 7], length 0
S > C: Flags [S.], seq 2000000000, ack 1000000001, win 65160, options [mss 1460,sackOK,TS val 9 ecr 1,nop,wscale 7], length 0
C > S: Flags [.],  ack 1, win 502, length 0
C > S: Flags [P.], seq 1:518, ack 1, win 502, length 517
S > C: Flags [.],  ack 518, win 507, length 0
S > C: Flags [.],  seq 1:1449, ack 518, win 507, length 1448
S > C: Flags [P.], seq 1449:2897, ack 518, win 507, length 1448
C > S: Flags [.],  ack 2897, win 501, length 0
C > S: Flags [P.], seq 518:644, ack 2897, win 501, length 126
S > C: Flags [P.], seq 2897:3151, ack 644, win 507, length 254
C > S: Flags [.],  ack 3151, win 501, length 0
     … данные …
C > S: Flags [F.], seq 644, ack 3151, win 501, length 0
S > C: Flags [F.], seq 3151, ack 645, win 507, length 0
C > S: Flags [.],  ack 3152, win 501, length 0

Читаем:

1–3. Рукопожатие. В SYN — MSS 1460, SACK разрешён, метки времени, окно с множителем 128. win 64240 в SYN — без множителя, он ещё не действует. Третий сегмент: ack 1 — относительный номер, «жду первый байт».

  1. Клиент шлёт 517 байт (seq 1:518 — байты с 1-го по 517-й) с PSH: это ClientHello, начало TLS. win 502 × 128 = 64 256 байт окна.

  2. Сервер подтверждает: ack 518.

6–7. Сервер шлёт два сегмента по 1448 байт. Не 1460, потому что 12 байт в каждом сегменте заняла опция Timestamps: MSS считается с ними. На первом флага PSH нет, на втором есть: это одна запись сервера на 2896 байт, разрезанная на два сегмента.

  1. Клиент подтверждает оба одним ACK: ack 2897 — delayed ACK в действии.

9–11. Ещё обмен: клиент 126 байт, сервер 254, подтверждение.

12–14. Клиент закончил: FIN с seq 644 (следующий за последним переданным байтом). Сервер отвечает FIN + ACK в одном сегменте, ack 645 — FIN занял номер 644. Клиент подтверждает ack 3152 и уходит в TIME-WAIT.

В Wireshark то же самое ещё нагляднее: Follow TCP Stream собирает поток в текст, Statistics → TCP Stream Graphs → Time/Sequence рисует рост номеров во времени, где ступеньки — ожидание ACK, а плато — потери, и фильтры tcp.analysis.retransmission, tcp.analysis.duplicate_ack, tcp.analysis.zero_window сразу показывают, что пошло не так.


Смотрим руками

Все соединения с состояниями:

$ ss -tan
State      Recv-Q Send-Q  Local Address:Port   Peer Address:Port
LISTEN     0      128           0.0.0.0:22          0.0.0.0:*
ESTAB      0      0        192.168.1.10:51234  203.0.113.10:443
TIME-WAIT  0      0        192.168.1.10:51230  203.0.113.10:443

Recv-Q у установленного соединения — байты, которые пришли, но приложение ещё не прочитало; Send-Q — отправлены, но не подтверждены. Растущий Recv-Q — тормозит ваше приложение; растущий Send-Q — тормозит сеть или удалённая сторона.

Внутренности одного соединения, всё, о чём была статья, одной строкой:

$ ss -tin dst 203.0.113.10
    cubic wscale:7,7 rto:216 rtt:12.3/1.5 mss:1448 cwnd:10 ssthresh:7 bytes_sent:644 bytes_acked:644
    segs_out:8 segs_in:9 send 9.4Mbps lastsnd:120 lastrcv:118 retrans:0/0 rcv_space:64256 minrtt:11.9

Алгоритм CUBIC, множители окна, RTO 216 мс, RTT 12,3 мс, cwnd 10 сегментов, повторов 0. Если retrans растёт — сеть теряет; если cwnd маленький при большом RTT — вот почему скорость низкая.

Счётчики повторов по всей системе: nstat -az | grep -i retrans. И проверка порта без приложения — nc -vz 203.0.113.10 443: «succeeded» значит рукопожатие прошло, «refused» — RST, долгая пауза — SYN уходит в никуда.


Что запомнить

  • TCP нумерует каждый байт; Sequence — номер первого байта сегмента, ACK — следующий ожидаемый, подтверждения кумулятивные. SYN и FIN занимают по номеру.
  • Установление — SYN, SYN+ACK, ACK с обменом начальными номерами и опциями; RST — «порт закрыт» сразу, тишина — повторы SYN две минуты.
  • Надёжность: повтор по таймеру RTO из измеренного RTT и быстрый повтор по трём дублирующим ACK; SACK говорит, какие блоки дошли.
  • Окно приёма защищает получателя (Window Scale — чтобы его хватало на произведение полосы на задержку); окно перегрузки защищает сеть: медленный старт, линейный рост, половина при потере. Потеря — это не процент, это разы.
  • Закрытие — FIN в каждую сторону; TIME-WAIT минуту у закрывшего первым; CLOSE-WAIT в количестве — утечка сокетов.
  • Поток не хранит границ сообщений; Nagle с delayed ACK дают 40 мс задержки; keepalive по умолчанию через два часа.

На этом транспорт закончен. Дальше — служебные протоколы, и первый из них DHCP: как машина без адреса получает адрес, маску, шлюз и DNS за четыре широковещательных пакета, что такое аренда и когда её продлевают, как запрос проходит через роутер в другую сеть и что бывает, когда DHCP-серверов в сети два.

Оставить комментарий могут только зарегистрированные пользователи.

Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.