Mga setting sa network gikan sa FreeRadius pinaagi sa DHCP

Mga setting sa network gikan sa FreeRadius pinaagi sa DHCP
Miabot ang tahas sa paghikay sa pag-isyu sa mga IP address sa mga subscriber. Kondisyon sa problema:

  • Dili ka namo hatagan ug bulag nga server para sa pagtugot - buhaton nimo 😉
  • Ang mga subscriber kinahanglang makadawat sa network settings pinaagi sa DHCP
  • Ang network kay heterogeneous. Naglakip kini sa kagamitan sa PON ug regular nga mga switch nga adunay gi-configure nga Opsyon 82 ug mga base sa WiFi nga adunay mga hotspot
  • Kung ang datos dili mahulog sa bisan unsang mga kondisyon alang sa pag-isyu sa usa ka IP, kinahanglan ka mag-isyu og IP gikan sa "bisita" nga network

Sa maayong bahin: naa pay server sa FreeBSD nga “makatrabaho”, pero “halayo” ;), dili “sa mismong network”.

Adunay usab usa ka talagsaon nga aparato nga gitawag Mikrotik. Ang kinatibuk-ang network diagram sama niini:

Mga setting sa network gikan sa FreeRadius pinaagi sa DHCP

Pagkahuman sa pipila ka mga hunahuna, nakahukom nga gamiton ang FreeRadius aron mag-isyu sa mga setting sa network sa mga subscriber. Sa prinsipyo, ang laraw mao ang naandan: gipalihok namo ang DHCP server sa Microtick, ug ang Radius Client niini. Among gi-configure ang DHCP server -> Radius Client -> Radius server connection.

Morag dili kini lisud. Apan! Ang yawa anaa sa mga detalye. Nga mao:

  • Kung gitugotan ang usa ka PON OLT gamit kini nga laraw, ang usa ka hangyo ipadala sa FreeRadius nga adunay usa ka User-Name nga katumbas sa MAC address sa headend, usa ka Agent-Circuit-Id nga katumbas sa MAC PON Onu ug usa ka walay sulod nga password.
  • Kung gitugotan gikan sa mga switch nga adunay kapilian 82, ang FreeRadius nakadawat usa ka hangyo nga adunay walay sulod nga User-Name nga katumbas sa MAC sa aparato sa subscriber ug napuno sa dugang nga mga hiyas nga Agent-Circuit-Id ug Agent-Remote-Id nga naglangkob, sa tinuud, usab ang MAC sa ang relay switch ug ang pantalan diin konektado ang subscriber.
  • Ang ubang mga subscriber nga adunay WiFI points gitugutan pinaagi sa PAP-CHAP protocols
  • Ang ubang mga subscriber gikan sa mga punto sa WIFI gitugutan sa usa ka User-Name nga katumbas sa MAC address sa WIFI point, nga walay password.

Kasaysayan sa background: unsa ang "Option 82" sa DHCP

Kini dugang nga mga kapilian alang sa DHCP protocol nga nagtugot kanimo sa pagbalhin sa dugang nga impormasyon, pananglitan sa Agent-Circuit-Id ug Agent-Remote-Id fields. Kasagaran nga gigamit sa pagpadala sa MAC address sa relay switch ug sa pantalan diin ang subscriber konektado. Sa kaso sa PON equipment o WIFI base stations, ang Agent-Circuit-Id field walay mapuslanong impormasyon (walay subscriber port). Ang kinatibuk-ang laraw sa operasyon sa DHCP niini nga kaso mao ang mosunod:

Mga setting sa network gikan sa FreeRadius pinaagi sa DHCP

Lakang sa lakang nga kini nga laraw molihok sama niini:

  1. Ang kagamitan sa gumagamit naghimo usa ka hangyo sa broadcast sa DHCP aron makakuha mga setting sa network
  2. Ang aparato (pananglitan, usa ka switch, WiFi o PON base station) diin ang kagamitan sa suskritor direktang konektado "nag-intercept" niini nga pakete ug nagbag-o niini, nagpaila sa dugang nga mga kapilian nga Opsyon 82 ug Relay agent IP address niini, ug gipasa kini labi pa. ang network.
  3. Gidawat sa DHCP server ang hangyo, nagmugna og tubag ug gipadala kini sa relay device
  4. Gipasa sa relay device ang response packet ngadto sa subscriber device

