DHCP аркылуу FreeRadius'тан тармак орнотуулары

DHCP аркылуу FreeRadius'тан тармак орнотуулары
Абоненттерге IP даректерди берүүнү уюштуруу тапшырмасы келди. Көйгөйдүн шарттары:

  • Биз сизге авторизация үчүн өзүнчө сервер бербейбиз - сиз жасайсыз😉
  • Абоненттер DHCP аркылуу тармак жөндөөлөрүн кабыл алышы керек
  • Тармак гетерогендүү. Буга PON жабдуулары жана конфигурацияланган Option 82 менен кадимки өчүргүчтөр жана хотспоттору бар WiFi базалары кирет
  • Эгерде маалыматтар IP берүү шарттарынын бирине да туура келбесе, анда сиз "конок" тармагынан IP беришиңиз керек.

Жакшы жагы: FreeBSDде дагы эле “иштей турган” сервер бар, бирок ал “бул тармакта” эмес, “алыста”;

Микротик деген сонун аппарат дагы бар. Жалпы тармак диаграммасы төмөнкүдөй:

DHCP аркылуу FreeRadius'тан тармак орнотуулары

Бир аз ойлонгондон кийин, абоненттерге тармак жөндөөлөрүн берүү үчүн FreeRadius колдонуу чечими кабыл алынды. Негизи схема кадимкидей: Microtickте DHCP серверин жана ага Radius Clientти иштетебиз. Биз DHCP сервер -> Radius Client -> Radius сервер байланышын конфигурациялайбыз.

Бул кыйын эмес окшойт. Бирок! Шайтан майда-чүйдөсүнө чейин. Тактап айтканда:

  • Бул схеманы колдонуу менен PON OLTге авторизациялоодо, FreeRadiusка суроо баштын MAC дарегине барабар Колдонуучунун аты, MAC PON Онуна барабар Agent-Circuit-Id жана бош сырсөз менен жөнөтүлөт.
  • 82 опциясы бар коммутаторлордон авторизациялоодо, FreeRadius абоненттин түзмөгүнүн MACсына барабар жана Agent-Circuit-Id жана Agent-Remote-Id кошумча атрибуттары менен толтурулган бош Колдонуучу-Аты бар суроо-талапты алат. релелик коммутатор жана абонент кошулган порт.
  • WiFI түйүндөрү бар айрым абоненттерге PAP-CHAP протоколдору аркылуу уруксат берилген
  • WIFI түйүндөрүнүн айрым абоненттерине WIFI түйүнүнүн MAC дарегине барабар Колдонуучу аты менен, сырсөзсүз уруксат берилет.

Тарыхый фон: DHCPдеги “82-вариант” деген эмне

Бул DHCP протоколунун кошумча параметрлери, алар кошумча маалыматты өткөрүүгө мүмкүндүк берет, мисалы, Агент-Схема-Идентификатор жана Агент-Алыскы-Идентификатор талааларында. Адатта релелик коммутатордун MAC дарегин жана абонент туташтырылган портту өткөрүү үчүн колдонулат. PON жабдыктары же WIFI базалык станциялары болгон учурда, Agent-Circuit-Id талаасында пайдалуу маалымат жок (абонент порту жок). Бул учурда DHCP ишинин жалпы схемасы төмөнкүдөй:

DHCP аркылуу FreeRadius'тан тармак орнотуулары

Этап-этабы менен бул схема төмөнкүдөй иштейт:

  1. Колдонуучу жабдыгы тармак жөндөөлөрүн алуу үчүн DHCP уктуруу сурамын берет
  2. Абоненттик жабдуу түздөн-түз туташтырылган түзүлүш (мисалы, коммутатор, WiFi же PON базалык станциясы) бул пакетти "кесиптешти" жана ага кошумча Option 82 жана Relay агентинин IP дарегин киргизип, аны андан ары өткөрүп берет. желе.
  3. DHCP сервери суроо-талапты кабыл алып, жоопту жаратат жана аны реледик түзүлүшкө жөнөтөт
  4. Реледик түзүлүш жооп пакетин абоненттик түзүлүшкө жөнөтөт

