Ipfw, mysql, shapper — Шейпим траффик при помощи IPFW, mySQL



Добрый день,

В один из зимних вечеров мой знакомый предложил мне интересный проект, запуск небольшого провайдера в одной из развивающихся стран, на тот момент я был свободен и согласился. Нами была составлена сетевая схема провайдера и так же определен перечень оборудования для запуска. Доступ в интернет должен будет обеспечиваться через Infininet R5000 и клиентские станции. Сказано….сделано… Поставили базовую станцию, поставили клиентский комплект (КК). Прописали КК на БС и интернет пошел, но нужна гибкость и мы решили авторизовать КК через радиус.

Настроили, поставили и… Infininet R5000 не дружит с радиусом, досадно конечно же, но сдавать проект нужно и нужно найти решение, как сделать все гибко и так, чтобы КК не ходил в инет, если клиент не оплатил.

И тут нам пришла мысль делать это на роутере под управлением FreeBSD, так же нужно было учитывать, что все действия будет выполнять обычные пользователи. Для этого мы выбрали pdmenu. Задали в конфигурации pdmenu нужные нам пункты меню — получилось так:
┌───────Clients───────┐
│ Enable Client │
│ Disable Client │
│ Set Bandwith │
│ Unset Bandwith │
│ Show Firewall Rules │
│ Show Shapper │
│ │
│ Main menu… │
└─────────────────────┘
Здесь оператору предлагается несколько действия, а именно включить или выключить клиента (подсеть), установить шейпер или же снять шейпер. Ниже приведен скрипт, как все это реализовывается. В зависимости от действия в скрипт передается переменная — enable, disable… и так далее

#!/usr/local/bin/bash

function save_q ()
{
str=$1
echo "$str" >> /usr/local/etc/queues
}

function remove_q()
{
str=$1
#echo $str
`sed -i "" "/\$1/d" /usr/local/etc/queues`
}

function save_rules ()
{
echo > /usr/local/etc/ipfw.rules
echo "#!/bin/sh
# Flush out the list before we begin.
ipfw -q -f flush
" > /usr/local/etc/ipfw.rules

cat /usr/local/etc/queues >> /usr/local/etc/ipfw.rules

`ipfw list | perl -nle 's/table\((\d+)\)/\"table($1)"/g; print "\/sbin/ipfw add $_";' >> /usr/local/etc/ipfw.rules`
}

function check_ip ()
{
ip=$1
if [ "$ip" = "0.0.0.0" ]
then
echo "can not block 0.0.0.0";
exit
elif [ -z "$ip" ]
then
echo "empty line"
exit
else
return
fi
}
function check_table_ip ()
{
ip=$1
fid=`mysql -h mysql firewall -e "select fid from f_rules where src='$ip'" | xargs | awk -F' ' '{print$2}'`
fid1=`mysql -h mysql firewall -e "select fid from f_rules where dst='$ip'" | xargs | awk -F' ' '{print$2}'`
if [[ -z $fid && -z $fid1 ]]
then
return
else
echo "Already in table. Please remove it"
exit
fi
}

function check_table_ip_shape ()
{
ip=$1
fid=`mysql -h mysql firewall -e "select uid from s_rules where src='$ip'" | xargs | awk -F' ' '{print$2}'`
fid1=`mysql -h mysql firewall -e "select uid from s_rules where dst='$ip'" | xargs | awk -F' ' '{print$2}'`
if [[ -z $fid && -z $fid1 ]]
then
return
else
echo "Already in table. Please remove it"
exit
fi
}

function check_speed ()
{
dwn=$1
up=$2
if [[ "$dwn" = "0" ]]
then
echo "Download Speed can not be 0"
exit
elif [ -z "$dwn" ]
then
echo "Download Speed can not empty"
exit
fi
if [[ "$up" = "0" ]]
then
echo "Upload Speed can not be 0"
exit
elif [ -z "$up" ]
then
echo "Upload Speed can not empty"
exit
fi

}

