Танзимоти шабака аз FreeRadius тавассути DHCP

Танзимоти шабака аз FreeRadius тавассути DHCP
Вазифа ба миён омад, ки ба муштариён додани IP-адресҳо ташкил карда шавад. Шартҳои мушкилот:

  • Мо ба шумо сервери алоҳида барои авторизатсия намедиҳем - шумо ин корро мекунед 😉
  • Муштариён бояд танзимоти шабакаро тавассути DHCP қабул кунанд
  • Шабака гетерогенӣ аст. Ба ин таҷҳизоти PON ва коммутаторҳои муқаррарӣ бо конфигуратсияи Опсияи 82 ва пойгоҳҳои WiFi бо нуқтаҳои дастрас дохил мешаванд
  • Агар маълумот ба ягон шарти додани IP мувофиқат накунад, шумо бояд IP-ро аз шабакаи "меҳмон" пешниҳод кунед.

Ҷониби хуб: дар FreeBSD то ҳол сервере мавҷуд аст, ки метавонад "кор кунад", аммо он "дур" аст;), на "дар ин шабака".

Дар ин чо асбоби ачоиб низ мавчуд аст, ки «Микротик» ном дорад. Диаграммаи умумии шабака чунин аст:

Танзимоти шабака аз FreeRadius тавассути DHCP

Пас аз каме фикр кардан, тасмим гирифта шуд, ки FreeRadiusро барои додани танзимоти шабака ба муштариён истифода барад. Аслан, схема муқаррарӣ аст: мо сервери DHCP-ро дар Microtick ва дар он Radius Client фаъол мекунем. Мо сервери DHCP -> Radius Client -> Пайвастшавии сервери Radius-ро танзим мекунем.

Ин мушкил ба назар намерасад. Аммо! Шайтон дар тафсилот аст. Аз ҷумла:

  • Ҳангоми иҷозат додан ба PON OLT бо истифода аз ин схема, дархост ба FreeRadius бо номи корбаре, ки ба суроғаи MAC-и сарлавҳа баробар аст, Agent-Circuit-Id баробар ба MAC PON Onu ва пароли холӣ фиристода мешавад.
  • Ҳангоми иҷозатдиҳӣ аз коммутаторҳо бо опсияи 82, FreeRadius дархостро бо номи холии корбарӣ баробар ба MAC-и дастгоҳи муштарӣ мегирад ва бо атрибутҳои иловагии Agent-Circuit-Id ва Agent-Remote-Id пур карда мешавад, ки мутаносибан боз MAC-и коммутатори реле ва порте, ки муштарӣ ба он пайваст аст.
  • Баъзе муштариёни дорои нуқтаҳои WiFi тавассути протоколҳои PAP-CHAP иҷозат дода шудаанд
  • Баъзе муштариёни нуқтаҳои WIFI бо номи корбар ба суроғаи MAC-и нуқтаи WIFI бидуни парол иҷозат дода шудаанд.

Заминаи таърихӣ: "Опсияи 82" дар DHCP чист

Инҳо имконоти иловагӣ барои протоколи DHCP мебошанд, ки ба шумо имкон медиҳанд маълумоти иловагӣ интиқол диҳед, масалан дар майдонҳои Agent-Circuit-Id ва Agent-Remote-Id. Одатан барои интиқоли суроғаи MAC-и коммутатори реле ва порте, ки муштарӣ ба он пайваст аст, истифода мешавад. Дар мавриди таҷҳизоти PON ё пойгоҳҳои WIFI, майдони Agent-Circuit-Id маълумоти муфидро дар бар намегирад (бандари муштарӣ вуҷуд надорад). Схемаи умумии амалиёти DHCP дар ин ҳолат чунин аст:

Танзимоти шабака аз FreeRadius тавассути DHCP

