diff --git a/agents/librad.go b/agents/librad.go
index ec5327ee8..dfffd5b09 100644
--- a/agents/librad.go
+++ b/agents/librad.go
@@ -19,23 +19,37 @@ along with this program. If not, see
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
}
diff --git a/agents/librad_test.go b/agents/librad_test.go
index 2f5d97c32..a26dcf563 100644
--- a/agents/librad_test.go
+++ b/agents/librad_test.go
@@ -17,3 +17,90 @@ along with this program. If not, see
*/
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")
+ }
+}
diff --git a/agents/radagent.go b/agents/radagent.go
index 59e3cb628..0798b551b 100644
--- a/agents/radagent.go
+++ b/agents/radagent.go
@@ -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(" request: %s, error: %s", utils.ToJSON(req), err.Error()))
+ return nil, nil
+ } else if !processed {
+ utils.Logger.Err(fmt.Sprintf(" 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(" request: %s, error: %s", utils.ToJSON(req), err.Error()))
+ return nil, nil
+ } else if !processed {
+ utils.Logger.Err(fmt.Sprintf(" 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
}
}
diff --git a/data/conf/samples/radagent/cgrates.json b/data/conf/samples/radagent/cgrates.json
index cb7f38a01..e950c93c4 100644
--- a/data/conf/samples/radagent/cgrates.json
+++ b/data/conf/samples/radagent/cgrates.json
@@ -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":[
diff --git a/glide.lock b/glide.lock
index ea042a637..142d23c60 100644
--- a/glide.lock
+++ b/glide.lock
@@ -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
diff --git a/utils/rsrfield.go b/utils/rsrfield.go
index 6c387081c..097031727 100644
--- a/utils/rsrfield.go
+++ b/utils/rsrfield.go
@@ -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