Gosodiadau rhwydwaith o FreeRadius trwy DHCP

Gosodiadau rhwydwaith o FreeRadius trwy DHCP
Cyrhaeddodd y dasg i drefnu issuance cyfeiriadau IP i danysgrifwyr. Amodau'r broblem:

  • Ni fyddwn yn rhoi gweinydd ar wahân i chi i'w awdurdodi - byddwch chi'n gwneud 😉
  • Rhaid i danysgrifwyr dderbyn gosodiadau rhwydwaith trwy DHCP
  • Mae'r rhwydwaith yn heterogenaidd. Mae hyn yn cynnwys offer PON a switshis rheolaidd gydag Opsiwn 82 wedi'i ffurfweddu a chanolfannau WiFi gyda mannau problemus
  • Os nad yw'r data yn dod o dan unrhyw un o'r amodau ar gyfer cyhoeddi IP, rhaid i chi gyhoeddi IP o'r rhwydwaith “gwestai”

Ar yr ochr dda: mae gweinydd ar FreeBSD o hyd a all “weithio”, ond mae'n “bell i ffwrdd” ;), nid “yn uniongyrchol ar y rhwydwaith hwn”.

Mae yna hefyd ddyfais wych o'r enw Mikrotik. Mae'r diagram rhwydwaith cyffredinol yn rhywbeth fel hyn:

Gosodiadau rhwydwaith o FreeRadius trwy DHCP

Ar ôl peth meddwl, penderfynwyd defnyddio FreeRadius i gyhoeddi gosodiadau rhwydwaith i danysgrifwyr. Mewn egwyddor, mae'r cynllun yn arferol: rydym yn galluogi'r gweinydd DHCP ar Microtick, a Radius Client arno. Rydym yn ffurfweddu'r gweinydd DHCP -> Cleient Radius -> Cysylltiad gweinydd Radius.

Nid yw'n ymddangos yn anodd. Ond! Mae'r diafol yn y manylion. sef:

  • Wrth awdurdodi PON OLT gan ddefnyddio'r cynllun hwn, anfonir cais at FreeRadius gydag Enw Defnyddiwr sy'n hafal i gyfeiriad MAC y pennawd, Agent-Circuit-Id hafal i'r MAC PON Onu a chyfrinair gwag.
  • Wrth awdurdodi switshis gydag opsiwn 82, mae FreeRadius yn derbyn cais gydag Enw Defnyddiwr gwag sy'n hafal i MAC dyfais y tanysgrifiwr ac wedi'i lenwi â phriodoleddau ychwanegol Agent-Circuit-Id ac Agent-Remote-Id yn cynnwys, yn y drefn honno, eto MAC o y switsh cyfnewid a'r porthladd y mae'r tanysgrifiwr wedi'i gysylltu ag ef.
  • Mae rhai tanysgrifwyr sydd â phwyntiau WiFI yn cael eu hawdurdodi trwy brotocolau PAP-CHAP
  • Mae rhai tanysgrifwyr o bwyntiau WIFI wedi'u hawdurdodi gydag Enw Defnyddiwr sy'n hafal i gyfeiriad MAC y pwynt WIFI, heb gyfrinair.

Cefndir hanesyddol: beth yw “Opsiwn 82” yn DHCP

Mae'r rhain yn opsiynau ychwanegol ar gyfer y protocol DHCP sy'n eich galluogi i drosglwyddo gwybodaeth ychwanegol, er enghraifft yn y meysydd Agent-Circuit-Id ac Agent-Remote-Id. Fe'i defnyddir yn nodweddiadol i drosglwyddo cyfeiriad MAC y switsh cyfnewid a'r porthladd y mae'r tanysgrifiwr wedi'i gysylltu ag ef. Yn achos offer PON neu orsafoedd sylfaen WIFI, nid yw maes Asiant-Circuit-Id yn cynnwys gwybodaeth ddefnyddiol (nid oes porthladd tanysgrifiwr). Mae cynllun cyffredinol gweithredu DHCP yn yr achos hwn fel a ganlyn:

Gosodiadau rhwydwaith o FreeRadius trwy DHCP

Cam wrth gam mae'r cynllun hwn yn gweithio fel hyn:

  1. Mae'r offer defnyddiwr yn gwneud cais darlledu DHCP i gael gosodiadau rhwydwaith
  2. Mae'r ddyfais (er enghraifft, switsh, WiFi neu orsaf sylfaen PON) y mae'r offer tanysgrifiwr wedi'i gysylltu'n uniongyrchol â hi yn “rhyng-gipio” y pecyn hwn ac yn ei newid, gan gyflwyno opsiynau ychwanegol Opsiwn 82 a chyfeiriad IP asiant Relay i mewn iddo, ac yn ei drosglwyddo ymhellach drosodd y rhwydwaith.
  3. Mae'r gweinydd DHCP yn derbyn y cais, yn cynhyrchu ymateb ac yn ei anfon i'r ddyfais gyfnewid
  4. Mae'r ddyfais ras gyfnewid yn anfon y pecyn ymateb ymlaen i'r ddyfais tanysgrifiwr