clear;
act=$1
if [[ "$act" = "enable" ]]
then
read -p "Please enter client's Name:" name
read -p "Please enter client's Subnet:" ip
check_ip $ip
last=`mysql -h mysql firewall -e "select id from f_rules ORDER BY ID DESC limit 1" | xargs | awk -F' ' '{print$2}'`
let "last=last+1000"
#check if IP in table or not
#check_table_ip_shape $ip
check_table_ip $ip
q=`mysql -h mysql firewall -e "INSERT INTO f_rules VALUES (NULL,'$last','$ip','any','$name')"`
#add rules
ipfw -q add $last allow all from $ip to any
let "last=last+1"
ipfw -q add $last allow all from any to $ip
q=`mysql -h mysql firewall -e "INSERT INTO f_rules VALUES (NULL,'$last','any','$ip','$name')"`
save_rules

elif [[ "$act" = "disable" ]]
then
read -p "Please enter client's Subnet:" ip
#check_table_ip_shape $ip
check_ip $ip
#check_table_ip $ip
fid=`mysql -h mysql firewall -e "select fid from f_rules where src='$ip'" | xargs | awk -F' ' '{print$2}'`
if [ -z $fid ]
then
echo "Subnet not found"
exit
fi
#remove rules - we will remove 2 rules
# echo $fid
ipfw delete $fid
let "fid=fid+1"
ipfw delete $fid
save_rules
#remove from table f_rules
`mysql -h mysql firewall -e "delete from f_rules where src='$ip'"`
`mysql -h mysql firewall -e "delete from f_rules where dst='$ip'"`

elif [[ "$act" = "set_ban" ]]
then
read -p "Please enter client's Subnet:" ip
read -p "Download Speed kbps/s:" dwn
read -p "Upload Speed kbps/s:" up
check_ip $ip
check_speed $dwn $up
last=`mysql -h mysql firewall -e "select id from s_rules ORDER BY ID DESC limit 1" | xargs | awk -F' ' '{print$2}'`
let "last=last+100"
q_m=$last;
check_table_ip_shape $ip
#add into table
q=`mysql -h mysql firewall -e "INSERT INTO s_rules VALUES (NULL,'$last','any','$dwn','$up','$ip')"`
#add rules download speed
#adding pipe
/sbin/ipfw pipe $q_m config bw "$dwn"Kbit/s
save_q '/sbin/ipfw pipe '$q_m' config bw '$dwn'Kbit/s'
#firewall
/sbin/ipfw add $last pipe $q_m ip from any to $ip out
#in recv em0
#add rules upload speed
let "q_m_n=q_m+1"
/sbin/ipfw pipe $q_m_n config bw "$up"Kbit/s
save_q '/sbin/ipfw pipe '$q_m_n' config bw '$up'Kbit/s'
#adding queue
#firewall rules
let "last=last+1"
/sbin/ipfw add $last pipe $q_m_n ip from $ip to any in
q=`mysql -h mysql firewall -e "INSERT INTO s_rules VALUES (NULL,'$last','$ip','$dwn','$up','any')"`
#out xmit em0
let "last=last+1"
ipfw add $last skipto 63800 ip from any to $ip
let "last=last+1"
ipfw add $last skipto 63800 ip from $ip to any
save_rules

elif [[ "$act" = "unset_ban" ]]
then
read -p "Please enter client's Subnet:" ip
check_ip $ip
res=`mysql -h mysql firewall -e "select uid,down,up from s_rules where dst='$ip'"`
fid=`echo $res | awk -F' ' '{print$4}'`
up=`echo $res | awk -F' ' '{print$6}'`
down=`echo $res | awk -F' ' '{print$5}'`

# | xargs | awk -F' ' '{print$2}'`
if [ -z $fid ]
then
echo "Subnet not found"
exit
fi
#remove firewall rules
pipe=$fid
uid=$fid
ipfw delete $fid
let "fid=fid+1"
ipfw delete $fid
#remove pipe
ipfw pipe delete $pipe
ipfw pipe delete $fid
let "fid=fid+1"
ipfw delete $fid
let "fid=fid+1"
ipfw delete $fid
save_rules

remove_down="pipe $uid config bw $down"
let "uid=uid+1"

remove_up="pipe $uid config bw $up"
remove_q "$remove_down"
remove_q "$remove_up"

`mysql -h mysql firewall -e "delete from s_rules where src='$ip'"`
`mysql -h mysql firewall -e "delete from s_rules where dst='$ip'"`

elif [[ "$act" = "show_firewall" ]]
then
read -p "Please enter client's Subnet:" ip
check_ip $ip
fid=`mysql -h mysql firewall -e "select fid from f_rules where src='$ip'" | xargs | awk -F' ' '{print$2}'`
if [ -z $fid ]
then
echo "Subnet not found"
exit
fi

