Radius.radPassesFieldFilter with tests

This commit is contained in:
DanB
2017-06-05 14:08:03 +02:00
parent 33504e92a4
commit a80176e6cb
6 changed files with 174 additions and 18 deletions

View File

@@ -19,23 +19,37 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package agents
import (
"strings"
"github.com/cgrates/cgrates/utils"
"github.com/cgrates/radigo"
)
/*
Various RADIUS helpers here
*/
func radPassesFieldFilter(pkt *radigo.Packet, fieldFilter *utils.RSRField, processorVars map[string]string) (pass bool) {
if fieldFilter == nil {
return true
}
if val, hasIt := processorVars[fieldFilter.Id]; hasIt { // ProcessorVars have priority
if fieldFilter.FilterPasses(val) {
return true
pass = true
}
return false
return
}
return
splt := strings.Split(fieldFilter.Id, "/")
var attrName, vendorName string
if len(splt) > 1 {
vendorName, attrName = splt[0], splt[1]
} else {
attrName = splt[0]
}
avps := pkt.AttributesWithName(attrName, vendorName)
if len(avps) == 0 { // no attribute found, filter not passing
return
}
for _, avp := range avps { // they all need to match the filter
if !fieldFilter.FilterPasses(avp.StringValue()) {
return
}
}
return true
}

View File

@@ -17,3 +17,90 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package agents
import (
"fmt"
"strings"
"testing"
"github.com/cgrates/cgrates/utils"
"github.com/cgrates/radigo"
)
var (
dictRad *radigo.Dictionary
coder radigo.Coder
)
var freeRADIUSDocDictSample = `
# Most of the lines are copied from freeradius documentation here:
# http://networkradius.com/doc/3.0.10/concepts/dictionary/introduction.html
# Attributes
ATTRIBUTE User-Name 1 string
ATTRIBUTE Password 2 string
# Alias values
VALUE Framed-Protocol PPP 1
# Vendors
VENDOR Cisco 9
VENDOR Microsoft 311
# Vendor AVPs
BEGIN-VENDOR Cisco
ATTRIBUTE Cisco-AVPair 1 string
ATTRIBUTE Cisco-NAS-Port 2 string
END-VENDOR Cisco
`
func init() {
dictRad = radigo.RFC2865Dictionary()
// Load some VSA for our tests
dictRad.ParseFromReader(strings.NewReader(freeRADIUSDocDictSample))
coder = radigo.NewCoder()
}
func TestRadPassesFieldFilter(t *testing.T) {
pkt := radigo.NewPacket(radigo.AccountingRequest, 1, dictRad, coder, "CGRateS.org")
if err := pkt.AddAVPWithName("User-Name", "flopsy", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Cisco-NAS-Port", "CGR1", "Cisco"); err != nil {
t.Error(err)
}
//ftr :=
if !radPassesFieldFilter(pkt, nil, nil) {
t.Error("not passing empty filter")
}
if !radPassesFieldFilter(pkt,
utils.NewRSRFieldMustCompile("User-Name(flopsy)"), nil) {
t.Error("not passing valid filter")
}
if radPassesFieldFilter(pkt,
utils.NewRSRFieldMustCompile("User-Name(notmatching)"), nil) {
t.Error("passing invalid filter value")
}
if !radPassesFieldFilter(pkt,
utils.NewRSRFieldMustCompile("Cisco/Cisco-NAS-Port(CGR1)"), nil) {
t.Error("not passing valid filter")
}
if radPassesFieldFilter(pkt,
utils.NewRSRFieldMustCompile("Cisco/Cisco-NAS-Port(notmatching)"), nil) {
t.Error("passing invalid filter value")
}
if !radPassesFieldFilter(pkt,
utils.NewRSRFieldMustCompile(fmt.Sprintf("%s(4)", MetaRadReqCode)),
map[string]string{MetaRadReqCode: "4"}) {
t.Error("not passing valid filter")
}
if radPassesFieldFilter(pkt,
utils.NewRSRFieldMustCompile(fmt.Sprintf("%s(4)", MetaRadReqCode)),
map[string]string{MetaRadReqCode: "5"}) {
t.Error("passing invalid filter")
}
if radPassesFieldFilter(pkt,
utils.NewRSRFieldMustCompile("UnknownField(notmatching)"), nil) {
t.Error("passing invalid filter value")
}
}

View File

