Awọn eto nẹtiwọki lati FreeRadius nipasẹ DHCP

Awọn eto nẹtiwọki lati FreeRadius nipasẹ DHCP
Iṣẹ naa de lati ṣeto ipinfunni ti awọn adirẹsi IP si awọn alabapin. Awọn ipo ti iṣoro naa:

  • A kii yoo fun ọ ni olupin lọtọ fun aṣẹ - iwọ yoo ṣe 😉
  • Awọn alabapin gbọdọ gba eto nẹtiwọki nipasẹ DHCP
  • Nẹtiwọọki jẹ orisirisi. Eyi pẹlu ohun elo PON ati awọn iyipada deede pẹlu aṣayan atunto 82 ati awọn ipilẹ WiFi pẹlu awọn aaye
  • Ti data naa ko ba ṣubu labẹ eyikeyi awọn ipo fun ipinfunni IP, o gbọdọ fun IP kan lati inu nẹtiwọọki “alejo”

Ni ẹgbẹ ti o dara: olupin tun wa lori FreeBSD ti o le “ṣiṣẹ”, ṣugbọn “o jinna”;), kii ṣe “ọtun lori nẹtiwọọki yii”.

Ẹrọ iyanu tun wa ti a npe ni Mikrotik. Aworan atọka nẹtiwọki gbogbogbo jẹ nkan bii eyi:

Awọn eto nẹtiwọki lati FreeRadius nipasẹ DHCP

Lẹhin ero diẹ, o pinnu lati lo FreeRadius lati fun awọn eto nẹtiwọọki fun awọn alabapin. Ni ipilẹ, ero naa jẹ igbagbogbo: a mu olupin DHCP ṣiṣẹ lori Microtick, ati alabara Radius lori rẹ. A tunto olupin DHCP -> Onibara Radius -> Asopọ olupin Radius.

Ko dabi enipe o soro. Sugbon! Bìlísì wa ninu awọn alaye. Eyun:

  • Nigbati o ba fun ni aṣẹ PON OLT nipa lilo ero yii, a firanṣẹ ibeere kan si FreeRadius pẹlu Orukọ olumulo kan ti o dọgba si adiresi MAC ti akọle, Agent-Circuit-Id ti o dọgba si MAC PON Onu ati ọrọ igbaniwọle ṣofo.
  • Nigbati o ba fun ni aṣẹ lati awọn iyipada pẹlu aṣayan 82, FreeRadius gba ibeere kan pẹlu Orukọ olumulo ti o ṣofo ti o dọgba si MAC ti ẹrọ alabapin ati kun pẹlu awọn eroja afikun Agent-Circuit-Id ati Agent-Remote-Id ti o ni, lẹsẹsẹ, lẹẹkansi MAC ti yiyi pada ati ibudo si eyiti a ti sopọ alabapin si.
  • Diẹ ninu awọn alabapin pẹlu awọn aaye WiFI ni a fun ni aṣẹ nipasẹ awọn ilana PAP-CHAP
  • Diẹ ninu awọn alabapin lati awọn aaye WIFI ni a fun ni aṣẹ pẹlu Orukọ olumulo kan ti o dọgba si adiresi MAC ti aaye WIFI, laisi ọrọ igbaniwọle kan.

Itan lẹhin: kini “Aṣayan 82” ni DHCP

Iwọnyi jẹ awọn aṣayan afikun fun ilana DHCP ti o gba ọ laaye lati gbe alaye ni afikun, fun apẹẹrẹ ni awọn aaye Agent-Circuit-Id ati Agent-Remote-Id. Ni igbagbogbo lo lati atagba adiresi MAC ti iyipada yii ati ibudo eyiti o ti sopọ si alabapin si. Ninu ọran ti ohun elo PON tabi awọn ibudo ipilẹ WIFI, aaye Agent-Circuit-Id ko ni alaye to wulo (ko si ibudo alabapin). Eto gbogbogbo ti iṣẹ DHCP ninu ọran yii jẹ atẹle yii:

Awọn eto nẹtiwọki lati FreeRadius nipasẹ DHCP

Igbese-nipasẹ-igbesẹ eto yii ṣiṣẹ bi eleyi:

  1. Ohun elo olumulo ṣe ibeere igbohunsafefe DHCP lati gba awọn eto nẹtiwọọki
  2. Ẹrọ naa (fun apẹẹrẹ, yipada, WiFi tabi ibudo ipilẹ PON) eyiti ohun elo alabapin ti sopọ taara “awọn idilọwọ” apo-iwe yii ati yi pada, ṣafihan awọn aṣayan afikun Aṣayan 82 ati adiresi IP aṣoju Relay sinu rẹ, ati gbejade siwaju lori nẹtiwọki.
  3. Olupin DHCP gba ibeere naa, ṣe ipilẹṣẹ esi ati firanṣẹ si ẹrọ yii
  4. Awọn ẹrọ yiyi dari awọn apo-iwe esi si ẹrọ alabapin

