Setelan jaringan saka FreeRadius liwat DHCP

Setelan jaringan saka FreeRadius liwat DHCP
Tugas teka kanggo ngatur penerbitan alamat IP kanggo pelanggan. Kondisi masalah:

  • Kita ora bakal menehi server kapisah kanggo wewenang - sampeyan bakal nindakake 😉
  • Pelanggan kudu nampa setelan jaringan liwat DHCP
  • Jaringan kasebut heterogen. Iki kalebu peralatan PON lan ngalih biasa karo Opsi diatur 82 lan basis WiFi karo hotspot
  • Yen data ora ana ing kahanan apa wae kanggo nerbitake IP, sampeyan kudu ngetokake IP saka jaringan "tamu"

Ing sisih apik: isih ana server ing FreeBSD sing bisa "bisa", nanging "adoh" ;), ora "tengen ing jaringan iki".

Ana uga piranti apik sing diarani Mikrotik. Diagram jaringan umum kaya iki:

Setelan jaringan saka FreeRadius liwat DHCP

Sawise sawetara pamikiran, diputusake nggunakake FreeRadius kanggo ngetokake setelan jaringan menyang pelanggan. Ing asas, skema kasebut biasane: kita ngaktifake server DHCP ing Microtick, lan Klien Radius ing kono. Kita ngatur server DHCP -> Radius Client -> Sambungan server Radius.

Kayane ora angel. Nanging! Iblis ana ing rincian. yaiku:

  • Nalika menehi wewenang PON OLT nggunakake skema iki, panjalukan dikirim menyang FreeRadius kanthi jeneng pangguna sing padha karo alamat MAC headend, Agen-Circuit-Id sing padha karo MAC PON Onu lan sandhi kosong.
  • Nalika menehi wewenang saka ngalih kanthi opsi 82, FreeRadius nampa panjalukan kanthi Jeneng Panganggo kosong sing padha karo MAC piranti pelanggan lan diisi atribut tambahan Agent-Circuit-Id lan Agen-Remote-Id sing ngemot, maneh MAC saka switch relay lan port sing disambungake karo pelanggan.
  • Sawetara pelanggan sing duwe titik WiFI diwenehi wewenang liwat protokol PAP-CHAP
  • Sawetara pelanggan saka titik WIFI diwenehi wewenang karo Jeneng Panganggo sing padha karo alamat MAC titik WIFI, tanpa sandhi.

Latar mburi sajarah: apa "Pilihan 82" ing DHCP

Iki minangka pilihan tambahan kanggo protokol DHCP sing ngidini sampeyan nransfer informasi tambahan, contone ing kolom Agent-Circuit-Id lan Agent-Remote-Id. Biasane digunakake kanggo ngirim alamat MAC saka switch relay lan port sing disambungake karo pelanggan. Ing kasus peralatan PON utawa stasiun pangkalan WIFI, kolom Agent-Circuit-Id ora ngemot informasi sing migunani (ora ana port pelanggan). Skema umum operasi DHCP ing kasus iki yaiku:

Setelan jaringan saka FreeRadius liwat DHCP

Langkah demi langkah skema iki dianggo kaya iki:

  1. Peralatan pangguna nggawe panjalukan siaran DHCP kanggo entuk setelan jaringan
  2. Piranti kasebut (contone, switch, WiFi utawa stasiun pangkalan PON) sing peralatan pelanggan disambungake langsung "nyegat" paket iki lan ngganti, ngenalake opsi tambahan Pilihan 82 lan alamat IP agen Relay menyang, lan ngirimake luwih akeh. jaringan.
  3. Server DHCP nampa panjalukan, ngasilake respon lan dikirim menyang piranti relay
  4. Piranti relay nerusake paket respon menyang piranti langganan

Mesthine, kabeh ora bisa digunakake kanthi gampang; sampeyan kudu ngatur peralatan jaringan kanthi cocog.

Nginstal FreeRadius

Mesthi, iki bisa digayuh kanthi setelan konfigurasi FreeRadius, nanging angel lan ora jelas ... utamane yen sampeyan pindhah menyang kana sawise N sasi lan "kabeh bisa digunakake." Mulane, kita mutusake kanggo nulis modul wewenang dhewe kanggo FreeRadius ing Python. Kita bakal njupuk data wewenang saka database MySQL. Ora ana gunane kanggo njlèntrèhaké strukturé; Nanging, saben wong bakal nggawe "kanggo awake dhewe." Ing tartamtu, Aku njupuk struktur sing ditawakake modul sql kanggo FreeRadius, lan rada diganti dening nambah lapangan mac lan port kanggo saben pelanggan, saliyane login-sandi.

Dadi, pisanan, instal FreeRadius:

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

Ing setelan, pilih kanggo nginstal:

Setelan jaringan saka FreeRadius liwat DHCP

Kita nggawe symlink menyang modul python (yaiku "ngaktifake"):

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

Ayo nginstal modul tambahan kanggo python:

pip install mysql-connector

Ing setelan modul python kanggo FreeRadius, sampeyan kudu nemtokake path panelusuran modul ing variabel python_path. Contone, aku duwe iki:

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"

Sampeyan bisa ngerteni dalan kasebut kanthi ngluncurake interpreter python lan ngetik perintah:

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

Yen sampeyan ora nindakake langkah iki, skrip sing ditulis nganggo python lan diluncurake dening FreeRadius ora bakal nemokake modul sing didaftar ing impor. Kajaba iku, sampeyan kudu mbusak komentar fungsi kanggo nelpon wewenang lan accounting ing setelan modul. Contone, modul iki katon kaya iki:

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}

}

Skrip work.py (lan kabeh liyane) kudu diselehake ing /usr/local/etc/raddb/mods-config/python Aku duwe telung skrip 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

Kaya sing sampeyan ngerteni saka kode kasebut, kita nyoba ngenali pelanggan nggunakake kabeh cara sing kasedhiya kanthi alamat MAC pelanggan sing dikenal utawa kombinasi Opsi 82, lan yen iki ora bisa digunakake, mula kita ngetokake alamat IP paling tuwa sing tau digunakake saka "tamu". "jaringan kab. Kabeh sing isih ana yaiku ngatur skrip standar ing folder sing diaktifake situs, supaya fungsi sing dibutuhake saka skrip python bakal kedutan ing wektu sing wis ditemtokake. Nyatane, cukup kanggo nggawa file menyang 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
}
}

Ayo nyoba mbukak lan ndeleng apa sing ana ing log debug:

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

Apa maneh. Nalika nyiyapake FreeRadius, trep kanggo nyoba operasi nggunakake sarana radclient. Contone wewenang:

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

Utawa 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

Aku pengin ngelingake sampeyan yen pancen ora bisa nggunakake skema lan skrip kasebut "tanpa owah-owahan" ing skala "industri". Paling ora katon:

  • iku bisa kanggo "palsu" alamat MAC. Cukup kanggo pelanggan ndhaptar MAC wong liya lan bakal ana masalah
  • logika nerbitake jaringan tamu ngluwihi kritik. Malah ora ana cek "Mungkin wis ana klien kanthi alamat IP sing padha?"

Iki mung "solusi cookie-cutter" sing dirancang kanggo bisa digunakake ing kahananku, ora liya. Aja ngejudge kanthi tegas 😉

Source: www.habr.com

Add a comment