Если вы когда-нибудь подключали Wireshark к сегменту шины процесса реальной цифровой подстанции, то картина вам знакома: через некоторое время после старта захвата Wireshark подвисает и это известная проблема. Особенно остро эта проблема проявляется на подстанциях, где есть трафик Sampled Values.

Причина прозаична: один поток Sampled Values с частотой 4000 кадров в секунду (80 выборок за период, 50 Гц) даёт серьёзную нагрузку. В реальном сегменте шины процесса таких потоков десятки — плюс GOOSE, плюс PTP, плюс широковещательный и служебный трафик. Wireshark не столько «не справляется» с анализом — он не успевает записывать и отображать все эти пакеты в реальном времени.

В вики Wireshark на этот счёт есть прямая рекомендация:

Если вас интересуют не все пакеты, фильтр захвата, отбирающий только нужные вам пакеты, может сократить общее время обработки. Это связано с тем, что пакеты могут отбрасываться фильтром захвата ещё до записи в файл захвата, а в системах с фильтрацией захвата на уровне ядра — ещё до того, как они будут скопированы из ядра в Wireshark.

Ключевая мысль — фильтр захвата отсекает пакеты до того, как они попадут в Wireshark. В отличие от фильтра отображения, который работает уже с записанным трафиком. Поэтому если вы заранее знаете, что именно ищете, фильтр захвата — ваш лучший друг.

Фильтр захвата vs Фильтр отображения: не путайте их!

В Wireshark есть два типа фильтров, которые часто путают. Разница принципиальна:

Параметр Capture Filter Display Filter
Когда применяется До записи пакета После записи пакета
Можно ли менять на лету Нет (требует остановки захвата) Да, в реальном времени
Синтаксис BPF (как в tcpdump) Собственный синтаксис Wireshark
Влияние на производительность Снижает нагрузку на диск и память На захват не влияет
Что делает с данными Безвозвратно отсекает Просто скрывает, не удаляет

Фильтр захвата решает, что попадёт в вашу «коллекцию» данных, а фильтр отображения — что вы будете разглядывать в уже собранной коллекции.

Для задач типа «посмотреть, а что там в сети» на нагруженном сегменте сети без фильтра захвата обойтись почти невозможно.

Как применить фильтр захвата в Wireshark

Есть три стандартных способа:

Способ 1. Стартовый экран. На стартовом экране перед выбором сетевого интерфейса введите выражение для фильтра захвата. Capture Filter Start Screen.png

Способ 2. Меню Capture. Capture → Options → поле Capture Filter for selected interfaces напротив выбранного интерфейса. Capture Options Menu.png

Есть еще способы, но этих двух достаточно для начала :)

Если в фильтре есть синтаксическая ошибка, Wireshark подсветит поле красным и не даст запустить захват. Зелёный цвет означает, что фильтр синтаксически валиден — но это не гарантия, что он делает то, что вы задумали. Проверять всё равно придётся.

Несколько особенностей BPF, о которых нужно помнить

Выражения для фильтров захвата пишутся на языке BPF — том же самом, что и в tcpdump. Синтаксис компактный, но при работе с подстанционным трафиком есть пара неочевидных моментов.

VLAN ломает смещения

Ключевое слово vlan в BPF-фильтре — это не простой предикат «есть VLAN-тег». Это директива, которая сдвигает все последующие смещения в фильтре так, как будто VLAN-тег отсутствует. То есть ether proto 0x88b8 до директивы vlan обеспечивает захват нетегированных кадров, а после — тегированных.

Классическая ситуация: вам нужно поймать весь GOOSE-трафик, и часть кадров идёт с VLAN-тегом, часть — без. Интуитивный вариант

(vlan and ether proto 0x88b8) or ether proto 0x88b8

работает не так, как ожидается. Нетегированный трафик в него не попадает, потому что после vlan смещения уже сдвинуты, и вторая ветка ищет EtherType в неправильном месте.

Правильный порядок — сначала нетегированный случай, потом тегированный:

ether proto 0x88b8 or (vlan and ether proto 0x88b8)

Универсальное правило: все выражения без VLAN должны идти до первого упоминания vlan в фильтре.

Частичный MAC-адрес

Конструкция ether host xx:xx:xx:xx:xx:xx требует полный MAC. Но если вам нужно отловить, например, трафик от всех устройств производителя с OUI 00:0C:22, то приходится писать byte-offset фильтр:

(ether[0:4] & 0xffffff00 = 0x000c2200) or (ether[6:4] & 0xffffff00 = 0x000c2200)

Здесь ether[0:4] — четыре байта начиная со смещения 0 (поле MAC назначения), ether[6:4] — четыре байта начиная со смещения 6 (поле MAC источника). Оператор : в BPF принимает размер только 1, 2 или 4 — трёх байт выбрать нельзя, поэтому мы берём четыре и маской 0xffffff00 обнуляем лишний четвёртый.

Альтернативно можно сравнивать побайтно — длиннее, но нагляднее:

(ether[0]=0x00 and ether[1]=0x0c and ether[2]=0x22) or (ether[6]=0x00 and ether[7]=0x0c and ether[8]=0x22)

Шпаргалка: фильтры захвата для МЭК 61850

Теперь то, ради чего всё затевалось. Ниже — готовые рецепты для типовых задач на цифровой подстанции.

GOOSE

Весь GOOSE (без VLAN):

