Netzwerkeinstellungen von FreeRadius über DHCP


Die Aufgabe ist angekommen, die Ausgabe von IP-Adressen an Abonnenten zu arrangieren. Aufgabenbedingungen:

  • Wir werden keinen separaten Server zur Autorisierung geben - Sie werden verwalten;)
  • Teilnehmer müssen Netzwerkeinstellungen über DHCP erhalten
  • Das Netzwerk ist vielfältig. Dies sind PON-Geräte und normale Switches mit konfigurierter Option 82 und WiFi-Basis mit Punkten
  • Wenn Daten nicht unter eine der Bedingungen für die Ausgabe von IP fallen, muss IP über das "Gast" -Netzwerk ausgegeben werden

Vom Guten: Es gibt einen Server unter FreeBSD, der "funktionieren" kann, aber "weit weg" ist;), nicht "direkt in diesem Netzwerk".

Es gibt auch ein wunderbares Mikrotik-Gerät. Das allgemeine Netzwerkdiagramm sieht ungefähr so ​​aus:



Nach einigem Überlegen wurde beschlossen, FreeRadius-Abonnenten zu verwenden, um Netzwerkeinstellungen vorzunehmen. Im Prinzip ist das Schema das übliche: Auf Microtick schalten wir den DHCP-Server ein, darauf denselben Radius-Client. Wir konfigurieren eine Reihe von DHCP-Servern -> Radius-Client -> Radius-Server.

Es scheint nicht schwierig zu sein. Aber! Der Teufel steckt im Detail. Nämlich:

  • Wenn PON OLT gemäß diesem Schema autorisiert ist, wird eine Anfrage an FreeRadius gesendet, deren Benutzername der MAC-Adresse der Kopfstation, die Agent-Circuit-ID dem PON Onu MAC und ein leeres Kennwort entspricht.
  • Bei der Autorisierung mit Switches mit Option 82 kommt eine Anfrage an FreeRadius mit einem leeren Benutzernamen, der dem MAC-Gerät des Teilnehmers entspricht, und den zusätzlichen Attributen Agent-Circuit-ID und Agent-Remote-ID, die wiederum den Relay-Switch-MAC und den Port enthalten, mit dem der Teilnehmer verbunden ist.
  • Einige Teilnehmer mit WiFI-Punkten sind über PAP-CHAP-Protokolle autorisiert
  • Einige Teilnehmer mit WIFI-Punkten haben eine Berechtigung mit einem Benutzernamen, der der MAC-Adresse des WIFI-Punkts entspricht, ohne Kennwort.

Historischer Hintergrund: Was ist Option 82 für DHCP?

Dies sind zusätzliche Optionen für das DHCP-Protokoll, mit denen Sie zusätzliche Informationen übertragen können, z. B. in den Feldern Agent-Circuit-ID und Agent-Remote-ID. Es wird normalerweise verwendet, um die MAC-Adresse des Relaisschalters und den Port, an den der Teilnehmer angeschlossen ist, zu übertragen. Bei PON-Geräten oder WIFI-Basisstationen enthält das Feld Agent-Circuit-Id keine nützlichen Informationen (es gibt keinen Teilnehmerport). In diesem Fall lautet das allgemeine Schema von DHCP in diesem Fall wie folgt:



Schritt für Schritt funktioniert dieses Schema folgendermaßen:

  1. Teilnehmergeräte stellen eine Broadcast-DHCP-Anforderung für Netzwerkeinstellungen
  2. Das Gerät (z. B. ein Switch, eine WiFi- oder PON-Basisstation), mit dem das Teilnehmergerät direkt verbunden ist, „fängt“ dieses Paket ab und ändert es, indem es die zusätzlichen Optionen für Option 82 und die IP-Adresse des Relay-Agenten einführt und es weiter über das Netzwerk überträgt.
  3. Der DHCP-Server akzeptiert die Anforderung, bildet eine Antwort und sendet sie an das Relay-Gerät
  4. Das Relaisgerät leitet das Antwortpaket an das Teilnehmergerät weiter