/sbin/ipfw show $fid
let "fid=fid+1"
/sbin/ipfw show $fid
read -p "Please press Enter" ip

elif [[ "$act" = "show_pipe" ]]
then
read -p "Please enter client's Subnet:" ip
check_ip $ip
fid=`mysql -h mysql firewall -e "select uid from s_rules where dst='$ip'" | xargs | awk -F' ' '{print$2}'`
if [ -z $fid ]
then
echo "Subnet not found"
exit
fi
ipfw pipe show $fid
let "fid=fid+1"
/sbin/ipfw pipe show $fid
read -p "Please press Enter" ip

elif [[ "$act" = "direct_access" ]]
then
read -p "Please enter client's Subnet:" ip
check_ip $ip
last=`mysql -h mysql firewall -e "select uid from s_rules where src='$ip'" | xargs | awk -F' ' '{print$2}'`
let "last=last+1"
ipfw delete $last
let "last=last+1"
ipfw delete $last
read -p "Please press Enter" ip

elif [[ "$act" = "dis_direct_access" ]]
then
read -p "Please enter client's Subnet:" ip
check_ip $ip
last=`mysql -h mysql firewall -e "select * from s_rules where src='$ip'" `
uid=`echo $last | awk -F' ' '{print$8}'`
up=`echo $last | awk -F' ' '{print$11}'`
down=`echo $last | awk -F' ' '{print$10}'`
dst=`echo $last | awk -F' ' '{print$12}'`
src=`echo $last | awk -F' ' '{print$9}'`
#ok lets add it
#download
let "uid=uid+1"
ipfw add $uid skipto 63800 ip from any to $ip
#up
let "uid=uid+1"
ipfw add $uid skipto 63800 ip from $ip to any
#let "last=last+1"
#ipfw delete $last
#let "last=last+1"
#ipfw delete $last
read -p "Please press Enter" ip

elif [[ "$act" = "show_client" ]]
then
read -p "Please enter client's Subnet:" ip
check_ip $ip
fid=`mysql -h mysql firewall -e "select uid from s_rules where src='$ip'" | xargs | awk -F' ' '{print$2}'`
fid1=`mysql -h mysql firewall -e "select uid from s_rules where dst='$ip'" | xargs | awk -F' ' '{print$2}'`
if [ -z $fid ]
then
echo "Subnet not found"
exit
fi
#show info for now
clname=`mysql -h mysql firewall -e "select clname from f_rules where src='$ip'" | xargs | awk -F' ' '{print$2}'`
up=`mysql -h mysql firewall -e "select up from s_rules where src='$ip'" | xargs | awk -F' ' '{print$2}'`
dwn=`mysql -h mysql firewall -e "select down from s_rules where dst='$ip'" | xargs | awk -F' ' '{print$2}'`
echo "Client name:" $clname
echo "Subnet:" $ip
echo "Upload speed:" $up "kbps/s"
echo "Download speed:" $dwn "kbps/s"

fi
save_rules
read -p "Please press Enter" ip

Это так сказать первая версия — полностью рабочая. Что не хватает в ней —
а) проверки правильности ввода подсети — пытался сделать регулярками, но что-то не вышло
б) не проверки указания скорости — число это или символ, чуть позже добавлю тоже

P.S. Чуть позже в скрипт будет добавлена возможность, использовать уже удаленные ID, чтобы не забивать ipfw:

Firewall построен следующим образом:

в промежутке от 100 до 1000 создаются правила pipe + skipt
в промежутке от 1000 до 3000 создаются правила — allow для вводимых скриптах subnets
63800 836210 738616662 allow ip from any to any via lo0
63900 369577 350437651 fwd 127.0.0.1,3128 tcp from clients_subnets to any dst-port 80 keep-state
65000 1250162 552186188 allow ip from any to any
65535 6663 831146 deny ip from any to any

Share and Enjoy:
  • Мой Мир
  • Facebook
  • Twitter

Related posts:

  1. Оптимизация mySQL
  2. Измерение Http Response при помощи mrtg
  3. Freeradius3 + mySQL на FreeBSD10
  4. Администрирование : Backups
  5. Избыточность.. Хорошо или плохо ?!

Вы можете пропустить чтение записи и оставить комментарий. Размещение ссылок запрещено.

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

Вы должны быть авторизованы, чтобы разместить комментарий.