Nitoribẹẹ, gbogbo rẹ ko ṣiṣẹ ni irọrun; o nilo lati tunto ohun elo nẹtiwọọki rẹ ni ibamu.

Fifi FreeRadius sori ẹrọ

Nitoribẹẹ, eyi le ṣee ṣe pẹlu awọn eto iṣeto FreeRadius, ṣugbọn o nira ati koyewa… paapaa nigbati o ba lọ sibẹ lẹhin awọn oṣu N ati “ohun gbogbo n ṣiṣẹ.” Nitorinaa, a pinnu lati kọ module aṣẹ tiwa fun FreeRadius ni Python. A yoo gba data aṣẹ lati aaye data MySQL. Ko si aaye ni apejuwe eto rẹ; lonakona, gbogbo eniyan yoo ṣe “fun ara wọn.” Ni pataki, Mo mu eto ti a funni pẹlu module sql fun FreeRadius, ati pe o yipada diẹ nipasẹ fifi mac ati aaye ibudo fun alabapin kọọkan, ni afikun si ọrọ igbaniwọle iwọle.

Nitorinaa, akọkọ, fi FreeRadius sori ẹrọ:

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

Ninu awọn eto, yan lati fi sori ẹrọ:

Awọn eto nẹtiwọki lati FreeRadius nipasẹ DHCP

A ṣe ọna asopọ kan si module Python (ie “tan” rẹ):

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

Jẹ ki a fi module afikun sii fun Python:

pip install mysql-connector

Ninu awọn eto module Python fun FreeRadius, o nilo lati pato awọn ọna wiwa module ni oniyipada python_path. Fun apẹẹrẹ Mo ni eyi:

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"

O le wa awọn ipa-ọna nipa ifilọlẹ onitumọ Python ati titẹ awọn aṣẹ:

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

Ti o ko ba ṣe igbesẹ yii, lẹhinna awọn iwe afọwọkọ ti a kọ sinu Python ati ifilọlẹ nipasẹ FreeRadius kii yoo rii awọn modulu ti o ṣe atokọ ni agbewọle. Ni afikun, o nilo lati ṣe akiyesi awọn iṣẹ fun aṣẹ pipe ati ṣiṣe iṣiro ni awọn eto module. Fun apẹẹrẹ, module yii dabi eyi:

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}

}

Iwe afọwọkọ work.py (ati gbogbo awọn miiran) gbọdọ wa ni gbe sinu /usr/local/etc/raddb/mods-config/python Mo ni awọn iwe afọwọkọ mẹta lapapọ.

ṣiṣẹ.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

Gẹgẹbi o ti le rii lati koodu naa, a n gbiyanju lati ṣe idanimọ awọn alabapin ni lilo gbogbo awọn ọna ti o wa nipasẹ awọn adirẹsi MAC ti o jẹ alabapin ti a mọ tabi Apapo Aṣayan 82, ati pe ti eyi ko ba ṣiṣẹ, lẹhinna a fun adirẹsi IP atijọ julọ ti a lo lati “alejo” ”nẹtiwọọki. Gbogbo ohun ti o ku ni lati tunto iwe afọwọkọ aiyipada ninu folda ti o ṣiṣẹ awọn aaye, ki awọn iṣẹ pataki lati iwe afọwọkọ Python yoo tẹ ni awọn akoko ti a yan. Ni otitọ, o to lati mu faili naa wa si fọọmu naa:

aiyipada

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

Jẹ ki a gbiyanju lati ṣiṣẹ ki o wo ohun ti o wa sinu akọọlẹ yokokoro:

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

Kini ohun miiran. Nigbati o ba ṣeto FreeRadius, o rọrun lati ṣe idanwo iṣẹ rẹ nipa lilo ohun elo radclient. Fun apẹẹrẹ aṣẹ:

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

Tabi akọọlẹ:

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

Mo fẹ kilọ fun ọ pe ko ṣee ṣe rara lati lo iru ero ati awọn iwe afọwọkọ “laisi awọn ayipada” lori iwọn “ile-iṣẹ”. O kere ju akiyesi:

  • o ṣee ṣe lati "iro" adirẹsi MAC. O to fun alabapin lati forukọsilẹ MAC elomiran ati pe awọn iṣoro yoo wa
  • ọgbọn ti ipinfunni awọn nẹtiwọki alejo ti kọja lodi. Ko si ayẹwo paapaa “boya awọn alabara wa tẹlẹ pẹlu adiresi IP kanna?”

Eyi jẹ “ojutu-ojutu kuki” ti a ṣe apẹrẹ lati ṣiṣẹ ni pataki ni awọn ipo mi, ko si nkankan diẹ sii. Maṣe ṣe idajọ muna 😉

orisun: www.habr.com

Fi ọrọìwòye kun