Pengaturan Jaringan dari FreeRadius via DHCP


Tugas telah tiba untuk mengatur penerbitan alamat IP untuk pelanggan. Kondisi tugas:

  • Kami tidak akan memberikan server terpisah untuk otorisasi - Anda akan mengelola;)
  • Pelanggan harus menerima pengaturan jaringan melalui DHCP
  • Jaringannya beragam. Ini adalah peralatan PON, dan sakelar biasa dengan Opsi 82 โ€‹โ€‹dan basis WiFi yang dikonfigurasi dengan titik
  • Jika data tidak termasuk dalam persyaratan untuk mengeluarkan IP, perlu mengeluarkan IP dari jaringan "tamu"

Dari yang baik: ada server di FreeBSD yang dapat "bekerja", tetapi "jauh";), itu tidak "tepat di jaringan ini."

Ada juga perangkat Mikrotik yang luar biasa. Diagram jaringan umum adalah sesuatu seperti ini:



Setelah sedikit berpikir, diputuskan untuk menggunakan pelanggan FreeRadius untuk mengeluarkan pengaturan jaringan. Pada prinsipnya, skema ini normal: pada Microtick kita nyalakan server DHCP, di atasnya Radius Client yang sama. Kami mengkonfigurasi sekelompok server DHCP -> Radius Client -> Radius server.

Sepertinya tidak sulit. Tapi! Iblis ada dalam perinciannya. Yaitu:

  • Ketika PON OLT diotorisasi sesuai dengan skema ini, permintaan dikirim ke FreeRadius dengan Nama Pengguna sama dengan alamat MAC dari stasiun pusat, Agent-Circuit-Id sama dengan PON Onu MAC dan kata sandi kosong.
  • Ketika mengotorisasi dengan sakelar dengan opsi 82, permintaan datang ke FreeRadius dengan Nama Pengguna kosong sama dengan perangkat MAC pelanggan dan tambahan Agent-Circuit-Id dan Agent-Remote-Id atribut tambahan yang mengandung, sekali lagi, relay switch MAC dan port dimana pelanggan terhubung.
  • Beberapa pelanggan dengan poin WiFI diotorisasi melalui protokol PAP-CHAP
  • Beberapa pelanggan dengan titik WIFI diotorisasi dengan Nama Pengguna sama dengan alamat MAC titik WIFI, tanpa kata sandi.

Latar Belakang Historis: Apa itu Opsi 82 โ€‹โ€‹untuk DHCP

Ini adalah opsi tambahan untuk protokol DHCP yang memungkinkan Anda mentransfer informasi tambahan, misalnya, dalam bidang Agent-Circuit-Id dan Agent-Remote-Id. Biasanya digunakan untuk mengirimkan alamat MAC dari sakelar relai dan port tempat pelanggan tersambung. Dalam hal peralatan PON atau BTS WIFI, bidang Agent-Circuit-Id tidak membawa informasi yang berguna (tidak ada port pelanggan). Dalam hal ini, skema umum DHCP dalam hal ini adalah sebagai berikut:



Langkah demi langkah, skema ini berfungsi seperti ini:

  1. Peralatan pelanggan membuat permintaan DHCP broadcast untuk pengaturan jaringan
  2. Perangkat (misalnya, saklar, WiFi atau PON base station) dimana peralatan pelanggan terhubung secara langsung "memotong" paket ini dan memodifikasinya, memperkenalkan opsi tambahan Opsi 82 โ€‹โ€‹dan Relay alamat IP agen ke dalamnya, dan mentransfernya lebih jauh melalui jaringan.
  3. Server DHCP menerima permintaan, membentuk respons dan mengirimkannya ke perangkat relai
  4. Perangkat relai meneruskan paket respons ke perangkat pelanggan

Jadi semua ini tidak berfungsi, tentu saja, Anda memerlukan konfigurasi peralatan jaringan yang sesuai.

Instal FreeRadius


Dengan pengaturan konfigurasi FreeRadius, tentu saja, Anda dapat mencapai semua ini, tetapi sulit dan tidak jelas ... terutama ketika Anda mengintip di sana setelah N bulan "semuanya berfungsi". Oleh karena itu, diputuskan untuk menulis modul otorisasi Anda untuk FreeRadius dengan Python. Kami akan mengambil data untuk otorisasi dari database MySQL. Tidak masuk akal untuk menggambarkan strukturnya, bagaimanapun, semua orang akan melakukannya "untuk diri mereka sendiri". Secara khusus, saya mengambil struktur yang diusulkan dengan modul sql untuk FreeRadius, dan sedikit mengubahnya dengan menambahkan bidang mac dan port untuk setiap pelanggan, selain kata sandi masuk.

Jadi, sebagai permulaan, instal FreeRadius:

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

Dalam pengaturan, kami menandai untuk instalasi:



Kami membuat symlink ke modul python (yaitu, aktifkan):

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

Instal modul tambahan untuk python:

 pip install mysql-connector 

Dalam pengaturan modul python untuk FreeRadius, Anda perlu menentukan jalur pencarian modul dalam variabel python_path. Misalnya, saya punya ini:

 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" 

Path dapat ditemukan dengan menjalankan interpreter python dan memasukkan 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'] > 

Jika Anda tidak mengambil langkah ini, maka skrip yang ditulis dengan python dan dijalankan oleh FreeRadius tidak akan menemukan modul-modul yang terdaftar dalam impor. Selain itu, perlu untuk membatalkan komentar fungsi otorisasi dan akuntansi dalam pengaturan modul. Sebagai contoh, modul ini terlihat seperti ini:

 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 (dan semua orang lain) harus diletakkan di / usr / local / etc / raddb / mods-config / python Total ada tiga skrip.

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 


Seperti yang dapat Anda lihat dari kode, kami mencoba dengan semua cara yang tersedia untuk mengidentifikasi pelanggan dengan alamat MAC pelanggan yang dikenal atau bundel Opsi 82, dan jika ini tidak berhasil, kami akan mengeluarkan alamat IP tertua yang digunakan dari jaringan "tamu". Masih mengkonfigurasi skrip default di folder situs-diaktifkan sehingga fungsi yang diperlukan dari skrip python berkedut pada waktu yang ditunjukkan. Bahkan, itu sudah cukup untuk membawa file ke 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 } } 


Kami mencoba menjalankan dan melihat apa yang terbang ke log debug:

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

Apa lagi Saat mengatur FreeRadius, akan lebih mudah untuk menguji operasinya menggunakan utilitas radclient. Misalnya 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 

Atau akuntansi:

 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 

Saya ingin memperingatkan bahwa tidak mungkin menggunakan skema dan skrip serupa "tanpa perubahan" pada skala "industri". Setidaknya mencolok:

  • kemungkinan alamat MAC "palsu". Sudah cukup bagi pelanggan untuk mendaftarkan MAC asing untuk dirinya sendiri dan akan ada masalah
  • logika mengeluarkan jaringan tamu di bawah semua kritik. Bahkan tidak ada cek "apakah Anda sudah memiliki klien dengan alamat IP seperti itu?"

Itu hanya "solusi pada lutut" untuk bekerja secara khusus dalam kondisi saya, tidak lebih. Jangan menilai dengan ketat;)

Source: https://habr.com/ru/post/id446586/


All Articles