20 May 2011

Бесплатный Wi-Fi, с небольшой изюминкой

System administration
Sandbox
Данная статья повествует о небольшом проекте бесплатной Wi-Fi сети, об основных технических проблемах и решениях. Цель – просто рассказать о достаточно оригинальном проекте.

Чуть меньше года назад, моё руководство решило на территории организации развернуть сеть бесплатного Wi-Fi для посетителей. Все было бы просто и прозаично, если бы не одно интересное условие: прежде чем пользователь попадет в Интернет, он должен посмотреть информационную страницу, с нашей «рекламой».

После небольшого анализа пришли к выводу, что подобное можно реализовать только с помощью отдельного сервера- шлюза, скорее всего на Linux-е. Краткое ТЗ с общим описанием задачи разосланное по компаниям, занимающимся разработкой/внедрением решений хотя бы приближенных к подобному, показало, что при заказе внешнего решения стоимость проекта взлетит за облака.

После долгих раздумий и фантазий на тему: как же это реализовать (с погружением в глубины протоколов, и десятками эротических фантазий как же подменить пакеты, заголовки и т.д.) было найдено простое и логичное решение.

Гениальное – просто



Краткий принцип работы:
  1. Пользователь подключаясь к сети получает ip-адрес с помощью обычного dhcp-сервера. В настройках указывается наш сервер в качестве шлюза по умолчанию.
  2. На сервере по умолчанию прописан такой набор правил iptables:

    [root@wifi ~]# iptables -L -t nat
    Chain PREROUTING (policy ACCEPT)
    target prot opt source destination
    REDIRECT tcp -- anywhere !192.168.143.1
    DROP udp -- anywhere !192.168.143.1 udp dpt:!domain

    Благодаря этому все запросы (кроме DNS- пакетов) от пользователя перенаправляются на локальный сервер.
  3. При первой попытке пользователя загрузить какую либо web-страницу он попадает на локальный nginx – сервер где ему демонстрируется html- страница содержащаястроку:
    <?php
    $run = "ts /usr/share/nginx/html/script.sh {$_SERVER['REMOTE_ADDR']}";
    exec($run);
    ?>

  4. И вот тут начинается самое интересное! Данная страница запускает лежащий на сервере скрипт (передавая ему в качестве параметра запуска ip-адрес клиента):

    #!/bin/sh
    LOG_FILE=/var/log/script_log
    {
    date | tr "\n" " "
    echo -n '|IP:'
    echo -n $1
    echo -n '|MAC:'
    /sbin/arp $1 -n | grep $1 | tr -s ' ' | cut -d ' ' -f 3 | tr "\n" " "
    mac=`/sbin/arp $1 -n | grep $1 | tr -s ' ' | cut -d ' ' -f 3 | tr "\n" " "`
    if ( sudo /sbin/iptables -n -L -t nat --line-numbers | grep $1 > /dev/null )
    then
    echo '|Rule exists!!'
    else
    echo -n '|Add rule '
    sudo /sbin/iptables -t nat --insert PREROUTING -s $1 -j ACCEPT
    echo -n '|Creat shedule:'
    echo "sudo /sbin/iptables -t nat -D PREROUTING -s $1 -j ACCEPT " | at -m now + 45 minutes
    sudo /usr/bin/python /usr/share/nginx/html/stat.py ${mac}
    fi
    } >> $LOG_FILE 2>&1


    Который делает два основных действия: добавляет правило в iptables, и создает отложенную задачу в at.

    Правило:

    sudo /sbin/iptables -t nat --insert PREROUTING -s $1 -j ACCEPT

    Помещается в цепочке PREROUTING на первую позицию, тем самым располагаясь ДО правил редиректа. В результате пользователь с данным адресом получает полный доступ в Интернет.

    Второе основное действие, создание правила в at:
    echo "sudo /sbin/iptables -t nat -D PREROUTING -s $1 -j ACCEPT " | at -m now + 45

    По сути ограничивает сессию пользователя 45-ю минутами, после чего правило будет удалено и пользователь снова попадет на редирект.


Схема была реализована, протестирована, и доказал свою жизнеспособность.

На этом стоило бы наверное закончить рассказ, но за «Чуть меньше года» было проделано работ по доводке проекта, устранению мелких косяков и добавлению достаточно оригинальных фич.