@@ -26,6 +26,11 @@ import (
"github.com/cgrates/rpcclient"
)
const (
MetaRadReqCode = "*req_code"
MetaRadReplyCode = "*reply_code"
)
func NewRadiusAgent(cgrCfg *config.CGRConfig, smg rpcclient.RpcClientConnection) (ra *RadiusAgent, err error) {
dicts := make(map[string]*radigo.Dictionary, len(cgrCfg.RadiusAgentCfg().ClientDictionaries))
for clntID, dictPath := range cgrCfg.RadiusAgentCfg().ClientDictionaries {
@@ -49,29 +54,71 @@ type RadiusAgent struct {
rsAcct *radigo.Server
}
// handleAuth handles RADIUS Authorization request
func (ra *RadiusAgent) handleAuth(req *radigo.Packet) (rpl *radigo.Packet, err error) {
req.SetAVPValues() // populate string values in AVPs
utils.Logger.Debug(fmt.Sprintf("RadiusAgent handleAuth, received request: %+v", req))
procVars := map[string]string{
MetaRadReqCode: "4",
}
rpl = req.Reply()
rpl.Code = radigo.AccessAccept
for _, avp := range req.AVPs {
rpl.AVPs = append(rpl.AVPs, avp)
var processed bool
for _, reqProcessor := range ra.cgrCfg.RadiusAgentCfg().RequestProcessors {
var lclProcessed bool
if lclProcessed, err = ra.processRequest(reqProcessor, req, procVars, rpl); lclProcessed {
processed = lclProcessed
}
if err != nil || (lclProcessed && !reqProcessor.ContinueOnSuccess) {
break
}
}
if err != nil {
utils.Logger.Err(fmt.Sprintf("<RadiusAgent> request: %s, error: %s", utils.ToJSON(req), err.Error()))
return nil, nil
} else if !processed {
utils.Logger.Err(fmt.Sprintf("<RadiusAgent> No request processor enabled for request: %s, ignoring request", utils.ToJSON(req)))
return nil, nil
}
return
}
// handleAcct handles RADIUS Accounting request
// supports: Acct-Status-Type = Start, Interim-Update, Stop
func (ra *RadiusAgent) handleAcct(req *radigo.Packet) (rpl *radigo.Packet, err error) {
req.SetAVPValues()
req.SetAVPValues() // populate string values in AVPs
utils.Logger.Debug(fmt.Sprintf("Received request: %s", utils.ToJSON(req)))
procVars := map[string]string{
MetaRadReqCode: "4",
}
rpl = req.Reply()
rpl.Code = radigo.AccountingResponse
rpl.SetAVPValues()
var processed bool
for _, reqProcessor := range ra.cgrCfg.RadiusAgentCfg().RequestProcessors {
var lclProcessed bool
if lclProcessed, err = ra.processRequest(reqProcessor, req, procVars, rpl); lclProcessed {
processed = lclProcessed
}
if err != nil || (lclProcessed && !reqProcessor.ContinueOnSuccess) {
break
}
}
if err != nil {
utils.Logger.Err(fmt.Sprintf("<RadiusAgent> request: %s, error: %s", utils.ToJSON(req), err.Error()))
return nil, nil
} else if !processed {
utils.Logger.Err(fmt.Sprintf("<RadiusAgent> No request processor enabled for request: %s, ignoring request", utils.ToJSON(req)))
return nil, nil
}
return
}
func (ra *RadiusAgent) processRequest(req *radigo.Packet, reqProcessor *config.RARequestProcessor) (processed bool, err error) {
// processRequest represents one processor processing the request
func (ra *RadiusAgent) processRequest(reqProcessor *config.RARequestProcessor,
req *radigo.Packet, processorVars map[string]string, reply *radigo.Packet) (processed bool, err error) {
passesAllFilters := true
for _, fldFilter := range reqProcessor.RequestFilter {
if passes := radPassesFieldFilter(req, fldFilter, nil); !passes {
if passes := radPassesFieldFilter(req, fldFilter, processorVars); !passes {
passesAllFilters = false
}
}

View File

@@ -81,8 +81,8 @@
{
"id": "KamailioAccountingStart",
"dry_run": false,
"request_filter": "Service-Type(Sip-Session);Sip-Method(Invite)",
"flags": [],
"request_filter": "*radcode(4);Service-Type(Sip-Session);Sip-Method(Invite)",
"flags": ["*radacct_start"], // flag accounting start since we have no Acct-Status-Type in request
"continue_on_success": false,
"append_reply": true,
"request_fields":[
@@ -94,8 +94,8 @@
{
"id": "KamailioAccountingStop",
"dry_run": false,
"request_filter": "Service-Type(Sip-Session);Sip-Method(Bye)",
"flags": [],
"request_filter": "*radcode(4)Service-Type(Sip-Session);Sip-Method(Bye)",
"flags": ["*radacct_stop"],
"continue_on_success": false,
"append_reply": true,
"request_fields":[

2
glide.lock generated
View File

@@ -18,7 +18,7 @@ imports:
- name: github.com/cgrates/osipsdagram
version: 3d6beed663452471dec3ca194137a30d379d9e8f
- name: github.com/cgrates/radigo
version: 89761b5c2d44a89393c57c52b6ee472e77138bfb
version: b6d2c57d9667095035ad4ffd0395bbbebb63ee94
- name: github.com/cgrates/rpcclient
version: dddae42e9344e877627cd4b7aba075d63b452c0b
- name: github.com/ChrisTrenkamp/goxpath

View File

@@ -88,6 +88,14 @@ func NewRSRField(fldStr string) (*RSRField, error) {
return rsrField, nil
}
func NewRSRFieldMustCompile(fldStr string) (rsrFld *RSRField) {
var err error
if rsrFld, err = NewRSRField(fldStr); err != nil {
return nil
}
return
}
type RSRField struct {
Id string // Identifier
Rules string // Rules container holding the string rules to be able to restore it after DB