diff --git a/agents/librad.go b/agents/librad.go index 8953960f6..1efa724c8 100644 --- a/agents/librad.go +++ b/agents/librad.go @@ -19,6 +19,7 @@ along with this program. If not, see 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 } diff --git a/agents/librad_test.go b/agents/librad_test.go index 05756e498..4e5a7a938 100644 --- a/agents/librad_test.go +++ b/agents/librad_test.go @@ -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) + } +} diff --git a/agents/radagent.go b/agents/radagent.go index 59acfc65b..c6cd2a525 100644 --- a/agents/radagent.go +++ b/agents/radagent.go @@ -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 diff --git a/data/radius/dict/dictionary.kamailio b/data/radius/dict/dictionary.kamailio index 2188030ca..a526298c0 100644 --- a/data/radius/dict/dictionary.kamailio +++ b/data/radius/dict/dictionary.kamailio @@ -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 diff --git a/engine/eventcost.go b/engine/eventcost.go index b6485279a..1a4031f1c 100644 --- a/engine/eventcost.go +++ b/engine/eventcost.go @@ -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 diff --git a/engine/libeventcost.go b/engine/libeventcost.go index 798d64792..6c375fca4 100644 --- a/engine/libeventcost.go +++ b/engine/libeventcost.go @@ -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 diff --git a/glide.lock b/glide.lock index 142d23c60..8fe05360d 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: b6d2c57d9667095035ad4ffd0395bbbebb63ee94 + version: 44900e0407cbc7cb713f6c2b06b843f1d22b2370 - name: github.com/cgrates/rpcclient version: dddae42e9344e877627cd4b7aba075d63b452c0b - name: github.com/ChrisTrenkamp/goxpath