Paramètres réseau de FreeRadius via DHCP


La tâche est arrivée d'organiser la délivrance d'adresses IP aux abonnés. Conditions de tâche:

  • Nous ne donnerons pas de serveur sĂ©parĂ© pour autorisation - vous gĂ©rerez;)
  • Les abonnĂ©s doivent recevoir les paramètres rĂ©seau via DHCP
  • Le rĂ©seau est diversifiĂ©. Il s'agit d'un Ă©quipement PON et de commutateurs ordinaires avec l'option 82 configurĂ©e et une base WiFi avec des points
  • Si les donnĂ©es ne relèvent d'aucune des conditions de dĂ©livrance de l'IP, il est nĂ©cessaire d'Ă©mettre l'IP Ă  partir du rĂ©seau «invité»

Du bon: il y a un serveur sur FreeBSD qui peut "fonctionner", mais il est "loin";), ce n'est pas "juste sur ce réseau".

Il y a aussi un merveilleux appareil Mikrotik. Le schéma de réseau général est quelque chose comme ceci:



Après un peu de réflexion, il a été décidé d'utiliser les abonnés FreeRadius pour définir les paramètres réseau. En principe, le schéma est habituel: sur Microtick on allume le serveur DHCP, sur lui le même Radius Client. Nous configurons un tas de serveurs DHCP -> Radius Client -> Radius server.

Cela ne semble pas difficile. Mais! Le diable est dans les détails. À savoir:

  • Lorsque PON OLT est autorisĂ© selon ce schĂ©ma, une demande est envoyĂ©e Ă  FreeRadius avec un nom d'utilisateur Ă©gal Ă  l'adresse MAC de la station principale, Agent-Circuit-Id Ă©gal au MAC PON Onu et un mot de passe vide.
  • Lors de l'autorisation avec des commutateurs avec l'option 82, une demande est envoyĂ©e Ă  FreeRadius avec un nom d'utilisateur vide Ă©gal au pĂ©riphĂ©rique MAC de l'abonnĂ© et les attributs supplĂ©mentaires Agent-Circuit-Id et Agent-Remote-Id contenant, encore une fois, le commutateur de relais MAC et le port auquel l'abonnĂ© est connectĂ©.
  • Certains abonnĂ©s avec des points WiFI sont autorisĂ©s via les protocoles PAP-CHAP
  • Certains abonnĂ©s avec des points WIFI sont autorisĂ©s avec un nom d'utilisateur Ă©gal Ă  l'adresse MAC du point WIFI, sans mot de passe.

Contexte historique: qu'est-ce que l'option 82 pour DHCP

Ce sont des options supplémentaires pour le protocole DHCP qui vous permettent de transférer des informations supplémentaires, par exemple, dans les champs Agent-Circuit-Id et Agent-Remote-Id. Il est généralement utilisé pour transmettre l'adresse MAC du commutateur de relais et le port auquel l'abonné est connecté. Dans le cas d'un équipement PON ou de stations de base WIFI, le champ Agent-Circuit-Id ne contient pas d'informations utiles (il n'y a pas de port d'abonné). Dans ce cas, le schéma général de DHCP dans ce cas est le suivant:



Pas à pas, ce schéma fonctionne comme ceci:

  1. L'équipement d'abonné effectue une demande de diffusion DHCP pour les paramètres réseau
  2. Le périphérique (par exemple, un commutateur, une station de base WiFi ou PON) auquel l'équipement d'abonné est directement connecté «intercepte» ce paquet et le modifie, y introduisant les options supplémentaires de l'option 82 et de l'adresse IP de l'agent de relais, et le transfère plus loin sur le réseau.
  3. Le serveur DHCP accepte la demande, forme une réponse et l'envoie au périphérique relais
  4. Le dispositif relais transmet le paquet de réponse au dispositif abonné

Donc, tout cela ne fonctionne tout simplement pas, bien sûr, vous avez besoin de la configuration appropriée de l'équipement réseau.

Installez FreeRadius


Avec les paramètres de configuration de FreeRadius, bien sûr, vous pouvez réaliser tout cela, mais c'est difficile et pas clair ... surtout lorsque vous espionnez après N mois "tout fonctionne". Par conséquent, il a été décidé d'écrire votre module d'autorisation pour FreeRadius en Python. Nous prendrons les données pour autorisation de la base de données MySQL. Cela n'a aucun sens de décrire sa structure, de toute façon, tout le monde le fera «pour lui-même». En particulier, j'ai pris la structure proposée avec le module sql pour FreeRadius, et l'ai légèrement modifiée en ajoutant les champs mac et port pour chaque abonné, en plus du mot de passe de connexion.

Donc, pour commencer, installez FreeRadius:

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

Dans les paramètres, nous marquons pour l'installation:



Nous faisons un lien symbolique vers le module python (c'est-Ă -dire, allumez-le):

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

Installez un module supplémentaire pour python:

 pip install mysql-connector 

Dans les paramètres du module python pour FreeRadius, vous devez spécifier les chemins de recherche du module dans la variable python_path. Par exemple, j'ai ceci:

 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" 

Les chemins peuvent être trouvés en exécutant l'interpréteur python et en entrant les commandes:

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

Si vous ne suivez pas cette étape, les scripts écrits en python et exécutés par FreeRadius ne trouveront pas les modules répertoriés dans l'importation. De plus, il est nécessaire de décommenter les fonctions d'autorisation et de comptabilité dans les paramètres du module. Par exemple, ce module ressemble à ceci:

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

Le script work.py (et tous les autres) doit être placé dans / usr / local / etc / raddb / mods-config / python Il y a trois scripts au 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 


Comme vous pouvez le voir dans le code, nous essayons par tous les moyens disponibles d'identifier l'abonné par ses adresses MAC d'abonné bien connues ou un tas d'Option 82, et si cela ne fonctionne pas, nous émettrons la plus ancienne adresse IP utilisée à partir du réseau `` invité ''. Il reste à configurer le script par défaut dans le dossier sites-activé afin que les fonctions nécessaires du script python se contracter aux moments indiqués. En fait, il suffit de mettre le fichier au format:

défaut
 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 } } 


Nous essayons d'exécuter et de voir ce qui vole dans le journal de débogage:

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

Quoi d'autre. Lors de la configuration de FreeRadius, il est pratique de tester son fonctionnement Ă  l'aide de l'utilitaire radclient. Par exemple autorisation:

 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 

Ou comptabilité:

 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 

Je tiens à vous avertir qu’il est impossible d’utiliser un schéma et des scripts similaires «sans modifications» à une échelle «industrielle». Au moins frappant:

  • possible "fausse" adresse MAC. Il suffit que l'abonnĂ© enregistre un MAC Ă©tranger pour lui-mĂŞme et il y aura des problèmes
  • la logique d'Ă©mission des rĂ©seaux d'invitĂ©s est au-dessous de toute critique. Il n'y a mĂŞme pas de contrĂ´le "pouvez-vous dĂ©jĂ  avoir des clients avec une telle adresse IP?"

C’est juste une «solution sur le genou» afin de travailler spécifiquement dans mes conditions, rien de plus. Ne jugez pas strictement;)

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


All Articles