Қадам ба қадам ин схема чунин кор мекунад:

  1. Таҷҳизоти корбар барои дарёфти танзимоти шабака дархости пахши DHCP медиҳад
  2. Таҷҳизоте (масалан, коммутатор, WiFi ё истгоҳи пойгоҳи PON), ки таҷҳизоти муштарӣ ба он мустақиман пайваст аст, ин бастаро "пайваст" мекунад ва онро тағир медиҳад ва имконоти иловагии Опсияи 82 ва суроғаи IP-ро ба он ворид мекунад ва минбаъд онро интиқол медиҳад. шабака.
  3. Сервери DHCP дархостро қабул мекунад, ҷавоб тавлид мекунад ва онро ба дастгоҳи реле мефиристад
  4. Дастгоҳи реле пакети ҷавобиро ба дастгоҳи муштарӣ мефиристад

Албатта, ҳама он қадар осон кор намекунад; шумо бояд таҷҳизоти шабакавии худро мувофиқи он танзим кунед.

Насб кардани FreeRadius

Албатта, инро бо танзимоти конфигуратсияи FreeRadius ба даст овардан мумкин аст, аммо ин мушкил ва норавшан аст... хусусан вақте ки шумо пас аз N моҳ ба он ҷо меравед ва "ҳама чиз кор мекунад". Аз ин рӯ, мо тасмим гирифтем, ки модули авторизатсияи худро барои FreeRadius дар Python нависем. Мо маълумоти авторизатсияро аз базаи MySQL мегирем. Тавсифи сохтори он маъно надорад, ба ҳар ҳол, ҳар кас онро "барои худ" месозад. Аз ҷумла, ман сохтореро, ки бо модули sql барои FreeRadius пешниҳод карда мешавад, гирифтам ва онро каме бо илова кардани майдони мак ва порт барои ҳар як муштарӣ, ба ғайр аз логин-парол иваз кардам.

Пас, аввал, FreeRadius насб кунед:

cd /usr/ports/net/freeradius3
make config
make
install clean

Дар танзимот, барои насб кардан интихоб кунед:

Танзимоти шабака аз FreeRadius тавассути DHCP

Мо ба модули python пайванди рамзӣ месозем (яъне онро фаъол созед):

ln -s /usr/local/etc/raddb/mods-available/python /usr/local/etc/raddb/mods-enabled

Биёед як модули иловагӣ барои python насб кунем:

pip install mysql-connector

Дар танзимоти модули python барои FreeRadius, шумо бояд роҳҳои ҷустуҷӯи модулро дар тағирёбандаи python_path муайян кунед. Масалан, ман инро дорам:

python_path="/usr/local/etc/raddb/mods-config/python:/usr/local/lib/python2.7:/usr/local/lib/python27.zip:/usr/local/lib/python2.7:/usr/local/lib/python2.7/plat-freebsd12:/usr/local/lib/python2.7/lib-tk:/usr/local/lib/python2.7/lib-old:/usr/local/lib/python2.7/lib-dynload:/usr/local/lib/python2.7/site-packages"

Шумо метавонед роҳҳоро тавассути оғоз кардани тарҷумони python ва ворид кардани фармонҳо пайдо кунед:

root@phaeton:/usr/local/etc/raddb/mods-enabled# python
Python 2.7.15 (default, Dec  8 2018, 01:22:25) 
[GCC 4.2.1 Compatible FreeBSD Clang 6.0.1 (tags/RELEASE_601/final 335540)] on freebsd12
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', '/usr/local/lib/python27.zip', '/usr/local/lib/python2.7', '/usr/local/lib/python2.7/plat-freebsd12', '/usr/local/lib/python2.7/lib-tk', '/usr/local/lib/python2.7/lib-old', '/usr/local/lib/python2.7/lib-dynload', '/usr/local/lib/python2.7/site-packages']
>

Агар шумо ин қадамро надиҳед, пас скриптҳои бо Python навишташуда ва аз ҷониби FreeRadius оғозшуда модулҳои дар воридот номбаршударо пайдо намекунанд. Илова бар ин, ба шумо лозим аст, ки дар танзимоти модул функсияҳои иҷозати занг ва баҳисобгириро бекор кунед. Масалан, ин модул чунин менамояд:

