Saitunan hanyar sadarwa daga FreeRadius ta hanyar DHCP

Saitunan hanyar sadarwa daga FreeRadius ta hanyar DHCP
Aikin ya isa don shirya bayar da adiresoshin IP ga masu biyan kuɗi. Yanayin matsalar:

  • Ba za mu ba ku wata uwar garken dabam don izini ba - za ku yi 😉
  • Dole ne masu biyan kuɗi su sami saitunan cibiyar sadarwa ta DHCP
  • Cibiyar sadarwa iri-iri ce. Wannan ya haɗa da kayan aikin PON da masu sauyawa na yau da kullun tare da saita zaɓi na 82 da sansanonin WiFi tare da wurare masu zafi.
  • Idan bayanan ba su faɗi ƙarƙashin kowane sharuɗɗan bayar da IP ba, dole ne ku fitar da IP daga cibiyar sadarwar “baƙo”.

A gefe mai kyau: har yanzu akwai uwar garken akan FreeBSD wanda zai iya "aiki", amma yana "da nisa" ;), ba "daidai akan wannan hanyar sadarwa ba".

Akwai kuma na'urar ban mamaki mai suna Mikrotik. Tsarin cibiyar sadarwa na gaba ɗaya shine wani abu kamar haka:

Saitunan hanyar sadarwa daga FreeRadius ta hanyar DHCP

Bayan wasu tunani, an yanke shawarar amfani da FreeRadius don ba da saitunan cibiyar sadarwa ga masu biyan kuɗi. A ka'ida, makircin ya saba: muna kunna uwar garken DHCP akan Microtick, da Radius Client akansa. Muna saita uwar garken DHCP -> Client Radius -> Haɗin uwar garken Radius.

Da alama ba wuya. Amma! Shaidan yana cikin cikakken bayani. Wato:

  • Lokacin ba da izini ga PON OLT ta amfani da wannan makirci, ana aika buƙatu zuwa FreeRadius tare da Sunan mai amfani daidai da adireshin MAC na headend, Agent-Circuit-Id daidai da MAC PON Onu da kalmar sirri mara komai.
  • Lokacin ba da izini daga masu sauyawa tare da zaɓi na 82, FreeRadius yana karɓar buƙatu tare da sunan mai amfani mara komai daidai da MAC na na'urar mai biyan kuɗi kuma yana cike da ƙarin halayen Agent-Circuit-Id da Agent-Remote-Id wanda ya ƙunshi, bi da bi, sake MAC na na'ura mai ba da hanya tsakanin hanyoyin sadarwa da tashar jiragen ruwa wanda ake haɗa masu biyan kuɗi zuwa gare ta.
  • Wasu masu biyan kuɗi masu maki WiFI suna da izini ta hanyar ka'idojin PAP-CHAP
  • Wasu masu biyan kuɗi daga maki WIFI suna da izini tare da Sunan mai amfani daidai da adireshin MAC na wurin WIFI, ba tare da kalmar sirri ba.

Bayanan tarihi: menene "Zaɓi 82" a cikin DHCP

Waɗannan ƙarin zaɓuɓɓuka ne don ka'idar DHCP waɗanda ke ba ku damar canja wurin ƙarin bayani, misali a cikin Agent-Circuit-Id da Agent-Remote-Id filayen. Yawanci ana amfani dashi don watsa adireshin MAC na maɓalli na relay da tashar jiragen ruwa wanda aka haɗa mai biyan kuɗi. Game da kayan aikin PON ko tashoshi na WIFI, filin Agent-Circuit-Id bai ƙunshi bayanai masu amfani ba (babu tashar jiragen ruwa mai biyan kuɗi). Babban makircin aikin DHCP a wannan yanayin shine kamar haka:

Saitunan hanyar sadarwa daga FreeRadius ta hanyar DHCP

Mataki-mataki wannan tsarin yana aiki kamar haka:

  1. Kayan aikin mai amfani yana yin buƙatar watsa shirye-shiryen DHCP don samun saitunan cibiyar sadarwa
  2. Na'urar (alal misali, maɓalli, WiFi ko tashar tashar PON) wanda kayan aikin masu biyan kuɗi ke haɗa kai tsaye "tsatse" wannan fakitin kuma ya canza shi, yana gabatar da ƙarin zaɓuɓɓukan zaɓi na 82 da adireshin IP na Relay a ciki, kuma yana watsa shi gabaɗaya. hanyar sadarwa.
  3. Sabar DHCP ta karɓi buƙatun, ta samar da amsa kuma ta aika zuwa na'urar watsa labarai
  4. Na'urar relay tana tura fakitin amsawa zuwa na'urar mai biyan kuɗi

