Radius radReqAsSMGEvent, handler for *usage_difference, tests

This commit is contained in:
DanB
2017-06-11 10:48:42 +02:00
parent 95e09e7a77
commit a98bf56562
7 changed files with 209 additions and 28 deletions

View File

@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package agents
import (
"errors"
"fmt"
"strings"
@@ -56,7 +57,7 @@ func radPassesFieldFilter(pkt *radigo.Packet, processorVars map[string]string, f
return
}
for _, avp := range avps { // they all need to match the filter
if !fieldFilter.FilterPasses(avp.StringValue()) {
if !fieldFilter.FilterPasses(avp.GetStringValue()) {
return
}
}
@@ -77,7 +78,7 @@ func radComposedFieldValue(pkt *radigo.Packet,
}
for _, avp := range pkt.AttributesWithName(
attrVendorFromPath(rsrTpl.Id)) {
outVal += rsrTpl.ParseValue(avp.StringValue())
outVal += rsrTpl.ParseValue(avp.GetStringValue())
}
}
return outVal
@@ -85,13 +86,31 @@ func radComposedFieldValue(pkt *radigo.Packet,
// radMetaHandler handles *handler type in configuration fields
func radMetaHandler(pkt *radigo.Packet, processorVars map[string]string,
handlerTag, arg string) (outVal string, err error) {
cfgFld *config.CfgCdrField) (outVal string, err error) {
handlerArgs := strings.Split(
radComposedFieldValue(pkt, processorVars, cfgFld.Value), utils.HandlerArgSep)
switch cfgFld.HandlerId {
case MetaUsageDifference: // expects tEnd|tStart in the composed val
if len(handlerArgs) != 2 {
return "", errors.New("unexpected number of arguments")
}
tEnd, err := utils.ParseTimeDetectLayout(handlerArgs[0], cfgFld.Timezone)
if err != nil {
return "", err
}
tStart, err := utils.ParseTimeDetectLayout(handlerArgs[1], cfgFld.Timezone)
if err != nil {
return "", err
}
fmt.Printf("tEnd: %v, tStart: %v\n", tEnd, tStart)
return tEnd.Sub(tStart).String(), nil
}
return
}
// radFieldOutVal formats the field value retrieved from RADIUS packet
func radFieldOutVal(pkt *radigo.Packet, processorVars map[string]string,
cfgFld *config.CfgCdrField, extraParam interface{}) (outVal string, err error) {
cfgFld *config.CfgCdrField) (outVal string, err error) {
// make sure filters are passing
passedAllFilters := true
for _, fldFilter := range cfgFld.FieldFilter {
@@ -113,7 +132,7 @@ func radFieldOutVal(pkt *radigo.Packet, processorVars map[string]string,
case utils.META_COMPOSED:
outVal = radComposedFieldValue(pkt, processorVars, cfgFld.Value)
case utils.META_HANDLER:
if outVal, err = radMetaHandler(pkt, processorVars, cfgFld.HandlerId, cfgFld.Layout); err != nil {
if outVal, err = radMetaHandler(pkt, processorVars, cfgFld); err != nil {
return "", err
}
default:
@@ -126,13 +145,12 @@ func radFieldOutVal(pkt *radigo.Packet, processorVars map[string]string,
}
// radPktAsSMGEvent converts a RADIUS packet into SMGEvent
func radReqAsSMGEvent(radPkt *radigo.Packet, procVars map[string]string,
cfgFlds []*config.CfgCdrField,
procFlags utils.StringMap) (smgEv sessionmanager.SMGenericEvent, err error) {
func radReqAsSMGEvent(radPkt *radigo.Packet, procVars map[string]string, procFlags utils.StringMap,
cfgFlds []*config.CfgCdrField) (smgEv sessionmanager.SMGenericEvent, err error) {
outMap := make(map[string]string) // work with it so we can append values to keys
outMap[utils.EVENT_NAME] = EvRadiusReq
for _, cfgFld := range cfgFlds {
fmtOut, err := radFieldOutVal(radPkt, procVars, cfgFld, nil)
fmtOut, err := radFieldOutVal(radPkt, procVars, cfgFld)
if err != nil {
if err == ErrFilterNotPassing {
continue // Do nothing in case of Filter not passing
@@ -145,6 +163,9 @@ func radReqAsSMGEvent(radPkt *radigo.Packet, procVars map[string]string,
outMap[cfgFld.FieldId] = fmtOut
}
}
if len(procFlags) != 0 {
outMap[utils.CGRFlags] = procFlags.String()
}
return sessionmanager.SMGenericEvent(utils.ConvertMapValStrIf(outMap)), nil
}

View File

@@ -20,9 +20,12 @@ package agents
import (
"fmt"
"reflect"
"strings"
"testing"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/sessionmanager"
"github.com/cgrates/cgrates/utils"
"github.com/cgrates/radigo"
)
@@ -52,6 +55,13 @@ BEGIN-VENDOR Cisco
ATTRIBUTE Cisco-AVPair 1 string
ATTRIBUTE Cisco-NAS-Port 2 string
END-VENDOR Cisco
ATTRIBUTE Sip-Method 101 integer
ATTRIBUTE Sip-Response-Code 102 integer
ATTRIBUTE Sip-From-Tag 105 string
ATTRIBUTE Sip-To-Tag 104 string
ATTRIBUTE Ascend-User-Acct-Time 143 integer
`
func init() {
@@ -60,6 +70,17 @@ func init() {
coder = radigo.NewCoder()
}
func TestAttrVendorFromPath(t *testing.T) {
if attrName, vendorName := attrVendorFromPath("User-Name"); attrName != "User-Name" ||
vendorName != "" {
t.Error("failed")
}
if attrName, vendorName := attrVendorFromPath("Cisco/Cisco-NAS-Port"); attrName != "Cisco-NAS-Port" ||
vendorName != "Cisco" {
t.Error("failed")
}
}
func TestRadPassesFieldFilter(t *testing.T) {
pkt := radigo.NewPacket(radigo.AccountingRequest, 1, dictRad, coder, "CGRateS.org")
if err := pkt.AddAVPWithName("User-Name", "flopsy", ""); err != nil {
@@ -100,3 +121,132 @@ func TestRadPassesFieldFilter(t *testing.T) {
t.Error("passing invalid filter value")
}
}
func TestRadComposedFieldValue(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)
}
eOut := fmt.Sprintf("%s|flopsy|CGR1", MetaRadAcctStart)
if out := radComposedFieldValue(pkt, map[string]string{MetaRadReqType: MetaRadAcctStart},
utils.ParseRSRFieldsMustCompile(fmt.Sprintf("%s;^|;User-Name;^|;Cisco/Cisco-NAS-Port", MetaRadReqType), utils.INFIELD_SEP)); out != eOut {
t.Errorf("Expecting: <%s>, received: <%s>", eOut, out)
}
}
func TestRadFieldOutVal(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)
}
eOut := fmt.Sprintf("%s|flopsy|CGR1", MetaRadAcctStart)
cfgFld := &config.CfgCdrField{Tag: "ComposedTest", Type: utils.META_COMPOSED, FieldId: utils.DESTINATION,
Value: utils.ParseRSRFieldsMustCompile(fmt.Sprintf("%s;^|;User-Name;^|;Cisco/Cisco-NAS-Port", MetaRadReqType), utils.INFIELD_SEP), Mandatory: true}
if outVal, err := radFieldOutVal(pkt, map[string]string{MetaRadReqType: MetaRadAcctStart}, cfgFld); err != nil {
t.Error(err)
} else if outVal != eOut {
t.Errorf("Expecting: <%s>, received: <%s>", eOut, outVal)
}
}
func TestRadReqAsSMGEvent(t *testing.T) {
pkt := radigo.NewPacket(radigo.AccountingRequest, 1, dictRad, coder, "CGRateS.org")
// Sample minimal packet sent by Kamailio
if err := pkt.AddAVPWithName("Acct-Status-Type", "2", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Service-Type", "15", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Sip-Response-Code", "200", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Sip-Method", "8", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Event-Timestamp", "1497106119", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Sip-From-Tag", "75c2f57b", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Sip-To-Tag", "51585361", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Acct-Session-Id", "e4921177ab0e3586c37f6a185864b71a@0:0:0:0:0:0:0:0", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("User-Name", "1001", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Called-Station-Id", "1002", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Ascend-User-Acct-Time", "1497106115", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("NAS-Port-Id", "5060", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("Acct-Delay-Time", "0", ""); err != nil {
t.Error(err)
}
if err := pkt.AddAVPWithName("NAS-IP-Address", "127.0.0.1", ""); err != nil {
t.Error(err)
}
cfgFlds := []*config.CfgCdrField{
&config.CfgCdrField{Tag: "TOR", FieldId: utils.TOR, Type: utils.META_CONSTANT,
Value: utils.ParseRSRFieldsMustCompile(utils.VOICE, utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "OriginID", FieldId: utils.ACCID, Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile("Acct-Session-Id;^-;Sip-From-Tag;^-;Sip-To-Tag", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "OriginHost", FieldId: utils.CDRHOST, Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile("NAS-IP-Address", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "RequestType", FieldId: utils.REQTYPE, Type: utils.META_CONSTANT,
Value: utils.ParseRSRFieldsMustCompile(utils.META_PREPAID, utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Direction", FieldId: utils.DIRECTION, Type: utils.META_CONSTANT,
Value: utils.ParseRSRFieldsMustCompile(utils.OUT, utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Tenant", FieldId: utils.TENANT, Type: utils.META_CONSTANT,
Value: utils.ParseRSRFieldsMustCompile("cgrates.org", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Category", FieldId: utils.CATEGORY, Type: utils.META_CONSTANT,
Value: utils.ParseRSRFieldsMustCompile("call", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Account", FieldId: utils.ACCOUNT, Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile("User-Name", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Destination", FieldId: utils.DESTINATION, Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile("Called-Station-Id", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "SetupTime", FieldId: utils.SETUP_TIME, Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile("Ascend-User-Acct-Time", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "AnswerTime", FieldId: utils.ANSWER_TIME, Type: utils.META_COMPOSED,
Value: utils.ParseRSRFieldsMustCompile("Ascend-User-Acct-Time", utils.INFIELD_SEP)},
&config.CfgCdrField{Tag: "Usage", FieldId: utils.USAGE, Type: utils.META_HANDLER, HandlerId: MetaUsageDifference,
Value: utils.ParseRSRFieldsMustCompile("Event-Timestamp;^|;Ascend-User-Acct-Time", utils.INFIELD_SEP)},
}
eSMGEv := sessionmanager.SMGenericEvent{
utils.EVENT_NAME: EvRadiusReq,
utils.TOR: utils.VOICE,
utils.ACCID: "e4921177ab0e3586c37f6a185864b71a@0:0:0:0:0:0:0:0-75c2f57b-51585361",
utils.REQTYPE: utils.META_PREPAID,
utils.DIRECTION: utils.OUT,
utils.TENANT: "cgrates.org",
utils.CATEGORY: "call",
utils.ACCOUNT: "1001",
utils.DESTINATION: "1002",
utils.SETUP_TIME: "1497106115",
utils.ANSWER_TIME: "1497106115",
utils.USAGE: "4s",
utils.CDRHOST: "127.0.0.1",
}
if smgEv, err := radReqAsSMGEvent(pkt, map[string]string{MetaRadReqType: MetaRadAcctStop}, nil, cfgFlds); err != nil {
t.Error(err)
} else if !reflect.DeepEqual(eSMGEv, smgEv) {
t.Errorf("Expecting: %+v\n, received: %+v", eSMGEv, smgEv)
}
}

View File

@@ -29,15 +29,17 @@ import (
)
const (
MetaRadReqCode = "*radReqCode"
MetaRadReplyCode = "*radReplyCode"
MetaRadAuth = "*radAuth"
MetaRadAcctStart = "*radAcctStart"
MetaRadAcctUpdate = "*radAcctUpdate"
MetaRadAcctStop = "*radAcctStop"
MetaRadAcctEvent = "*radAcctEvent"
MetaCGRMaxUsage = "*cgrMaxUsage"
EvRadiusReq = "RADIUS_REQ"
MetaRadReqCode = "*radReqCode"
MetaRadReplyCode = "*radReplyCode"
MetaRadAuth = "*radAuth"
MetaRadAcctStart = "*radAcctStart"
MetaRadAcctUpdate = "*radAcctUpdate"
MetaRadAcctStop = "*radAcctStop"
MetaRadAcctEvent = "*radAcctEvent"
MetaCGRMaxUsage = "*cgrMaxUsage"
MetaRadReqType = "*radReqType"
EvRadiusReq = "RADIUS_REQUEST"
MetaUsageDifference = "*usage_difference"
)
func NewRadiusAgent(cgrCfg *config.CGRConfig, smg rpcclient.RpcClientConnection) (ra *RadiusAgent, err error) {
@@ -103,7 +105,7 @@ func (ra *RadiusAgent) handleAcct(req *radigo.Packet) (rpl *radigo.Packet, err e
utils.Logger.Debug(fmt.Sprintf("Received request: %s", utils.ToJSON(req)))
procVars := make(map[string]string)
if avps := req.AttributesWithName("Acct-Status-Type", ""); len(avps) != 0 { // populate accounting type
switch avps[0].StringValue() { // first AVP found will give out the type of accounting
switch avps[0].GetStringValue() { // first AVP found will give out the type of accounting
case "Start":
procVars[MetaRadAcctStart] = "true"
case "Interim-Update":
@@ -149,13 +151,14 @@ func (ra *RadiusAgent) processRequest(reqProcessor *config.RARequestProcessor,
for k, v := range reqProcessor.Flags { // update processorVars with flags from processor
processorVars[k] = strconv.FormatBool(v)
}
smgEv, err := radReqAsSMGEvent(req, processorVars, reqProcessor.RequestFields, reqProcessor.Flags)
smgEv, err := radReqAsSMGEvent(req, processorVars, reqProcessor.Flags, reqProcessor.RequestFields)
if err != nil {
return false, err
}
var maxUsage time.Duration
if processorVars[MetaRadReqCode] == "3" { // auth attempt, make sure that MaxUsage is enough
switch processorVars[MetaRadReqType] {
case MetaRadAuth: // auth attempt, make sure that MaxUsage is enough
if err = ra.smg.Call("SMGenericV2.GetMaxUsage", smgEv, &maxUsage); err != nil {
return
}
@@ -166,13 +169,15 @@ func (ra *RadiusAgent) processRequest(reqProcessor *config.RARequestProcessor,
} else if reqUsage.(time.Duration) < maxUsage {
reply.Code = radigo.AccessReject
}
} else if _, has := processorVars[MetaRadAcctStart]; has {
err = ra.smg.Call("SMGenericV1.InitiateSession", smgEv, &maxUsage)
} else if _, has := processorVars[MetaRadAcctUpdate]; has {
err = ra.smg.Call("SMGenericV1.UpdateSession", smgEv, &maxUsage)
} else if _, has := processorVars[MetaRadAcctStop]; has {
case MetaRadAcctStart:
err = ra.smg.Call("SMGenericV2.InitiateSession", smgEv, &maxUsage)
case MetaRadAcctUpdate:
err = ra.smg.Call("SMGenericV2.UpdateSession", smgEv, &maxUsage)
case MetaRadAcctStop:
var rpl string
err = ra.smg.Call("SMGenericV1.TerminateSession", smgEv, &rpl)
default:
err = fmt.Errorf("unsupported radius request type: <%s>", processorVars[MetaRadReqType])
}
if err != nil {
return false, err

View File

@@ -50,6 +50,7 @@ ATTRIBUTE Digest-Body-Digest 1069 string
ATTRIBUTE Digest-CNonce 1070 string
ATTRIBUTE Digest-Nonce-Count 1071 string
ATTRIBUTE Digest-User-Name 1072 string
ATTRIBUTE Ascend-User-Acct-Time 143 integer
ATTRIBUTE Sip-Uri-User 208 string # Proprietary, auth_radius
ATTRIBUTE Sip-Group 211 string # Proprietary, group_radius

View File

@@ -218,7 +218,7 @@ func (ec *EventCost) GetCost() float64 {
if ec.Cost == nil {
var cost float64
for _, ci := range ec.Charges {
cost += ci.Cost() * float64(ci.CompressFactor)
cost += ci.TotalCost()
}
cost = utils.Round(cost, globalRoundingDecimals, utils.ROUNDING_MIDDLE)
ec.Cost = &cost

View File

@@ -147,6 +147,10 @@ func (cIt *ChargingIncrement) TotalUsage() time.Duration {
return time.Duration(cIt.Usage.Nanoseconds() * int64(cIt.CompressFactor))
}
func (cIt *ChargingIncrement) TotalCost() float64 {
return cIt.Cost * float64(cIt.CompressFactor)
}
// BalanceCharge represents one unit charged to a balance
type BalanceCharge struct {
AccountID string // keep reference for shared balances

2
glide.lock generated
View File

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