Mipangilio ya mtandao kutoka FreeRadius kupitia DHCP

Mipangilio ya mtandao kutoka FreeRadius kupitia DHCP
Jukumu lilifika la kupanga utoaji wa anwani za IP kwa waliojisajili. Masharti ya shida:

  • Hatutakupa seva tofauti ili uidhinishe - utafanya 😉
  • Wasajili lazima wapokee mipangilio ya mtandao kupitia DHCP
  • Mtandao ni tofauti. Hii ni pamoja na vifaa vya PON na swichi za kawaida zilizo na Chaguo 82 kilichosanidiwa na besi za WiFi zilizo na maeneo-hewa.
  • Ikiwa data haiko chini ya masharti yoyote ya kutoa IP, lazima utoe IP kutoka kwa mtandao wa "mgeni".

Kwa upande mzuri: bado kuna seva kwenye FreeBSD ambayo inaweza "kufanya kazi", lakini iko "mbali";), sio "haki kwenye mtandao huu".

Pia kuna kifaa cha ajabu kinachoitwa Mikrotik. Mchoro wa jumla wa mtandao ni kitu kama hiki:

Mipangilio ya mtandao kutoka FreeRadius kupitia DHCP

Baada ya mawazo fulani, iliamuliwa kutumia FreeRadius kutoa mipangilio ya mtandao kwa waliojisajili. Kimsingi, mpango huo ni wa kawaida: tunawezesha seva ya DHCP kwenye Microtick, na Mteja wa Radius juu yake. Tunasanidi seva ya DHCP -> Mteja wa Radius -> Muunganisho wa seva ya Radius.

Haionekani kuwa ngumu. Lakini! Ibilisi yuko katika maelezo. Yaani:

  • Wakati wa kuidhinisha PON OLT kwa kutumia mpango huu, ombi hutumwa kwa FreeRadius na Jina la Mtumiaji sawa na anwani ya MAC ya kichwa, Kitambulisho cha Mzunguko wa Wakala sawa na MAC PON Onu na nenosiri tupu.
  • Inapoidhinisha kutoka kwa swichi zilizo na chaguo la 82, FreeRadius hupokea ombi lenye Jina tupu la Mtumiaji sawa na MAC ya kifaa cha mteja na kujazwa na sifa za ziada za Agent-Circuit-Id na Agent-Remote-Id zenye, mtawalia, MAC ya swichi ya relay na bandari ambayo mteja ameunganishwa.
  • Baadhi ya wateja walio na visambazaji mtandao wa WiFi wameidhinishwa kupitia itifaki za PAP-CHAP
  • Baadhi ya waliojisajili kutoka kwa pointi za WIFI wameidhinishwa na Jina la Mtumiaji sawa na anwani ya MAC ya kisambazaji cha WIFI, bila nenosiri.

Mandharinyuma ya kihistoria: "Chaguo la 82" katika DHCP ni nini

Hizi ni chaguo za ziada za itifaki ya DHCP zinazokuruhusu kuhamisha maelezo ya ziada, kwa mfano katika sehemu za Agent-Circuit-Id na Agent-Remote-Id. Kawaida hutumika kusambaza anwani ya MAC ya swichi ya relay na mlango ambao mteja ameunganishwa. Kwa upande wa vifaa vya PON au vituo vya msingi vya WIFI, sehemu ya Agent-Circuit-Id haina taarifa muhimu (hakuna mlango wa mteja). Mpango wa jumla wa operesheni ya DHCP katika kesi hii ni kama ifuatavyo.

Mipangilio ya mtandao kutoka FreeRadius kupitia DHCP

Hatua kwa hatua mpango huu hufanya kazi kama hii:

  1. Vifaa vya mtumiaji hufanya ombi la utangazaji la DHCP ili kupata mipangilio ya mtandao
  2. Kifaa (kwa mfano, swichi, WiFi au kituo cha msingi cha PON) ambacho kifaa cha mteja kimeunganishwa moja kwa moja "huingilia" pakiti hii na kuibadilisha, ikitambulisha chaguo za ziada Chaguo 82 na anwani ya IP ya wakala wa Relay ndani yake, na kuisambaza zaidi. mtandao.
  3. Seva ya DHCP inakubali ombi, hutoa jibu na kutuma kwa kifaa cha relay
  4. Kifaa cha relay hupeleka mbele pakiti ya majibu kwa kifaa cha mteja