python {
    python_path="/usr/local/etc/raddb/mods-config/python:/usr/local/lib/python2.7:/usr/local/lib/python2.7/site-packages:/usr/local/lib/python27.zip:/usr/local/lib/python2.7:/usr/local/lib/python2.7/plat-freebsd12:/usr/local/lib/python2.7/lib-tk:/usr/local/lib/python2.7/lib-old:/usr/local/lib/python2.7/lib-dynload:/usr/local/lib/python2.7/site-packages"
    module = work
    mod_instantiate = ${.module}
    mod_detach = ${.module}

    mod_authorize = ${.module}
    func_authorize = authorize

    mod_authenticate = ${.module}
    func_authenticate = authenticate

    mod_preacct = ${.module}
    func_preacct = preacct

    mod_accounting = ${.module}
    func_accounting = accounting

    mod_checksimul = ${.module}
    mod_pre_proxy = ${.module}
    mod_post_proxy = ${.module}
    mod_post_auth = ${.module}
    mod_recv_coa = ${.module}
    mod_send_coa = ${.module}

}

Скрипти work.py (ва ҳамаи дигарон) бояд дар /usr/local/etc/raddb/mods-config/python ҷойгир карда шавад, ман дар маҷмӯъ се скрипт дорам.

work.py:

#!/usr/local/bin/python
# coding=utf-8
import radiusd
import func
import sys
from pprint import pprint
mysql_host="localhost"
mysql_username="укацук"
mysql_password="ыукаыукаыук"
mysql_base="ыукаыкуаыу"
def instantiate(p):
print ("*** instantiate ***")
print (p)
# return 0 for success or -1 for failure
def authenticate(p):
print ("*** Аутенфикация!!***")
print (p)
def authorize(p):
radiusd.radlog(radiusd.L_INFO, '*** radlog call in authorize ***')    
conn=func.GetConnectionMysql(mysql_host, mysql_username, mysql_password, mysql_base);
param=func.ConvertArrayToNames(p);
pprint(param)
print ("*** Авторизация ***")
reply = ()
conf = ()
cnt=0
username="";mac="";
# сначала проверяем "как положено", по связке логин/пароль
if ("User-Name" in param) and ("User-Password" in param) :
print ("Вариант авторизации (1): есть логин-пароль")
pprint(param["User-Name"])
pprint(param["User-Password"])
pprint(conn)
print(sys.version_info)
print (radiusd.config)
sql="select radreply.attribute,radreply.value from radcheck inner join radreply on radreply.username=radcheck.username where radcheck.username=%s and radcheck.value=%s"
print(sql)
cursor = conn.cursor(dictionary=True,buffered=True)
cursor.execute(sql,[param["User-Name"], param["User-Password"]]);
row = cursor.fetchone()	
while row is not None:    
cnt=cnt+1
username=row["username"]
reply = reply+((str(row["attribute"]),str(row["value"])), )
row = cursor.fetchone()	          
# вариант, что User-Name - это МАС адрес БС,пароля и порта нет                
if ("User-Name" in param)  and ("User-Password" in param) and (cnt==0):
if param["User-Password"] =='':
if ":" in param["User-Name"]:
pprint(param["User-Name"])            
print ("Вариант авторизации (2): User-Name - это MAC адрес базовой станции, порта и пароля нет")
sql="select radreply.username,radreply.attribute,radreply.value from radcheck inner join radreply on radreply.username=radcheck.username where REPLACE(radcheck.mac,':','') = REPLACE(REPLACE('"+str(param["User-Name"])+"','0x',''),':','') and radcheck.sw_port=''"
print (sql)
cursor = conn.cursor(dictionary=True,buffered=True)
cursor.execute(sql);
row = cursor.fetchone()	
while row is not None:                  
cnt=cnt+1
username=row["username"]
mac=param["User-Name"]
reply = reply+((str(row["attribute"]),str(row["value"])), )
row = cursor.fetchone()	          
if ("Agent-Remote-Id" in param)  and ("User-Password" in param) and (cnt==0):
if param["User-Password"] =='':
pprint(param["Agent-Remote-Id"])            
print ("Вариант авторизации (2.5): Agent-Remote-Id - это MAC адрес PON оборудования")
sql="select radreply.username,radreply.attribute,radreply.value from radcheck inner join radreply on radreply.username=radcheck.username where REPLACE(radcheck.mac,':','') = REPLACE(REPLACE('"+str(param["Agent-Remote-Id"])+"','0x',''),':','') and radcheck.sw_port=''"
print (sql)
cursor = conn.cursor(dictionary=True,buffered=True)
cursor.execute(sql);
row = cursor.fetchone()	
while row is not None:                  
cnt=cnt+1
username=row["username"]
mac=param["User-Name"]
reply = reply+((str(row["attribute"]),str(row["value"])), )
row = cursor.fetchone()	          
#Вариант, что Agent-Remote-Id - это МАС адрес БС,пароля и порта нет и предыдущие варианты поиска IP результата не дали                
if ("Agent-Remote-Id" in param)  and ("User-Password" not in param) and (cnt==0):
pprint(param["Agent-Remote-Id"])            
print ("Вариант авторизации (3): Agent-Remote-Id - МАС базовой станции/пон. Порта в биллинге нет")
sql="select radreply.username,radreply.attribute,radreply.value from radcheck inner join radreply on radreply.username=radcheck.username where REPLACE(radcheck.mac,':','') = REPLACE(REPLACE('"+str(param["Agent-Remote-Id"])+"','0x',''),':','') and radcheck.sw_port=''"
print(sql)
cursor = conn.cursor(dictionary=True,buffered=True)
cursor.execute(sql);
row = cursor.fetchone()	
while row is not None:    
cnt=cnt+1
mac=param["Agent-Remote-Id"]
username=row["username"]
reply = reply+((str(row["attribute"]),str(row["value"])), )
row = cursor.fetchone()	          
#Вариант, что предыдущие попытки результата не дали, но есть Agent-Remote-Id и Agent-Circuit-Id
if ("Agent-Remote-Id" in param)  and ("Agent-Circuit-Id" in param) and (cnt==0):
pprint(param["Agent-Remote-Id"])            
pprint(param["Agent-Circuit-Id"])            
print ("Вариант авторизации (4): авторизация по Agent-Remote-Id и Agent-Circuit-Id, в биллинге есть порт/мак")
sql="select radreply.username,radreply.attribute,radreply.value from radcheck inner join radreply on radreply.username=radcheck.username where upper(radcheck.sw_mac)=upper(REPLACE('"+str(param["Agent-Remote-Id"])+"','0x','')) and upper(radcheck.sw_port)=upper(RIGHT('"+str(param["Agent-Circuit-Id"])+"',2)) and radcheck.sw_port<>''"
print(sql)
cursor = conn.cursor(dictionary=True,buffered=True)
cursor.execute(sql);
row = cursor.fetchone()	
while row is not None:    
cnt=cnt+1
mac=param["Agent-Remote-Id"]
username=row["username"]
reply = reply+((str(row["attribute"]),str(row["value"])), )
row = cursor.fetchone()	          
# если так до сих пор IP не получен, то выдаю иего из гостевой сети..
if cnt==0:      
print ("Ни один из вариантов авторизации не сработал, получаю IP из гостевой сети..")
ip=func.GetGuestNet(conn)      
if ip!="": 
cnt=cnt+1;
reply = reply+(("Framed-IP-Address",str(ip)), )
# если совсем всё плохо, то Reject
if cnt==0:
conf = ( ("Auth-Type", "Reject"), ) 
else:
#если авторизация успешная (есть такой абонент), то запишем историю авторизации
if username!="":
func.InsertToHistory(conn,username,mac, reply);
conf = ( ("Auth-Type", "Accept"), )             
pprint (reply)
conn=None;
return radiusd.RLM_MODULE_OK, reply, conf
def preacct(p):
print ("*** preacct ***")
print (p)
return radiusd.RLM_MODULE_OK
def accounting(p):
print ("*** Аккаунтинг ***")
radiusd.radlog(radiusd.L_INFO, '*** radlog call in accounting (0) ***')  
print (p)
conn=func.GetConnectionMysql(mysql_host, mysql_username, mysql_password, mysql_base);
param=func.ConvertArrayToNames(p);
pprint(param)  
print("Удалим старые сессии (более 20 минут нет аккаунтинга)");
sql="delete from radacct where TIMESTAMPDIFF(minute,acctupdatetime,now())>20"
cursor = conn.cursor(dictionary=True,buffered=True)
cursor.execute(sql);
conn.commit()
print("Обновим/добавим информацию о сессии")
if (("Acct-Unique-Session-Id" in param) and ("User-Name" in param) and ("Framed-IP-Address" in param)):
sql='insert into radacct (radacctid,acctuniqueid,username,framedipaddress,acctstarttime) values (null,"'+str(param['Acct-Unique-Session-Id'])+'","'+str(param['User-Name'])+'","'+str(param['Framed-IP-Address'])+'",now()) ON DUPLICATE KEY update acctupdatetime=now()'
print(sql)
cursor = conn.cursor(dictionary=True,buffered=True)
cursor.execute(sql)
conn.commit()
conn=None;
return radiusd.RLM_MODULE_OK
def pre_proxy(p):
print ("*** pre_proxy ***")
print (p)
return radiusd.RLM_MODULE_OK
def post_proxy(p):
print ("*** post_proxy ***")
print (p)
return radiusd.RLM_MODULE_OK
def post_auth(p):
print ("*** post_auth ***")
print (p)
return radiusd.RLM_MODULE_OK
def recv_coa(p):
print ("*** recv_coa ***")
print (p)
return radiusd.RLM_MODULE_OK
def send_coa(p):
print ("*** send_coa ***")
print (p)
return radiusd.RLM_MODULE_OK
def detach():
print ("*** На этом всё детишечки ***")
return radiusd.RLM_MODULE_OK

