Как сделать прозрачный WWW-proxy

В некоторый момент я обнаружил, что большую часть входящего траффика в моей сети составляет 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. После установки этого патча, Squid начнет понимать директиву httpd_accel_ uses_ipfilter_redirect в конфигурационном файле. Использовать ее нужно так:

httpd_accel_uses_ipfilter_redirect on

В 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.

Алексей Тутубалин

editorial

"Все это конечно очень здорово, - скажете вы, - но знаете-ли, в нашей стране, в отличие от соседской дружественной России, культ FreeBSD не распространился столь обширно - мы все "чыста по Линуху". Что же нам с этими рекомендациями делать?". Вы правы, уважаемый читатель :) Поэтому, не вдаваясь в подробности, я решила черкануть пару слов об обустройстве прозрачной прокси под Линуксом.

Если предположить, что ваши клиенты также подключены к цыске, все описанное Алексеем в этом разделе остается без изменений. Для ядер 2.0.х (берется из примера для 2.0.29 - осторожно, с другими ядрами могут быть нюансы!) -

1. компилируем ядро с поддержкой следующих вещей:
# Code maturity level options
#
CONFIG_EXPERIMENTAL=y
#
# Networking options
#
CONFIG_FIREWALL=y
# CONFIG_NET_ALIAS is not set
CONFIG_INET=y
CONFIG_IP_FORWARD=y
# CONFIG_IP_MULTICAST is not set
CONFIG_IP_FIREWALL=y
# CONFIG_IP_FIREWALL_VERBOSE is not set
CONFIG_IP_MASQUERADE=y
CONFIG_IP_TRANSPARENT_PROXY=y
CONFIG_IP_ALWAYS_DEFRAG=y
# CONFIG_IP_ACCT is not set
CONFIG_IP_ROUTER=y
и в ipfwadm, наряду с другими нужными вам вещами, прописываем нечто вроде:
ipfwadm -I -a accept -P tcp -D 208.206.76.44 80 #предотвращаем зацикливание. здесь 208.206.76.44 80 - адрес этой машины
ipfwadm -I -a accept -P tcp -D 0/0 80 -r 3128

Для ядер 2.2.х все это хозяйство будет выглядеть примерно так:
Собираем ядро с поддержкой следующего:
[*] Network firewalls
[ ] Socket Filtering
[*] Unix domain sockets
[*] TCP/IP networking
[ ] IP: multicasting
[ ] IP: advanced router
[ ] IP: kernel level autoconfiguration
[*] IP: firewalling
[ ] IP: firewall packet netlink device
[*] IP: always defragment (required for masquerading)
[*] IP: transparent proxy support

Цепочка ipchains, отвечающая за переадресацию веб-трафика юзеров на веб-прокси, может выглядеть так:

/sbin/ipchains -A input -p tcp -s 10.0.3.22/16 -d 0/0 80 -j REDIRECT 8081

Ну вот, в общем и целом все. За подробностями обращайтесь к соответствующим HOWTO и man'ам, в первую очередь по ipfwadm, ipchains, squid, например, кhttp://www.squid-cache.org/Doc/FAQ/, откуда и была почерпнута значительная часть информации этого послесловия;)


Сетевые решения. Статья была опубликована в номере 01 за 2001 год в рубрике sysadmin

©1999-2024 Сетевые решения