Setélan jaringan tina FreeRadius via DHCP

Setélan jaringan tina FreeRadius via DHCP
Tugas dugi ka ngatur penerbitan alamat IP ka palanggan. Kaayaan masalah:

  • Kami moal masihan anjeun server anu misah pikeun otorisasi - anjeun bakalan 😉
  • Palanggan kedah nampi setélan jaringan ngalangkungan DHCP
  • Jaringanna hétérogén. Ieu kalebet alat-alat PON sareng saklar biasa sareng Pilihan 82 anu dikonpigurasi sareng pangkalan WiFi sareng hotspot
  • Upami data henteu aya dina kaayaan naon waé pikeun ngaluarkeun IP, anjeun kedah ngaluarkeun IP tina jaringan "tamu"

Dina sisi alus: masih aya server on FreeBSD nu bisa "digawe", tapi "jauh" ;), teu "katuhu dina jaringan ieu".

Aya ogé hiji alat éndah disebut Mikrotik. Diagram jaringan umum nyaéta sapertos kieu:

Setélan jaringan tina FreeRadius via DHCP

Saatos sababaraha pamikiran, éta mutuskeun pikeun ngagunakeun FreeRadius pikeun ngaluarkeun setélan jaringan ka palanggan. Sacara prinsip, skéma ieu biasa: urang ngaktifkeun server DHCP dina Microtick, sareng Radius Client di dinya. Urang ngonpigurasikeun server DHCP -> Radius klien -> Radius sambungan server.

Sigana mah hese. Tapi! Iblis aya dina detil. Nyaéta:

  • Nalika otorisasi PON OLT nganggo skéma ieu, pamenta dikirim ka FreeRadius kalayan nami pangguna anu sami sareng alamat MAC tina headend, Agen-Circuit-Id sami sareng MAC PON Onu sareng kecap akses kosong.
  • Nalika otorisasi tina saklar sareng pilihan 82, FreeRadius nampi pamundut nganggo Ngaran-Pamaké kosong anu sami sareng MAC alat palanggan sareng ngeusi atribut tambahan Agén-Circuit-Id sareng Agén-Remote-Id anu ngandung, masing-masing, deui MAC tina switch relay jeung port nu palanggan disambungkeun.
  • Sababaraha palanggan sareng titik WiFI diidinan ngalangkungan protokol PAP-CHAP
  • Sababaraha palanggan tina titik WIFI otorisasi sareng nami pangguna anu sami sareng alamat MAC titik WIFI, tanpa kecap akses.

Latar sajarah: naon "Pilihan 82" dina DHCP

Ieu pilihan tambahan pikeun protokol DHCP nu ngidinan Anjeun pikeun nransper informasi tambahan, contona dina widang Agen-Circuit-Id jeung Agen-Jauh-Id. Biasana dianggo pikeun ngirimkeun alamat MAC tina saklar relay sareng port anu nyambungkeun palanggan. Dina hal alat PON atanapi base station WIFI, kolom Agent-Circuit-Id henteu ngandung inpormasi anu mangpaat (teu aya palabuhan langganan). Skéma umum operasi DHCP dina hal ieu nyaéta kieu:

Setélan jaringan tina FreeRadius via DHCP

Step by step skéma ieu jalan sapertos kieu:

  1. Parabot pamaké nyieun pamundut siaran DHCP pikeun ménta setélan jaringan
  2. Alat (contona, switch, WiFi atawa base station PON) nu alat palanggan disambungkeun langsung "intercepts" pakét ieu sarta ngarobahna, ngenalkeun pilihan tambahan Pilihan 82 sarta Relay agén alamat IP kana eta, sarta transmits eta salajengna leuwih. jaringan.
  3. DHCP server narima pamundut nu, dibangkitkeun respon sarta ngirimkeun ka alat relay
  4. Alat relay neruskeun pakét respon ka alat palanggan

