1 авг. 2012 г.

Строительство беспроводной сети своими руками. Или интернет в массы.

Не рассматривается прокидывание кабеля. Рассматривается только активное оборудование и настройка серверной части.

Дано:
Сервер с freebsd, точки доступа UniFi Ap (Ubiquiti Networks), машина с OS Windows.

Задача:
1. Построить беспроводную сеть с авторизацией через web-интерфейс
2. Обеспечить логирование доступа.

Решение.
1. Включаем точки доступа в уже существующую сеть. В эту же сеть выносим машину с OS windows.
2. Производим настройку точек доступа через программу, прилагаемую на диске. Описывать настройки точек доступа не вижу смысла, ибо там все и так ясно.
3. Настраиваем FreeBSD.

Для FreeBSD нам потребуется:
  1. Пересборка ядра;
  2. настроить pf;
  3. написать пару скриптов;
  4. apache22;
  5. php5;
  6. mysql-server;
  7. isc-dhcpd-server;
  8. sudo;
  9. mrtg
1. Собираем ядро со следующими параметрами:

device        pf
device        pflog

options        ALTQ
options        ALTQ_CBQ        # Class Bases Queueing
options        ALTQ_RED        # Random Early Detection
options        ALTQ_RIO        # RED In/Out
options        ALTQ_HFSC       # Hierarchical Packet Scheduler
options        ALTQ_CDNR       # Traffic conditioner
options        ALTQ_PRIQ       # Priority Queueing
options        ALTQ_NOPCC      # Required for SMP build


2. Настройка pf:

table <  rfcnets  >    {10.0.0.0/8, !192.168.1.0/24, 192.168.0.0/16, !172.16.0.0/16, 172.16.0.0/12 }
table <  me  >      { self, 172.16.0.1}

table <  permgroup  >

# Options: tune the behavior of pf, default values are given.
set timeout { interval 10, frag 30 }
set timeout { tcp.first 120, tcp.opening 30, tcp.established 86400 }
set timeout { tcp.closing 900, tcp.finwait 45, tcp.closed 5 }
set timeout { udp.first 60, udp.single 30, udp.multiple 60 }
set timeout { icmp.first 20, icmp.error 10 }
set timeout { other.first 60, other.single 30, other.multiple 60 }
set timeout { adaptive.start 0, adaptive.end 0 }
set limit { states 10000000, frags 50000 }
set loginterface none
#set optimization normal
set block-policy drop
set require-order yes
set fingerprints "/etc/pf.os"
#set state-policy if-bound

# Normalization: reassemble fragments and resolve or reduce traffic ambiguities.
scrub in all

# Поднимаем nat для всех, кто попал в таблицу permgroup, остальных редиректим на 10.10.0.2 при попытке перехода на любой внешний сайт, для авторизации.
nat on em0 from <  permgroup  > to any -  > em0
rdr on em1 proto tcp from !<  permgroup  > to any port 80 -  > 10.10.0.2


# Разрешаем loopback
pass quick on lo from any to any

# Описываем IN правила
#Запрещаем rfc сети на внешнем интерфейсе
block in quick on em0 from <  rfcnets  > to any

# Правила для сервисов (я открываю только то, что нужно и для внутренней сети)
# icmp
pass in quick inet proto icmp from any to 10.10.0.2 icmp-type echoreq keep state
# ssh
pass in quick proto tcp from any to 10.10.0.2 port 22 flags S/SA keep state
# http,https
pass in quick proto tcp from any to 10.10.0.2 port {443, 80} flags S/SA keep state
# dns
pass in quick proto {tcp, udp} from any to <  me  > port domain flags S/SA keep state

# Все остальное к серверу блокируем
block in quick from any to <  me  >

# Разрешаем доступ в инет для permgroup, при этом логируем доступ
pass in log quick from any to <  permgroup  >
pass in log quick from <  permgroup  > to any

# Остальное на вход блокируем
block in quick all

# Правила для OUT
# запрещаем rfc сети на внешнем интерфейсе.
block out quick on em0 from <  rfcnets  > to any

#Разрешаем все от самого сервера.
pass out quick from <  me  > to any keep state

# Разрешаем доступ в инет для permgroup, при этом логируем доступ
pass out log quick from any to <  permgroup  >
pass out log quick from <  permgroup  > to any

block out quick all



3. Скрипты для управления, администрирования и авторизации:

3.1. Управление сессиями (pf.sh):#!/bin/sh
/bin/echo `/usr/local/bin/sudo /sbin/pfctl -t permgroup -T $1 $2` >> /usr/home/admin/wifi/w.txt

