Cilësimet e rrjetit nga FreeRadius nëpërmjet DHCP

Cilësimet e rrjetit nga FreeRadius nëpërmjet DHCP
Arriti detyra për të rregulluar lëshimin e adresave IP për pajtimtarët. Kushtet e problemit:

  • Ne nuk do t'ju japim një server të veçantë për autorizim - ju do ta bëni 😉
  • Abonentët duhet të marrin cilësimet e rrjetit nëpërmjet DHCP
  • Rrjeti është heterogjen. Kjo përfshin pajisjet PON dhe çelsat e rregullt me ​​opsionin 82 të konfiguruar dhe bazat WiFi me pikat e nxehta
  • Nëse të dhënat nuk bien në asnjë nga kushtet për lëshimin e një IP, ju duhet të lëshoni një IP nga rrjeti "mysafir".

Në anën e mirë: ekziston ende një server në FreeBSD që mund të "funksionojë", por është "larg" ;), jo "me të drejtë në këtë rrjet".

Ekziston edhe një pajisje e mrekullueshme e quajtur Mikrotik. Diagrami i përgjithshëm i rrjetit është diçka si kjo:

Cilësimet e rrjetit nga FreeRadius nëpërmjet DHCP

Pas disa mendimeve, u vendos që të përdoret FreeRadius për t'u dhënë abonentëve cilësimet e rrjetit. Në parim, skema është e zakonshme: ne aktivizojmë serverin DHCP në Microtick dhe Radius Client në të. Ne konfigurojmë serverin DHCP -> Klienti Radius -> Lidhja e serverit Radius.

Nuk duket e vështirë. Por! Djalli është në detaje. Gjegjësisht:

  • Kur autorizon një PON OLT duke përdorur këtë skemë, një kërkesë i dërgohet FreeRadius me një emër përdoruesi të barabartë me adresën MAC të kokës, një Agent-Circuit-Id të barabartë me MAC PON Onu dhe një fjalëkalim bosh.
  • Kur autorizon nga çelsat me opsionin 82, FreeRadius merr një kërkesë me një Emër Përdoruesi të zbrazët të barabartë me MAC të pajisjes së pajtimtarit dhe të mbushur me atribute shtesë Agent-Circuit-Id dhe Agent-Remote-Id që përmbajnë, përkatësisht, përsëri MAC të ndërprerësi rele dhe porti në të cilin është lidhur pajtimtari.
  • Disa abonentë me pika WiFI autorizohen nëpërmjet protokolleve PAP-CHAP
  • Disa abonentë nga pikat WIFI janë të autorizuar me një Emër Përdoruesi të barabartë me adresën MAC të pikës WIFI, pa fjalëkalim.

Sfondi historik: çfarë është "Opsioni 82" në DHCP

Këto janë opsione shtesë për protokollin DHCP që ju lejojnë të transferoni informacion shtesë, për shembull në fushat Agent-Circuit-Id dhe Agent-Remote-Id. Zakonisht përdoret për të transmetuar adresën MAC të ndërprerësit rele dhe portit me të cilin është lidhur pajtimtari. Në rastin e pajisjeve PON ose stacioneve bazë WIFI, fusha Agent-Circuit-Id nuk përmban informacione të dobishme (nuk ka port abonenti). Skema e përgjithshme e funksionimit të DHCP në këtë rast është si më poshtë:

Cilësimet e rrjetit nga FreeRadius nëpërmjet DHCP

Hap pas hapi kjo skemë funksionon si kjo:

  1. Pajisja e përdoruesit bën një kërkesë transmetimi DHCP për të marrë cilësimet e rrjetit
  2. Pajisja (për shembull, një ndërprerës, WiFi ose stacion bazë PON) me të cilin është lidhur drejtpërdrejt pajisja e pajtimtarit "përgjon" këtë paketë dhe e ndryshon atë, duke futur në të opsionet shtesë Opsionin 82 dhe adresën IP të agjentit të transmetimit dhe e transmeton atë më tej. rrjetin.
  3. Serveri DHCP pranon kërkesën, gjeneron një përgjigje dhe e dërgon atë në pajisjen rele
  4. Pajisja rele e përcjell paketën e përgjigjes në pajisjen e pajtimtarit