Tangtosna, éta henteu tiasa dianggo kalayan gampang; anjeun kedah ngonpigurasikeun alat jaringan anjeun sasuai.

Masang FreeRadius

Tangtosna, ieu tiasa dihontal ku setélan konfigurasi FreeRadius, tapi sesah sareng teu jelas ... utamina upami anjeun angkat ka dinya saatos N sasih sareng "sadayana jalan." Kituna, urang mutuskeun nulis modul otorisasina sorangan pikeun FreeRadius di Python. Kami bakal nyandak data otorisasi tina pangkalan data MySQL. Teu aya gunana pikeun ngajelaskeun strukturna; kumaha waé, sadayana bakal ngajantenkeun éta "pikeun dirina." Khususna, kuring nyandak struktur anu ditawarkeun ku modul sql pikeun FreeRadius, sareng rada robih ku nambihan lapangan mac sareng port pikeun tiap palanggan, salian login-sandi.

Janten, kahiji, pasang FreeRadius:

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

Dina setélan, pariksa pamasangan:

Setélan jaringan tina FreeRadius via DHCP

Urang nyieun symlink kana modul python (ie "ngahurungkeun" eta):

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

Hayu urang pasang modul tambahan pikeun python:

pip install mysql-connector

Dina setélan modul python pikeun FreeRadius, anjeun kudu nangtukeun jalur pilarian modul dina variabel python_path. Salaku conto kuring gaduh ieu:

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"

Anjeun tiasa mendakan jalur ku ngaluncurkeun juru python sareng ngalebetkeun paréntah:

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

Upami anjeun henteu nyandak léngkah ieu, maka skrip anu ditulis dina python sareng diluncurkeun ku FreeRadius moal mendakan modul anu didaptarkeun dina impor. Salaku tambahan, anjeun kedah ngahapus koméntar fungsi pikeun nelepon otorisasi sareng akuntansi dina setélan modul. Contona, modul ieu kasampak kawas kieu:

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}

}

Aksara work.py (sareng sadayana anu sanésna) kedah ditempatkeun dina /usr/local/etc/raddb/mods-config/python Kuring gaduh tilu naskah total.

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

Sakumaha anjeun tiasa tingali tina kode éta, kami nyobian ngaidentipikasi palanggan nganggo sadaya metode anu sayogi ku alamat MAC palanggan anu dipikanyaho atanapi kombinasi Pilihan 82, sareng upami ieu henteu jalan, maka kami ngaluarkeun alamat IP pangkolotna anu kantos dianggo tina "tamu". "jaringan. Sadaya anu tetep nyaéta ngonpigurasikeun skrip standar dina folder anu diaktipkeun situs, supados fungsi anu dipikabutuh tina skrip python bakal kedutan dina waktos anu ditunjuk. Kanyataanna, éta cukup pikeun mawa file kana formulir:

standar

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

Hayu urang cobian ngajalankeun éta sareng tingali naon anu asup kana log debug:

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

Naon deui. Nalika nyetél FreeRadius, éta gampang pikeun nguji operasina nganggo utilitas radclient. Contona otorisasi:

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

Atawa akun:

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

Abdi hoyong ngingetkeun yén mustahil pisan ngagunakeun skéma sareng skrip sapertos "tanpa parobihan" dina skala "industri". Sahenteuna noticeable:

  • kasebut nyaéta dimungkinkeun pikeun "palsu" alamat MAC. Cukup pikeun palanggan ngadaptarkeun MAC batur sareng bakal aya masalah
  • logika ngaluarkeun jaringan tamu saluareun kritik. Malah teu aya cek "panginten parantos aya klien sareng alamat IP anu sami?"

Ieu ngan hiji "solusi cookie-cutter" dirancang pikeun digawé husus dina kaayaan kuring, nanaon deui. Ulah nangtoskeun ketat 😉

sumber: www.habr.com

Tambahkeun komentar