Скрипт выполняет действия $1 для $2 (опц.) с таблицей permgroup, которая ранее была описана в файле pf.conf.
Пример:
  1. pf.sh add 10.10.0.1/32 - Добавит запись в таблицу permgroup ip-адрес 10.10.0.1/32;
  2. pf.sh delete 10.10.0.2/32 - Добавит запись в таблицу permgroup ip-адрес 10.10.0.2/3;2;
  3. pf.sh show - Выведет список адресов из таблицы permgroup.

3.2. Авторизация клиента:
3.2.1. index.html:
<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN" "http://www.wapforum.org/DTD/xhtml-mobile10.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <meta name="MobileOptimized" content="240"/>
    <meta name="viewport" content="initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=yes, width=240" />
    <title></title>
  </head>

  <body bgcolor="#ffffff">
    <form action="valid.php" id="valid" method="post">
    <table>
      <tr>
        <td colspan="2" align="center">
          <img src="img/logo_150x40.gif" >
        </td>
      </tr>
      <tr>
        <td colspan="2" align="center">
          &nbsp;
        </td>
      </tr>
      <tr>
        <td>
          Логин
        </td>
        <td>
          <INPUT type="text" size="15" id="user" name="user" value="" placeholder="Логин">
        </td>
      </tr>
      <tr>
        <td>
          Пароль:
        </td>
        <td>
          <INPUT type="password" size="15" name="passwd" value="" placeholder="Пароль">
        </td>
      </tr>
      <tr>
        <td colspan="2" align="center">
          &nbsp;
        </td>
      </tr>
      <tr>
        <td colspan="2" align="center">
          <INPUT style = "width:100%; height:50; font-size: 25pt;" type="submit" value="Вход">
        </td>
      </tr>
    </table>
  </body> 