func.py:

#!/usr/bin/python2.7
# coding=utf-8
import mysql.connector
from mysql.connector import Error
# Функция возвращает соединение с MySQL
def GetConnectionMysql(mysql_host, mysql_username, mysql_password, mysql_base):    
try:
conn = mysql.connector.connect(host=mysql_host,database=mysql_base,user=mysql_username,password=mysql_password)
if conn.is_connected(): print('---cоединение с БД '+mysql_base+' установлено')
except Error as e:
print("Ошибка: ",e);
exit(1);       
return conn
def ConvertArrayToNames(p):
mass={};
for z in p:
mass[z[0]]=z[1]
return mass
# Функция записывает историю соединения по известным данным
def InsertToHistory(conn,username,mac, reply):
print("--записываю для истории")
repl=ConvertArrayToNames(reply)
if "Framed-IP-Address" in repl:
sql='insert into radpostauth (username,reply,authdate,ip,mac,session_id,comment) values ("'+username+'","Access-Accept",now(),"'+str(repl["Framed-IP-Address"])+'","'+str(mac)+'","","")'
print(sql)
cursor = conn.cursor(dictionary=True,buffered=True)          
cursor.execute(sql);
conn.commit()
# Функция выдает последний по дате выдачи IP адрес из гостевой сети        
def GetGuestNet(conn):
ip="";id=0
sql="select * from guestnet order by dt limit 1"
print (sql)
cursor = conn.cursor(dictionary=True,buffered=True)          
cursor.execute(sql);
row = cursor.fetchone()	
while row is not None:    
ip=row["ip"]
id=row["id"]
row = cursor.fetchone()	          
if id>0:
sql="update guestnet set dt=now() where id="+str(id)
print (sql)
cursor = conn.cursor(dictionary=True,buffered=True)          
cursor.execute(sql);
conn.commit()
return ip         