Sigurisht, gjithçka nuk funksionon aq lehtë; ju duhet të konfiguroni pajisjet e rrjetit tuaj në përputhje me rrethanat.

Instalimi i FreeRadius

Sigurisht, kjo mund të arrihet me cilësimet e konfigurimit të FreeRadius, por është e vështirë dhe e paqartë... veçanërisht kur shkon atje pas N muajsh dhe "çdo gjë funksionon". Prandaj, vendosëm të shkruajmë modulin tonë të autorizimit për FreeRadius në Python. Ne do të marrim të dhënat e autorizimit nga baza e të dhënave MySQL. Nuk ka kuptim të përshkruhet struktura e saj; gjithsesi, të gjithë do ta bëjnë atë "për vete". Në veçanti, mora strukturën që ofrohet me modulin sql për FreeRadius dhe e ndryshova pak duke shtuar një fushë mac dhe port për çdo pajtimtar, përveç fjalëkalimit të hyrjes.

Pra, së pari, instaloni FreeRadius:

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

Në cilësimet, zgjidhni për të instaluar:

Cilësimet e rrjetit nga FreeRadius nëpërmjet DHCP

Ne bëjmë një lidhje simbolike me modulin python (d.m.th. "ndizeni" atë):

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

Le të instalojmë një modul shtesë për python:

pip install mysql-connector

Në cilësimet e modulit python për FreeRadius, duhet të specifikoni shtigjet e kërkimit të modulit në variablin python_path. Për shembull, unë kam këtë:

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"

Ju mund t'i zbuloni shtigjet duke nisur interpretuesin python dhe duke futur komandat:

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']
>

Nëse nuk e ndërmerrni këtë hap, atëherë skriptet e shkruara në python dhe të lançuara nga FreeRadius nuk do të gjejnë modulet që janë të listuara në import. Përveç kësaj, duhet të hiqni komentet e funksioneve për autorizimin e thirrjes dhe kontabilitetin në cilësimet e modulit. Për shembull, ky modul duket si ky:

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}

}

Skripti work.py (dhe të gjithë të tjerët) duhet të vendoset në /usr/local/etc/raddb/mods-config/python Unë kam tre skripta gjithsej.

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

Siç mund ta shihni nga kodi, ne po përpiqemi të identifikojmë pajtimtarin duke përdorur të gjitha metodat e disponueshme nga adresat e njohura MAC të pajtimtarit të tij ose kombinimi i opsionit 82, dhe nëse kjo nuk funksionon, atëherë ne lëshojmë adresën IP më të vjetër të përdorur ndonjëherë nga "mysafiri ” rrjeti. E tëra që mbetet është të konfiguroni skriptin e paracaktuar në dosjen e aktivizuar nga faqet, në mënyrë që funksionet e nevojshme nga skripti python të dridhen në momentet e caktuara. Në fakt, mjafton të sillni skedarin në formën:

parazgjedhur

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
}
}

Le të përpiqemi ta ekzekutojmë dhe të shohim se çfarë hyn në regjistrin e korrigjimit:

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

Çfarë tjetër. Kur konfiguroni FreeRadius, është i përshtatshëm për të testuar funksionimin e tij duke përdorur programin radclient. Për shembull autorizimi:

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

Ose llogaria:

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

Dua t'ju paralajmëroj se është absolutisht e pamundur të përdoret një skemë dhe skriptet e tilla "pa ndryshime" në një shkallë "industriale". Të paktën vihet re:

  • është e mundur të "falsifikuar" adresën MAC. Mjafton që pajtimtari të regjistrojë MAC-në e dikujt tjetër dhe do të ketë probleme
  • logjika e lëshimit të rrjeteve të mysafirëve është përtej kritikës. Nuk ka as një kontroll "ndoshta ka tashmë klientë me të njëjtën adresë IP?"

Kjo është thjesht një "zgjidhje prerëse biskotash" e krijuar për të punuar posaçërisht në kushtet e mia, asgjë më shumë. Mos gjykoni rreptësisht 😉

Burimi: www.habr.com

Shto një koment