</html>
В принципе, стандартная форма авторизации за исключением того, что она адаптирована под мобильные платформы.
3.2.2. valid.php:
<?php
  session_start(); 
  error_reporting(E_ALL); 
  if (!isset($_POST["user"])) { 
    header("Location: "."index.html"); 
    exit(1); 

  require "config.php"; 
  require "func.php"; 
  
  date_default_timezone_set('Asia/Yekaterinburg'); 
  $pUser = ""; 
  $pPass = ""; 
  $pIp = ""; 
  $pId = ""; 
  $date = date("Y-m-d H:i:s"); 
  $day = date("Y-m-d 00:00:00"); 

  if (!preg_match ('/[^\w]/', $_POST["user"])) { 
    $pUser = $_POST["user"]; 
  else { 
    srv_error(1); 

  if (!preg_match ('/[^\w]/', $_POST["passwd"])) { 
    $pPass = $_POST["passwd"]; 
  else { 
    srv_error(1); 
  }
 
  $db1 = mysql_connect($db_host1, $db_user1, $db_pass1) or die(); 
  mysql_select_db($db_name1, $db1); 
  $result1 = mysql_query("SELECT * FROM abonent WHERE username ='$pUser' AND password='$pPass';", $db1); 

  $db2 = mysql_connect("127.0.0.1", "root", "") or die(); 
  mysql_select_db("wifi", $db2); 

  $row = mysql_fetch_array($result1, MYSQL_NUM); 
  if ($row === FALSE) { 
    accesslog($logfile, $_SERVER["REMOTE_ADDR"], $pUser, $pPass, 1); 
    srv_error(1); 
  else { 
    $result2 = mysql_query("SELECT ip FROM log WHERE user ='$pUser' AND dt >= '$day';", $db2); 
    while(($row = mysql_fetch_array($result2, MYSQL_NUM)) !== FALSE) { 
      shell_exec("/usr/home/admin/wifi/pf.sh" . " delete " . $row[0] . "/32"); 
    }
    shell_exec("/usr/home/admin/wifi/pf.sh" . " add " . $_SERVER["REMOTE_ADDR"] . "/32");
 
    mysql_query("INSERT INTO log (dt, ip, user, passwd, ok) VALUES ('$date', '{$_SERVER["REMOTE_ADDR"]}' ,'$pUser' , '', 1)", $db2) or die(); 
    header("Location: "."http://ya.ru"); 
?>
Скрипт берет данные о логине и пароле из db1, а логирование авторизации ведет в db2.
Скрипт открывает доступ в инет, в случае успешной авторизации. Если есть 2 сессии, то первая открытая сессия будет закрыта.
3.2.3. func.php: <?php function srv_error($value) {   if ($value == 1) {     $meta = "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n<meta http-equiv=\"refresh\" content=\"15; url=index.html\"><title>Ошибка доступа.</title>";     $msg = "<h1>Не знаю что не правильно вы ввели, но вы не авторизовались.</h1>";   }   if ($value == 2) {     $meta = "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n<meta http-equiv=\"refresh\" content=\"15; url=index.html\"><title>Ошибка сервера.</title>";     $msg = "<h1>Внутренняя ошибка сервера.</h1>";   }     echo "<html>     <head>"       . $meta .         "</head>         <body> "          . $msg .         "</body>       </html>";     unset($_SESSION["id"]);     exit(1); } function accesslog($file, $ip, $login, $passwd, $flag) {   $date = date("d.m.Y H:i:s");   if ($flag == 0) { $log = "[" . $date . "] from " . $ip . " " . $login . " " . $passwd . " GRANTED\n";   }   elseif ($flag == 1) {   $log = "[" . $date . "] from " . $ip . " " . $login . " " . $passwd . " DENIED!\n";   }

    $fp = fopen($file, 'a+');
    fwrite($fp, $log);
    fclose($fp); 
  }    
?>


srv_error - функция подставляет сообщение об ошибке (неверный логин/пароль или внутренняя ошибка). accesslog - функция, которая ведет запись в файл попытки авторизации, сохраняя логин и пароль. 4 и 5. Apache и php оставляем без изменений. Единственное, что надо сделать, так это установить php5-extentions пакет для того, чтобы php понимал как работать с СУБД. 6. MySQL-server, Установка mysql сервера проходит в штатном режиме, а вот БД создать придется.
CREATE DATABASE wifi;
CREATE TABLE `log` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `dt` datetime DEFAULT NULL,
  `ip` varchar(32) DEFAULT NULL,
  `user` varchar(255) DEFAULT NULL,
  `passwd` varchar(255) DEFAULT NULL,
  `ok` tinyint(1) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=latin1;



Одна единственная таблица в БД.




7. dhcp

Настройка dhcp делается за 2 минуты. Я выдаю ip на 12 часов.

Конфиг:

option domain-name "example.org"; 
option domain-name-servers ns1.example.org, ns2.example.org; 
default-lease-time 43200; 
max-lease-time 43201; 
log-facility local7;

subnet 10.10.0.0 netmask 255.255.0.0 {
  range 10.10.228.1 10.10.228.254;
  option routers 10.10.0.2;
  option domain-name-servers 10.10.254.253;
}

Выделяем ip адреса для этого dhcp из диапазона 10.10.228.0/24, 10.10.0.2 - адрес этого же сервера, который еще и является шлюзом.

8. sudo
Сразу расставим все точки. Дабы не было путаницы. Политика безопасности в freebsd определяется группами, т.е. если пользователь принадлежит группе wheel, то пользователь может выполнить команду su, в остальных случаях sorry. В linux (в частности ubuntu/debian)  sudo используется для разграничения пользователей, которые могут выполнять ту самую команду su. Мы будем использовать sudo для того, чтобы выполнять действия, на которые нужны привилегия su, а именно приложение pfctl на право добавления/удаления записи в таблицу. Вот, собственно, конфиг sudoers:

Cmnd_Alias WIFI = /sbin/pfctl , /bin/sh
%www ALL = NOPASSWD : WIFI


Эти 2 строки добавляются в самый конец файла. Поясню, что создается алиас WIFI для списка программ pfctl и sh. Далее разрешаем использовать команды, прописанные в WIFI для группы www. Именно для группы. Почему для www? потому что скрипт valid.php выполняется от пользователя www.

9. mrtg
Чтобы хоть как-то мониторить количество открытых сессий на сервере можно набросать скрипт для mrtg, который будет отрисовывать график с подключениями.

#!/bin/sh

in=`/sbin/pfctl -t permgroup -T show | /usr/bin/wc -l`
out=`/sbin/pfctl -t permgroup -T show | /usr/bin/wc -l`

/bin/echo $in
/bin/echo $out
/bin/echo `/usr/bin/uptime | /usr/bin/cut -d, -f1`
/bin/echo authuser



В заключении рассмотрим настройку самого pflog. 
Настраивается он добавлением этих 2х строк в /etc/rc.conf:
pflog_enable="YES"
pflog_logfile="/var/log/pflog"


Комментариев нет:

Отправить комментарий

Примечание. Отправлять комментарии могут только участники этого блога.