Mîhengên torê ji FreeRadius bi rêya DHCP

Mîhengên torê ji FreeRadius bi rêya DHCP
Wezîf hat ku danasîna navnîşanên IP-yê ji aboneyan re saz bike. Mercên pirsgirêkê:

  • Em ê serverek cihêreng ji bo destûr nedin we - hûn ê bikin 😉
  • Divê abonet mîhengên torê bi riya DHCP-ê bistînin
  • Tora heterojen e. Di nav de amûrên PON û guheztinên birêkûpêk ên bi Vebijarka 82-yê vesazkirî û bingehên WiFi yên bi xalên germ hene
  • Ger dane nekevin bin yek ji şertên derxistina IP-yê, divê hûn IP-yek ji tora "mêvan" derxînin.

Aliyê baş: hîn serverek li ser FreeBSD heye ku dikare "xebat bike", lê ew "dûr" e ;), ne "rast li ser vê torê".

Bi navê Mikrotik jî amûrek hêja heye. Diagrama torê ya gelemperî tiştek wiha ye:

Mîhengên torê ji FreeRadius bi rêya DHCP

Piştî hin ramanan, biryar hate girtin ku FreeRadius bikar bînin da ku mîhengên torê ji aboneyan re bişînin. Di prensîbê de, nexşe asayî ye: em servera DHCP li ser Microtick, û Radius Client li ser wê çalak dikin. Em servera DHCP -> Radius Client -> Girêdana servera Radius mîheng dikin.

Ew ne dijwar xuya dike. Lebê! Şeytan di hûrguliyan de ye. Ango:

  • Dema ku destûrdayîna PON OLT bi karanîna vê nexşeyê, daxwazek ji FreeRadius re tê şandin ku Navek Bikarhêner bi navnîşana MAC-ê ya serî, Agent-Circuit-Id wekhev MAC PON Onu û şîfreyek vala ye.
  • Dema ku destûr ji guhêzbarên bi vebijarka 82-ê re dide, FreeRadius daxwazek bi navek bikarhêner-navê vala ku bi MAC-ya cîhaza aboneyê re wekhev e û bi taybetmendiyên din ên Agent-Circuit-Id û Agent-Remote-Id tijî ye, bi rêzê, dîsa MAC-ê digire. guheztina relay û porta ku abonet pê ve girêdayî ye.
  • Hin aboneyên xwedan xalên WiFI bi protokolên PAP-CHAP têne destûr kirin
  • Hin aboneyên ji xalên WIFI bi navek Bikarhêner wekhev bi navnîşana MAC ya xala WIFI re, bêyî şîfre têne destûr kirin.

Paşnava dîrokî: "Vebijêrk 82" di DHCP de çi ye

Ji bo protokola DHCP ev vebijarkên din in ku destûrê didin we ku hûn agahdariya zêde veguhezînin, mînakî di qadên Agent-Circuit-Id û Agent-Remote-Id. Bi gelemperî ji bo veguheztina navnîşana MAC-a veguheztina relay û porta ku abonet pê ve girêdayî ye tê bikar anîn. Di mijara alavên PON an stasyonên bingehîn ên WIFI de, qada Agent-Circuit-Id agahdariya kêrhatî nahewîne (porta aboneyê tune). Plana giştî ya operasyona DHCP di vê rewşê de wiha ye:

Mîhengên torê ji FreeRadius bi rêya DHCP

Gav bi gav ev plan wiha dixebite:

  1. Amûrên bikarhêner ji bo bidestxistina mîhengên torê daxwazek weşana DHCP dike
  2. Amûra (mînakî, guhezek, WiFi an stasyona bingehîn PON) ya ku amûrên aboneyê rasterast pê ve girêdayî ye, vê pakêtê "navdêr" dike û wê diguhezîne, vebijarkên din Vebijarka 82 û navnîşana IP-ya nûnerê Relay têxe nav wê, û wê bêtir dişîne. torê.
  3. Pêşkêşkara DHCP daxwazê ​​qebûl dike, bersivek çêdike û dişîne cîhaza relay
  4. Amûra rele pakêta bersivê ber bi cîhaza aboneyê ve dişîne

