tee + process substitution: один поток и несколько получателей
Иногда нужно одновременно: увидеть вывод команды в терминале, записать его в файл и передать на обработку другой программе Если делать это по очереди - потеряется поток данных. Здесь помогает связка tee + process substitution.
▪️ Что делает tee. tee дублирует поток:
вывод остается в терминале и одновременно пишется в
▪️ Несколько получателей. tee может писать сразу в несколько файлов:
Но иногда нужно не просто файл, а другую команду.
▪️ Process substitution. Bash позволяет подставить вывод команды как файл:
>(command)
Это называется process substitution.
▪️ Комбинируем
Одна копия идет в терминал, а другая в grep. Если найден ERROR, запись попадет в errors.log.
▪️ Более реальный пример. Допустим, идет сбор логов:
Теперь: полный поток остается в терминале, ошибки автоматически сохраняются
▪️ Можно делать несколько обработчиков
Один поток и сразу несколько фильтров.
BashTex📱 #bash #utils
Иногда нужно одновременно: увидеть вывод команды в терминале, записать его в файл и передать на обработку другой программе Если делать это по очереди - потеряется поток данных. Здесь помогает связка tee + process substitution.
command | tee file.log
вывод остается в терминале и одновременно пишется в
file.logcommand | tee out1.log out2.log
Но иногда нужно не просто файл, а другую команду.
>(command)
Это называется process substitution.
command | tee >(grep ERROR > errors.log)
command генерирует потокtee дублирует егоОдна копия идет в терминал, а другая в grep. Если найден ERROR, запись попадет в errors.log.
journalctl -f | tee >(grep ERROR >> errors.log)
Теперь: полный поток остается в терминале, ошибки автоматически сохраняются
journalctl -f | tee \
>(grep ERROR >> errors.log) \
>(grep WARN >> warn.log)
Один поток и сразу несколько фильтров.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
Telegram
BashTex | Linux
Авторский канал для тех, кто хочет глубже погрузиться в мир Linux.
Подойдет для разработчиков, системных администраторов и DevOps
Реклама: @dad_admin
Подойдет для разработчиков, системных администраторов и DevOps
Реклама: @dad_admin
🔥6👍2
DEBUG trap - как Bash выполняет код перед каждой командой
В Bash есть специальный
Это удобно не только для отладки. Через него можно посмотреть, какие команды реально выполняет скрипт, с какими аргументами и в каком контексте.
▪️ Самый простой пример
Перед каждой командой Bash вызовет trap:
▪️ Можно добавить PID и функцию
Теперь при выполнении функций можно получить что-то вроде:
Это уже превращается в простой трассировщик Bash-скрипта.
▪️ Но
Trap срабатывает перед выполнением простых команд,
Поведение также зависит от того, включено ли наследование trap внутри функций и subshell.
Например:
Чтобы
или:
▪️ У trap есть интересный побочный эффект
Сам код внутри
Для диагностики лучше держать его простым:
А после диагностики обязательно убрать:
▪️ Практический сценарий
Есть большой deployment-скрипт, который запускает десятки функций и команд. Лог показывает только:
Вместо того чтобы расставлять
и получаем последовательность реально выполнявшихся команд.
Важно:
BashTex📱 #bash #utils
В Bash есть специальный
DEBUG trap, который позволяет выполнить собственный код непосредственно перед выполнением команды.Это удобно не только для отладки. Через него можно посмотреть, какие команды реально выполняет скрипт, с какими аргументами и в каком контексте.
trap 'echo "CMD: $BASH_COMMAND"' DEBUG
echo "hello"
mkdir /tmp/test
rm -rf /tmp/test
Перед каждой командой Bash вызовет trap:
CMD: echo "hello"
CMD: mkdir /tmp/test
CMD: rm -rf /tmp/test
$BASH_COMMAND содержит команду, которую Bash собирается выполнить.trap 'printf "[%s] %s: %s\n" "$$" "${FUNCNAME[1]:-main}" "$BASH_COMMAND"' DEBUGТеперь при выполнении функций можно получить что-то вроде:
[4217] main: prepare
[4217] deploy: mkdir -p /srv/app
[4217] deploy: cp app.conf /srv/app/
Это уже превращается в простой трассировщик Bash-скрипта.
DEBUG не означает буквально «перед каждой строкой»Trap срабатывает перед выполнением простых команд,
for, case, некоторых условных конструкций и других элементов shell execution.Поведение также зависит от того, включено ли наследование trap внутри функций и subshell.
Например:
trap 'echo "DEBUG: $BASH_COMMAND"' DEBUG
foo() {
echo "inside"
}
foo
Чтобы
DEBUG распространялся внутрь функций, часто используют:set -T
или:
shopt -s functrace
Сам код внутри
DEBUG тоже выполняется Bash, поэтому слишком сложный обработчик может сам стать источником неожиданных эффектов.Для диагностики лучше держать его простым:
trap 'printf "%s\n" "$BASH_COMMAND" >> /tmp/bash-debug.log' DEBUG
А после диагностики обязательно убрать:
trap - DEBUG
Есть большой deployment-скрипт, который запускает десятки функций и команд. Лог показывает только:
deployment failed
Вместо того чтобы расставлять
echo по всему скрипту, временно включаем:trap 'printf "[%s] %s\n" "$SECONDS" "$BASH_COMMAND"' DEBUG
и получаем последовательность реально выполнявшихся команд.
Важно:
DEBUG предназначен прежде всего для трассировки. Для полноценного production-аудита он неудобен: trap может влиять на поведение скрипта и генерировать очень много вывода.BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
flock vs fcntl - почему две блокировки одного файла могут вести себя совершенно по-разному
В Linux есть несколько механизмов блокировки файлов. Наиболее часто встречаются
На первый взгляд они делают одно и то же: не дают нескольким процессам одновременно работать с одним файлом.
Но внутри ядра это разные механизмы, поэтому блокировки могут вообще не замечать друг друга.
▪️
Простейший вариант:
Пока первый процесс держит блокировку, второй:
получит ошибку и сразу завершится из-за
Часто этот механизм используют для защиты cron-задач:
▪️
Здесь можно блокировать не только весь файл, но и конкретный диапазон байтов.
На уровне C:
Можно заблокировать первые 100 байт, а другой процесс при этом сможет работать с другим диапазоном.
▪️ Главная ловушка
Процесс A:
Процесс B использует
Они не обязаны конфликтовать.
Причина в том, что
То есть наличие:
не означает автоматически:
И наоборот.
▪️ Обе блокировки advisory
Ни
Если приложение вообще не проверяет блокировку, оно может спокойно сделать:
Поэтому механизм работает только тогда, когда все участники системы договариваются его соблюдать.
▪️ Есть ещё одна важная разница
Для
У
Из-за этого без понимания модели владения FD легко получить ситуацию, когда один
▪️ Практический вывод
Если нужно просто гарантировать, что cron или несколько экземпляров shell-скрипта не выполняются одновременно:
обычно достаточно.
Если приложение должно блокировать отдельные диапазоны файла или ему нужна POSIX-модель блокировок, используют
И главное: нельзя считать
BashTex📱 #bash #utils
В Linux есть несколько механизмов блокировки файлов. Наиболее часто встречаются
flock() и fcntl().На первый взгляд они делают одно и то же: не дают нескольким процессам одновременно работать с одним файлом.
Но внутри ядра это разные механизмы, поэтому блокировки могут вообще не замечать друг друга.
flock блокирует сам файлПростейший вариант:
flock /tmp/app.lock -c 'echo "working"; sleep 10'
Пока первый процесс держит блокировку, второй:
flock -n /tmp/app.lock -c 'echo "working"'
получит ошибку и сразу завершится из-за
-n.Часто этот механизм используют для защиты cron-задач:
flock -n /run/myjob.lock /usr/local/bin/myjob
fcntl работает через диапазоны файлаЗдесь можно блокировать не только весь файл, но и конкретный диапазон байтов.
На уровне C:
struct flock lock = {
.l_type = F_WRLCK,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 100
};
fcntl(fd, F_SETLK, &lock);Можно заблокировать первые 100 байт, а другой процесс при этом сможет работать с другим диапазоном.
Процесс A:
flock /tmp/test.lock
Процесс B использует
fcntl() для того же файла.Они не обязаны конфликтовать.
Причина в том, что
flock и традиционные POSIX advisory locks через fcntl исторически используют разные пространства блокировок.То есть наличие:
flock → locked
не означает автоматически:
fcntl → blocked
И наоборот.
Ни
flock, ни fcntl сами по себе не запрещают процессу открыть и изменить файл.Если приложение вообще не проверяет блокировку, оно может спокойно сделать:
echo "data" >> /tmp/file
Поэтому механизм работает только тогда, когда все участники системы договариваются его соблюдать.
Для
flock блокировка связана с открытым file description. При fork() дочерний процесс наследует соответствующую блокировку.У
fcntl семантика другая: POSIX locks связаны с процессом и имеют собственные правила поведения при close().Из-за этого без понимания модели владения FD легко получить ситуацию, когда один
close() неожиданно освобождает блокировку.Если нужно просто гарантировать, что cron или несколько экземпляров shell-скрипта не выполняются одновременно:
flock -n /run/myjob.lock /usr/local/bin/myjob
обычно достаточно.
Если приложение должно блокировать отдельные диапазоны файла или ему нужна POSIX-модель блокировок, используют
fcntl().И главное: нельзя считать
flock и fcntl взаимозаменяемыми только потому, что оба называются file locking.BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
madvise() - как процесс подсказывает ядру, как он собирается использовать память
Когда процесс работает с большой областью памяти, ядро не всегда знает, как именно приложение будет обращаться к этим страницам.
Linux предоставляет
Это особенно интересно для
▪️ Последовательный доступ
Если программа собирается читать память последовательно:
ядро получает подсказку, что страницы будут использоваться примерно по порядку.
Это позволяет иначе управлять page cache и упреждающим чтением.
Например, обработчик большого файла может пройти по нему от начала до конца вместо случайных обращений.
▪️ Случайный доступ
Для случайного доступа есть:
Если приложение читает страницы в произвольном порядке, агрессивное read-ahead может оказаться бесполезным.
Подсказка позволяет ядру учитывать такой сценарий.
▪️ Можно сообщить, что страницы больше не нужны
Особенно интересен:
Процесс говорит ядру, что содержимое этой области ему сейчас не требуется.
Для анонимной памяти это может позволить освободить физические страницы. Для файлового отображения поведение связано с отображёнными страницами и page cache.
Важно: это не то же самое, что
Виртуальный адрес всё ещё может оставаться отображённым, но физические страницы могут быть освобождены и восстановлены при следующем обращении.
▪️ Есть и подсказка
Она сообщает ядру, что страницы, вероятно, скоро понадобятся.
Это может помочь подготовить данные заранее, например перед обработкой большого memory-mapped файла.
Но
▪️ Практический сценарий
Представим программу, которая через
Вместо случайного поведения с page cache она может сделать:
Теперь приложение явно сообщает ядру характер будущего доступа.
А когда большой участок больше не нужен:
Это может уменьшить давление на память, не требуя немедленно уничтожать само отображение.
▪️ Важный нюанс
Эффект зависит от конкретного флага, типа mapping, версии ядра и сценария доступа.
И особенно важно не путать
То есть
BashTex📱 #bash #utils
Когда процесс работает с большой областью памяти, ядро не всегда знает, как именно приложение будет обращаться к этим страницам.
Linux предоставляет
madvise() - системный вызов, через который процесс может сообщить ядру предполагаемый характер использования памяти.Это особенно интересно для
mmap() и больших memory-mapped файлов.Если программа собирается читать память последовательно:
madvise(addr, length, MADV_SEQUENTIAL);
ядро получает подсказку, что страницы будут использоваться примерно по порядку.
Это позволяет иначе управлять page cache и упреждающим чтением.
Например, обработчик большого файла может пройти по нему от начала до конца вместо случайных обращений.
Для случайного доступа есть:
madvise(addr, length, MADV_RANDOM);
Если приложение читает страницы в произвольном порядке, агрессивное read-ahead может оказаться бесполезным.
Подсказка позволяет ядру учитывать такой сценарий.
Особенно интересен:
madvise(addr, length, MADV_DONTNEED);
Процесс говорит ядру, что содержимое этой области ему сейчас не требуется.
Для анонимной памяти это может позволить освободить физические страницы. Для файлового отображения поведение связано с отображёнными страницами и page cache.
Важно: это не то же самое, что
free().Виртуальный адрес всё ещё может оставаться отображённым, но физические страницы могут быть освобождены и восстановлены при следующем обращении.
WILLNEEDmadvise(addr, length, MADV_WILLNEED);
Она сообщает ядру, что страницы, вероятно, скоро понадобятся.
Это может помочь подготовить данные заранее, например перед обработкой большого memory-mapped файла.
Но
madvise() именно подсказывает, а не заставляет ядро выполнить конкретную стратегию.Представим программу, которая через
mmap() обрабатывает несколько гигабайт файла строго последовательно.Вместо случайного поведения с page cache она может сделать:
void *p = mmap(NULL, size,
PROT_READ,
MAP_PRIVATE,
fd, 0);
madvise(p, size, MADV_SEQUENTIAL);
Теперь приложение явно сообщает ядру характер будущего доступа.
А когда большой участок больше не нужен:
madvise(p, size, MADV_DONTNEED);
Это может уменьшить давление на память, не требуя немедленно уничтожать само отображение.
madvise() не является универсальной кнопкой «ускорить память».Эффект зависит от конкретного флага, типа mapping, версии ядра и сценария доступа.
И особенно важно не путать
MADV_DONTNEED с гарантированным физическим освобождением каждой страницы: это рекомендация ядру о том, что содержимое региона можно считать ненужным.То есть
madvise() - это интерфейс, через который userspace сообщает kernel memory manager: «я примерно знаю, что собираюсь делать с этой памятью».BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥1
name_to_handle_at() - как получить файловый объект без обычного пути
В Linux обычно обращаются к файлу через путь:
Но ядро умеет работать с файловым объектом иначе. Системный вызов
Это используется низкоуровневыми инструментами, которым нужно сохранить ссылку на объект, а не его текстовый путь.
▪️ Получаем handle
Сам системный вызов выглядит примерно так:
Например, программа может передать:
и получить структуру с handle и идентификатором mount.
Важно: это не файловый дескриптор. Handle не позволяет просто сделать
▪️ Зачем тогда он нужен
Основная идея - получить устойчивое представление объекта внутри filesystem, которое затем можно использовать для повторного обращения к нему.
Для этого существует парный системный вызов:
Схема получается такой:
То есть сначала filesystem выдаёт идентификатор объекта, а позже по нему можно снова получить FD.
▪️ Почему это отличается от обычного пути
Представим:
Путь зависит от directory entries. Файл могут переименовать:
Сам inode при этом остаётся тем же объектом.
File handle предназначен именно для работы с объектом filesystem, а не с конкретным именем в каталоге.
▪️ Но есть важное ограничение
Handle не является универсальным идентификатором любого файла на всех filesystem.
Его формат зависит от filesystem, а поддержка механизма должна быть реализована самой filesystem.
Кроме того,
▪️ Где это реально встречается
Механизм особенно интересен для низкоуровневых инструментов резервного копирования, мониторинга и работы с filesystem, где обычный путь может быть неудобен или недостаточно надёжен.
И главное:
Он просит конкретную filesystem предоставить handle для объекта, а затем этот handle может быть использован через соответствующий kernel API.
BashTex📱 #bash #utils
В Linux обычно обращаются к файлу через путь:
/etc/hosts
Но ядро умеет работать с файловым объектом иначе. Системный вызов
name_to_handle_at() позволяет получить специальный file handle, связанный с объектом filesystem.Это используется низкоуровневыми инструментами, которым нужно сохранить ссылку на объект, а не его текстовый путь.
Сам системный вызов выглядит примерно так:
int name_to_handle_at(
int dirfd,
const char *path,
struct file_handle *handle,
int *mount_id,
int flags
);
Например, программа может передать:
/etc/hosts
и получить структуру с handle и идентификатором mount.
Важно: это не файловый дескриптор. Handle не позволяет просто сделать
read().Основная идея - получить устойчивое представление объекта внутри filesystem, которое затем можно использовать для повторного обращения к нему.
Для этого существует парный системный вызов:
open_by_handle_at()
Схема получается такой:
path
↓
name_to_handle_at()
↓
file handle + mount ID
↓
open_by_handle_at()
↓
file descriptor
То есть сначала filesystem выдаёт идентификатор объекта, а позже по нему можно снова получить FD.
Представим:
/var/data/report
Путь зависит от directory entries. Файл могут переименовать:
mv /var/data/report /var/archive/report
Сам inode при этом остаётся тем же объектом.
File handle предназначен именно для работы с объектом filesystem, а не с конкретным именем в каталоге.
Handle не является универсальным идентификатором любого файла на всех filesystem.
Его формат зависит от filesystem, а поддержка механизма должна быть реализована самой filesystem.
Кроме того,
open_by_handle_at() требует соответствующих привилегий. Обычное приложение не получает возможность произвольно открывать объекты по filesystem handles.Механизм особенно интересен для низкоуровневых инструментов резервного копирования, мониторинга и работы с filesystem, где обычный путь может быть неудобен или недостаточно надёжен.
И главное:
name_to_handle_at() не «обходит filesystem по inode».Он просит конкретную filesystem предоставить handle для объекта, а затем этот handle может быть использован через соответствующий kernel API.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Ephemeral ports - почему заканчиваются исходящие TCP-порты
Когда приложение устанавливает исходящее TCP-соединение, порт выбирается автоматически. Это ephemeral port, временный исходный порт клиента.
Например:
Если приложение создаёт тысячи соединений, диапазон таких портов может стать узким местом.
▪️ Посмотреть доступный диапазон
В Linux его задаёт:
Например:
Это около 28 тысяч портов для исходящих соединений с одного локального IP.
▪️ Но количество соединений не всегда ограничено этим числом
TCP-соединение определяется не одним портом, а набором:
То есть:
это разные соединения.
Но тот же локальный порт теоретически может использоваться снова, если меняется destination tuple.
Поэтому несколько destination IP/портов значительно расширяют пространство комбинаций.
▪️ Как увидеть исходящие соединения
А чтобы посмотреть локальные порты:
Если приложение создаёт огромное количество короткоживущих TCP-соединений, дополнительно стоит посмотреть
▪️ Почему
После закрытия TCP-соединения локальный порт не всегда сразу можно использовать для нового соединения.
Это нужно для защиты TCP от старых сегментов предыдущего соединения.
При большом количестве коротких запросов можно получить ситуацию:
В итоге новое
▪️ Практический сценарий
Проблема часто появляется у прокси, API-клиентов, NAT-шлюзов и сервисов, которые вместо переиспользования соединений постоянно создают новые.
Поэтому сначала стоит проверить:
А уже потом решать проблему: расширять диапазон, использовать connection pooling или уменьшать количество коротких TCP-соединений.
Важно: увеличение
BashTex📱 #bash #utils
Когда приложение устанавливает исходящее TCP-соединение, порт выбирается автоматически. Это ephemeral port, временный исходный порт клиента.
Например:
10.0.0.10:49152 → 142.250.74.14:443
Если приложение создаёт тысячи соединений, диапазон таких портов может стать узким местом.
В Linux его задаёт:
cat /proc/sys/net/ipv4/ip_local_port_range
Например:
32768 60999
Это около 28 тысяч портов для исходящих соединений с одного локального IP.
TCP-соединение определяется не одним портом, а набором:
source IP
source port
destination IP
destination port
protocol
То есть:
10.0.0.10:50000 → 1.1.1.1:443
10.0.0.10:50001 → 1.1.1.1:443
это разные соединения.
Но тот же локальный порт теоретически может использоваться снова, если меняется destination tuple.
Поэтому несколько destination IP/портов значительно расширяют пространство комбинаций.
ss -tan state established
А чтобы посмотреть локальные порты:
ss -tan | awk 'NR>1 {print $4}' | sort | uniq -c | sort -nr | headЕсли приложение создаёт огромное количество короткоживущих TCP-соединений, дополнительно стоит посмотреть
TIME-WAIT:ss -tan state time-wait | wc -l
TIME_WAIT особенно неприятенПосле закрытия TCP-соединения локальный порт не всегда сразу можно использовать для нового соединения.
Это нужно для защиты TCP от старых сегментов предыдущего соединения.
При большом количестве коротких запросов можно получить ситуацию:
requests
↓
тысячи TCP connections
↓
close()
↓
TIME_WAIT
↓
исчерпание доступных комбинаций
В итоге новое
connect() начинает получать ошибки вроде:Cannot assign requested address
Проблема часто появляется у прокси, API-клиентов, NAT-шлюзов и сервисов, которые вместо переиспользования соединений постоянно создают новые.
Поэтому сначала стоит проверить:
ss -s
cat /proc/sys/net/ipv4/ip_local_port_range
ss -tan state time-wait | wc -l
А уже потом решать проблему: расширять диапазон, использовать connection pooling или уменьшать количество коротких TCP-соединений.
Важно: увеличение
ip_local_port_range не создаёт новые IP-адреса и не отменяет остальные ограничения TCP.BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
/dev/tcp - сетевое соединение средствами Bash
В Bash есть малоизвестная возможность работать с TCP через псевдоустройства
Никакого
▪️ Проверить, доступен ли TCP-порт
Bash сам пытается выполнить TCP
▪️ Можно получить обычный файловый дескриптор
Теперь FD
То есть сетевой сокет можно использовать почти как файл.
▪️ Почему это удобно в скриптах
Например, простая проверка нескольких сервисов:
Это удобно для минимальных recovery/health-check скриптов, когда не хочется зависеть от
▪️ Но есть важный нюанс
Поэтому:
не покажет какой-то TCP-интерфейс.
И работает это именно как возможность Bash, а не универсальная функция любого
Например, в Bash:
а поведение другого shell может быть совершенно другим.
BashTex📱 #bash #TCP
В Bash есть малоизвестная возможность работать с TCP через псевдоустройства
/dev/tcp.Никакого
nc, telnet или отдельного сетевого клиента не требуется.if (echo > /dev/tcp/127.0.0.1/5432) 2>/dev/null; then
echo "порт открыт"
else
echo "соединение не установлено"
fi
Bash сам пытается выполнить TCP
connect() к указанному адресу.exec 3<>/dev/tcp/example.com/80
Теперь FD
3 связан с TCP-соединением:printf 'GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' >&3
cat <&3
exec 3>&-
То есть сетевой сокет можно использовать почти как файл.
Например, простая проверка нескольких сервисов:
for port in 22 80 443 8080; do
if (echo > /dev/tcp/127.0.0.1/$port) 2>/dev/null; then
echo "$port: open"
else
echo "$port: closed"
fi
done
Это удобно для минимальных recovery/health-check скриптов, когда не хочется зависеть от
nc./dev/tcp - не настоящий файл устройства. Bash распознаёт специальный путь и превращает обращение к нему в сетевой системный вызов.Поэтому:
ls -l /dev/tcp
не покажет какой-то TCP-интерфейс.
И работает это именно как возможность Bash, а не универсальная функция любого
/bin/sh.Например, в Bash:
bash -c 'echo > /dev/tcp/127.0.0.1/22'
а поведение другого shell может быть совершенно другим.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Bridge FDB - как Linux bridge узнаёт, где находится MAC
Linux bridge не знает заранее, за каким интерфейсом находится конкретный MAC-адрес.
Он изучает это динамически, наблюдая за входящими кадрами.
Для этого используется FDB - Forwarding Database.
▪️ Сначала bridge учится
Допустим:
PC-A отправляет кадр с:
Bridge видит, что этот MAC пришёл через
он не может выбрать конкретный порт.
Поэтому кадр отправляется через все подходящие порты, кроме того, откуда он пришёл.
Это unknown unicast flooding.
Если MAC назначения уже есть в FDB:
bridge отправляет кадр только через eth1.
Получается:
▪️Запись не вечная
FDB содержит динамические записи, и они имеют lifetime.
Посмотреть подробнее:
Удалить конкретную запись можно, например:
После этого bridge снова должен будет изучить расположение MAC.
▪️Почему это важно
Если FDB постоянно заполняется, очищается или содержит неожиданные MAC, это уже может быть полезным диагностическим сигналом.
Например:
может показать, какой MAC bridge сейчас связывает с каждым портом.
А если один MAC начинает «прыгать» между интерфейсами, появляется MAC flapping. Такое бывает при петлях, неправильной коммутации или некоторых схемах виртуализации.
FDB - одна из причин, почему обычный Ethernet-коммутатор не рассылает каждый кадр на все порты: после обучения он знает, куда отправить unicast напрямую.
BashTex📱 #bash #linux
Linux bridge не знает заранее, за каким интерфейсом находится конкретный MAC-адрес.
Он изучает это динамически, наблюдая за входящими кадрами.
Для этого используется FDB - Forwarding Database.
Допустим:
eth0 ── PC-A
eth1 ── PC-B
PC-A отправляет кадр с:
src MAC = aa:aa:aa:aa:aa:aa
Bridge видит, что этот MAC пришёл через
eth0, и запоминает:aa:aa:aa:aa:aa:aa → eth0
Посмотреть таблицу:
bridge fdb show
▪️Что происходит с первым кадром
Если bridge ещё не знает MAC назначения:
PC-A → bridge → неизвестный MAC
он не может выбрать конкретный порт.
Поэтому кадр отправляется через все подходящие порты, кроме того, откуда он пришёл.
Это unknown unicast flooding.
Если MAC назначения уже есть в FDB:
aa:aa:aa:aa:aa:aa → eth1
bridge отправляет кадр только через eth1.
Получается:
unknown MAC
↓
flooding
↓
bridge learns MAC
↓
known MAC
↓
unicast
▪️Запись не вечная
FDB содержит динамические записи, и они имеют lifetime.
Посмотреть подробнее:
bridge -d fdb show
Удалить конкретную запись можно, например:
bridge fdb del aa:aa:aa:aa:aa:aa dev eth0
После этого bridge снова должен будет изучить расположение MAC.
▪️Почему это важно
Если FDB постоянно заполняется, очищается или содержит неожиданные MAC, это уже может быть полезным диагностическим сигналом.
Например:
bridge fdb show br br0
может показать, какой MAC bridge сейчас связывает с каждым портом.
А если один MAC начинает «прыгать» между интерфейсами, появляется MAC flapping. Такое бывает при петлях, неправильной коммутации или некоторых схемах виртуализации.
FDB - одна из причин, почему обычный Ethernet-коммутатор не рассылает каждый кадр на все порты: после обучения он знает, куда отправить unicast напрямую.
BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
xargs против while read: где быстрее и безопаснееОбе конструкции позволяют обработать список файлов или строк, но работают они по-разному. Из-за этого одна может быть заметно быстрее, а другая - безопаснее.
xargsДопустим, нужно удалить все
.log-файлы:find . -name "*.log" | xargs rm
xargs собирает несколько аргументов и передаёт их одной команде, поэтому вместо сотен запусков rm будет всего несколько.Для большого количества файлов разница в производительности может быть существенной.
По умолчанию
xargs разделяет вход по пробелам и переводам строки.Если встретится файл:
my file.log
или
backup
2025.log
команда отработает некорректно.
Безопасный вариант:
find . -name "*.log" -print0 | xargs -0 rm
Здесь разделителем становится символ
NULL, поэтому пробелы и спецсимволы больше не проблема.while readЕсли над каждой строкой нужно выполнить несколько действий:
find . -name "*.log" -print0 |
while IFS= read -r -d '' file; do
echo "Deleting: $file"
rm "$file"
done
Такой код легче расширять: добавить проверки, логирование, условия или обработку ошибок.
Для простого запуска одной команды обычно выигрывает
xargs.Для сложной логики, где каждая строка проходит несколько этапов обработки, удобнее использовать
while read.Многие используют
xargs и while read как взаимозаменяемые инструменты. На практике выбор зависит не только от скорости, но и от формата входных данных. Если есть вероятность встретить пробелы, переносы строк или необычные символы в именах файлов, безопасные варианты - xargs -0 или while read -d ''.BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥2
openat2(): как Linux защищает от выхода за пределы каталога
Допустим, приложение должно читать файлы только из
На первый взгляд достаточно проверить путь. Но если внутри каталога есть символические ссылки, всё становится сложнее.
▪️ В чём проблема
Например, пользователь передаёт:
Приложение проверяет, что путь находится внутри
Но между проверкой и открытием файла кто-то может заменить один из каталогов символической ссылкой на
Получается классическая TOCTOU-проблема: проверили один объект, а открыли уже другой.
▪️ Что меняет openat2()
В Linux есть системный вызов
Например:
Здесь два важных ограничения:
▪️ Как открыть файл
Сначала приложение получает дескриптор каталога:
Затем открывает файл относительно него:
Теперь ядро проверяет ограничения непосредственно при разрешении пути.
Если один из компонентов окажется символической ссылкой, операция завершится ошибкой.
▪️ А если символические ссылки нужны?
Полностью запрещать их необязательно.
Можно использовать:
В этом режиме указанный каталог становится временным корнем для разрешения пути. Даже абсолютные пути и переходы через
Это полезно, когда приложению нужно работать с деревом файлов, не позволяя обращаться к объектам за его пределами.
▪️ Почему это важно
Обычная проверка строки пути не гарантирует безопасность: файловая система может измениться между проверкой и открытием.
BashTex📱 #bash #linux
Допустим, приложение должно читать файлы только из
/srv/uploads. Пользователь передаёт имя файла, а программа открывает его.На первый взгляд достаточно проверить путь. Но если внутри каталога есть символические ссылки, всё становится сложнее.
Например, пользователь передаёт:
documents/report.pdf
Приложение проверяет, что путь находится внутри
/srv/uploads, а затем вызывает open().Но между проверкой и открытием файла кто-то может заменить один из каталогов символической ссылкой на
/etc.Получается классическая TOCTOU-проблема: проверили один объект, а открыли уже другой.
В Linux есть системный вызов
openat2(), который позволяет задать ограничения прямо во время разрешения пути.Например:
struct open_how how = {
.flags = O_RDONLY,
.resolve = RESOLVE_BENEATH |
RESOLVE_NO_SYMLINKS
};Здесь два важных ограничения:
RESOLVE_BENEATH - запрещает разрешение пути за пределами указанного каталога.RESOLVE_NO_SYMLINKS - запрещает проходить через символические ссылки в любом компоненте пути.Сначала приложение получает дескриптор каталога:
int dirfd = open("/srv/uploads",
O_PATH | O_DIRECTORY);Затем открывает файл относительно него:
int fd = syscall(SYS_openat2,
dirfd,
"documents/report.pdf",
&how,
sizeof(how));
Теперь ядро проверяет ограничения непосредственно при разрешении пути.
Если один из компонентов окажется символической ссылкой, операция завершится ошибкой.
Полностью запрещать их необязательно.
Можно использовать:
.resolve = RESOLVE_IN_ROOT
В этом режиме указанный каталог становится временным корнем для разрешения пути. Даже абсолютные пути и переходы через
.. интерпретируются относительно него.Это полезно, когда приложению нужно работать с деревом файлов, не позволяя обращаться к объектам за его пределами.
Обычная проверка строки пути не гарантирует безопасность: файловая система может измениться между проверкой и открытием.
openat2() позволяет перенести ограничения на уровень ядра и избежать многих ошибок при работе с недоверенными путями. При этом это не полноценная песочница: ограничения действуют на конкретное открытие файла, а не на все действия процесса.BashTex
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3