Модификация ToS/DSCP/TTL/etc. на FreeBSD: ng_patch

Jul 15, 2010 04:15

Патчи, позволяющие матчить и изменять ToS/DSCP на FreeBSD, ходят в разных вариантах по сети уже лет шесть, вот очередная инкарнация, например. К сожалению, ни один из них так и не попал в базовую систему, хотя пример по ссылке, скажем, для конкретной задачи удобнее того, что описано ниже. Даже возникает подозрение, что это всё потому, что предыдущие решения были недостаточно общие :) Вот Maxim Ignatenko написал ноду ng_patch(4) - и её, наконец, закоммиттили, а недавно смержили в 8-STABLE (r209843) и вчера - в 7-STABLE (r210019). Работает также на 6.4, если убрать в коде CSUM_SCTP, я проверял.
В настоящее время ng_patch - единственный штатный способ поменять что-то в проходящем пакете. Зато, в отличие от других решений (в том числе на других ОС), эта нода позволяет производить последовательность операций над произвольными байтами пакета, не только ToS или TTL. В мане рассмотрены примеры изменения ToS и TTL, я же расскажу чуть подробнее, с примером для DSCP.
Байты пакета для операций рассматриваются как беззнаковые целые длиной 1, 2, 4 или 8 байт, применять можно такие операции из языка Си: присвоение нового значения (=), добавление (+=), вычитание (-=), умножение (*=), деление (/=), отрицание (= -), побитовое AND (&=), побитовое OR (|=), побитовое XOR (^=), сдвиг влево (<<=), сдвиг вправо (>>=). Исключением является отрицание, здесь данные рассмтариваются как знаковые, а аргумент не используется.
Конфигурируется нода следующими структурами, ниже рассмотрим на примере:

struct ng_patch_op {
uint64_t value;
uint32_t offset;
uint16_t length; /* 1,2,4 or 8 bytes */
uint16_t mode;
};
/* Patching modes */
#define NG_PATCH_MODE_SET 1
#define NG_PATCH_MODE_ADD 2
#define NG_PATCH_MODE_SUB 3
#define NG_PATCH_MODE_MUL 4
#define NG_PATCH_MODE_DIV 5
#define NG_PATCH_MODE_NEG 6
#define NG_PATCH_MODE_AND 7
#define NG_PATCH_MODE_OR 8
#define NG_PATCH_MODE_XOR 9
#define NG_PATCH_MODE_SHL 10
#define NG_PATCH_MODE_SHR 11

struct ng_patch_config {
uint32_t count;
uint32_t csum_flags;
struct ng_patch_op ops[];
};
Теперь собственно пример. Допустим, в проходящих пакетах нам нужно выставить DSCP в AF33. Открываем RFC 791 и видим начало заголовка IP:

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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Нужный нам байт (ровно один байт) - ToS - второй, то есть, имеет смещение 1 от начала заголовка. Далее, мы знаем, что байт ToS был переопределен под использование DSCP, в RFC 3168 он определяется так:

0 1 2 3 4 5 6 7
+-----+-----+-----+-----+-----+-----+-----+-----+
| DS FIELD, DSCP | ECN FIELD |
+-----+-----+-----+-----+-----+-----+-----+-----+

DSCP: differentiated services codepoint
ECN: Explicit Congestion Notification
Нужный нам AF33 описан в RFC 2597 (впрочем, в любом справочнике по цискам и т.п. - тоже): AF33 = '011110'.
Что теперь? А вот 2 бита ECN нам трогать не надо, надо их оставить как есть. Минимальный же размер для операции - 1 байт. На помощь спешит Капитан Очевидность булева алгебра и прочая двоичная логика с дискретной математикой - применением очень сложных операций И и ИЛИ сначала надо обнулить искомые биты, а потом - сделать из них нужное нам значение. Два шага, сначала:

+-+-+-+-+-+-+-+-+
|s t u v w x y z| исходный байт
+-+-+-+-+-+-+-+-+
AND
+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 1 1| значение 0x03
+-+-+-+-+-+-+-+-+
=
+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 y z|
+-+-+-+-+-+-+-+-+
а потом:

+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 y z| промежуточный байт
+-+-+-+-+-+-+-+-+
OR
+-+-+-+-+-+-+-+-+
|0 1 1 1 1 0 0 0| значение AF33, сдвинутое на 2 влево: 0x78 (7=0111, 8=1000)
+-+-+-+-+-+-+-+-+
=
+-+-+-+-+-+-+-+-+
|0 1 1 1 1 0 y z| то, что надо!
+-+-+-+-+-+-+-+-+
Итак, составляем структурки для конфигурации узла и получаем итоговые команды (это уже полная конфигурация):

/usr/sbin/ngctl -f- <<-SEQ
mkpeer ipfw: patch 600 in
name ipfw:600 dscp_af33
msg dscp_af33: setconfig { count=2 csum_flags=1 ops=[ \
{ mode=7 value=0x03 length=1 offset=1 } \
{ mode=8 value=0x78 length=1 offset=1 } ] }
SEQ
/sbin/ipfw add 160 netgraph 600 ip from any to any not dst-port 80
Как можно догадаться, в командах и по тексту одинаковым цветом выделены одинаковые значения, для удобства сопоставления читателем. Операций у нас две, и поэтому count сообщает, что в массиве ops=[ ... ] будет две структуры ng_patch_op. Отдельно осталось рассмотреть поле csum_flags - оно может принимать такие значения (см. ), которые можно OR-ить:

#define CSUM_IP 0x0001 /* will csum IP */
#define CSUM_TCP 0x0002 /* will csum TCP */
#define CSUM_UDP 0x0004 /* will csum UDP */
#define CSUM_SCTP 0x0040 /* will csum SCTP */
Поле инструктирует ноду сообщить IP-стеку о необходимости пересчитать контрольную сумму. Сделано это будет позже, не в самой ноде. В данном примере дается команда пересчитать контрольную сумму только IP-заголовка, другие не трогать.

Надеюсь, вышеприведенной информации достаточно, чтобы самостоятельно менять в пакетах что угодно. NB: Только учтите, что это средство - в стиле Unix, и позволяет админу прострелить себе ногу. Хитро поигравшись значениями, можно так изменить пакет, что на выходном пути IP-стека ядро свалится в панику или будут какие-нибудь еще непредсказуемые результаты.

Да, быть может, этот способ не очень дружелюбен, если нужно поменять только TOS или TTL, зато он позволяет делать куда больше вещей, и это единственный штатный способ на текущий момент.

На том всё. Удачи.

сети, netgraph, freebsd, ipfw, админское

Previous post Next post
Up