diff --git a/agents/radagent.go b/agents/radagent.go index 77e6eade4..9d667c158 100644 --- a/agents/radagent.go +++ b/agents/radagent.go @@ -518,11 +518,11 @@ func (ra *RadiusAgent) V1DisconnectSession(_ *context.Context, attr utils.AttrDi return nil } -// V1ChangeAuthorization updates session authorization using RADIUS CoA functionality. -func (ra *RadiusAgent) V1ChangeOfAuthorization(ctx *context.Context, sessionID string, reply *string) error { - replyCode, err := ra.sendRadDaReq(radigo.CoARequest, ra.cgrCfg.RadiusAgentCfg().CoATemplate, sessionID, nil) +// V1ReAuthorize updates session authorization using RADIUS CoA functionality. +func (ra *RadiusAgent) V1ReAuthorize(_ *context.Context, originID string, reply *string) error { + replyCode, err := ra.sendRadDaReq(radigo.CoARequest, ra.cgrCfg.RadiusAgentCfg().CoATemplate, originID, nil) if err != nil { - return fmt.Errorf("change of authorization failed: %w", err) + return err } switch replyCode { case radigo.CoAACK: @@ -566,7 +566,7 @@ func (ra *RadiusAgent) sendRadDaReq(requestType radigo.PacketCode, requestTempla } dynAuthReq := dynAuthClient.NewRequest(requestType, 1) if err = radAppendAttributes(dynAuthReq, agReq.radDAReq); err != nil { - return 0, errors.New("could not append attributes to the request packet") + return 0, fmt.Errorf("could not append attributes to the request packet: %w", err) } dynAuthReply, err := dynAuthClient.SendRequest(dynAuthReq) if err != nil { @@ -593,11 +593,6 @@ func dmRemoteAddr(remoteAddr string, dynAuthAddresses map[string]string) (string return "", "", utils.ErrNotFound } -// V1ReAuthorize is needed to satisfy the sessions.BiRPClient interface -func (*RadiusAgent) V1ReAuthorize(_ *context.Context, _ string, _ *string) error { - return utils.ErrNotImplemented -} - // V1WarnDisconnect is needed to satisfy the sessions.BiRPClient interface func (*RadiusAgent) V1WarnDisconnect(_ *context.Context, _ map[string]any, _ *string) error { return utils.ErrNotImplemented diff --git a/agents/radius_disconnect_it_test.go b/agents/radius_disconnect_it_test.go index 116d86fad..97b0ad647 100644 --- a/agents/radius_disconnect_it_test.go +++ b/agents/radius_disconnect_it_test.go @@ -25,6 +25,7 @@ import ( "bytes" "encoding/base64" "path" + "sync" "testing" "time" @@ -36,26 +37,28 @@ import ( ) /* -TestRadiusDisconnect scenario: +TestRadiusCoADisconnect scenario: 1. Configure a radius_agent with: - a bidirectional connection to sessions - dmr_template field pointing to the predefined *dmr template + - coa_template field pointing to the predefined *coa template - localhost:3799 inside client_da_addresses - an auth request processor - an accounting request processor -2. Set up a 'client' (acting as a server) that will handle incoming Disconnect Requests. +2. Set up a 'client' (acting as a server) that will handle incoming CoA/Disconnect Requests. 3. Send an AccessRequest to cgr-engine's RADIUS server in order to register the packet. 4. Send an AccountingRequest to initialize a session. -5. Send a SessionSv1ForceDisconnect request, that will attempt to remotely disconnect the -session created previously. +5. Send a SessionSv1ReAuthorize request, that will send a CoA request to the client. The +client will then verify that the packet was populated correctly. -6. Verify that the request fields from the 'client' handler are correctly sent. +6. Send a SessionSv1ForceDisconnect request, that will attempt to remotely disconnect the +session created previously and verify the request packet fields. */ -func TestRadiusDisconnect(t *testing.T) { +func TestRadiusCoADisconnect(t *testing.T) { switch *dbType { case utils.MetaInternal: case utils.MetaMySQL, utils.MetaMongo, utils.MetaPostgres: @@ -65,7 +68,7 @@ func TestRadiusDisconnect(t *testing.T) { } // Set up test environment. - cfgPath := path.Join(*dataDir, "conf", "samples", "radius_disconnect") + cfgPath := path.Join(*dataDir, "conf", "samples", "radius_coa_disconnect") raDiscCfg, err := config.NewCGRConfigFromPath(cfgPath) if err != nil { t.Fatal(err) @@ -92,9 +95,15 @@ func TestRadiusDisconnect(t *testing.T) { time.Sleep(time.Duration(*waitRater) * time.Millisecond) // Testing the functionality itself starts here. - done := make(chan struct{}) // signal to end the test when the handler has finished processing + var wg sync.WaitGroup + done := make(chan struct{}) // signal to end the test when the handlers have finished processing + go func() { + wg.Wait() + close(done) + }() + wg.Add(2) handleDisconnect := func(request *radigo.Packet) (*radigo.Packet, error) { - defer close(done) + defer wg.Done() encodedNasIPAddr := "fwAAAQ==" decodedNasIPAddr, err := base64.StdEncoding.DecodeString(encodedNasIPAddr) if err != nil { @@ -112,6 +121,25 @@ func TestRadiusDisconnect(t *testing.T) { } return reply, nil } + handleCoA := func(request *radigo.Packet) (*radigo.Packet, error) { + defer wg.Done() + encodedNasIPAddr := "fwAAAQ==" + decodedNasIPAddr, err := base64.StdEncoding.DecodeString(encodedNasIPAddr) + if err != nil { + t.Error("error decoding base64 NAS-IP-Address:", err) + } + reply := request.Reply() + if string(request.AVPs[0].RawValue) != "1001" || + !bytes.Equal(request.AVPs[1].RawValue, decodedNasIPAddr) || + string(request.AVPs[2].RawValue) != "e4921177ab0e3586c37f6a185864b71a@0:0:0:0:0:0:0:0" || + string(request.AVPs[3].RawValue) != "custom_filter" { + t.Errorf("unexpected request received: %v", utils.ToJSON(request)) + reply.Code = radigo.CoANAK + } else { + reply.Code = radigo.CoAACK + } + return reply, nil + } type testNAS struct { clientAuth *radigo.Client clientAcct *radigo.Client @@ -123,6 +151,7 @@ func TestRadiusDisconnect(t *testing.T) { testRadClient.server = radigo.NewServer("udp", "127.0.0.1:3799", secrets, dicts, map[radigo.PacketCode]func(*radigo.Packet) (*radigo.Packet, error){ radigo.DisconnectRequest: handleDisconnect, + radigo.CoARequest: handleCoA, }, nil, utils.Logger) stopChan := make(chan struct{}) defer close(stopChan) @@ -203,14 +232,18 @@ func TestRadiusDisconnect(t *testing.T) { t.Errorf("unexpected reply received to AccountingRequest: %+v", replyPacket) } - var replyFD string - if err = raDiscRPC.Call(context.Background(), utils.SessionSv1ForceDisconnect, nil, &replyFD); err != nil { + var reply string + if err := raDiscRPC.Call(context.Background(), utils.SessionSv1ReAuthorize, &utils.SessionFilter{}, &reply); err != nil { + t.Error(err) + } + + if err = raDiscRPC.Call(context.Background(), utils.SessionSv1ForceDisconnect, nil, &reply); err != nil { t.Error(err) } select { case <-done: case <-time.After(time.Second): - t.Error("client did not receive a DisconnectRequest in time") + t.Error("client did not receive a the expected requests in time") } } diff --git a/config/config_defaults.go b/config/config_defaults.go index 3abdc55c4..b87c9650f 100644 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -1226,16 +1226,16 @@ const CGRATES_CFG_JSON = ` {"tag": "Reply-Message", "path": "*radDAReq.Reply-Message", "type": "*variable", "value": "~*vars.DisconnectCause"}, ], - // "*coa": [ - // {"tag": "User-Name", "path": "*radDAReq.User-Name", "type": "*variable", - // "value": "~*req.User-Name"}, - // {"tag": "NAS-IP-Address", "path": "*radDAReq.NAS-IP-Address", "type": "*variable", - // "value": "~*req.NAS-IP-Address"}, - // {"tag": "Acct-Session-Id", "path": "*radDAReq.Acct-Session-Id", "type": "*variable", - // "value": "~*req.Acct-Session-Id"}, - // {"tag": "Filter-ID", "path": "*radDAReq.Filter-ID", "type": "*constant", - // "value": "custom_filter"}, - // ], + "*coa": [ + {"tag": "User-Name", "path": "*radDAReq.User-Name", "type": "*variable", + "value": "~*req.User-Name"}, + {"tag": "NAS-IP-Address", "path": "*radDAReq.NAS-IP-Address", "type": "*variable", + "value": "~*req.NAS-IP-Address"}, + {"tag": "Acct-Session-Id", "path": "*radDAReq.Acct-Session-Id", "type": "*variable", + "value": "~*req.Acct-Session-Id"}, + {"tag": "Filter-Id", "path": "*radDAReq.Filter-Id", "type": "*constant", + "value": "custom_filter"}, + ], "*errSip": [ {"tag": "Request", "path": "*rep.Request", "type": "*constant", "value": "SIP/2.0 500 Internal Server Error", "mandatory": true}, diff --git a/data/conf/samples/diamagent_internal/cgrates.json b/data/conf/samples/diamagent_internal/cgrates.json index de15120bf..c1491c0b1 100644 --- a/data/conf/samples/diamagent_internal/cgrates.json +++ b/data/conf/samples/diamagent_internal/cgrates.json @@ -56,9 +56,10 @@ "diameter_agent": { "enabled": true, + "sessions_conns": ["*bijson_localhost"], "asr_template": "*asr", "rar_template": "*rar", - "forced_disconnect": "*asr", // the request to send to diameter on DisconnectSession <*none|*asr|*rar> + "forced_disconnect": "*asr" // the request to send to diameter on DisconnectSession <*none|*asr|*rar> }, "apiers": { diff --git a/data/conf/samples/radius_disconnect/accounting.json b/data/conf/samples/radius_coa_disconnect/accounting.json similarity index 100% rename from data/conf/samples/radius_disconnect/accounting.json rename to data/conf/samples/radius_coa_disconnect/accounting.json diff --git a/data/conf/samples/radius_disconnect/auth.json b/data/conf/samples/radius_coa_disconnect/auth.json similarity index 100% rename from data/conf/samples/radius_disconnect/auth.json rename to data/conf/samples/radius_coa_disconnect/auth.json diff --git a/data/conf/samples/radius_disconnect/cgrates.json b/data/conf/samples/radius_coa_disconnect/cgrates.json similarity index 95% rename from data/conf/samples/radius_disconnect/cgrates.json rename to data/conf/samples/radius_coa_disconnect/cgrates.json index a62b80a45..3b423b410 100644 --- a/data/conf/samples/radius_disconnect/cgrates.json +++ b/data/conf/samples/radius_coa_disconnect/cgrates.json @@ -61,7 +61,8 @@ "acct_address": "127.0.0.1:1813" } ], - "dmr_template": "*dmr" + "dmr_template": "*dmr", + "coa_template": "*coa" }, "apiers": {