Kwa kweli, yote hayafanyi kazi kwa urahisi; unahitaji kusanidi vifaa vya mtandao wako ipasavyo.

Inasakinisha FreeRadius

Bila shaka, hii inaweza kupatikana kwa mipangilio ya usanidi wa FreeRadius, lakini ni vigumu na haijulikani ... hasa unapoenda huko baada ya miezi N na "kila kitu kinafanya kazi." Kwa hivyo, tuliamua kuandika moduli yetu ya idhini ya FreeRadius katika Python. Tutachukua data ya idhini kutoka kwa hifadhidata ya MySQL. Hakuna maana katika kuelezea muundo wake; hata hivyo, kila mtu ataitengeneza "kwa ajili yao wenyewe." Hasa, nilichukua muundo unaotolewa na moduli ya sql kwa FreeRadius, na nikabadilisha kidogo kwa kuongeza uwanja wa mac na bandari kwa kila mteja, pamoja na nenosiri la kuingia.

Kwa hivyo, kwanza, sasisha FreeRadius:

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

Katika mipangilio, chagua kusakinisha:

Mipangilio ya mtandao kutoka FreeRadius kupitia DHCP

Tunatengeneza ulinganifu kwa moduli ya python (yaani "kuwasha"):

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

Wacha tusakinishe moduli ya ziada ya python:

pip install mysql-connector

Katika mipangilio ya moduli ya python ya FreeRadius, unahitaji kutaja njia za utaftaji wa moduli kwenye utofauti wa python_path. Kwa mfano nina hii:

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"

Unaweza kujua njia kwa kuzindua mkalimani wa python na kuingiza amri:

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

Ikiwa hautachukua hatua hii, basi hati zilizoandikwa kwa python na kuzinduliwa na FreeRadius hazitapata moduli ambazo zimeorodheshwa katika uingizaji. Kwa kuongeza, unahitaji kufuta kazi za idhini ya kupiga simu na uhasibu katika mipangilio ya moduli. Kwa mfano, moduli hii inaonekana kama hii:

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}

}

Hati ya work.py (na zingine zote) lazima iwekwe ndani /usr/local/etc/raddb/mods-config/python Nina hati tatu kwa jumla.

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

Kama unavyoona kutoka kwa msimbo, tunajaribu kumtambua aliyejisajili kwa kutumia mbinu zote zinazopatikana kwa kutumia anwani za MAC za mteja anayejulikana au mchanganyiko wa Chaguo 82, na ikiwa hii haifanyi kazi, basi tunatoa anwani ya IP ya zamani zaidi iliyowahi kutumika kutoka kwa "mgeni". ” mtandao. Kilichobaki ni kusanidi hati chaguo-msingi kwenye folda iliyowezeshwa na tovuti, ili kazi zinazohitajika kutoka kwa hati ya python zitetemeke kwa wakati uliowekwa. Kwa kweli, inatosha kuleta faili kwenye fomu:

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

Wacha tujaribu kuiendesha na tuone kinachokuja kwenye logi ya utatuzi:

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

Nini kingine. Wakati wa kuanzisha FreeRadius, ni rahisi kupima uendeshaji wake kwa kutumia matumizi ya radclient. Kwa mfano idhini:

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

Au akaunti:

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

Ninataka kukuonya kuwa haiwezekani kabisa kutumia mpango kama huo na maandishi "bila mabadiliko" kwa kiwango cha "viwanda". Angalau inaonekana:

  • inawezekana "bandia" anwani ya MAC. Inatosha kwa msajili kusajili MAC ya mtu mwingine na kutakuwa na shida
  • mantiki ya kutoa mitandao ya wageni ni zaidi ya kukosolewa. Hakuna hata cheki "labda tayari kuna wateja walio na anwani sawa ya IP?"

Hii ni "suluhisho la kukata kuki" iliyoundwa kufanya kazi haswa katika hali yangu, hakuna zaidi. Usihukumu kwa ukali 😉

Chanzo: mapenzi.com

Kuongeza maoni