Bê guman, ew hemî ew qas hêsan naxebite; hûn hewce ne ku li gorî vê amûrê torê xwe mîheng bikin.

Sazkirina FreeRadius

Bê guman, ev dikare bi mîhengên veavakirina FreeRadius re were bidestxistin, lê ew dijwar û ne diyar e ... nemaze gava ku hûn piştî N mehan diçin wir û "her tişt dixebite." Ji ber vê yekê, me biryar da ku em modula destûrnameya xwe ya ji bo FreeRadius di Python de binivîsin. Em ê daneyên destûrnameyê ji databasa MySQL bigirin. Ti wateya danasîna avahiya wê tune; her kes wê "ji bo xwe" bike. Bi taybetî, min avahiya ku bi modula sql-ê ji bo FreeRadius tê pêşkêş kirin girt, û bi lêzêdekirina qada mac û portê ji bo her aboneyê, ji bilî têketin-şîfreya, hinekî guhart.

Ji ber vê yekê, pêşî, FreeRadius saz bikin:

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

Di mîhengan de, ji bo sazkirinê hilbijêrin:

Mîhengên torê ji FreeRadius bi rêya DHCP

Em ji modula python re sîmlinkek çêdikin (ango "wê vekin"):

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

Ka em ji bo python modulek din saz bikin:

pip install mysql-connector

Di mîhengên modula python de ji bo FreeRadius, hûn hewce ne ku rêyên lêgerîna modulê di guhêrbara python_path de diyar bikin. Mînak min ev heye:

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"

Hûn dikarin bi destpêkirina wergêrê python û têketina fermanan rêyan fêr bibin:

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

Heke hûn vê gavê neavêjin, wê hingê skrîptên ku bi python hatine nivîsandin û ji hêla FreeRadius ve hatine destpêkirin dê modulên ku di importê de têne navnîş kirin nebînin. Digel vê yekê, hûn hewce ne ku fonksiyonên ji bo gazîkirina destûr û hesabkirinê di mîhengên modulê de şîrove bikin. Mînakî, ev modul bi vî rengî xuya dike:

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}

}

Nivîsara work.py (û hemî yên din) divê li /usr/local/etc/raddb/mods-config/python were danîn. Bi tevahî sê nivîsarên min hene.

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

Wekî ku hûn ji kodê dibînin, em hewl didin ku aboneyê bi hemî awayên berdest bi navnîşanên MAC-ê yên naskirî yên abonetiya wî an jî tevliheviya Vebijarka 82-ê ve nas bikin, û heke ev nexebite, wê hingê em navnîşana IP-ya herî kevn a ku heya niha ji "mêvan" hatî bikar anîn derdixin. ” tora. Tiştê ku dimîne ev e ku meriv skrîpta xwerû di peldanka çalak-sîteyan de mîheng bike, da ku fonksiyonên pêwîst ji nivîsara python di demên destnîşankirî de biqelişe. Bi rastî, bes e ku meriv pelê bîne forma:

destçûnî

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

Ka em biceribînin ku wê bimeşînin û bibînin ka çi tê navnîşa debugê:

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

Êdî çi. Dema ku FreeRadius saz dike, hêsan e ku meriv xebata wê bi karanîna karûbar radclient ceribandin. Ji bo nimûne destûr:

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

An jî hesab:

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

Ez dixwazim we hişyar bikim ku ne gengaz e ku meriv nexşeyek û nivîsarên weha "bêyî guhertin" li ser pîvanek "pîşesazî" bikar bîne. Bi kêmanî berbiçav:

  • gengaz e ku meriv navnîşana MAC-ê "sexte" bike. Ji bo aboneyê bes e ku MAC-a kesek din tomar bike û dê pirsgirêk hebin
  • mantiqa derxistina torên mêvanan li derveyî rexneyê ye. Tewra kontrolek jî tune "dibe ku jixwe xerîdarên bi heman navnîşana IP-yê hebin?"

Ev tenê "çareseriyek kuxikê" ye ku ji bo ku bi taybetî di şert û mercên min de bixebite hatî çêkirin, ne tiştek din. Bi tundî dadbar nekin 😉

Source: www.habr.com

Add a comment