Wrth gwrs, nid yw popeth yn gweithio mor hawdd â hynny; mae angen i chi ffurfweddu'ch offer rhwydwaith yn unol â hynny.

Gosod FreeRadius

Wrth gwrs, gellir cyflawni hyn gyda gosodiadau cyfluniad FreeRadius, ond mae'n anodd ac yn aneglur ... yn enwedig pan ewch yno ar ôl N mis a "popeth yn gweithio." Felly, penderfynasom ysgrifennu ein modiwl awdurdodi ein hunain ar gyfer FreeRadius yn Python. Byddwn yn cymryd data awdurdodi o gronfa ddata MySQL. Nid oes unrhyw ddiben disgrifio ei strwythur; beth bynnag, bydd pawb yn ei wneud “drostyn nhw eu hunain.” Yn benodol, cymerais y strwythur a gynigir gyda'r modiwl sql ar gyfer FreeRadius, a'i newid ychydig trwy ychwanegu maes mac a phorthladd ar gyfer pob tanysgrifiwr, yn ogystal â'r cyfrinair mewngofnodi.

Felly, yn gyntaf, gosodwch FreeRadius:

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

Yn y gosodiadau, dewiswch osod:

Gosodiadau rhwydwaith o FreeRadius trwy DHCP

Rydyn ni'n gwneud cyswllt syml i'r modiwl python (h.y. "trowch ymlaen"):

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

Gadewch i ni osod modiwl ychwanegol ar gyfer python:

pip install mysql-connector

Yn y gosodiadau modiwl python ar gyfer FreeRadius, mae angen i chi nodi'r llwybrau chwilio modiwl yn y newidyn python_path. Er enghraifft mae gen i hwn:

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"

Gallwch ddarganfod y llwybrau trwy lansio'r dehonglydd python a nodi'r gorchmynion:

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

Os na chymerwch y cam hwn, ni fydd sgriptiau a ysgrifennwyd yn python ac a lansiwyd gan FreeRadius yn dod o hyd i'r modiwlau a restrir yn mewnforio. Yn ogystal, mae angen i chi ddadwneud y swyddogaethau ar gyfer galw awdurdodi a chyfrifo yng ngosodiadau'r modiwl. Er enghraifft, mae'r modiwl hwn yn edrych fel hyn:

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}

}

Rhaid gosod y sgript work.py (a'r lleill i gyd) yn /usr/local/etc/raddb/mods-config/python Mae gen i dair sgript i gyd.

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

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

Fel y gallwch weld o'r cod, rydym yn ceisio adnabod y tanysgrifiwr gan ddefnyddio'r holl ddulliau sydd ar gael trwy ei gyfeiriadau MAC tanysgrifiwr hysbys neu gyfuniad Opsiwn 82, ac os nad yw hyn yn gweithio, yna rydym yn cyhoeddi'r cyfeiriad IP hynaf a ddefnyddiwyd erioed o'r “gwestai ” rhwydwaith. Y cyfan sy'n weddill yw ffurfweddu'r sgript ddiofyn yn y ffolder sydd wedi'i alluogi gan safleoedd, fel y bydd y swyddogaethau angenrheidiol o'r sgript python yn plysio ar yr eiliadau dynodedig. Mewn gwirionedd, mae'n ddigon dod â'r ffeil i'r ffurflen:

diofyn

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

Gadewch i ni geisio ei redeg a gweld beth sy'n dod i mewn i'r log dadfygio:

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

Beth arall. Wrth sefydlu FreeRadius, mae'n gyfleus profi ei weithrediad gan ddefnyddio'r cyfleustodau radclient. Er enghraifft awdurdodiad:

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

Neu gyfrif:

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

Rwyf am eich rhybuddio ei bod yn gwbl amhosibl defnyddio cynllun o’r fath a sgriptiau “heb newidiadau” ar raddfa “ddiwydiannol”. O leiaf yn amlwg:

  • mae'n bosibl "ffug" y cyfeiriad MAC. Mae'n ddigon i'r tanysgrifiwr gofrestru MAC rhywun arall a bydd problemau
  • mae'r rhesymeg o gyhoeddi rhwydweithiau gwesteion y tu hwnt i feirniadaeth. Nid oes siec hyd yn oed “efallai bod yna gleientiaid eisoes â'r un cyfeiriad IP?”

Dim ond “ateb torrwr cwci” yw hwn sydd wedi'i gynllunio i weithio'n benodol yn fy amodau, dim byd mwy. Peidiwch â barnu'n llym 😉

Ffynhonnell: hab.com

Ychwanegu sylw