radiusd.py:

#!/usr/bin/python2.7
# coding=utf-8
# from modules.h
RLM_MODULE_REJECT = 0
RLM_MODULE_FAIL = 1
RLM_MODULE_OK = 2
RLM_MODULE_HANDLED = 3
RLM_MODULE_INVALID = 4
RLM_MODULE_USERLOCK = 5
RLM_MODULE_NOTFOUND = 6
RLM_MODULE_NOOP = 7
RLM_MODULE_UPDATED = 8
RLM_MODULE_NUMCODES = 9
# from log.h
L_AUTH = 2
L_INFO = 3
L_ERR = 4
L_WARN = 5
L_PROXY = 6
L_ACCT = 7
L_DBG = 16
L_DBG_WARN = 17
L_DBG_ERR = 18
L_DBG_WARN_REQ = 19
L_DBG_ERR_REQ = 20
# log function
def radlog(level, msg):
import sys
sys.stdout.write(msg + 'n')
level = level

Тавре ки шумо аз код мебинед, мо кӯшиш мекунем, ки муштариро бо истифода аз тамоми усулҳои мавҷуда тавассути суроғаҳои муштарии маълуми MAC ё комбинатсияи Опсияи 82 муайян кунем ва агар ин кор накунад, мо суроғаи IP-и қадимтаринро, ки то ҳол аз "меҳмон истифода шудааст" мебарорем. ” шабака. Танҳо конфигуратсияи скрипти пешфарз дар ҷузвдони аз ҷониби сайтҳо фаъолшуда боқӣ мемонад, то ки функсияҳои зарурӣ аз скрипти python дар лаҳзаҳои таъиншуда ҷунбиш шаванд. Дар асл, файлро ба шакл овардан кифоя аст:

