LINUX.ORG.RU
ФорумAdmin

Фильтрация DNS ANY через cBPF

 , ,


0

1

Приветствую уважаемое сообщество.

Будучи обычным «админом локалхоста» столкнулся с задачей:
Необходимо на уровне файервола (до попадания пакета к демону BIND9) фильтровать пакеты с запросом DNS ANY. По сути надо в кадре DNS после доменного имени найти последовательность 0x00FF0001 (QTYPE ANY, CLASS IN).

Условия задачи и проблемы, с которыми я столкнулся:
- BIND9 не патчим, пользуемся ванильной сборкой;
- Модуль string для iptables отрабатывает не совсем хорошо, да ещё и создаёт большую нагрузку.
- Модуль u32 не даёт достаточной гибкости, чтобы вычислить требуемое смещение для сравнения.
- Кадр DNS QUERY содержит также анонс EDNS(0), так что прочитать последние 4 байта в пакете уже не канает.
- Надо рассчитывать на то, что доменное имя может иметь произвольную длину.

В ходе исследования вопроса я пришёл к выводу, что наиболее разумный вариант - написать код для регистровой машины cBPF. Писать решил сразу на ассемблере cBPF, так как на сишечке - лютый геморрой и куча промежуточных ступеней. Перевожу в опкоды с помощью компилятора bpfc из пакета netsniff-ng. Отлаживаю в bpf_dbg из сборки Cloudflare bpftools.

Сам код фильтра такой:

; Check if we've got the IP packet on top of Ethernet frame:
check_ip:
	ldh [12]                ; Load the value from offset 12 into A register.
	jneq #0x800, pass       ; Check if we're dealing with IP packet or pass it AS IS.

; Check the protocol field for packet type == 17 (UDP)
check_proto:
	ldb [23]                ; Check. tcpdump has advised me to use offset 23 instead of 27.
	jneq #0x11, pass        ; Pass the packet if it's type is not UDP.

; Check for the packet dport == 53.
check_dport:
	ldh [36]                ; Load the u16 into A register from offset 36.
	jneq #0x35, pass        ; Check if the dport is 53.

; Check if we have the unfragmented packet (or the first fragment).
check_fragmentation:
	ldh [20]                ; Skip all headers right to UDP payload (Load UDP Fragment Offset).
	jset #0x1fff, pass      ; Returns 0. Test if we have the unfragmented packet or packet's first fragment, drop the packet otherwise.
	ldxb 4*([14]&0xf)
	ldb [x + 24]
	jset #0x80, pass

; Check for DNS QTYPE == ANY and QCLASS == IN
find_qtype:
	ldb [54]               ; Load first byte of the query RR into A. It contains relative count/offset of query bytes or 0x00 in the end.
	add #0x37              ; Add 54 (query offset byte position) + 1 (+ 1 byte forward) to the offset value in A to make the offset absolute.
	tax                    ; Move the absolute offset to X.
	ldb [x + 0x0]          ; Load the value from the absolute offset.
	jeq #0x00, check_qtype ; Move to the QTYPE check code if it's the end of the query string.
	add x                  ; Add the absolute offset from A to relative offset in X.
	add #0x1               ; Move the absolute offset 1 byte forward, to the next offset value.
	tax                    ; Move the new (adjusted) absolute offset from A to X.
	ldb [x + 0x0]          ; Load the value by the new calcaulated offset.
	jeq #0x00, check_qtype ; Check against end of the query once again.
	add x
	add #0x1
	tax
	ldb [x + 0x0]
	jeq #0x00, check_qtype ; And again, and again...
	jmp pass               ; Pass the packet if we were unable to find the desired value. A little failover.

; Check the query type in case we were able to find the end of the string!
check_qtype:
	txa                     ; Move the absolute offset from X into A.
	add #0x1                ; Move the offset 1 byte forward from query end.
	tax                     ; Move the absolute offset value to the X register from A.
	ld [x + 0x0]            ; Load the u32 from absolute offset specified in X register.
	jeq #0x00FF0001, drop   ; Drop the packet if the value in A is 0x00FF0001 or else pass the packet.

pass:
	ret #-1           ; Label/Return code signaling to pass the inspected packet.

drop:
	ret #0            ; Label/Return code signaling to drop packet.


Код гружу в xt_bpf командой iptables -I INPUT 1 -m bpf --bytecode '<байт-код>'.

Проблема: В отладчике оно пакет матчит и исправно уходит на метку drop:, но в реальности, как BIND9 ругался на избыток таких пакетов, так и ругается. Может, не всё матчит, а, может, неправильно правило прописываю.

Если у кого есть идеи, но нет знания тематики вопроса, то в комментариях я готов пояснить нюансы размещения данных в пакете и описанные в коде действия. Сам я, конечно, не великий специалист.


а куда join в правиле ?

anonymous
()

bpf в iptables работает с L3, а не с L2, что логично.

В man iptables-extensions есть пример, где смещение до поля протокол равно 9.

vel ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.