Vadim Goncharov (nuclight) wrote,
Vadim Goncharov
nuclight

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

Патчи, позволяющие матчить и изменять 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 - оно может принимать такие значения (см. <sys/mbuf.h>), которые можно 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, зато он позволяет делать куда больше вещей, и это единственный штатный способ на текущий момент.

На том всё. Удачи.
Tags: freebsd, ipfw, netgraph, админское, сети
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 27 comments