Suidhichidhean lìonra bho FreeRadius tro DHCP

Suidhichidhean lìonra bho FreeRadius tro DHCP
Ràinig an obair foillseachadh sheòlaidhean IP a chuir air dòigh do luchd-aontachaidh. Suidheachaidhean na trioblaid:

  • Cha toir sinn frithealaiche air leth dhut airson cead - nì thu sin 😉
  • Feumaidh luchd-aontachaidh roghainnean lìonra fhaighinn tro DHCP
  • Tha an lìonra ioma-ghnèitheach. Tha seo a’ toirt a-steach uidheamachd PON agus suidsichean cunbhalach le roghainnean rèiteachaidh 82 agus ionadan WiFi le àiteachan teth
  • Mura h-eil an dàta a’ tighinn fo chumhachan sam bith airson IP a chuir a-mach, feumaidh tu IP a chuir a-mach bhon lìonra “aoigh”

Air an taobh mhath: tha frithealaiche air FreeBSD fhathast as urrainn “obrachadh”, ach tha e “fada air falbh” ;), chan e “dìreach air an lìonra seo”.

Tha inneal mìorbhaileach ann cuideachd ris an canar Mikrotik. Tha an diagram lìonra coitcheann rudeigin mar seo:

Suidhichidhean lìonra bho FreeRadius tro DHCP

Às deidh beagan smaoineachaidh, chaidh co-dhùnadh FreeRadius a chleachdadh gus roghainnean lìonra a chuir a-mach gu luchd-aontachaidh. Ann am prionnsabal, tha an sgeama àbhaisteach: bidh sinn a 'toirt comas don fhrithealaiche DHCP air Microtick, agus Radius Client air. Bidh sinn a’ rèiteachadh an fhrithealaiche DHCP -> Radius Client -> Ceangal frithealaiche Radius.

Chan eil e coltach gu bheil e duilich. Ach! Tha an diabhal anns na mion-fhiosrachadh. Is e sin:

  • Nuair a bhios tu a’ ceadachadh PON OLT a’ cleachdadh an sgeama seo, thèid iarrtas a chuir gu FreeRadius le Ainm-cleachdaidh co-ionann ri seòladh MAC an headend, Agent-Circuit-Id co-ionann ris an MAC PON Onu agus facal-faire falamh.
  • Nuair a gheibh e cead bho suidsichean le roghainn 82, gheibh FreeRadius iarrtas le Ainm-cleachdaidh falamh co-ionann ri MAC inneal an neach-clàraidh agus air a lìonadh le buadhan a bharrachd Agent-Circuit-Id agus Agent-Remote-Id anns a bheil, fa leth, a-rithist MAC de an tionndadh sealaidheachd agus am port ris a bheil an neach-clàraidh ceangailte.
  • Tha cuid de luchd-aontachaidh le puingean WiFI ùghdarraichte tro phròtacalan PAP-CHAP
  • Tha cuid de luchd-aontachaidh bho phuingean WIFI ùghdarraichte le Ainm-cleachdaidh co-ionann ri seòladh MAC a’ phuing WIFI, gun fhacal-faire.

Cùl-fhiosrachadh eachdraidheil: dè a th’ ann an “Roghainn 82” ann an DHCP

Tha iad sin nan roghainnean a bharrachd airson protocol DHCP a leigeas leat fiosrachadh a bharrachd a ghluasad, mar eisimpleir anns na raointean Agent-Circuit-Id agus Agent-Remote-Id. Mar as trice air a chleachdadh airson seòladh MAC an t-suidse sealaidheachd agus am port ris a bheil an neach-clàraidh ceangailte. A thaobh uidheamachd PON no stèiseanan bunaiteach WIFI, chan eil fiosrachadh feumail anns an raon Agent-Circuit-Id (chan eil port ballrachd ann). Tha an sgeama coitcheann airson obrachadh DHCP sa chùis seo mar a leanas:

Suidhichidhean lìonra bho FreeRadius tro DHCP

Ceum air cheum bidh an sgeama seo ag obair mar seo:

  1. Bidh an uidheamachd cleachdaiche a’ dèanamh iarrtas craolaidh DHCP gus roghainnean lìonra fhaighinn
  2. Bidh an inneal (mar eisimpleir, suidse, WiFi no stèisean bonn PON) ris a bheil an uidheamachd ballrachd ceangailte gu dìreach “a’ toirt a-steach ”a’ phacaid seo agus ga atharrachadh, a ’toirt a-steach roghainnean a bharrachd Roghainn 82 agus seòladh IP àidseant Relay a-steach dha, agus ga chuir a-null thairis. an lìonra.
  3. Bidh am frithealaiche DHCP a’ gabhail ris an iarrtas, a’ gineadh freagairt agus ga chuir chun inneal sealaidheachd
  4. Bidh an inneal sealaidheachd a’ toirt a’ phacaid freagairt air adhart chun inneal ballrachd