Siyempre, kining tanan dili sayon ​​​​buhaton; kinahanglan nimo nga i-configure ang imong kagamitan sa network sumala niana.

Pag-instalar sa FreeRadius

Siyempre, mahimo kini nga makab-ot gamit ang mga setting sa pagsumpo sa FreeRadius, apan lisud ug dili klaro ... labi na kung moadto ka didto pagkahuman sa N nga mga bulan ug "ang tanan molihok." Busa, nakahukom kami sa pagsulat sa among kaugalingong authorization module para sa FreeRadius sa Python. Magkuha kami og data sa pagtugot gikan sa database sa MySQL. Walay kapuslanan ang paghubit sa estraktura niini; bisan pa niana, ang tanan maghimo niini nga “alang sa ilang kaugalingon.” Sa partikular, gikuha nako ang istruktura nga gitanyag sa sql module para sa FreeRadius, ug gamay nga giusab kini pinaagi sa pagdugang usa ka mac ug port field alang sa matag subscriber, dugang sa login-password.

Busa, una, i-install ang FreeRadius:

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

Sa mga setting, pilia ang pag-install:

Mga setting sa network gikan sa FreeRadius pinaagi sa DHCP

Naghimo kami usa ka symlink sa module sa python (ie "i-on" kini):

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

Atong i-install ang dugang nga module para sa python:

pip install mysql-connector

Sa mga setting sa module sa python alang sa FreeRadius, kinahanglan nimo nga ipiho ang mga agianan sa pagpangita sa module sa variable nga python_path. Pananglitan aduna ako niini:

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"

Mahibal-an nimo ang mga agianan pinaagi sa paglansad sa python interpreter ug pagsulod sa mga mando:

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

Kung dili nimo buhaton kini nga lakang, nan ang mga script nga gisulat sa python ug gilunsad sa FreeRadius dili makit-an ang mga module nga gilista sa import. Dugang pa, kinahanglan nimo nga i-uncomment ang mga gimbuhaton sa pagtawag sa pagtugot ug accounting sa mga setting sa module. Pananglitan, kini nga module ingon niini:

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}

}

Ang work.py script (ug ang uban pa) kinahanglang ibutang sa /usr/local/etc/raddb/mods-config/python Ako adunay tulo ka mga script sa kinatibuk-an.

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

Sama sa imong makita gikan sa code, gisulayan namon nga mailhan ang subscriber gamit ang tanan nga magamit nga mga pamaagi pinaagi sa iyang nahibal-an nga mga MAC address sa suskritor o kombinasyon sa Opsyon 82, ug kung dili kini molihok, nan among gi-isyu ang labing karaan nga IP address nga gigamit gikan sa "bisita. ” network. Ang nahabilin mao ang pag-configure sa default nga script sa folder nga gipagana sa mga site, aron ang kinahanglan nga mga gimbuhaton gikan sa script sa python mokibot sa gitudlo nga mga higayon. Sa tinuud, igo na ang pagdala sa file sa porma:

Default

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

Atong sulayan ang pagpadagan niini ug tan-awon kung unsa ang moabut sa debug log:

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

Unsa pa. Kung nag-set up sa FreeRadius, dali nga sulayan ang operasyon niini gamit ang radclient utility. Pananglitan sa pagtugot:

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

O account:

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

Gusto nako nga pasidan-an ka nga imposible nga gamiton ang ingon nga laraw ug mga script nga "walay mga pagbag-o" sa usa ka "industriyal" nga sukod. Labing menos mamatikdan:

  • posible nga "peke" ang MAC address. Igo na alang sa subscriber nga magparehistro sa MAC sa laing tawo ug adunay mga problema
  • ang lohika sa pag-isyu sa mga bisita nga network labaw pa sa pagsaway. Walay bisan usa ka tseke "tingali naa nay mga kliyente nga parehas ang IP address?"

Kini usa lang ka "solusyon sa cookie-cutter" nga gidisenyo aron molihok nga espesipiko sa akong mga kondisyon, wala na. Ayaw paghusga ug estrikto 😉

Source: www.habr.com

Idugang sa usa ka comment