Привет! Не так давно, в нашей организации встала задача узаконить wifi доступ, но чтобы в дальнейшем использование системы было бесплатно. (Согласно постановлению Правительства №758 от 31 июля 2014г. и №801 от 12 августа 2014 г. — все публичные WIFI сети обязаны производить идентификацию пользователей). У нас 10 залов для мероприятий (от 30 до 400 человек), а в день в среднем проходит от 4 до 12, плюс постоянная текучка народа и капризные пользователи.
Для начала расскажу, как у нас организована сеть. Если не вдаваться в подробности, то развешены точки доступа HP MSM430(J9651), управление через HP MSM760(J9420A) и шлюз HP F1000-EI (JG214A). Выбор оборудования не удачен, но работаем с тем, что имеем.
Так сложилось, что по жизни я больше полюбил Windows системы, но получив задачу, начитавшись кучу статей, пришел к выводу, что для этой цели лучше всего подойдет *nix система. Выбор пал на Ubuntu сервер 16.04. Дальше было несколько дней мучений, но в итоге все получилось.
Эта статья для тех, кто любит Windows, смотрит на Ubuntu и ставит с нуля кучу софта.
Как я решил все организовать:
Имеется открытая сеть, назовем ее Free. Пользователь при подключении переадресуется на хотспот с страницей авторизации. Там ему выдается код доступа к интернету и номер телефона, на которое он должен отправить сообщение (немного необычно, но стояла задача экономии, в том числе и на смс). Как только сообщение приходит к нам, доступ в интернет сразу открывается. Оговорюсь, симку и номер в модеме лучше использовать МТС (не реклама), так как только у них не нашел возможности отправить СМС с сайта без указания левого номера (хотя это проблема, но в процессе решения).
Что нам потребуется:
- лошадка Ubuntu Server 16.04
- прокси Squid в прозрачном режиме
- Mysql (MariaDB)
- Nginx
- PHP FPM
- USB redirector (виртуальная машинка под HyperV)
- модем Huawei E153 МТС
- SMS tools для чтения смс
- и тд. и тп.
Начнем.
Установка Ubuntu Server 16.04
Здесь все довольно банально:
- Создаем виртуальную машинку (2 ядра, 4 Гб памяти, 2 сетевых интерфейса, 30 Гб диск)
- Качаем последний дистрибутив, подключаем, ставим...
- Настраиваем сетевые интерфейсы, один смотрит в vlan сети wifi, второй в сторону шлюза
Настройка сети и форвардинг пакетов
Редактируем файл настройки сетевых интерфейсов:
nano /etc/network/interfaces
auto eth0
iface eth0 inet static
# в сторону интернет шлюза
address 10.66.66.6
netmask 255.255.255.240
network 10.66.66.0
broadcast 10.66.66.15
gateway 10.66.66.1
dns-nameservers 10.66.66.1
auto eth1
iface eth1 inet static
# в сторону wifi пользователей
address 10.0.87.254
netmask 255.255.248.0
network 10.0.80.0
broadcast 10.0.87.255
Убедимся в том, что в системе действительно присутствуют IPv6 интерфейсы:
ip a | grep inet
Также можно увидеть, что некоторые приложения вывешивают TCP прослушиватели на интерфейсах IPv6. Посмотреть все прослушиваемые в системе порты можно командой:
sudo ss -lnptu | sort
Чтобы выключить поддержку IPv6 на всех сетевых интерфейсах сразу, открываем на редактирование файл sysctl.conf
sudo nano -Y sh /etc/sysctl.conf
В конец файла добавляем строки для включения форвардинга и отключения IPv6:
net.ipv4.ip_forward=1
net.ipv6.conf.all.disable_ipv6 = 1
Для проверки того, что наша опция сможет быть прочитана sysctl во время загрузки выполним:
sudo sysctl -p
/etc/init.d/networking restart
Установка MySQL
В качестве сервера баз данных, выбрал MaridDB. По функционалу в чем-то даже лучше MySQL, но статья не об этом.
apt-get install mariadb-server
#настраиваем пароли на доступ
mysql_secure_installation
#запускаем mysql и сбрасываем привилегии
mysql -u root
use mysql;
update user set plugin='' where User='root';
flush privileges;
Проверяем, все ли хорошо у нас запустилось:
service mysql status
Установка Squid с поддержкой SSL и отключением IPv6
Есть много статей, как собирать и устанавливать прокси, но этот этап оказался наверное самый муторный. По умолчанию, в репозитарии убунты лежит Squid без поддержки SSL. Решил пересобрать, и так прошло 2 дня… В итоге получился свой мануал, как собрать под x64 последнюю версию 3.5.20.
Ставим необходимый софт для сборки:
apt-get install git fakeroot checkinstall build-essential devscripts patch libssl-dev libgnutls28-dev
apt-cache policy squid3
apt-get build-dep squid3
Расcкомментируем репозитарии исходников и добавим новый:
nano /etc/apt/sources.list
deb-src http://ftp.de.debian.org/debian/ testing main contrib non-free
Новый репозитарий, будет ругаться на ключи, поэтому сразу их получим:
gpg --keyserver keyserver.ubuntu.com --recv 8B48AD6246925553
gpg --export --armor 8B48AD6246925553 | sudo apt-key add -
gpg --keyserver keyserver.ubuntu.com --recv 7638D0442B90D010
gpg --export --armor 7638D0442B90D010 | sudo apt-key add -
Не забываем обновить информацию о репозитариях:
apt-get update
Чтобы не захламить рабочую папку, переходим в tmp и скачиваем из testing последнюю версию squid с правилами для сборки под debian
cd /tmp/
apt-get source squid3
Текущая версия 3.5.19, обновляемся до последней 3.5.20
wget http://www.squid-cache.org/Versions/v3/3.5/squid-3.5.20.tar.gz
tar -xf squid-3.5.20.tar.gz
mkdir ./squid-3.5.20/debian/
cp -r ./squid3-3.5.19/debian/* ./squid-3.5.20/debian/
cd squid-3.5.20/
nano debian/rules
Добавляем строчки (не забываем указать путь к openssl.cnf, у меня это /etc/ssl)
--disable-ipv6
--enable-icap-client
--enable-ssl-crtd
--with-openssl=/etc/ssl
Подтверждаем патч и собираем (ждем примерно 10-15 минут)
dpkg-source --commit
#patch update to squid 3.5.20
debuild
Смотрим, какие пакеты у нас собрались:
ls -l /tmp/ | grep .deb$
И начинаем установку Squid:
apt-get install squid-langpack libdbi-perl
dpkg -i squid-common_3.5.19-1_all.deb
dpkg -i squid_3.5.19-1_amd64.deb
dpkg -i squid3_3.5.19-1_all.deb
dpkg -i squidclient_3.5.19-1_amd64.deb
если по каким-либо причинам, подвис установщик, то сбрасываем блокировку:
fuser -vki /var/lib/dpkg/lock
запускаем и проверяем статус
service squid start
systemctl status -l squid
в ответ должны увидеть что-то похожее
squid.service - LSB: Squid HTTP Proxy version 3.x
Loaded: loaded (/etc/init.d/squid; bad; vendor preset: enabled)
Active: active (running)
Теперь займемся его настройкой, для начала создадим SSL сертификат и сохраним конфиг по умолчанию:
cd /etc/squid
openssl req -new -newkey rsa:1024 -days 365 -nodes -x509 -keyout squidCA.pem -out squidCA.pem
mv ./squid.conf ./squid.conf.default
nano ./squid.conf
Файл конфигурации, почти без изменений взят из статьи.
acl localnet src 10.0.80.0/21
acl SSL_ports port 443
acl Safe_ports port 80 # http
acl Safe_ports port 21 # ftp
acl Safe_ports port 443 # https
acl Safe_ports port 70 # gopher
acl Safe_ports port 210 # wais
acl Safe_ports port 1025-65535 # unregistered ports
acl Safe_ports port 280 # http-mgmt
acl Safe_ports port 488 # gss-http
acl Safe_ports port 591 # filemaker
acl Safe_ports port 777 # multiling http
acl CONNECT method CONNECT
dns_nameservers 10.66.66.1
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow localhost manager
http_access deny manager
http_access allow localnet
http_access allow localhost
http_access deny all
#http_port 3128
#прозрачный порт указывается опцией intercept
http_port 10.0.87.254:3128 intercept options=NO_SSLv3:NO_SSLv2
#также нужно указать непрозрачный порт, ибо если захотите вручную указать адрес
#прокси в браузере, указав прозрачный порт, вы получите ошибку доступа, поэтому нужно
#указывать непрозрачный порт в браузере, если конечно такое желание будет, к тому же в логах #сыпятся ошибки о том, что непрохрачный порт не указан=)
http_port 10.0.87.254:3130 options=NO_SSLv3:NO_SSLv2
#и наконец, указываем HTTPS порт с нужными опциями
https_port 10.0.87.254:3129 intercept ssl-bump options=ALL:NO_SSLv3:NO_SSLv2 connection-auth=off cert=/etc/squid/squidCA.pem
always_direct allow all
sslproxy_cert_error allow all
sslproxy_flags DONT_VERIFY_PEER
#укажем правило со списком блокируемых ресурсов (в файле домены вида .domain.com)
acl blocked ssl::server_name "/etc/squid/blocked_https.txt"
acl step1 at_step SslBump1
ssl_bump peek step1
#терминируем соединение, если клиент заходит на запрещенный ресурс
ssl_bump terminate blocked
ssl_bump splice all
sslcrtd_program /usr/lib/squid/ssl_crtd -s /var/lib/ssl_db -M 4MB
coredump_dir /var/spool/squid
refresh_pattern ^ftp: 1440 20% 10080
refresh_pattern ^gopher: 1440 0% 1440
refresh_pattern -i (/cgi-bin/|?) 0 0% 0
refresh_pattern . 0 20% 4320
cache_dir aufs /var/spool/squid 2048 49 256
maximum_object_size 61440 KB
minimum_object_size 3 KB
cache_swap_low 90
cache_swap_high 95
maximum_object_size_in_memory 512 KB
memory_replacement_policy lru
#logfile_rotate 31
logfile_daemon /usr/lib/squid/log_db_daemon
access_log daemon:/127.0.0.1:3306/base/table/user/password squid
Создаем файл со списком блокируемых ресурсов
nano ./blocked_https.txt
Обращаю внимание, что логи будем писать в базу данных. Для этого создаем таблицу:
CREATE TABLE `access_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`time_since_epoch` decimal(15,3) DEFAULT NULL,
`time_response` int(11) DEFAULT NULL,
`ip_client` char(15) DEFAULT NULL,
`ip_server` char(15) DEFAULT NULL,
`http_status_code` varchar(10) DEFAULT NULL,
`http_reply_size` int(11) DEFAULT NULL,
`http_method` varchar(20) DEFAULT NULL,
`http_url` varchar(500) DEFAULT NULL,
`http_username` varchar(20) DEFAULT NULL,
`http_mime_type` varchar(50) DEFAULT NULL,
`squid_request_status` varchar(50) DEFAULT NULL,
`squid_hier_status` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
запускаем и проверяем статус
service squid start
systemctl status -l squid
в ответ должны увидеть что-то похожее
squid.service - LSB: Squid HTTP Proxy version 3.x
Loaded: loaded (/etc/init.d/squid; bad; vendor preset: enabled)
Active: active (running)
Проверяем используемые порты и версию установленного squid
sudo ss -lnptu | grep :3128
sudo ss -lnptu | grep :3129
sudo ss -lnptu | grep :3130
squid -version
Squid Cache: Version 3.5.20
Ставим USB-Redirector и модем
Так как, используется кластер под HyperV 2012R2, то возникает проблема, а именно как нам воткнуть модем. В нашей организации довольно успешно используется: USB-Redirector. Ставим его под Ubuntu
cd /tmp/
wget http://www.incentivespro.com/usb-redirector-linux-x86_64.tar.gz
tar -xf usb-redirector-linux-x86_64.tar.gz
./usb-redirector-linux-x86_64/installer.sh install-client
подключаемся к серверу, куда подключили модем
usbclnt -addserver 10.X.X.X:32032
usbclnt -autoconnect on 1
смотрим перечень всех usb устройств
usbclnt -l
================= USB CLIENT OPERATION SUCCESSFUL ===============
List of USB servers and devices:
1: USB server at 10.X.X.X:32032
Mode: auto-connect Status: connected
- 7: HUAWEI Mobile
Vid: 12d1 Pid: 1001 Port: 3-2
Mode: manual-connect Status: disconnected
устанавливаем драйверы модема
apt-get install usb-modeswitch usb-modeswitch-data
подключаемся и проверяем, установлен ли модем
usbclnt -connect 1-7
ls /dev | grep ttyUSB
в ответ должны увидеть:
ttyUSB0
ttyUSB1
ttyUSB2
Устанавливаем SMS-tools
Информацию о пакеты можно найти на сайте разработчика.
Устанавливаем и настраиваем:
apt-get install smstools
nano /etc/smsd.conf
находим строчку [GSM1]
[GSM1]
device = /dev/ttyUSB0
incoming = yes
baudrate = 9600
eventhandler = /var/www/sms_recieve.php
создаем файл обработчика входящих смс и делаем его исполняемым
nano /var/www/sms_recieve.php
chmod 755 /var/www/sms_recieve.php
service smstools restart
Настраиваем DHCP сервер
apt-get install isc-dhcp-server
nano /etc/default/isc-dhcp-server
#указываем, на каком интерфейсе будет слушать dhcp сервер
INTERFACES="eth1"
sudo nano /etc/dhcp/dhcpd.conf
# указываем
authoritative;
subnet 10.0.80.0 netmask 255.255.248.0 {
range 10.0.80.1 10.0.86.254;
option domain-name-servers 10.0.87.254;
option domain-name "wifi-free";
option subnet-mask 255.255.248.0;
option routers 10.0.87.254;
option broadcast-address 10.0.87.255;
default-lease-time 7200; #2h
max-lease-time 72000; #20h
}
/etc/init.d/isc-dhcp-server restart
Настраиваем Nginx и PHP 5.6 FPM
Так как php обновилось до 7 версии и во всех репозитариях ubuntu уже лежит php7, то добавляем новый и ставим:
add-apt-repository ppa:ondrej/php
apt-get install php5.6-cli php5.6-common php5.6-mysql php5.6-gd php5.6-fpm php5.6-cgi php-pear
останавливаем установленный сервис и редактируем файлы настройки
service php5.6-fpm stop
nano /etc/php/5.6/fpm/php.ini
cgi.fix_pathinfo = 0
post_max_size = 200M
upload_max_filesize = 200M
nano /etc/php/5.6/fpm/pool.d/www.conf
security.limit_extensions = .php .php3 .php4 .php5
listen = /run/php/php5.6-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
service php5.6-fpm start
Можно убедится в том, что права доступа к сокету установлены верно:
ls -la /run/php/php5.6-fpm.sock
#srw-rw---- 1 www-data www-data 0 May 2 16:36 /run/php/php5.6-fpm.sock
Проверяем:
php -v
PHP 5.6.23-2+deb.sury.org~xenial+1 (cli)
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies
with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2016, by Zend Technologies
Переходим к Nginx
apt-get install nginx nginx-extras
Основные настройки Nginx хранятся в файле /etc/nginx/nginx.conf.
Настройки базового сайта хранятся в файле /etc/nginx/sites-available/default.
Базовый конфигурационный файл сайта принято помещать в папку /etc/nginx/sites-available/ и затем включить его путём добавления символической ссылки на этот файл в папке /etc/nginx/sites-enabled/.
touch /etc/nginx/sites-available/hotspot.domain.com
ln -s /etc/nginx/sites-available/hotspot.domain.com /etc/nginx/sites-enabled/
mkdir /etc/nginx/common
Дальше буду краток, так как пояснения можно почитать по ссылке. Создаем общие файлы настройки сервера, в которых описываем настройки безопасности, сжатия, кэширования и php.
touch /etc/nginx/common/upstream
nano /etc/nginx/common/upstream
upstream php-fpm
{
# PHP5.6-FPM сервер
server unix:/run/php/php5.6-fpm.sock;
}
touch /etc/nginx/common/security
nano /etc/nginx/common/security
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
touch /etc/nginx/common/gzip
nano /etc/nginx/common/gzip
gzip on;
gzip_disable "msie6";
gzip_comp_level 6;
gzip_min_length 1100;
gzip_buffers 16 8k;
gzip_proxied any;
gzip_types text/plain application/xml text/css text/js text/xml application/x-javascript text/javascript application/javascript application/json application/xml+rss;
touch /etc/nginx/common/php-fpm
nano /etc/nginx/common/php-fpm
# Настройки порта или сокета PHP-FPM производятся в файле "/etc/php/5.6/fpm/pool.d/www.conf"
fastcgi_pass php-fpm;
# Порядок важен - строчка "include fastcgi_params" должна быть первой
include fastcgi_params;
fastcgi_split_path_info ^(.+?.php)(/.*)?$;
# Вместо переменной "$document_root" можно указать адрес к корневому каталогу сервера и это желательно (см. http://wiki.nginx.org/Pitfalls)
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_script_name;
# См. http://trac.nginx.org/nginx/ticket/321
set $path_info $fastcgi_path_info;
fastcgi_param PATH_INFO $path_info;
# Additional variables
fastcgi_param SERVER_ADMIN email@example.com;
fastcgi_param SERVER_SIGNATURE nginx/$nginx_version;
fastcgi_index index.php;
touch /etc/nginx/common/cache
nano /etc/nginx/common/cache
location ~* ".+.(?:ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|css|swf|js|atom|jpe?g|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$"
{
access_log off;
log_not_found off;
expires max;
}
В моем случаи, у меня был публичный wildcard сертификат для моего домена. Распаковываю файл сертификата и устанавливаю права доступа
openssl pkcs12 -in certname.pfx -nocerts -out /etc/nginx/ssl/key.pem -nodes
openssl pkcs12 -in certname.pfx -nokeys -out /etc/nginx/ssl/cert.pem
openssl rsa -in /etc/nginx/ssl/key.pem -out /etc/nginx/ssl/domain.key
cd /etc/nginx/ssl/
chown www-data:www-data domain.key
chmod 400 domain.key
touch /etc/nginx/common/ssl
nano /etc/nginx/common/ssl
ssl_certificate /etc/nginx/ssl/domain.crt;
ssl_certificate_key /etc/nginx/ssl/domain.key;
ssl_session_timeout 20m; # время 20 минут
ssl_session_cache shared:SSL:20m; # размер кеша 20МБ
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AE$
Настраиваем наш сервер:
nano /etc/nginx/sites-available/hotspot.domain.com
include common/upstream;
server
{
listen 80;
server_name hotspot.domain.com;
root /var/www;
index index.php index.html index.htm;
client_max_body_size 200m; # увеличение максимального объема файла для загрузки до 200МБ
# Buffers
fastcgi_buffers 64 4K;
include common/security;
include common/gzip;
location "/"
{
index index.php index.html index.htm; # варианты индексных файлов если имя файла в запросе не задано
try_files $uri $uri/ =404; # проверить есть ли файл из запроса на диске, иначе - вернуть ошибку 404
include common/deny;
include common/cache;
include common/php-fpm;
}
}
и делаем редирект с сервера по умолчанию (не забываем, что DNS сервер должен резолвить имя hotspot.domain.com):
nano /etc/nginx/sites-available/default
server {
listen 80 default_server;
listen [::]:80 default_server;
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
include common/ssl;
rewrite ^ http://hotspot.domain.com?url=$scheme://$host$request_uri? redirect;
...
}
Настраиваем phpmyadmin
Для более удобной работы с базой данных, устанавливаем phpmyadmin.
apt-get install phpmyadmin
apt-get install mcrypt php5.6-mcrypt php-gettext
touch /etc/nginx/common/phpmyadmin
nano /etc/nginx/common/phpmyadmin
location /phpmyadmin {
root /usr/share/;
index index.htm index.html index.php;
location ~ ^/phpmyadmin/(.+.php)$ {
try_files $uri = 404;
root /usr/share/;
fastcgi_pass unix:/run/php/php5.6-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $request_filename;
include /etc/nginx/fastcgi_params;
}
location ~* ^/phpmyadmin/(.+.(html|ico|xml|css|jpg|png|js|txt|gif|jpeg))$ {
root /usr/share/;
}
}
location /phpMyAdmin {
rewrite ^/* /phpmyadmin last;
}
и аналогично подключаем к nginx
nano /etc/nginx/sites-available/hotspot.domain.com
include common/phpmyadmin;
перезапускаемся
service nginx restart
service php5.6-fpm restart
Настройка фаервола iptables
В данном разделе, нам требуется ограничить доступ к серверу и завернуть трафик на прокси. Для начала, настроим сохранение всех правил после перезагрузки:
apt-get install iptables-persistent ipset
так как в нашей задаче, нам необходимо, чтобы сохранялись и таблицы ipset, то немного правим следующий файл:
nano /usr/share/netfilter-persistent/plugins.d/15-ip4tables
#в функцию save_rules()
touch /etc/iptables/rules.v4
touch /etc/iptables/ipset.rules
chmod 0640 /etc/iptables/ipset.rules
chmod 0640 /etc/iptables/rules.v4
iptables-save > /etc/iptables/rules.v4
ipset save > /etc/iptables/ipset.rules
#в функцию load_rules()
ipset restore < /etc/iptables/ipset.rules
iptables-restore < /etc/iptables/rules.v4 2> /dev/null
Для сохранения правил на фаерволе используем команду:
netfilter-persistent save
Для отслеживания и сброса установленных соединений используем conntrack
apt-get install conntrack
Настраиваем правила iptables
#создаем таблицу авторизованных mac-ip
ipset --create authorized macipmap --network 10.0.80.0/21
ipset -L authorized
#создаем дополнительные цепочки обработки правил
iptables -t nat -N toSQUID
iptables -t nat -N toHOTSPOT
#очищаем все ранее созданные правила
iptables -t nat -F PREROUTING
#пробрасываем DNS на наш сервер
iptables -t nat -A PREROUTING -i eth1 -p udp --dport 53 -j DNAT --to 10.66.66.1
#если ip,mac авторизован, то перенаправляем на squid
iptables -t nat -A PREROUTING -i eth1 -m set --match-set authorized src,src -j toSQUID
#если ip,mac не авторизован, то перенаправляем на заглушку
iptables -t nat -A PREROUTING -i eth1 -j toHOTSPOT
iptables -t nat -F toHOTSPOT
# в заглушке, все запросы перенаправляем на наш сервер nginx
iptables -t nat -A toHOTSPOT -p tcp -m multiport --dports 80,8080 -j DNAT --to-destination 10.0.87.254:80
iptables -t nat -A toHOTSPOT -p tcp -m multiport --dports 443 -j DNAT --to-destination 10.0.87.254:443
iptables -t nat -A toHOTSPOT -j RETURN
iptables -t nat -F toSQUID
# перенаправляем разрешенные порты на прокси
#создаем обход хотспота для telegram (так и не смог заставить работать через squid)
iptables -t nat -A toSQUID -p tcp -d 149.154.164.0/22 --dport 443 -j ACCEPT
#аналогично для whatsapp
iptables -t nat -A toSQUID -p tcp -m multiport --dport 4244,5242,5228,5223,5222 -j ACCEPT
iptables -t nat -A toSQUID -p tcp -m multiport --dports 80,25,465,110,995,119,563,8080 -j REDIRECT --to-ports 3128
iptables -t nat -A toSQUID -p tcp -m multiport --dports 443 -j REDIRECT --to-ports 3129
iptables -t nat -A toSQUID -j RETURN
#Включаем NAT для прошедших пакетов
iptables -t nat -A POSTROUTING -s 10.0.80.0/21 -o eth0 -j MASQUERADE
#политика по умолчанию для входящих пакетов
iptables -P INPUT ACCEPT
iptables -F INPUT
#разрешаем траффик на lo
iptables -A INPUT -i lo -j ACCEPT
#разрешаем доступ к портам управления из вышестоящей сети
iptables -A INPUT -i eth0 -p tcp -m multiport --dports 22,80,443 -j ACCEPT
#разрешаем пинг и трасерт
iptables -A INPUT -p icmp -m icmp --icmp-type 0 -j ACCEPT
iptables -A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT
#разрешаем работу уже открытых соединений
iptables -A INPUT -p TCP -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p UDP -m state --state ESTABLISHED,RELATED -j ACCEPT
#разрешаем доступ на прокси
iptables -A INPUT -i eth1 -p tcp -m multiport --dport 3128,3129,3130 -j ACCEPT
iptables -A INPUT -i eth1 -p udp -m multiport --dport 3128,3129,3130 -j ACCEPT
№разрешаем доступ на заглушку
iptables -A INPUT -i eth1 -p tcp -m multiport --dports 80,443 -j ACCEPT
#политика по умолчанию для входящих пакетов
iptables -P INPUT DROP
#создаем цепочку для проблемных сервисов, которым разрешена пересылка
iptables -N FORWARD_AUTHORIZED
iptables -F FORWARD_AUTHORIZED
# разрешаем пересылку telegram
iptables -A FORWARD_AUTHORIZED -p tcp -d 149.154.164.0/22 --dport 443 -j ACCEPT
# разрешаем пересылку whatsapp
iptables -A FORWARD_AUTHORIZED -p tcp -m multiport --dport 4244,5242,5228,5223,5222 -j ACCEPT
iptables -A FORWARD_AUTHORIZED -j RETURN
#политика по умолчанию для пересылаемых пакетов
iptables -P FORWARD ACCEPT
iptables -F FORWARD
#разрешаем пересылку на dns
iptables -A FORWARD -i eth1 -p udp --dport 53 -d 10.66.66.1 -j ACCEPT
#разрешаем работу уже открытых соединений
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
#перенаправляем авторизованных клиентов на проверку в цепочку FORWARD_AUTHORIZED
iptables -A FORWARD -i eth1 -m set --match-set authorized src,src -j FORWARD_AUTHORIZED
#политика по умолчанию для пересылаемых пакетов
iptables -P FORWARD DROP
Для отлавливания проблем используем следующие команды
#сброс установленных соединений с заданного ip
conntrack -D conntrack --orig-src 10.0.8X.XXX
#просмотр новых записей в логе
tail -f /var/log/firewall | grep 10.0.8X.XXX
Настраиваем php файлы обработки запросов и скрипты
Создаем таблицы в нашей базе данных:
SET FOREIGN_KEY_CHECKS=0;
CREATE TABLE IF NOT EXISTS `mac-auth` (
`mac` char(17) NOT NULL COMMENT 'мак адрес устройства',
`code` int(6) NOT NULL COMMENT 'код авторизации',
`phone` varchar(15) NOT NULL DEFAULT '' COMMENT 'телефонный номер с которого пришло смс',
`updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'дата последнего изменения',
`created` datetime DEFAULT CURRENT_TIMESTAMP COMMENT 'дата создания',
UNIQUE KEY `mac` (`mac`),
KEY `code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `mac-ip` (
`mac` char(17) NOT NULL COMMENT 'мак адрес устройства',
`ip` varchar(15) NOT NULL COMMENT 'ip адрес устройства',
`date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'дата изменения',
KEY `mac` (`mac`),
KEY `ip` (`ip`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `mac-phone` (
`mac` char(17) NOT NULL COMMENT 'мак адрес устройства',
`phone` varchar(15) NOT NULL COMMENT 'номер телефона',
`date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'дата изменения',
KEY `mac` (`mac`),
KEY `ip` (`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `mac-ip`
ADD CONSTRAINT `mac` FOREIGN KEY (`mac`) REFERENCES `mac-auth` (`mac`) ON DELETE CASCADE ON UPDATE CASCADE;
SET FOREIGN_KEY_CHECKS=1;
создаем скрипт, при входе в систему, для проверки работы всех служб:
nano /var/www/check_services.sh
#!/bin/bash
function check {
printf %-30s "check $1"
if (( $(ps -ef | grep -v grep | grep $1 | wc -l) > 0 ))
then
echo -e "[33[32;1m OK 33[0m]"
else
echo -e "[33[31;1m ERROR 33[0m]"
fi
}
check dhcpd
check nginx
check mysql
check php-fpm
check smstools
check squid
check usbsrvd
устанавливаем права запуска и добавляем на старт
chmod 755 /var/www/check_services.sh
nano ~/.profile
/var/www/check_services.sh
а теперь исходники php файлов:
nano /var/www/sms_recieve.php
#!/usr/bin/php
<?php
require_once 'hotspot/connect.php';
$sms_type = $argv[1];
$sms_file = $argv[2];
$sms_file_content = file_get_contents($sms_file);
$i = strpos($sms_file_content, "nn");
$sms_headers_part = substr($sms_file_content, 0, $i);
$sms_message_body = substr($sms_file_content, $i + 2);
$sms_header_lines = split("n", $sms_headers_part);
$sms_headers = array();
foreach ($sms_header_lines as $header)
{
$i = strpos($header, ":");
if ($i !== false)
$sms_headers[substr($header, 0, $i)] = substr($header, $i + 2);
}
$phone = (float)$sms_headers['From'];
$code = (int)$sms_message_body;
//проверяем базу на старые записи
$interval = '30 DAY';
//$interval = '1 MINUTE';
$macs = sql_getRows('SELECT `mac` FROM `mac-auth` WHERE `code` = 1 AND `created` < DATE_SUB(NOW(),INTERVAL '.$interval .')');
if (!empty($macs)){
$data = sql_getColumn('SELECT DISTINCT(`ip`) as `ip`,`mac` FROM `mac-ip` WHERE `mac` IN ("'.implode('","',$macs).'") ORDER BY `date` DESC','mac');
foreach ($data as $mac=>$ip){
sql_query('UPDATE `mac-auth` SET `code` = "0" WHERE `mac` IN ("'.implode('","',$macs).'")');
//удаляем авторизацию на фаерволе
shell_exec('sudo ipset -D authorized '.$ip .','.$mac);
//сбрасываем все подключения данного ip
shell_exec('sudo conntrack -D conntrack --orig-src '.$ip);
}
shell_exec('ipset save > /etc/iptables/ipset.rules');
}
echo $phone.'-'.$code;
$mac = sql_getValue('SELECT `mac` FROM `mac-auth` WHERE `code` = '.$code);
if ($mac){
$ip = sql_getValue('SELECT DISTINCT(`ip`) FROM `mac-ip` WHERE `mac` = "'.$mac.'" ORDER BY `date` DESC');
echo '-'.$mac.'-'.$ip;
//вносим в базу информацию о номере телефона
sql_query('INSERT `mac-phone` (`mac`,`phone`) VALUES("'.$mac.'","'.$phone.'")');
sql_query('UPDATE `mac-auth` SET `phone` = "'.$phone.'", `code` = "1" WHERE `mac` = "'.$mac.'"');
//разрешаем доступ на фаерволе
shell_exec('sudo ipset -A authorized '.$ip .','.$mac);
shell_exec('ipset save > /etc/iptables/ipset.rules');
shell_exec('sudo conntrack -D conntrack --orig-src '.$ip);
}
//удаляем сообщение
unlink($sms_file);
?>
nano /var/www/hotspot/index.php
<?php
//устанавливаем подключение с базой данных
require_once 'connect.php';
//определяем ip адрес
$ip = '';
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
if (empty($ip)) $ip = $_SERVER['REMOTE_ADDR'];
//определяем мак адрес
$mac = trim(shell_exec("arp -a ".$ip." | awk '{print $4}'"));
if ($mac == "entries") $mac = false;
//определяем адрес по которому обращался пользователь
$url = $_GET['url'];
if (strpos($url, 'gstatic.com')!==false) $url = 'http://www.domain.ru';
//выключаем кэширование браузером
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Дата в прошлом
// строка ошибки
$error = false;
if (!$mac){
$error = 'Ошибка с Вашей сетевой картой. <br/>Доступ в интернет не может быть предоставлен.<br/><font class="eng error">Error with your network card. Internet access can not be granted</font>';
} else {
//проверяем, не изменился ли ip адрес у абонента
$base_ip = sql_getValue('SELECT DISTINCT(`ip`) FROM `mac-ip` WHERE `mac` = "'.$mac.'" ORDER BY `date` DESC');
if ($base_ip != $ip){
sql_query('INSERT INTO `mac-ip` (`mac`,`ip`) VALUES ("'.$mac.'","'.$ip.'")');
}
//проверяем авторизацию мак адреса
$code = sql_getValue('SELECT `code` FROM `mac-auth` WHERE `mac` = "'.$mac.'"');
switch ($code){
case '1':
//абонент авторизован
shell_exec('sudo ipset -A authorized '.$ip .','.$mac);
shell_exec('ipset save > /etc/iptables/ipset.rules');
header('Location: '.$url);
//shell_exec('sudo conntrack -D conntrack -p tcp --dport 80 --state ESTABLISHED --src '.$ip.' --dst 10.0.87.254');
die;
break;
case '':
//новый абонент
sql_query('INSERT INTO `mac-auth` (`mac`,`code`) VALUES ("'.$mac.'",0)');
case '0':
//абонент без авторизации и без кода доступа
//генерируем код
do {
$code = rand(10000,99999);
} while(sql_getValue('SELECT `mac` FROM `mac-auth` WHERE `code` = "'.$code.'"'));
sql_query('UPDATE `mac-auth` SET `code` = '.$code.' WHERE `mac` = "'.$mac.'"');
default:
//абонент без авторизации, но с генерированным кодом
break;
}
}
?>
<!DOCTYPE HTML>
<html>
<head>
<title>Хотспот</title>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<meta name="viewport" content="width=device-width, initial-scale=0.8, maximum-scale=1" />
</head>
<body>
<style type="text/css">
html, body {
width: auto;
margin: 0px;
padding: 0px;
}
* {
font-family: "Tahoma", sans-serif;
font-size: 14px;
text-align:center;
font-style: normal;
font-variant: normal;
font-weight: normal;
color: #000;
}
.center {
width: 400px;
padding: 10px;
margin: auto;
}
.logo {
background-image: url("/logo2.gif");
width: 177px;
height: 209px;
margin: auto;
}
.header {
font-size: 16px;
padding: 10px;
}
.repeat {
padding: 10px;
}
.sms {
font-size: 11px;
color: #4c4b4b;
padding: 10px;
}
.law {
font-size: 11px;
color: #4c4b4b;
padding: 10px;
}
.code {
font-size: 16px;
font-weight: bold;
}
.num {
font-size: 16px;
font-weight: bold;
}
.internet {
background-color: #3e315f;
#border: none;
color: white;
padding: 7px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
border: 2px solid #ffffff;
border-radius: 4px;
box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2), 0 6px 20px 0 rgba(0,0,0,0.19);
}
.internet:hover {
background-color: #ffffff;
color: #3e315f;
border: 2px solid #3e315f;
box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);
}
.eng {
font-size: 12px;
line-height: 12px;
}
.error {
color: #d11313;
}
</style>
<div class="center">
<div class="logo"></div>
<div class="header">Добро пожаловать в открытую сеть <br/><font class="eng">Welcome to the open network</font></div>
<?php if ($error) { echo '<div class="error">'.$error.'</div>';} else { ?>
<div>Для получения доступа в интернет, Вам необходимо c Вашего телефона отправить смс с кодом: <font class="code"><?php echo $code;?></font> на номер: <font class="num">+7 917 XXX-XX-XX.</font><br/><font class="eng">To get access to the Internet, you need to send SMS with code <b><?php echo $code;?></b> to number <b>+7 917 XXX-XX-XX.</b></font></div>
<div class="repeat">Если Вы уже отправили смс с кодом, то нажмите на кнопку: <br/><font class="eng">If you have to send SMS with the code, then click on the button:</font></div>
<a target="_self" href="<?php echo $url;?>" class="internet">перейти в интернет</a>
<div class="repeat">Задержка предоставления доступа в интернет зависит от скорости доставки нам Вашего смс сообщения.<br/><font class="eng">Delayed access to the Internet depends on the speed of delivery of SMS messages to us.</font></div>
<div class="sms">Стоимость SMS-сообщений тарифицируется согласно вашему тарифному плану.<br/>Cost of SMS-messages is charged according to your tariff plan.</div>
<div class="law">Согласно постановлению Правительства №758 от 31 июля 2014г. и №801 от 12 августа 2014 г. - все публичные WIFI сети обязаны производить идентификацию пользователей.<br/>According to the decree №758 of the Government dated 31 July 2014. and №801 from 12 August 2014 - all public WIFI network required to make user authentication.</div>
<?php } ?>
</div>
</body>
</html>
nano /var/www/hotspot/connect.php
# mysql login params
$mysql_host = '';
$mysql_db = '';
$mysql_login = '';
$mysql_password = '';
$sql_link = mysqli_connect($mysql_host, $mysql_login, $mysql_password, $mysql_db) or die(mysql_error());
function sql_query($sql, $unbuffered = false){
global $sql_link;
$resource = $unbuffered ? mysqli_real_query($sql_link, $sql) : mysqli_query($sql_link, $sql);
if (mysqli_errno($sql_link) === 1016) {
if (preg_match("/'(S*).MYD'. (errno: 145)/", mysqli_error($sql_link), $m)) {
mysqli_unbuffered_query("REPAIR TABLE ".$m[1]);
$resource = $unbuffered ? mysqli_unbuffered_query($sql_link, $sql) : mysqli_query($sql_link, $sql);
}
}
return $resource;
}
function sql_getValue($sql, $unbuffered = false){
$resource = sql_query($sql, $unbuffered);
$ret = false;
if (is_resource($resource) || is_object($resource)) {
$row = mysqli_fetch_row($resource);
$ret = $row[0];
}
return $ret;
}
function sql_getRows($sql, $use_key = false, $unbuffered = false){
$resource = sql_query($sql, $unbuffered);
$ret = false;
if (is_resource($resource) || is_object($resource)) {
$row = true;
while ($row) {
$row = mysqli_fetch_array($resource, MYSQLI_ASSOC);
if (!$row) continue;
$_row = $row;
if ($use_key) {
if (isset($_row[$use_key])) {
$link = &$ret[$_row[$use_key]];
$link = $_row;
}
} else {
$link = &$ret[];
$link = (count($_row) == 1) ? array_shift($_row) : $_row;
}
}
}
return $ret;
}
function sql_getColumn($sql, $id_field = 'id', $unbuffered = false){
$resource = sql_query($sql, $unbuffered);
$ret = false;
if (is_resource($resource) || is_object($resource)) {
$row = true;
while ($row) {
$row = mysqli_fetch_array($resource, MYSQLI_ASSOC);
if (!$row) continue;
$_row = $row;
$id = $_row[$id_field];
unset($_row[$id_field]);
$value = current($_row);
$ret[$id] = $value;
}
}
return $ret;
}
function sql_getRow($sql, $unbuffered = false){
$resource = sql_query($sql, $unbuffered);
$ret = false;
if (is_resource($resource) || is_object($resource)) {
$ret = mysqli_fetch_assoc($resource);
}
return $ret;
}
Ну а теперь, добавим в sudoers права для управления iptables из php
nano /etc/sudoers
smsd ALL=(ALL) NOPASSWD: /sbin/ipset
smsd ALL=(ALL) NOPASSWD: /sbin/iptables
www-data ALL=(ALL) NOPASSWD: /sbin/ipset
www-data ALL=(ALL) NOPASSWD: /sbin/iptables
smsd ALL=(ALL) NOPASSWD: /usr/sbin/conntrack
www-data ALL=(ALL) NOPASSWD: /usr/sbin/conntrack
На этом вроде всё. Система в работе уже месяц, полёт нормальный. Спасибо за внимание!
Автор: vetal-pro