Anviwònman rezo soti nan FreeRadius atravè DHCP

Anviwònman rezo soti nan FreeRadius atravè DHCP
Travay la te rive pou fè aranjman pou emisyon adrès IP pou abonnés. Kondisyon pwoblèm nan:

  • Nou p ap ba w yon sèvè separe pou otorizasyon - w ap fè sa 😉
  • Abonnés dwe resevwa paramèt rezo via DHCP
  • Rezo a se etewojèn. Sa a gen ladan ekipman PON ak switch regilye ak Opsyon 82 configuré ak baz WiFi ak otspo
  • Si done yo pa tonbe anba okenn nan kondisyon pou bay yon IP, ou dwe bay yon IP nan rezo "envite" la.

Sou bò a bon: toujou gen yon sèvè sou FreeBSD ki ka "travay", men li se "byen lwen" ;), pa "dwa sou rezo sa a".

Genyen tou yon aparèy bèl bagay ki rele Mikrotik. Dyagram rezo jeneral la se yon bagay tankou sa a:

Anviwònman rezo soti nan FreeRadius atravè DHCP

Apre kèk reflechi, li te deside sèvi ak FreeRadius bay anviwònman rezo a abonnés. Nan prensip, konplo a se nòmal: nou pèmèt sèvè DHCP a sou Microtick, ak Radius Kliyan sou li. Nou konfigirasyon sèvè DHCP -> Radius Kliyan -> Radius sèvè koneksyon.

Li pa sanble difisil. Men! Dyab la se nan detay yo. Savwa:

  • Lè w otorize yon PON OLT lè l sèvi avèk konplo sa a, yo voye yon demann bay FreeRadius ak yon Non itilizatè ki egal a adrès MAC nan headend la, yon Agent-Circuit-Id ki egal ak MAC PON Onu a ak yon modpas vid.
  • Lè FreeRadius otorize soti nan switch ak opsyon 82, FreeRadius resevwa yon demann ki gen yon non itilizatè vid ki egal ak MAC aparèy abònen an epi ki ranpli ak lòt atribi ajan-sikwi-id ak ajan-remote-id ki genyen, respektivman, ankò MAC nan. switch relè a ak pò a ki konekte abònen an.
  • Gen kèk abònen ki gen pwen WiFI yo otorize atravè pwotokòl PAP-CHAP
  • Gen kèk abonnés ki soti nan pwen WIFI yo otorize ak yon Non itilizatè ki egal a adrès MAC nan pwen WIFI a, san yo pa yon modpas.

Istorik background: ki sa ki "Opsyon 82" nan DHCP

Sa yo se opsyon adisyonèl pou pwotokòl DHCP ki pèmèt ou transfere plis enfòmasyon, pa egzanp nan jaden Agent-Circuit-Id ak Agent-Remote-Id. Tipikman yo itilize transmèt adrès MAC la nan switch la relè ak pò a ki konekte abònen an. Nan ka a nan ekipman PON oswa estasyon baz WIFI, jaden an Agent-Circuit-Id pa gen enfòmasyon itil (pa gen okenn pò abònen). Konplo jeneral operasyon DHCP nan ka sa a se jan sa a:

Anviwònman rezo soti nan FreeRadius atravè DHCP

Etap pa etap konplo sa a ap travay konsa:

  1. Ekipman itilizatè a fè yon demann emisyon DHCP pou jwenn paramèt rezo a
  2. Aparèy la (pa egzanp, yon switch, WiFi oswa estasyon debaz PON) kote ekipman abònen an konekte dirèkteman "segman aks dèz" pake sa a epi chanje li, entwodwi opsyon adisyonèl Opsyon 82 ak adrès IP ajan relè ladan l, epi li transmèt li pi lwen. rezo a.
  3. Sèvè a DHCP aksepte demann lan, jenere yon repons epi voye li nan aparèy relè a
  4. Aparèy relè a voye pake repons lan bay aparèy abònen an

Natirèlman, tout bagay pa travay fasil; ou bezwen konfigirasyon ekipman rezo ou kòmsadwa.

Enstale FreeRadius

Natirèlman, sa a ka reyalize ak anviwònman yo konfigirasyon FreeRadius, men li difisil ak klè ... espesyalman lè ou ale la apre N mwa ak "tout bagay ap mache." Se poutèt sa, nou deside ekri pwòp modil otorizasyon nou an pou FreeRadius nan Python. Nou pral pran done otorizasyon nan baz done MySQL. Pa gen okenn pwen nan dekri estrikti li; de tout fason, tout moun pral fè li "pou tèt yo." An patikilye, mwen te pran estrikti a ki ofri ak modil sql pou FreeRadius, ak yon ti kras chanje li pa ajoute yon mac ak pò jaden pou chak abònen, anplis login-modpas la.

Se konsa, premye, enstale FreeRadius:

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

Nan paramèt yo, chwazi enstale:

Anviwònman rezo soti nan FreeRadius atravè DHCP

Nou fè yon lyen senbolik nan modil la python (sa vle di "aktive" li):

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

Ann enstale yon modil adisyonèl pou python:

pip install mysql-connector

Nan anviwònman modil python pou FreeRadius, ou bezwen presize chemen rechèch modil yo nan varyab python_path. Pou egzanp mwen gen sa a:

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"

Ou ka jwenn chemen yo lè w lanse entèprèt python la epi antre kòmandman yo:

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

Si ou pa pran etap sa a, Lè sa a, scripts ekri nan piton ak lanse pa FreeRadius pa pral jwenn modil yo ki nan lis enpòte. Anplis de sa, ou bezwen dekomantè fonksyon yo pou rele otorizasyon ak kontablite nan paramèt modil yo. Pou egzanp, modil sa a sanble tankou sa a:

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 script (ak tout lòt yo) dwe mete nan /usr/local/etc/raddb/mods-config/python Mwen gen twa scripts nan total.

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

Kòm ou ka wè nan kòd la, nou ap eseye idantifye abònen a lè l sèvi avèk tout metòd ki disponib pa adrès MAC abònen li te ye oswa konbinezon Opsyon 82, epi si sa a pa travay, Lè sa a, nou bay pi ansyen adrès IP ki te janm itilize nan "envite". ” rezo. Tout sa ki rete se konfigirasyon script default la nan katab ki pèmèt sit la, se konsa ke fonksyon ki nesesè yo soti nan script python yo pral twitch nan moman yo deziyen. An reyalite, li ase pou pote dosye a nan fòm lan:

default

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

Ann eseye kouri li epi wè sa ki vini nan jounal debug la:

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

Kisa ankò. Lè w mete FreeRadius, li bon pou teste operasyon li lè l sèvi avèk sèvis piblik radclient la. Pa egzanp otorizasyon:

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

Oswa kont:

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

Mwen vle avèti ou ke li se absoliman enposib yo sèvi ak yon konplo ak scripts "san chanjman" sou yon echèl "endistriyèl". Omwen aparan:

  • li posib pou "fo" adrès MAC la. Li ase pou abònen an anrejistre MAC yon lòt moun epi pral gen pwoblèm
  • lojik pou bay rezo envite pi lwen pase kritik. Pa gen menm yon chèk "petèt gen deja kliyan ki gen menm adrès IP la?"

Sa a se jis yon "solisyon kouto bonbon" ki fèt pou travay espesyalman nan kondisyon mwen yo, pa gen anyen plis. Pa jije strictement 😉

Sous: www.habr.com

Add nouvo kòmantè