Менталитет



Первая возникшая проблема была в «злоупотребляющих». Было замечено, что некоторые особо жаждущие до халявы личности пользуются сетью чрезмерно долго. Для выявления подобных было решено вести статистику. Основная проблема в том что все биллинг- системы (или простенькие модули для ведения статистики) считают базовым элементом либо логин, либо ip-адрес. В нашем же случае первого просто нет, а ко второму привязываться нельзя, т.к. ip-адреса не статичны. Нужная была статистика опирающаяся на mac- адрес. После долгих изысканий пригодных решений найдено не было. Поэтому было решено изобрести очередной велосипед написать свое. Поскольку по сути необходимо, и достаточно, было считать кол-во сессий проведенных mac- адресом был написан простенький скрипт на python (к сожалению мои познания bash-а не смогли вытянуть данную задачу):

import sys
file = open('/var/log/stat.file','r')
line = file.readlines()
line2 = ''
i = 0
while len(line) > i :
line2 = str(line[i])
line2 = line2.rstrip()
line2 = line2.split()
if line2[0] == str(sys.argv[1]) :
line2[1] = str(int(line2[1])+1 )
del line[i]
line.insert(i,line2[0]+' '+line2[1]+'\n')
break
i = i + 1
else :
line.append(str(sys.argv[1])+' '+'1'+'\n')
###########
file.close()
file = open('/var/log/stat.file','w')
file.writelines(line)
file.close()


Который вызывался из основного скрипта. В logrotate была добавлена секция для данного файла с периодом в неделю. И через две недели бы получен список «постоянных клиентов» mac-адреса которых были занесены в черные списки на коммутаторе установленном перед сервером.

Велосипеды не пройдут!



Следующая проблема была более оригинальна: при одновременном запуске нескольких скриптов один из них выдавал ошибку iptables (чтото из серии “ iptables: Unknown error 4294967295 ” ), не выполняясь. Если это был скрипт на создание правила, то ничего страшного не происходило – пользователь просто обновлял страницу и запустившийся заново скрипт отрабатывал без ошибок (конечно же если опять не попал на одновременный запуск, судя по логам однажды одному бедолаге сильно не везло, и он 5 раз попадал на одновременный запуск ).
Если же с такой ошибкой вылетала задача удаления правила, то ip-адрес оставался в списках, и пользователь мог бесконечно долго и безнаказанно сидеть в Интернете.

Первое временное решение пришло в виде простенького bash-скрипта сверяющего список адресов в iptables и пуле задач at:

