Примечание. Написаное имеет смысл только для Linux. ipfw из FreeBSD поддерживает правило RESET, которое делает то же самое, что и описаные извращения.

Очень часто хочется ограничить доступ к определенным TCP-портам конечным списком адресов или подсетью. Например, мне бы хотелось, чтобы сервис telnet был доступен только из внутренней сети. Стандартное решение в этом случае - использование брандмауэра.

Пример для ipchains (linux)
$ ipchains -A input -p TCP --sport 23 ! -s 10.0.0.0/8 -jDENY

Правило блокирует входящие пакеты на порт 23 с адресов, не находящихся в подсети 10.0.0.0/8. Но, как известно, брандмаэур обнаруживает свое присутствие. В данном случае, послав SYN-пакет, мы не получим от хоста ответа, т.к. пакет до него не дойдет. Если вместо -jDENY использовать -jREJECT, то мы получим ответ - ICMP-пакет, также свидетельствующий, что работает брандмауэр, закрывающий доступ к портам. Есть и другие признаки.

А нам бы хотелось, чтобы создавалось впечателение, что никакого брандмауэра нет, просто TCP-порт не открыт. Для того, чтобы увидеть, как ведет себя система в этом случае, попробуем подключиться к заведомо закрытому порту.

$ telnet 10.0.0.250 999

Вот обрезаный вывод tcpdump.

debian.1034 > 10.0.0.250.999: S 3696197726:3696197726(0) win 32120
10.0.0.250.999 > debian.1034: R 0:0(0) ack 3696197727 win 0

Порт 999 закрыт, и на SYN-пакет идет ответ RST.

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

Есть два способа. Первый - не пропускать SYN-пакет, запоминать sequence и имитировать RST ответ сервера. Такое ПО может быть установлено на программном брандмауэре, тогда возможно защитить всю сеть. Например, это может быть сочетание встроенного брандмауэра (того же ipchains), настроенного на блокировку приходящих пакетов, и снифера, следящего за входящими пакетами, анализирующего их и имитирующего ответ путем отправки RST-пакета через RAW-сокет.

Второе решение - модификация процедуры ядра операционной системы, реагирующей на входящий SYN-пакет. У этого варианта много ограничений: необходимость вмешательства в ядро OS (так что реализация на windows-системах исключается), необходимость установки на каждый защищаемый хост, сложность динамически обновлять таблицу правил - требуется реализация взаимодействия с ядром. Однако, для некоторых случаев использовать второй способ проще.

Далее - побробное рассмотрения реализации второго способа для ядра linux. Расмотрим пример, когда подсоединение к 23му порту разрешается только с адреса 10.0.0.250.

Одна из процедур, отвечающая за реакцию на SYN-пакет - tcp_v4_conn_request из :./net/ipv4/tcp_ipv4.c.

int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb, __u32 isn)

Она вызывается, если пришел SYN-пакет на открытый порт. Все параметры пакета находятся в переменной skb. В данном случае нас интересуют два: адрес источника пакета и порт назначения.

В стандартной процедуре обработки запроса на подключение есть такой код:

struct tcphdr *th = skb->h.th;
__u32 saddr = skb->nh.iph->saddr;

Адрес источника берется из заголовка IP-пакета (saddr), порт - из заголовка TCP-пакета (th->dest). Добавим в процедуру такой код:

if ((saddr!=*(__u32*)&xdst)&&(th->dest==htons(23)))

goto dead;

dead - метка для обработки умершего сокета. В xdst хранится адрес 10.0.0.250. Если источник - не xdst, а порт равен 23, то делаем вид, что сокет мертв.

Перекомпилируем ядро и перезагрузимся. Проверка удачна: на попытку соединиться с адреса, не равного 10.0.0.250, получаем ответ RST, как будто порт закрыт. На работу с разрешенного адреса это не влияет.