Gu dearbh, chan obraich e cho furasta sin; feumaidh tu an uidheamachd lìonra agad a rèiteachadh a rèir sin.

Stàladh an-asgaidh Radius

Gu dearbh, faodar seo a choileanadh le roghainnean rèiteachaidh FreeRadius, ach tha e duilich agus neo-shoilleir ... gu sònraichte nuair a thèid thu ann às deidh N mìosan agus “tha a h-uile dad ag obair.” Mar sin, chuir sinn romhainn am modal ùghdarrais againn fhèin a sgrìobhadh airson FreeRadius ann am Python. Gabhaidh sinn dàta cead bhon stòr-dàta MySQL. Chan eil feum air cunntas a thoirt air an structar aige; co-dhiù, nì a h-uile duine e “dhaibh fhèin.” Gu sònraichte, ghabh mi an structar a tha air a thabhann leis a ’mhodal sql airson FreeRadius, agus dh’ atharraich mi e beagan le bhith a ’cur raon mac agus port airson gach neach-clàraidh, a bharrachd air am facal-faire logadh a-steach.

Mar sin, an toiseach, stàlaich FreeRadius:

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

Anns na roghainnean, tagh airson stàladh:

Suidhichidhean lìonra bho FreeRadius tro DHCP

Bidh sinn a’ dèanamh symlink ris a’ mhodal python (ie “tionndaidh air”) e):

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

Nach stàlaich sinn modal a bharrachd airson python:

pip install mysql-connector

Ann an roghainnean modal python airson FreeRadius, feumaidh tu na slighean sgrùdaidh modal a shònrachadh anns an caochladair python_path. Mar eisimpleir tha seo agam:

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"

Gheibh thu a-mach na slighean le bhith a’ cur air bhog an eadar-theangair python agus a’ dol a-steach do na h-òrdughan:

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

Mura gabh thu an ceum seo, an uairsin chan fhaigh sgriobtaichean sgrìobhte ann am python agus a chuir FreeRadius air bhog na modalan a tha air an liostadh ann an in-mhalairt. A bharrachd air an sin, feumaidh tu na gnìomhan airson cead a ghairm agus cunntas a thoirt a-mach ann an roghainnean a’ mhodal. Mar eisimpleir, tha coltas mar seo air a’ mhodal seo:

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}

}

Feumar an sgriobt work.py (agus a h-uile càil eile) a chuir ann an /usr/local/etc/raddb/mods-config/python Tha trì sgriobtaichean agam uile gu lèir.

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

Mar a chì thu bhon chòd, tha sinn a’ feuchainn ris an neach-clàraidh a chomharrachadh a’ cleachdadh a h-uile dòigh a tha ri fhaighinn leis an neach-clàraidh aithnichte aige seòlaidhean MAC no cothlamadh Roghainn 82, agus mura h-obraich seo, bidh sinn a’ toirt a-mach an seòladh IP as sine a chaidh a chleachdadh a-riamh bhon “aoigh "lìonra. Chan eil air fhàgail ach an sgriobt bunaiteach a rèiteachadh anns a’ phasgan le comas làraich, gus am bi na gnìomhan riatanach bhon sgriobt python a’ tionndadh aig na h-amannan ainmichte. Gu dearbh, tha e gu leòr am faidhle a thoirt chun fhoirm:

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

Feuchaidh sinn ri a ruith agus faicinn dè a thig a-steach don log deasbaid:

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

Dè eile. Nuair a bhios tu a’ stèidheachadh FreeRadius, tha e goireasach an obair aige a dhearbhadh a’ cleachdadh goireas radclient. Mar eisimpleir cead:

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

No cunntas:

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

Tha mi airson rabhadh a thoirt dhut gu bheil e gu tur eu-comasach a leithid de sgeama agus sgriobtaichean a chleachdadh “gun atharrachaidhean” air sgèile “gnìomhachais”. Co-dhiù follaiseach:

  • tha e comasach “fake” an seòladh MAC. Tha e gu leòr airson an neach-clàraidh MAC cuideigin eile a chlàradh agus bidh duilgheadasan ann
  • tha an loidsig airson lìonraidhean aoighean a chuir a-mach nas fhaide na càineadh. Chan eil eadhon seic ann “is dòcha gu bheil teachdaichean ann mu thràth leis an aon sheòladh IP?”

Is e dìreach “fuasgladh gearraidh briosgaid” a tha seo a chaidh a dhealbhadh gus obrachadh gu sònraichte anns na suidheachaidhean agam, gun dad a bharrachd. Na bi a’ breithneachadh gu teann 😉

Source: www.habr.com

Cuir beachd ann