How To Make a Transparent WWW Proxy
[english version]
Введение в проблему
В некоторый момент я обнаружил, что большую часть входящего траффика в
моей сети составляет WWW. При этом клиенты не пользовались Proxy,
хотя это заметно ускорило бы их работу (hit ratio составляет у нас
почти 50%), а пройтись по всем клиентам и поправить настройки их
броузеров я не имел возможности (да и желания тоже). Думаю,
подобные проблемы часто возникают и у ISP, когда поправить настройки
клиентов просто нельзя.
Почитав Squid'овский
FAQ, я
нашел там описание того, как заставить
клиентов насильно использовать Proxy, проблема заключалась только
в том, что предложенный способ не работал.
С другой стороны, я знал, что в
RadioMSU таки заставили
работать всех своих клиентов через Proxy.
После консультации с Ярославом Тихим все стало на свои места
и окончательное решение было сделано за пару часов.
Как это у меня работает
Действующие лица и исполнители:
- Роутер Cisco 2511 производства
Cisco Systems. К нему
подключены все клиенты (локальная сеть на
Ethernet и клиенты по Dialup), внешний канал на Demos тоже
включен туда же. Для простоты представим, что LAN имеет
адреса/маску 192.168.1/24, а dialup-клиенты - 192.168.2/24.
- Машина на которой установлен кэширующий proxy - Pentium-200,
64M RAM, 8Gb дисков. Работает под управлением
FreeBSD 2.2.5. На машине
установлен
IPFilter 3.2.3
и Squid 1.NOVM.20.
Для простоты изложения, представим, что адрес этой машины -
192.168.1.1
Настройки
- На Cisco
- Нужно завернуть все транзитные пакетики с destination eq 80
на Proxy. Это делается примерно так:
! Prevent loops
access-list 101 deny tcp host 192.168.1.1 any eq 80
! All other clients:
access-list 101 permit tcp 192.168.1.0 0.0.0.255 any eq 80
access-list 101 permit tcp 192.168.2.0 0.0.0.255 any eq 80
! Route-map
route-map forced-proxy permit 10
match ip address 101
set ip next-hop 192.168.1.1
!
! Интерфейсы:
! LAN
interface Ethernet 0
ip policy route-map forced-proxy
!
! Dialup
interface Group-Async 1
group-range 1 16
ip policy route-map forced-proxy
- На машине с proxy
-
Первым делом нужно завернуть приходящие на 80-й порт пакеты
на proxy. Предполагая, что proxy работает на порту 3128,
это делается как-то так:
ipnat -f - <<EOM
rdr ed0 192.168.1.1/32 port 80 -> 192.168.1.1 port 80
rdr ed0 0.0.0.0/0 port 80 -> 192.168.1.1 port 3128
EOM
Первое правило позволяет работать локальному WWW-серверу -
он будет продолжать получать свои пакетики. Вторая строчка
перенаправит все остальные пакеты с destination port 80
на локальный Squid.
В squid.conf пишутся строчки вида
httpd_accel www.your.domain 80
httpd_accel_with_proxy on
httpd_accel_uses_host_header on
В FAQ
рекомендовано вместо имени сервера в строчке httpd_accel
писать слово virtual. Это - неверно, во всяком случае
для Squid-1.NOVM.18..20. Проблема в том, что в этом режиме
Squid определяет destination server по вызову getsockname(2),
однако этим вызовом он может получить только один из своих
адресов, а это явно не подходит к условиям задачи.
Получившаяся конструкция работает по такой вот схеме (я рисовал эту
картинку минут 10 и хотя она тут почти не нужна, не пропадать же
добру):
Улучшения
При такой настройке, адрес реального сервера к которому
обращается пользователь будет браться из заголовка Host:
HTTP-запроса. Для подавляющего большинства клиентов этого
достаточно - все сколько-нибудь распространенные броузеры этот
заголовок ставят. Однако хочется большего.
В-принципе, можно было бы написать отдельную маленькую программу
(вместо proxy), которая спрашивала бы у IPFilter реальный
destination-address пакета, формировала бы заголовок Host:
и отдавала бы все это кэширующему proxy. Однако такой подход
имеет свои недостатки - в логах Squid будут только локальные
адреса и сопоставить, скажем, Hit Ratio с конкретным клиентом
будет непросто. Я пошел по другому пути и встроил необходимую
функциональность прямо в Squid. Патч для версии
1.NOVM.20 вы можете взять прямо отсюда (
для версии 1.NOVM.20,
для версии 1.NOVM.22).
(Необходимые замечания: Патч предполагает,
что *.h-файлы от ipfilter лежат у вас в /sys/netinet, а все
прочие добавленные includes необходимы на FreeBSD 2.2.x, а про
прочие системы я не в курсе).
После установки этого патча, Squid начнет понимать директиву
httpd_accel_uses_ipfilter_redirect в конфигурационном файле.
Использовать ее нужно так:
httpd_accel_uses_ipfilter_redirect on
И еще одно замечание - для пользователя с правами которого работает
squid файл /dev/ipnat должен быть доступен на чтение.
В squid 2.x эта функциональность
присутствует, если запускать configure --enable-ipf-transparent ,
при этом никаких дополнительных директив не нужно, это включено по умолчанию.
После всех этих изменений все работает и без заголовка Host.
Правда требование, чтобы клиент был HTTP-1.x (а не HTTP/0.9)
остается, но уж HTTP/0.9 клиентов я вообще очень давно не встречал.
Возможные проблемы
Возможные проблемы связаны с ICMP-сообщениями 'packet too big'.
Дело тут вот в чем. Допустим, между HTTP-Proxy и клиентом есть
линк с маленьким MTU, при этом и клиент и proxy живут на media
с большим MTU. Представим, что proxy начинает отвечать клиенту
с каким-то размером пакета, который не пролезет через этот тонкий
линк. Ipnat подменит source-address у этого пакета на IP-address
сервера к которому обращался клиент. Если на пути встретится
слишком маленький MTU, то ICMP-сообщение об этом уедет на какой-
нибудь www.microsoft.com, которому он совершенно ни к чему.
Идея лечения понятна - завернуть на той же Cisco все ICMP-пакеты
на хост с WWW-proxy, там их проверять на предмет соответствия
с какой-то строчкой из таблицы трансляции Ipnat, все которые
"неправильные" отдавать прямо местному TCP-стеку, все прочие -
отсылать дальше (можно при этом подменять у них source address
на какой-то левый). Это теория. До практики у меня руки пока не
дошли за полной ненадобностью - у меня все клиенты имеют MTU 1500.
|