#!/bin/bash
LOG_FILE=/var/log/script_log
{
echo -n "Checking rules: "
date | tr "\n" " "
ar_ip=(`sudo iptables -L -t nat -n | grep ACCEPT | grep -v policy | cut -d ' ' -f 10 | tr '\n' ' '`)
ar_at=(`grep 168.143. /var/spool/at/* | cut -d ' ' -f 9 | tr '\n' ' '`)
count=${#ar_ip[@]}
for ((i=0; i <= count ; i++ ))
do
for h in "${ar_at[@]}"
do
if [[ ${ar_ip[$i]} == $h ]]
then
unset ar_ip[$i]
continue 2
fi
done
done
echo -n "|Delete rules: "
for i in ${!ar_ip[*]}
do
sudo /sbin/iptables -t nat -D PREROUTING -s ${ar_ip[$i]} -j ACCEPT
echo -n ${ar_ip[$i]} " "
done
echo "|Checking done"
} >> $LOG_FILE 2>&1


Скрипт был помещен в /etc/cron.hourly, и исправно удалял последствия ошибок iptables.В принципе решение было достаточным, но «нет предела совершенству» и я начал бороздить бескрайние просторы в поисках лучшего решения. И оно было найдено. На одном из «уютненьких» linux- форумов, один из мастодонтов указал мне в сторону данного проекта



По сути, данный проект – это небольшая разработка для запуска bash-скриптов, помещающая их в пул и выполняющая в порядке строгой очередности, один за другим.
Строчка запуска скрипта на веб- странице была модифицирована до вида

ts /usr/share/nginx/html/script.sh {$_SERVER['REMOTE_ADDR']}

И ошибки iptables исчезли.

Проблема третья, или как я помог open-source сообществу



Когда проект уже работал около полу года, и люди уже достаточно о нем узнали, появилась еще одна серьезная проблема: точки доступа (Lynksys wap54g) начали самопроизвольно отключатся/виснуть/перезагружаться. Естественно первое что пришло в голову это посмотреть логи. Как оказалось Lynksys не зря относят к «домашнему» сегменту, ведь домохозяйкам читать логи ни к чему. Единственное что содержалось в логах это когда какой mac- адрес подключился. К тому же формат логов был уникальный и обычный syslog- сервер его не понимал. Как всегда Lynksys предлагал какую- то подозрительную утилиту содержащее что- то волшебное в свое названии, якобы способную самостоятельно и мигом настроить вашу домашнюю сеть. После краткого изучения утилиты был сделан вывод о её непригодности, и опять началось долгое путешествие по бескрайним просторам. Где то в бескрайних степях На sf.net был найден маленький загнувшийся в 2009 проект (http://sourceforge.net/projects/wap54g-log/) который понимал формат логов точек и умел складывать их в файлик. «А большего нам и не надо!» ©

Оказалось, что рано радовались. Запись в логах имела вид:

May 20 12:04:42 /usr/local/bin/wap54g-log[17544] Wireless PC connected 00:26:C7:61:BB:26

И понять к какой же из точек подсоединился клиент, было невозможно. А следовательно и сделать вывод о нагрузке, как одной и самых возможных причин недостойного поведения точек.
И тут пришлось вспоминать С. Как оказалось не вспоминать, а изучать заново, т.к. то, что писалось на втором курсе в институте в Visual Studio, лишь отдаленно напоминало код этой небольшой утилиты

После вдумчивого чтения была обнаружена структура theiraddr, относящаяся к типу sockaddr _in (стандартный для unix тип сокета) который содержит в себе еще одну структуру in_addr которая содержит в себе единственную переменную s_addr типа in_addr_t. «Яйцо в утке, утка в зайце, заяц в шоке» ©. Самым веселым был комментарий «То, что используется такая структура, а не просто переменная типа in_addr_t, сложилось исторически».

Углубляясь все дальше и дальше, была обнаружена библиотека <arpa/inet.h>(О господи! Призрак арпы!) содержащая в себе функции для работы с данными структурами. Путем преобразования функцией inet_ntoa(strcu) и помещение всего это в вывод утилиты, получилось примерно так:

May 20 12:23:38 192.168.143.114 /usr/local/bin/wap54g-log[17544] Wireless PC connected 00:26:C7:61:BB:26

Ключевое отличие, в наличии адреса точки. Т.е. теперь можно судить о распределении нагрузки по точкам.
Как оказалось интуиция/логика/5-ая точка, меня не подвела. И после небольших наблюдений выяснилось, что Wap54g не выдерживает даже 30-ти одновременно подключенных пользователей.

Эту проблему еще предстоит решить. Пока вариантов кроме замены точек на что-то более «промышленное» я не вижу. Руководство решает что же делать, а разработчику утилиты было отправлено письмо с благодарностями, и предложениями под доработке.

Были и более мелкие проблемы: nginx выдавал 404 страницу, если адрес с которого происходил редирект, содержал название страницы и get-запрос в с троке адреса. Решилось одной строчкой в конфиге виртуального сервера «rewrite ^/(.*) /index.php last;». Но о подобных мелочах здесь не стоит рассказывать т.к. все они банальщина и повседневность.

Вместо заключения



Окончательное решение о написании данной стать утвердилось после прочтения пары статей «Как стать системным администратором — пособие для начинающих». Я не считаю себя каким- либо гуру, трезво понимаю что я – системный администратор linux- направленности средней руки. В это статье показана лишь малая часть того объема знаний что необходима для повседневной работы. Всего лишь html,bash,python, да небольшое знание С. Ну и как обязательная база: знание теории (всего ИТ, от сетей до программирования), и ОС (утилиты, системные приложения) с которой в основном приходится работать. Думаю, это будет неплохим дополнением.
Tags:linuxbashpythonWi-Fiсистемное администрирование
Hubs: System administration
+21
46.1k 125
Comments 40