Пешфарз

server default {
listen {
type = auth
ipaddr = *
port = 0
limit {
max_connections = 16
lifetime = 0
idle_timeout = 30
}
}
listen {
ipaddr = *
port = 0
type = acct
limit {
}
}
listen {
type = auth
port = 0
limit {
max_connections = 1600
lifetime = 0
idle_timeout = 30
}
}
listen {
ipv6addr = ::
port = 0
type = acct
limit {
}
}
authorize {
python
filter_username
preprocess
expiration
logintime
}
authenticate {
Auth-Type PAP {
pap
python
}
Auth-Type CHAP {
chap
python
}
Auth-Type MS-CHAP {
mschap
python
}
eap
}
preacct {
preprocess
acct_unique
suffix
files
}
accounting {
python
exec
attr_filter.accounting_response
}
session {
}
post-auth {
update {
&reply: += &session-state:
}
exec
remove_reply_message_if_eap
Post-Auth-Type REJECT {
attr_filter.access_reject
eap
remove_reply_message_if_eap
}
Post-Auth-Type Challenge {
}
}
pre-proxy {
}
post-proxy {
eap
}
}

Биёед кӯшиш кунем, ки онро иҷро кунем ва бубинем, ки дар сабти ислоҳот чӣ ворид мешавад:

/usr/local/etc/rc.d/radiusd debug

Боз чӣ. Ҳангоми насб кардани FreeRadius, санҷиши кори он бо истифода аз утилитаи radclient қулай аст. Масалан, иҷозат:

echo "User-Name=4C:5E:0C:2E:7F:15,Agent-Remote-Id=0x9845623a8c98,Agent-Circuit-Id=0x00010006" | radclient -x  127.0.0.1:1812 auth testing123

Ё ҳисоб:

echo "User-Name=4C:5E:0C:2E:7F:15,Agent-Remote-Id=0x00030f26054a,Agent-Circuit-Id=0x00010002" | radclient -x  127.0.0.1:1813 acct testing123

Мехоҳам шуморо огоҳ кунам, ки дар миқёси «саноатӣ» истифода бурдани чунин схема ва скриптҳоро «бе тағирот» комилан ғайриимкон аст. Ақаллан назаррас:

  • суроғаи MAC-ро "қалбакӣ" кардан мумкин аст. Барои муштарӣ сабти МАЗ-и ягон каси дигар кофӣ аст ва мушкилот пеш меояд
  • мантиқи додани шабакаҳои меҳмонон берун аз интиқод аст. Ҳатто чек нест "шояд аллакай муштариён бо суроғаи IP-и якхела ҳастанд?"

Ин танҳо як "ҳалли кукиҳо" аст, ки махсусан дар шароити ман кор мекунад, чизи дигаре нест. Қатъиян ҳукм накунед 😉

Манбаъ: will.com

Илова Эзоҳ