All dies funktioniert einfach nicht. Natürlich benötigen Sie die entsprechende Konfiguration der Netzwerkgeräte.

Installieren Sie FreeRadius


Mit den FreeRadius-Konfigurationseinstellungen können Sie dies natürlich erreichen, aber es ist schwierig und nicht klar ... besonders wenn Sie nach N Monaten "alles funktioniert". Daher wurde beschlossen, Ihr Autorisierungsmodul für FreeRadius in Python zu schreiben. Wir werden die Daten zur Autorisierung aus der MySQL-Datenbank entnehmen. Es macht keinen Sinn, seine Struktur zu beschreiben, jedenfalls wird es jeder "für sich" tun. Insbesondere habe ich die mit dem SQL-Modul für FreeRadius vorgeschlagene Struktur übernommen und sie geringfügig geändert, indem ich zusätzlich zum Anmeldekennwort das Mac- und das Portfeld für jeden Abonnenten hinzugefügt habe.

Installieren Sie zunächst FreeRadius:

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

In den Einstellungen markieren wir für die Installation:



Wir stellen einen Symlink zum Python-Modul her (dh schalten Sie es ein):

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

Installieren Sie ein zusätzliches Modul für Python:

 pip install mysql-connector 

In den Python-Moduleinstellungen für FreeRadius müssen Sie die Modul-Suchpfade in der Variablen python_path angeben. Zum Beispiel habe ich Folgendes:

 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" 

Die Pfade können gefunden werden, indem Sie den Python-Interpreter ausführen und die folgenden Befehle eingeben:

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

Wenn Sie diesen Schritt nicht ausführen, finden in Python geschriebene und von FreeRadius ausgeführte Skripte nicht die Module, die beim Import aufgelistet sind. Darüber hinaus müssen die Berechtigungs- und Abrechnungsfunktionen in den Moduleinstellungen auskommentiert werden. Dieses Modul sieht beispielsweise folgendermaßen aus:

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

Das Skript work.py (und alle anderen) muss in / usr / local / etc / raddb / mods-config / python abgelegt werden. Insgesamt gibt es drei Skripte.

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 


Wie Sie dem Code entnehmen können, versuchen wir mit allen verfügbaren Mitteln, den Teilnehmer anhand seiner offensichtlich bekannten Teilnehmer-MAC-Adressen oder des Option 82-Bundles zu identifizieren. Wenn dies nicht funktioniert, geben wir die älteste vom Gastnetzwerk verwendete IP-Adresse aus. Das Standardskript muss weiterhin im Ordner "sites-enabled" konfiguriert werden, damit die erforderlichen Funktionen des Python-Skripts zu den angegebenen Zeiten zucken. Tatsächlich reicht es aus, die Datei in das Formular zu bringen:

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


Wir versuchen zu laufen und zu sehen, was zum Debug-Protokoll fliegt:

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

Was sonst. Beim Einrichten von FreeRadius ist es praktisch, den Betrieb mit dem Dienstprogramm radclient zu testen. Zum Beispiel Autorisierung:

 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 

Oder Buchhaltung:

 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 

Ich möchte warnen, dass es unmöglich ist, ein ähnliches Schema und Skripte "ohne Änderungen" im "industriellen" Maßstab zu verwenden. Zumindest auffällig:

  • mögliche "gefälschte" MAC-Adresse. Es reicht aus, wenn der Teilnehmer einen ausländischen MAC für sich registriert, und es wird Probleme geben
  • Die Logik der Herausgabe von Gastnetzwerken ist unter allen Kritikpunkten. Es wird nicht einmal überprüft, ob Sie bereits Clients mit einer solchen IP-Adresse haben können.

Es ist nur eine "Lösung auf dem Knie", um speziell unter meinen Bedingungen zu arbeiten, nichts weiter. Nicht streng beurteilen;)

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


All Articles