Tabbas, duk ba ya aiki da sauƙi; kuna buƙatar saita kayan aikin cibiyar sadarwar ku daidai.

Shigar da FreeRadius

Tabbas, ana iya samun wannan tare da saitunan saitunan FreeRadius, amma yana da wuya kuma ba a sani ba ... musamman lokacin da kuka je can bayan watanni N kuma "komai yana aiki." Saboda haka, mun yanke shawarar rubuta namu tsarin izini don FreeRadius a Python. Za mu ɗauki bayanan izini daga bayanan MySQL. Babu ma'ana a kwatanta tsarinsa; ko ta yaya, kowa zai yi shi "na kansa." Musamman ma, na ɗauki tsarin da aka bayar tare da tsarin sql don FreeRadius, kuma na ɗan canza shi ta ƙara mac da filin tashar jiragen ruwa ga kowane mai biyan kuɗi, ban da kalmar wucewa.

Don haka, da farko, shigar da FreeRadius:

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

A cikin saitunan, zaɓi don shigarwa:

Saitunan hanyar sadarwa daga FreeRadius ta hanyar DHCP

Muna yin alamar haɗin kai zuwa tsarin Python (watau "kunna" shi):

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

Bari mu shigar da ƙarin samfuri don Python:

pip install mysql-connector

A cikin saitunan tsarin python na FreeRadius, kuna buƙatar ƙididdige hanyoyin bincike na module a cikin ma'aunin Python_path. Misali ina da wannan:

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"

Kuna iya nemo hanyoyin ta hanyar ƙaddamar da fassarar python da shigar da umarni:

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

Idan ba ku ɗauki wannan matakin ba, to, rubutun da aka rubuta cikin Python kuma FreeRadius ya ƙaddamar ba za su sami samfuran da aka jera a shigo da su ba. Bugu da kari, kuna buƙatar rashin gamsuwa da ayyuka don izini na kira da lissafin kuɗi a cikin saitunan tsarin. Misali, wannan module din yayi kama da haka:

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}

}

Rubutun aikin.py (da duk sauran) dole ne a sanya su a cikin /usr/local/etc/raddb/mods-config/python Ina da rubutun uku a duka.

aiki.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

Kamar yadda kake gani daga lambar, muna ƙoƙarin gano mai biyan kuɗi ta amfani da duk hanyoyin da aka samo ta hanyar sanannun adiresoshin MAC ko Option 82, kuma idan wannan bai yi aiki ba, to muna ba da adireshin IP mafi tsufa da aka taɓa amfani da shi daga “baƙo ” network. Abin da ya rage shi ne saita tsoffin rubutun a cikin babban fayil ɗin da ke kunna rukunin yanar gizo, ta yadda ayyukan da ake buƙata daga rubutun python za su kunna a lokacin da aka keɓe. A zahiri, ya isa ya kawo fayil ɗin zuwa fom ɗin:

tsoho

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

Bari mu yi ƙoƙari mu gudanar da shi mu ga abin da ya zo cikin log ɗin cire matsala:

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

Me kuma. Lokacin kafa FreeRadius, yana dacewa don gwada aikinta ta amfani da kayan aikin radclient. Misali izini:

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

Ko asusu:

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

Ina so in yi muku gargaɗi cewa ba shi da wuya a yi amfani da irin wannan makirci da rubutun "ba tare da canje-canje" akan sikelin "masana'antu" ba. Akalla abin lura:

  • yana yiwuwa a "karya" adireshin MAC. Ya isa ga mai biyan kuɗi don yin rajistar MAC na wani kuma za a sami matsaloli
  • dabarar bayar da hanyoyin sadarwar baƙi ya wuce zargi. Babu ma rajistan "wataƙila akwai abokan ciniki da adireshin IP iri ɗaya?"

Wannan shine kawai "maganin yankan kuki" da aka tsara don yin aiki musamman a cikin yanayi na, ba komai ba. Kar a yanke hukunci sosai 😉

source: www.habr.com

Add a comment