ether proto 0x88b8

Весь GOOSE с учётом VLAN-тегированных кадров:

ether proto 0x88b8 or (vlan and ether proto 0x88b8)

GOOSE от конкретного IED (фильтр по MAC-источника):

ether proto 0x88b8 and ether src 00:0c:22:12:34:56

GOOSE на конкретный мультикастный адрес:

ether proto 0x88b8 and ether dst 01:0c:cd:01:00:01

GOOSE от всех устройств одного производителя (по OUI, например 00:0C:22):

ether proto 0x88b8 and (ether[6:4] & 0xffffff00 = 0x000c2200)

GOOSE с конкретным APPID (например, 0x1000). APPID — это два байта сразу после EtherType, смещение 14–15 в нетегированном кадре:

ether proto 0x88b8 and ether[14:2] = 0x1000

С учётом VLAN:

(ether proto 0x88b8 and ether[14:2] = 0x1000) or (vlan and ether proto 0x88b8 and ether[14:2] = 0x1000)

Диапазон GOOSE APPID (например, от 0x1000 до 0x10FF):

ether proto 0x88b8 and ether[14:2] & 0xff00 = 0x1000

Мультикаст GOOSE по диапазону назначения:

ether proto 0x88b8 and (ether[0:4] & 0xffffff00 = 0x010ccd00) and (ether[4:2] & 0xff00 = 0x0100)

Sampled Values

Все SV (без VLAN):

ether proto 0x88ba

Все SV с учётом VLAN:

ether proto 0x88ba or (vlan and ether proto 0x88ba)

SV от конкретного MU (по MAC-источника):

ether proto 0x88ba and ether src 00:0c:22:aa:bb:cc

SV на конкретный мультикастный адрес:

ether proto 0x88ba and ether dst 01:0c:cd:04:00:01

SV с конкретным APPID (например, 0x4000):

ether proto 0x88ba and ether[14:2] = 0x4000

SV от всех MU одного производителя (по OUI):

ether proto 0x88ba and (ether[6:4] & 0xffffff00 = 0x000c2200)

SV из диапазона 01:0C:CD:04:xx:xx:

ether proto 0x88ba and (ether[0:4] & 0xffffff00 = 0x010ccd00) and (ether[4:2] & 0xff00 = 0x0400)

Про фильтрацию по goID и svID

Здесь приходится разочаровать: надёжно отфильтровать GOOSE по goID или SV по svID средствами фильтра захвата нельзя. Причина в том, что эти поля живут внутри BER-кодированного APDU, и их смещение от начала кадра зависит от длины предыдущих полей (gocbRef/svID-заголовка и так далее). BPF же умеет работать только с фиксированными смещениями.

Что можно сделать на практике:

  1. Использовать APPID как прокси. У «правильно» сконфигурированного публикатора APPID уникален и соответствует одному gocbRef/svCB. Фильтр по APPID — самый надёжный способ поймать конкретный поток на уровне захвата.
  2. Использовать MAC-адрес назначения групповой рассылки. Тоже рекомендуется иметь его уникальным для конкретного потока в нормально спроектированной и налаженной системе.
  3. Сначала захватить весь трафик определенного типа, потом отфильтровать фильтром отображения. Если поток не слишком большой — вариант рабочий. Для SV на нагруженной шине процесса — идея так себе.
  4. Написать byte-offset фильтр под конкретный кейс. Захватываете несколько кадров нужного потока без фильтра, смотрите, на каком смещении лежит интересующая вас строка, пишете фильтр вида ether[N:4] = 0xXXXXXXXX. Метод рабочий, но крайне хрупкий — любое изменение в предыдущих полях APDU сдвинет смещение, и фильтр перестанет ловить.

В 90 % случаев комбинация «EtherType + APPID» или «EtherType + dst MAC» закрывает задачу.

Исключающие фильтры — когда проще отсечь лишнее

Отдельный полезный приём — захватить всё, кроме одного-двух особо шумных потоков. Например, снять весь процесс-бас без двух конкретных SV-потоков, чтобы Wireshark не завис:

not (ether proto 0x88ba and ether dst 01:0c:cd:04:00:01) and not (ether proto 0x88ba and ether dst 01:0c:cd:04:00:02)

Или, наоборот, захватить GOOSE и PTP, но не SV:

ether proto 0x88b8 or ether proto 0x88f7

Для случаев, когда хочется посмотреть «всё, кроме MMS-коммуникаций с конкретным сервером»:

not (tcp port 102 and host 192.168.1.10)

Несколько практических советов напоследок

  1. Всегда начинайте с фильтра захвата, если работаете на шине процесса. Попытка «посмотрю, что там, а потом разберусь» закончится зависанием Wireshark.
  2. Держите шпаргалку рядом. BPF не тот язык, который запоминается с первого раза. Таблица с готовыми выражениями экономит десятки минут на каждой отладке.
  3. Для диагностики реальных проблем с GOOSE/SV чаще работает связка фильтр захвата по EtherType + фильтр отображения. Грубый фильтр на захвате отсекает 95% шума, а точечный фильтр отображения позволяет быстро переключаться между потоками в уже собранном трейсе.

BPF не самый дружелюбный язык, но освоить десяток конструкций из этого материала достаточно, чтобы закрыть практически все задачи по захвату трафика МЭК 61850. А заодно перестать наблюдать, как Wireshark борется за жизнь и умирает на шине процесса.