Албетте, мунун баары оңой иштебейт; сиз тармактык жабдууларды ошого жараша конфигурациялашыңыз керек.

FreeRadius орнотулууда

Албетте, бул FreeRadius конфигурация орнотуулары менен жетишүүгө болот, бирок бул кыйын жана түшүнүксүз ... өзгөчө N айдан кийин ал жакка барганда жана "баары иштейт." Ошондуктан, биз Pythonдо FreeRadius үчүн өзүбүздүн авторизация модулубузду жазууну чечтик. Биз MySQL маалымат базасынан авторизация маалыматтарын алабыз. Анын түзүлүшүн сүрөттөп берүүнүн эч кандай мааниси жок, баары бир, аны ар ким "өзү үчүн" жасайт. Тактап айтканда, мен FreeRadius үчүн sql модулу менен сунушталган структураны алып, логин-парольдон тышкары, ар бир абонент үчүн Mac жана порт талаасын кошуп, аны бир аз өзгөрттүм.

Ошентип, биринчиден, FreeRadius орнотуу:

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

Орнотуулардан орнотууну тандаңыз:

DHCP аркылуу FreeRadius'тан тармак орнотуулары

Биз python модулуна символдук шилтеме жасайбыз (б.а. аны "күйгүзүү"):

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

Python үчүн кошумча модулду орнотобуз:

pip install mysql-connector

FreeRadius үчүн python модулунун орнотууларында сиз python_path өзгөрмөсүндө модулдун издөө жолдорун көрсөтүшүңүз керек. Мисалы, менде бул бар:

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"

Сиз python котормочу ишке киргизүү жана буйруктарды киргизүү менен жолдорду таба аласыз:

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

Эгер сиз бул кадамга барбасаңыз, анда питондо жазылган жана FreeRadius тарабынан ишке киргизилген скрипттер импортто тизмеленген модулдарды таба албайт. Мындан тышкары, модулдун жөндөөлөрүндө чалуу авторизациясы жана эсепке алуу функцияларын жокко чыгарышыңыз керек. Мисалы, бул модул төмөнкүдөй көрүнөт:

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}

}

work.py скрипти (жана башкалары) /usr/local/etc/raddb/mods-config/python ичинде жайгаштырылышы керек Менде бардыгы болуп үч скрипт бар.

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

Коддон көрүнүп тургандай, биз абонентти анын белгилүү абонентинин MAC даректери же Option 82 комбинациясынын бардык жеткиликтүү ыкмаларын колдонуу менен аныктоого аракет кылып жатабыз, эгер бул иштебесе, анда биз “конокто” мурда колдонулган эң эски IP даректи чыгарабыз. ” тармагы. Болгону, Python скриптинен керектүү функциялар белгиленген учурларда кыйшайып калышы үчүн, сайттар иштетилген папкадагы демейки скриптти конфигурациялоо гана калды. Чындыгында, файлды формага алып келүү жетиштүү:

жарыяланбаган

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

Келгиле, аны иштетип, мүчүлүштүктөрдү оңдоо журналына эмне киргенин карап көрөлү:

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

Дагы эмне. FreeRadius орнотууда, анын иштешин radclient утилитасын колдонуу менен текшерүү ыңгайлуу. Мисалы, авторизация:

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

Же эсеп:

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

Мен мындай схеманы жана скрипттерди "өнөр жайлык" масштабда "өзгөртүүсүз" колдонуу таптакыр мүмкүн эмес экенин эскертким келет. Жок дегенде байкалат:

  • MAC дарегин "жасалма" кылуу мүмкүн. Абонентке башка бирөөнүн MAC каттоодон өтүшү жетиштүү жана көйгөйлөр пайда болот
  • конок тармактарды чыгаруу логикасы сындан тышкары. "Балким, бир эле IP дареги бар кардарлар бардыр?" деген текшерүү да жок.

Бул жөн гана менин шарттарымда иштөө үчүн иштелип чыккан "куки кесүүчү чечим", башка эч нерсе эмес. Катуу соттобоңуз😉

Source: www.habr.com

Комментарий кошуу