├── VERSION ├── docs ├── images │ ├── README-AMF.png │ ├── Flow-Diagram.drawio.license │ └── README-AMF.png.license └── SECURITY.md ├── VERSION.license ├── .github ├── CODEOWNERS ├── workflows │ ├── stale.yml │ ├── push.yml │ └── main.yml └── dependabot.yml ├── go.mod.license ├── go.sum.license ├── NOTICE.txt ├── util ├── testdata │ ├── amfcfg_with_custom_webui_url_and_amfid.yaml │ ├── no_telemetry.yaml │ ├── telemetry_no_endpoint.yaml │ ├── telemetry_no_ratio.yaml │ ├── telemetry_zero_ratio.yaml │ ├── telemetry.yaml │ └── amfcfg.yaml ├── search_nf_service.go ├── mock.drsm.go └── convert.go ├── .pre-commit-config.yaml ├── .gitignore ├── gmm ├── init_test.go ├── mock_gmm.go └── init.go ├── mt ├── api_ue_reach_ind_document.go ├── api_ue_context_document.go └── routers.go ├── communication ├── api_non_uen2_messages_collection_document.go ├── api_non_uen2_messages_subscriptions_collection_document.go ├── api_non_uen2_message_notification_individual_subscription_document.go ├── api_n1_n2_individual_subscription_document.go ├── api_n1_n2_subscriptions_collection_for_individual_ue_contexts_document.go ├── api_subscriptions_collection_document.go ├── api_individual_subscription_document.go ├── api_n1_n2_message_collection_document.go └── routers.go ├── protos ├── server.proto └── sdcoreAmfServer │ └── server_grpc.pb.go ├── amf.go ├── context ├── 3gpp_types.go ├── timer.go ├── transaction.go ├── common_function.go └── sm_context.go ├── Dockerfile ├── producer ├── callback │ ├── ue_context.go │ └── subscription.go ├── mt.go ├── location_info.go ├── oam_test.go └── subscription.go ├── nas ├── dispatch.go └── handler.go ├── tracing └── tracing.go ├── httpcallback ├── api_n1_message_notify.go ├── api_nf_subscribe_notify.go ├── api_sm_context_status_notify.go ├── api_dereg_notify.go ├── router.go └── api_am_policy_control_update_notify.go ├── oam ├── api_purge_ue_context.go ├── api_registered_ue_context.go └── routers.go ├── eventexposure ├── api_subscriptions_collection_document.go ├── routers.go └── api_individual_subscription_document.go ├── location ├── routers.go └── api_individual_ue_context_document.go ├── metrics ├── telemetry.go └── kafka.go ├── factory └── factory.go ├── .golangci.yml ├── ngap └── ngap_test.go ├── README.md ├── consumer ├── ue_context_management.go ├── nsselection.go └── nf_discovery.go ├── Makefile ├── go.mod ├── logger └── logger.go ├── polling └── nf_configuration.go ├── msgtypes └── ngapmsgtypes │ └── ngapmsgtypes.go └── service └── amf_server.go /VERSION: -------------------------------------------------------------------------------- 1 | 2.0.3-dev 2 | -------------------------------------------------------------------------------- /docs/images/README-AMF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omec-project/amf/HEAD/docs/images/README-AMF.png -------------------------------------------------------------------------------- /docs/images/Flow-Diagram.drawio.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Canonical Ltd. 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /VERSION.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | * @omec-project/5gc-maintainers 5 | -------------------------------------------------------------------------------- /docs/images/README-AMF.png.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2022 Open Networking Foundation 2 | 3 | SPDX-License-Identifier: Apache-2.0 4 | -------------------------------------------------------------------------------- /go.mod.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | Copyright 2019 free5GC.org 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | -------------------------------------------------------------------------------- /go.sum.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | Copyright 2019 free5GC.org 3 | 4 | SPDX-License-Identifier: Apache-2.0 5 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | amf 2 | Copyright 2021 Open Networking Foundation 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | 6 | The Initial Developer of the work is free5GC (https://www.free5gc.org/). 7 | At the time of forking, no copyright statements were found 8 | in the free5GC files or directories. 9 | Copyright 2019 free5GC.org. 10 | -------------------------------------------------------------------------------- /util/testdata/amfcfg_with_custom_webui_url_and_amfid.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # SPDX-FileCopyrightText: 2024 Canonical Ltd. 3 | 4 | info: 5 | version: 1.0.0 6 | description: AMF initial local configuration 7 | 8 | configuration: 9 | amfName: AMF # the name of this AMF 10 | amfId: cafe01 # AMF ID for GUAMIs 11 | webuiUri: https://myspecialwebui:5002 # a valid URI of Webui 12 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | repos: 5 | - repo: https://github.com/gitleaks/gitleaks 6 | rev: v8.29.0 7 | hooks: 8 | - id: gitleaks 9 | - repo: https://github.com/golangci/golangci-lint 10 | rev: v2.6.1 11 | hooks: 12 | - id: golangci-lint 13 | - repo: https://github.com/pre-commit/pre-commit-hooks 14 | rev: v6.0.0 15 | hooks: 16 | - id: end-of-file-fixer 17 | - id: trailing-whitespace 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | # Copyright 2019 free5GC.org 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | 7 | # Toolchain 8 | # Goland project folder 9 | .idea/ 10 | # Visual Studio Code 11 | .vscode/ 12 | # emacs/vim 13 | GPATH 14 | GRTAGS 15 | GTAGS 16 | TAGS 17 | tags 18 | cscope.* 19 | # mac 20 | .DS_Store 21 | 22 | # debug 23 | *.log 24 | *.pcap 25 | vendor* 26 | *.patch 27 | 28 | gmm/gmm.dot 29 | 30 | # coverage 31 | ./.coverage 32 | -------------------------------------------------------------------------------- /gmm/init_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | // Copyright 2019 free5GC.org 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // 6 | 7 | package gmm_test 8 | 9 | import ( 10 | "fmt" 11 | "testing" 12 | 13 | "github.com/omec-project/amf/gmm" 14 | "github.com/omec-project/util/fsm" 15 | ) 16 | 17 | func TestGmmFSM(t *testing.T) { 18 | if err := fsm.ExportDot(gmm.GmmFSM, "gmm"); err != nil { 19 | fmt.Printf("fsm export data return error: %+v", err) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # Copyright 2024 Intel Corporation 3 | # Copyright 2025 Canonical Ltd. 4 | on: 5 | schedule: 6 | - cron: "0 0 * * *" 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | contents: read 12 | 13 | jobs: 14 | stale: 15 | permissions: 16 | issues: write 17 | pull-requests: write 18 | contents: read 19 | actions: read 20 | uses: omec-project/.github/.github/workflows/stale-issue.yml@76c248f1621bfe102956c558ea8cecfe5df143bf # v0.0.3 21 | with: 22 | days_before_stale: 120 23 | days_before_close: 15 24 | secrets: inherit 25 | -------------------------------------------------------------------------------- /mt/api_ue_reach_ind_document.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | /* 7 | * Namf_MT 8 | * 9 | * AMF Mobile Termination Service 10 | * 11 | * API version: 1.0.0 12 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 13 | */ 14 | 15 | package mt 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/gin-gonic/gin" 21 | "github.com/omec-project/amf/logger" 22 | ) 23 | 24 | // EnableUeReachability - Namf_MT EnableUEReachability service Operation 25 | func HTTPEnableUeReachability(c *gin.Context) { 26 | logger.MtLog.Warnf("Handle Enable Ue Reachability is not implemented.") 27 | c.JSON(http.StatusOK, gin.H{}) 28 | } 29 | -------------------------------------------------------------------------------- /communication/api_non_uen2_messages_collection_document.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | /* 7 | * Namf_Communication 8 | * 9 | * AMF Communication Service 10 | * 11 | * API version: 1.0.0 12 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 13 | */ 14 | 15 | package communication 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/gin-gonic/gin" 21 | "github.com/omec-project/amf/logger" 22 | ) 23 | 24 | // NonUeN2MessageTransfer - Namf_Communication Non UE N2 Message Transfer service Operation 25 | func HTTPNonUeN2MessageTransfer(c *gin.Context) { 26 | logger.CommLog.Warnf("Handle Non Ue N2 Message Transfer is not implemented.") 27 | c.JSON(http.StatusOK, gin.H{}) 28 | } 29 | -------------------------------------------------------------------------------- /communication/api_non_uen2_messages_subscriptions_collection_document.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | /* 7 | * Namf_Communication 8 | * 9 | * AMF Communication Service 10 | * 11 | * API version: 1.0.0 12 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 13 | */ 14 | 15 | package communication 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/gin-gonic/gin" 21 | "github.com/omec-project/amf/logger" 22 | ) 23 | 24 | // NonUeN2InfoSubscribe - Namf_Communication Non UE N2 Info Subscribe service Operation 25 | func HTTPNonUeN2InfoSubscribe(c *gin.Context) { 26 | logger.CommLog.Warnf("Handle Non Ue N2 Info Subscribe is not implemented.") 27 | c.JSON(http.StatusOK, gin.H{}) 28 | } 29 | -------------------------------------------------------------------------------- /communication/api_non_uen2_message_notification_individual_subscription_document.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | /* 7 | * Namf_Communication 8 | * 9 | * AMF Communication Service 10 | * 11 | * API version: 1.0.0 12 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 13 | */ 14 | 15 | package communication 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/gin-gonic/gin" 21 | "github.com/omec-project/amf/logger" 22 | ) 23 | 24 | // NonUeN2InfoUnSubscribe - Namf_Communication Non UE N2 Info UnSubscribe service Operation 25 | func HTTPNonUeN2InfoUnSubscribe(c *gin.Context) { 26 | logger.CommLog.Warnf("Handle Non Ue N2 Info UnSubscribe is not implemented.") 27 | c.JSON(http.StatusOK, gin.H{}) 28 | } 29 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # Copyright 2022 Intel Corporation 3 | 4 | version: 2 5 | updates: 6 | 7 | - package-ecosystem: "docker" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | day: "sunday" 12 | time: "21:00" 13 | timezone: "America/Los_Angeles" 14 | 15 | - package-ecosystem: "gomod" 16 | directory: "/" 17 | schedule: 18 | interval: "weekly" 19 | day: "sunday" 20 | time: "21:00" 21 | timezone: "America/Los_Angeles" 22 | 23 | - package-ecosystem: github-actions 24 | directory: / 25 | schedule: 26 | interval: "weekly" 27 | day: "sunday" 28 | time: "21:00" 29 | timezone: "America/Los_Angeles" 30 | groups: 31 | actions-deps: 32 | patterns: 33 | - "*" 34 | -------------------------------------------------------------------------------- /protos/server.proto: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | syntax = "proto3"; 5 | package sdcoreAmfServer; 6 | option go_package = "./sdcoreAmfServer"; 7 | 8 | enum msgType { 9 | UNKNOWN = 0; 10 | INIT_MSG = 1; 11 | GNB_MSG = 2; 12 | AMF_MSG = 3; 13 | REDIRECT_MSG = 4; 14 | GNB_DISC = 5; 15 | GNB_CONN = 6; 16 | } 17 | 18 | message SctplbMessage { 19 | string SctplbId = 1; 20 | msgType Msgtype = 2; 21 | string GnbIpAddr = 3; 22 | string VerboseMsg = 4; 23 | bytes Msg = 5; 24 | string GnbId = 6; 25 | } 26 | 27 | message AmfMessage { 28 | string AmfId = 1; 29 | string RedirectId = 2; 30 | msgType Msgtype = 3; 31 | string GnbIpAddr = 4; 32 | string GnbId = 5; 33 | string VerboseMsg = 6; 34 | bytes Msg = 7; 35 | } 36 | 37 | service NgapService { 38 | rpc HandleMessage(stream SctplbMessage) returns (stream AmfMessage) {} 39 | } 40 | -------------------------------------------------------------------------------- /amf.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Intel Corporation 2 | // SPDX-FileCopyrightText: 2021 Open Networking Foundation 3 | // Copyright 2019 free5GC.org 4 | // 5 | // SPDX-License-Identifier: Apache-2.0 6 | // 7 | 8 | package main 9 | 10 | import ( 11 | "context" 12 | "fmt" 13 | "os" 14 | 15 | "github.com/omec-project/amf/logger" 16 | "github.com/omec-project/amf/service" 17 | "github.com/urfave/cli/v3" 18 | ) 19 | 20 | var AMF = &service.AMF{} 21 | 22 | func main() { 23 | app := &cli.Command{} 24 | app.Name = "amf" 25 | logger.AppLog.Infoln(app.Name) 26 | app.Usage = "Access & Mobility Management function" 27 | app.UsageText = "amf -cfg " 28 | app.Action = action 29 | app.Flags = AMF.GetCliCmd() 30 | if err := app.Run(context.Background(), os.Args); err != nil { 31 | logger.AppLog.Fatalf("AMF run error: %v", err) 32 | } 33 | } 34 | 35 | func action(ctx context.Context, c *cli.Command) error { 36 | if err := AMF.Initialize(ctx, c); err != nil { 37 | logger.CfgLog.Errorf("%+v", err) 38 | return fmt.Errorf("failed to initialize") 39 | } 40 | 41 | AMF.Start() 42 | 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /context/3gpp_types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | package context 7 | 8 | import ( 9 | "time" 10 | 11 | "github.com/omec-project/openapi/models" 12 | ) 13 | 14 | const ( 15 | maxNumOfTAI int = 16 16 | maxNumOfBroadcastPLMNs int = 12 17 | maxNumOfPLMNs int = 12 18 | MaxNumOfSlice int = 1024 19 | maxValueOfAmfUeNgapId int64 = 1099511627775 20 | MaxNumOfServedGuamiList int = 256 21 | MaxNumOfPDUSessions int = 256 22 | MaxNumOfDRBs int = 32 23 | MaxNumOfAOI int = 64 24 | ) 25 | 26 | // timers at AMF side, defined in TS 24.501 table 10.2.2 27 | const ( 28 | TimeT3513 time.Duration = 6 * time.Second 29 | TimeT3522 time.Duration = 6 * time.Second 30 | TimeT3550 time.Duration = 6 * time.Second 31 | TimeT3560 time.Duration = 6 * time.Second 32 | TimeT3565 time.Duration = 6 * time.Second 33 | ) 34 | 35 | type LADN struct { 36 | Dnn string 37 | TaiLists []models.Tai 38 | } 39 | 40 | type CauseAll struct { 41 | Cause *models.Cause 42 | NgapCause *models.NgApCause 43 | Var5GmmCause *int32 44 | } 45 | -------------------------------------------------------------------------------- /docs/SECURITY.md: -------------------------------------------------------------------------------- 1 | 5 | # Security Policy 6 | 7 | ## Supported Versions 8 | 9 | We release patches for security vulnerabilities in the following versions: 10 | 11 | | Version | Supported | 12 | | ------- | ------------------ | 13 | | 2.0.x | :white_check_mark: | 14 | | 1.x.x | :x: | 15 | 16 | ## Reporting a Vulnerability 17 | 18 | If you discover a security vulnerability, please: 19 | 20 | 1. **DO NOT** create a public GitHub issue 21 | 2. Email us at: info@aetherproject.org 22 | 3. Include detailed information about the vulnerability 23 | 4. Allow us reasonable time to address the issue before public disclosure 24 | 25 | ### What to Include 26 | 27 | - Description of the vulnerability 28 | - Steps to reproduce the issue 29 | - Potential impact assessment 30 | - Any proof-of-concept code (if applicable) 31 | 32 | ## Security Best Practices 33 | 34 | When using this project: 35 | - Keep dependencies up to date 36 | - Use the latest supported version 37 | - Follow secure coding practices 38 | - Regularly audit your implementation 39 | 40 | ## Contact 41 | 42 | Please see [here](https://github.com/omec-project/amf/?tab=readme-ov-file#reach-out-to-us-through) 43 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | 6 | FROM golang:1.25.5-bookworm@sha256:09f53deea14d4019922334afe6258b7b776afc1d57952be2012f2c8c4076db05 AS builder 7 | 8 | RUN apt-get update && \ 9 | apt-get -y install --no-install-recommends \ 10 | apt-transport-https \ 11 | ca-certificates \ 12 | gcc \ 13 | cmake \ 14 | autoconf \ 15 | libtool \ 16 | pkg-config \ 17 | libmnl-dev \ 18 | libyaml-dev && \ 19 | apt-get clean 20 | 21 | WORKDIR $GOPATH/src/amf 22 | 23 | COPY . . 24 | RUN make all 25 | 26 | FROM alpine:3.23@sha256:51183f2cfa6320055da30872f211093f9ff1d3cf06f39a0bdb212314c5dc7375 AS amf 27 | 28 | LABEL maintainer="Aether SD-Core " \ 29 | description="Aether open source 5G Core Network" \ 30 | version="Stage 3" 31 | 32 | ARG DEBUG_TOOLS 33 | 34 | RUN apk update && apk add --no-cache -U bash 35 | 36 | # Install debug tools ~ 50MB (if DEBUG_TOOLS is set to true) 37 | RUN if [ "$DEBUG_TOOLS" = "true" ]; then \ 38 | apk update && apk add --no-cache -U vim strace net-tools curl netcat-openbsd bind-tools; \ 39 | fi 40 | 41 | # Copy executable 42 | COPY --from=builder /go/src/amf/bin/* /usr/local/bin/. 43 | -------------------------------------------------------------------------------- /producer/callback/ue_context.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Intel Corporation 2 | // Copyright 2019 free5GC.org 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // 6 | 7 | package callback 8 | 9 | import ( 10 | "context" 11 | "fmt" 12 | 13 | amf_context "github.com/omec-project/amf/context" 14 | "github.com/omec-project/amf/logger" 15 | "github.com/omec-project/openapi/Namf_Communication" 16 | "github.com/omec-project/openapi/models" 17 | ) 18 | 19 | func SendN2InfoNotifyN2Handover(ue *amf_context.AmfUe, releaseList []int32) error { 20 | if ue.HandoverNotifyUri == "" { 21 | return fmt.Errorf("N2 Info Notify N2Handover failed(uri dose not exist)") 22 | } 23 | configuration := Namf_Communication.NewConfiguration() 24 | client := Namf_Communication.NewAPIClient(configuration) 25 | 26 | n2InformationNotification := models.N2InformationNotification{ 27 | N2NotifySubscriptionId: ue.Supi, 28 | ToReleaseSessionList: releaseList, 29 | NotifyReason: models.N2InfoNotifyReason_HANDOVER_COMPLETED, 30 | } 31 | 32 | _, httpResponse, err := client.N2MessageNotifyCallbackDocumentApiServiceCallbackDocumentApi. 33 | N2InfoNotify(context.Background(), ue.HandoverNotifyUri, n2InformationNotification) 34 | 35 | if err == nil { 36 | // TODO: handle Msg 37 | } else { 38 | if httpResponse == nil { 39 | logger.HttpLog.Errorln(err.Error()) 40 | } else if err.Error() != httpResponse.Status { 41 | logger.HttpLog.Errorln(err.Error()) 42 | } 43 | } 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /communication/api_n1_n2_individual_subscription_document.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | /* 7 | * Namf_Communication 8 | * 9 | * AMF Communication Service 10 | * 11 | * API version: 1.0.0 12 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 13 | */ 14 | 15 | package communication 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/gin-gonic/gin" 21 | "github.com/omec-project/amf/logger" 22 | "github.com/omec-project/amf/producer" 23 | "github.com/omec-project/openapi" 24 | "github.com/omec-project/openapi/models" 25 | "github.com/omec-project/util/httpwrapper" 26 | ) 27 | 28 | // N1N2MessageUnSubscribe - Namf_Communication N1N2 Message UnSubscribe (UE Specific) service Operation 29 | func HTTPN1N2MessageUnSubscribe(c *gin.Context) { 30 | req := httpwrapper.NewRequest(c.Request, nil) 31 | req.Params["ueContextId"] = c.Params.ByName("ueContextId") 32 | req.Params["subscriptionId"] = c.Params.ByName("subscriptionId") 33 | 34 | rsp := producer.HandleN1N2MessageUnSubscribeRequest(req) 35 | 36 | responseBody, err := openapi.Serialize(rsp.Body, "application/json") 37 | if err != nil { 38 | logger.CommLog.Errorln(err) 39 | problemDetails := models.ProblemDetails{ 40 | Status: http.StatusInternalServerError, 41 | Cause: "SYSTEM_FAILURE", 42 | Detail: err.Error(), 43 | } 44 | c.JSON(http.StatusInternalServerError, problemDetails) 45 | } else { 46 | c.Data(rsp.Status, "application/json", responseBody) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /mt/api_ue_context_document.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | /* 7 | * Namf_MT 8 | * 9 | * AMF Mobile Termination Service 10 | * 11 | * API version: 1.0.0 12 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 13 | */ 14 | 15 | package mt 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/gin-gonic/gin" 21 | "github.com/omec-project/amf/logger" 22 | "github.com/omec-project/amf/producer" 23 | "github.com/omec-project/openapi" 24 | "github.com/omec-project/openapi/models" 25 | "github.com/omec-project/util/httpwrapper" 26 | ) 27 | 28 | // ProvideDomainSelectionInfo - Namf_MT Provide Domain Selection Info service Operation 29 | func HTTPProvideDomainSelectionInfo(c *gin.Context) { 30 | req := httpwrapper.NewRequest(c.Request, nil) 31 | req.Params["ueContextId"] = c.Params.ByName("ueContextId") 32 | infoClassQuery := c.Query("info-class") 33 | req.Query.Add("info-class", infoClassQuery) 34 | supportedFeaturesQuery := c.Query("supported-features") 35 | req.Query.Add("supported-features", supportedFeaturesQuery) 36 | 37 | rsp := producer.HandleProvideDomainSelectionInfoRequest(req) 38 | 39 | responseBody, err := openapi.Serialize(rsp.Body, "application/json") 40 | if err != nil { 41 | logger.MtLog.Errorln(err) 42 | problemDetails := models.ProblemDetails{ 43 | Status: http.StatusInternalServerError, 44 | Cause: "SYSTEM_FAILURE", 45 | Detail: err.Error(), 46 | } 47 | c.JSON(http.StatusInternalServerError, problemDetails) 48 | } else { 49 | c.Data(rsp.Status, "application/json", responseBody) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /util/search_nf_service.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | package util 7 | 8 | import ( 9 | "fmt" 10 | 11 | "github.com/omec-project/openapi/models" 12 | ) 13 | 14 | func SearchNFServiceUri(nfProfile models.NfProfile, serviceName models.ServiceName, 15 | nfServiceStatus models.NfServiceStatus, 16 | ) (nfUri string) { 17 | if nfProfile.NfServices != nil { 18 | for _, service := range *nfProfile.NfServices { 19 | if service.ServiceName == serviceName && service.NfServiceStatus == nfServiceStatus { 20 | if nfProfile.Fqdn != "" { 21 | nfUri = nfProfile.Fqdn 22 | } else if service.Fqdn != "" { 23 | nfUri = service.Fqdn 24 | } else if service.ApiPrefix != "" { 25 | nfUri = service.ApiPrefix 26 | } else if service.IpEndPoints != nil { 27 | point := (*service.IpEndPoints)[0] 28 | if point.Ipv4Address != "" { 29 | nfUri = getSbiUri(service.Scheme, point.Ipv4Address, point.Port) 30 | } else if len(nfProfile.Ipv4Addresses) != 0 { 31 | nfUri = getSbiUri(service.Scheme, nfProfile.Ipv4Addresses[0], point.Port) 32 | } 33 | } 34 | } 35 | if nfUri != "" { 36 | break 37 | } 38 | } 39 | } 40 | return 41 | } 42 | 43 | func getSbiUri(scheme models.UriScheme, ipv4Address string, port int32) (uri string) { 44 | if port != 0 { 45 | uri = fmt.Sprintf("%s://%s:%d", scheme, ipv4Address, port) 46 | } else { 47 | switch scheme { 48 | case models.UriScheme_HTTP: 49 | uri = fmt.Sprintf("%s://%s:80", scheme, ipv4Address) 50 | case models.UriScheme_HTTPS: 51 | uri = fmt.Sprintf("%s://%s:443", scheme, ipv4Address) 52 | } 53 | } 54 | return 55 | } 56 | -------------------------------------------------------------------------------- /nas/dispatch.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | package nas 7 | 8 | import ( 9 | ctxt "context" 10 | "errors" 11 | "fmt" 12 | 13 | "github.com/omec-project/amf/context" 14 | "github.com/omec-project/amf/gmm" 15 | "github.com/omec-project/nas" 16 | "github.com/omec-project/openapi/models" 17 | "github.com/omec-project/util/fsm" 18 | "go.opentelemetry.io/otel" 19 | "go.opentelemetry.io/otel/attribute" 20 | "go.opentelemetry.io/otel/trace" 21 | ) 22 | 23 | var tracer = otel.Tracer("amf/nas") 24 | 25 | func Dispatch(ctx ctxt.Context, ue *context.AmfUe, accessType models.AccessType, procedureCode int64, msg *nas.Message) error { 26 | if msg.GmmMessage == nil { 27 | return errors.New("gmm message is nil") 28 | } 29 | 30 | if msg.GsmMessage != nil { 31 | return errors.New("GSM Message should include in GMM Message") 32 | } 33 | 34 | if ue.State[accessType] == nil { 35 | return fmt.Errorf("UE State is empty (accessType=%q). Can't send GSM Message", accessType) 36 | } 37 | 38 | msgTypeName := nas.MessageName(msg.GmmHeader.GetMessageType()) 39 | spanName := fmt.Sprintf("AMF NAS %s", msgTypeName) 40 | 41 | _, span := tracer.Start(ctx, spanName, 42 | trace.WithAttributes( 43 | attribute.String("nas.accessType", string(accessType)), 44 | attribute.Int64("nas.procedureCode", procedureCode), 45 | attribute.String("nas.messageType", msgTypeName), 46 | ), 47 | ) 48 | defer span.End() 49 | 50 | return gmm.GmmFSM.SendEvent(ctx, ue.State[accessType], gmm.GmmMessageEvent, fsm.ArgsType{ 51 | gmm.ArgAmfUe: ue, 52 | gmm.ArgAccessType: accessType, 53 | gmm.ArgNASMessage: msg.GmmMessage, 54 | gmm.ArgProcedureCode: procedureCode, 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /tracing/tracing.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2025 Canonical Ltd. 3 | 4 | package tracing 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "go.opentelemetry.io/otel" 11 | "go.opentelemetry.io/otel/attribute" 12 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" 13 | "go.opentelemetry.io/otel/propagation" 14 | sdkresource "go.opentelemetry.io/otel/sdk/resource" 15 | sdktrace "go.opentelemetry.io/otel/sdk/trace" 16 | semconv "go.opentelemetry.io/otel/semconv/v1.4.0" 17 | ) 18 | 19 | type TelemetryConfig struct { 20 | Enabled bool 21 | OTLPEndpoint string 22 | ServiceName string 23 | ServiceVersion string 24 | Ratio float64 25 | } 26 | 27 | // InitTracer sets up a global TracerProvider based on the given configuration. 28 | func InitTracer(ctx context.Context, cfg TelemetryConfig) (*sdktrace.TracerProvider, error) { 29 | exp, err := otlptracegrpc.New(ctx, 30 | otlptracegrpc.WithEndpoint(cfg.OTLPEndpoint), 31 | otlptracegrpc.WithInsecure(), 32 | ) 33 | if err != nil { 34 | return nil, fmt.Errorf("failed to create OTLP exporter: %w", err) 35 | } 36 | 37 | sampler := sdktrace.TraceIDRatioBased(cfg.Ratio) 38 | 39 | res, err := sdkresource.New(ctx, 40 | sdkresource.WithAttributes( 41 | semconv.ServiceNameKey.String(cfg.ServiceName), 42 | attribute.String("service.version", cfg.ServiceVersion), 43 | ), 44 | ) 45 | if err != nil { 46 | return nil, fmt.Errorf("failed to create resource: %w", err) 47 | } 48 | 49 | tp := sdktrace.NewTracerProvider( 50 | sdktrace.WithSampler(sampler), 51 | sdktrace.WithBatcher(exp), 52 | sdktrace.WithResource(res), 53 | ) 54 | 55 | otel.SetTracerProvider(tp) 56 | otel.SetTextMapPropagator(propagation.TraceContext{}) 57 | 58 | return tp, nil 59 | } 60 | -------------------------------------------------------------------------------- /util/mock.drsm.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Open Networking Foundation 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | package util 7 | 8 | import ( 9 | "github.com/omec-project/amf/logger" 10 | "github.com/omec-project/util/drsm" 11 | ) 12 | 13 | type MockDrsmInterface interface { 14 | AllocateInt32ID() (int32, error) 15 | ReleaseInt32ID(id int32) error 16 | FindOwnerInt32ID(id int32) (*drsm.PodId, error) 17 | AcquireIp(pool string) (string, error) 18 | ReleaseIp(pool, ip string) error 19 | CreateIpPool(poolName string, ipPool string) error 20 | DeleteIpPool(poolName string) error 21 | DeletePod(string) 22 | } 23 | type MockDrsm struct{} 24 | 25 | func MockDrsmInit() (drsm.DrsmInterface, error) { 26 | // db := drsm.DbInfo{"mongodb://mongodb", "amf"} 27 | // podId := drsm.PodId{"amf-instance1", "1.1.1.1"} 28 | // opt := &drsm.Options{ResIdSize: 24, Mode: drsm.ResourceClient} 29 | d := &MockDrsm{} 30 | return d, nil 31 | } 32 | 33 | func (d *MockDrsm) DeletePod(s string) { 34 | logger.AppLog.Info("MockDeletePod") 35 | } 36 | 37 | func (d *MockDrsm) AllocateInt32ID() (int32, error) { 38 | logger.AppLog.Info("MockAllocate") 39 | return 1, nil 40 | } 41 | 42 | func (d *MockDrsm) ReleaseInt32ID(id int32) error { 43 | logger.AppLog.Info("MockRelease") 44 | return nil 45 | } 46 | 47 | func (d *MockDrsm) FindOwnerInt32ID(id int32) (*drsm.PodId, error) { 48 | return nil, nil 49 | } 50 | 51 | func (d *MockDrsm) AcquireIp(pool string) (string, error) { 52 | return "", nil 53 | } 54 | 55 | func (d *MockDrsm) ReleaseIp(pool, ip string) error { 56 | return nil 57 | } 58 | 59 | func (d *MockDrsm) CreateIpPool(poolName string, ipPool string) error { 60 | return nil 61 | } 62 | 63 | func (d *MockDrsm) DeleteIpPool(poolName string) error { 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /httpcallback/api_n1_message_notify.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | package httpcallback 7 | 8 | import ( 9 | "net/http" 10 | 11 | "github.com/gin-gonic/gin" 12 | "github.com/omec-project/amf/logger" 13 | "github.com/omec-project/amf/producer" 14 | "github.com/omec-project/openapi" 15 | "github.com/omec-project/openapi/models" 16 | "github.com/omec-project/util/httpwrapper" 17 | ) 18 | 19 | func HTTPN1MessageNotify(c *gin.Context) { 20 | var n1MessageNotification models.N1MessageNotification 21 | 22 | requestBody, err := c.GetRawData() 23 | if err != nil { 24 | logger.CallbackLog.Errorf("Get Request Body error: %+v", err) 25 | problemDetail := models.ProblemDetails{ 26 | Title: "System failure", 27 | Status: http.StatusInternalServerError, 28 | Detail: err.Error(), 29 | Cause: "SYSTEM_FAILURE", 30 | } 31 | c.JSON(http.StatusInternalServerError, problemDetail) 32 | return 33 | } 34 | 35 | err = openapi.Deserialize(&n1MessageNotification, requestBody, "application/json") 36 | if err != nil { 37 | problemDetail := "[Request Body] " + err.Error() 38 | rsp := models.ProblemDetails{ 39 | Title: "Malformed request syntax", 40 | Status: http.StatusBadRequest, 41 | Detail: problemDetail, 42 | } 43 | logger.CallbackLog.Errorln(problemDetail) 44 | c.JSON(http.StatusBadRequest, rsp) 45 | return 46 | } 47 | 48 | req := httpwrapper.NewRequest(c.Request, n1MessageNotification) 49 | 50 | rsp := producer.HandleN1MessageNotify(req) 51 | 52 | responseBody, err := openapi.Serialize(rsp.Body, "application/json") 53 | if err != nil { 54 | logger.CallbackLog.Errorln(err) 55 | problemDetails := models.ProblemDetails{ 56 | Status: http.StatusInternalServerError, 57 | Cause: "SYSTEM_FAILURE", 58 | Detail: err.Error(), 59 | } 60 | c.JSON(http.StatusInternalServerError, problemDetails) 61 | } else { 62 | c.Data(rsp.Status, "application/json", responseBody) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # Copyright 2024-Present Intel Corporation 3 | # Copyright 2025 Canonical Ltd. 4 | name: Release Pipeline 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | paths: 11 | - "VERSION" 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | tag-github: 18 | permissions: 19 | contents: write 20 | actions: read 21 | id-token: write 22 | uses: omec-project/.github/.github/workflows/tag-github.yml@76c248f1621bfe102956c558ea8cecfe5df143bf # v0.0.3 23 | secrets: inherit 24 | 25 | release-image: 26 | needs: tag-github 27 | permissions: 28 | contents: read 29 | packages: write 30 | actions: read 31 | id-token: write 32 | attestations: write 33 | uses: omec-project/.github/.github/workflows/release-image.yml@76c248f1621bfe102956c558ea8cecfe5df143bf # v0.0.3 34 | with: 35 | changed: ${{ needs.tag-github.outputs.changed }} 36 | version: ${{ needs.tag-github.outputs.version }} 37 | secrets: inherit 38 | 39 | update-version: 40 | needs: tag-github 41 | permissions: 42 | contents: write 43 | pull-requests: write 44 | actions: read 45 | id-token: write 46 | uses: omec-project/.github/.github/workflows/update-version.yml@76c248f1621bfe102956c558ea8cecfe5df143bf # v0.0.3 47 | with: 48 | changed: ${{ needs.tag-github.outputs.changed }} 49 | version: ${{ needs.tag-github.outputs.version }} 50 | secrets: inherit 51 | 52 | branch-release: 53 | needs: tag-github 54 | permissions: 55 | contents: write 56 | actions: read 57 | id-token: write 58 | uses: omec-project/.github/.github/workflows/branch-release.yml@76c248f1621bfe102956c558ea8cecfe5df143bf # v0.0.3 59 | with: 60 | release_branch: ${{ needs.tag-github.outputs.release_branch }} 61 | version_branch: ${{ needs.tag-github.outputs.version_branch }} 62 | secrets: inherit 63 | -------------------------------------------------------------------------------- /oam/api_purge_ue_context.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Open Networking Foundation 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | package oam 7 | 8 | import ( 9 | "net/http" 10 | 11 | "github.com/gin-gonic/gin" 12 | "github.com/omec-project/amf/context" 13 | "github.com/omec-project/amf/logger" 14 | "github.com/omec-project/amf/producer" 15 | "github.com/omec-project/openapi/models" 16 | "github.com/omec-project/util/httpwrapper" 17 | ) 18 | 19 | func HTTPPurgeUEContext(c *gin.Context) { 20 | setCorsHeader(c) 21 | 22 | amfSelf := context.AMF_Self() 23 | req := httpwrapper.NewRequest(c.Request, nil) 24 | if supi, exists := c.Params.Get("supi"); exists { 25 | req.Params["supi"] = supi 26 | reqUri := req.URL.RequestURI() 27 | if ue, ok := amfSelf.AmfUeFindBySupi(supi); ok { 28 | sbiMsg := context.SbiMsg{ 29 | UeContextId: ue.Supi, 30 | ReqUri: reqUri, 31 | Msg: nil, 32 | Result: make(chan context.SbiResponseMsg, 10), 33 | } 34 | ue.EventChannel.UpdateSbiHandler(producer.HandleOAMPurgeUEContextRequest) 35 | ue.EventChannel.SubmitMessage(sbiMsg) 36 | msg := <-sbiMsg.Result 37 | if msg.ProblemDetails != nil { 38 | c.JSON(int(msg.ProblemDetails.(models.ProblemDetails).Status), msg.ProblemDetails) 39 | } else { 40 | c.JSON(http.StatusOK, nil) 41 | } 42 | } else { 43 | logger.ProducerLog.Errorln("No Ue found by the provided supi") 44 | c.JSON(http.StatusNotFound, nil) 45 | } 46 | } 47 | } 48 | 49 | func HTTPAmfInstanceDown(c *gin.Context) { 50 | setCorsHeader(c) 51 | 52 | nfId, _ := c.Params.Get("nfid") 53 | logger.ProducerLog.Infof("AMF Instance Down Notification from NRF: %v", nfId) 54 | req := httpwrapper.NewRequest(c.Request, nil) 55 | if nfInstanceId, exists := c.Params.Get("nfid"); exists { 56 | req.Params["nfid"] = nfInstanceId 57 | self := context.AMF_Self() 58 | if self.EnableDbStore { 59 | self.Drsm.DeletePod(nfInstanceId) 60 | } 61 | c.JSON(http.StatusOK, nil) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /producer/callback/subscription.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Intel Corporation 2 | // Copyright 2019 free5GC.org 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // 6 | 7 | package callback 8 | 9 | import ( 10 | "context" 11 | "reflect" 12 | 13 | amf_context "github.com/omec-project/amf/context" 14 | "github.com/omec-project/amf/logger" 15 | "github.com/omec-project/openapi/Namf_Communication" 16 | "github.com/omec-project/openapi/models" 17 | ) 18 | 19 | func SendAmfStatusChangeNotify(amfStatus string, guamiList []models.Guami) { 20 | amfSelf := amf_context.AMF_Self() 21 | 22 | amfSelf.AMFStatusSubscriptions.Range(func(key, value interface{}) bool { 23 | subscriptionData := value.(models.SubscriptionData) 24 | 25 | configuration := Namf_Communication.NewConfiguration() 26 | client := Namf_Communication.NewAPIClient(configuration) 27 | amfStatusNotification := models.AmfStatusChangeNotification{} 28 | amfStatusInfo := models.AmfStatusInfo{} 29 | 30 | for _, guami := range guamiList { 31 | for _, subGumi := range subscriptionData.GuamiList { 32 | if reflect.DeepEqual(guami, subGumi) { 33 | // AMF status is available 34 | amfStatusInfo.GuamiList = append(amfStatusInfo.GuamiList, guami) 35 | } 36 | } 37 | } 38 | 39 | amfStatusInfo = models.AmfStatusInfo{ 40 | StatusChange: (models.StatusChange)(amfStatus), 41 | TargetAmfRemoval: "", 42 | TargetAmfFailure: "", 43 | } 44 | 45 | amfStatusNotification.AmfStatusInfoList = append(amfStatusNotification.AmfStatusInfoList, amfStatusInfo) 46 | uri := subscriptionData.AmfStatusUri 47 | 48 | logger.ProducerLog.Infof("[AMF] Send Amf Status Change Notify to %s", uri) 49 | httpResponse, err := client.AmfStatusChangeCallbackDocumentApiServiceCallbackDocumentApi. 50 | AmfStatusChangeNotify(context.Background(), uri, amfStatusNotification) 51 | if err != nil { 52 | if httpResponse == nil { 53 | logger.HttpLog.Errorln(err.Error()) 54 | } else if err.Error() != httpResponse.Status { 55 | logger.HttpLog.Errorln(err.Error()) 56 | } 57 | } 58 | return true 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /httpcallback/api_nf_subscribe_notify.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Infosys Limited 2 | // Copyright 2019 free5GC.org 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // 6 | 7 | package httpcallback 8 | 9 | import ( 10 | "net/http" 11 | 12 | "github.com/gin-gonic/gin" 13 | "github.com/omec-project/amf/logger" 14 | "github.com/omec-project/amf/producer" 15 | "github.com/omec-project/openapi" 16 | "github.com/omec-project/openapi/models" 17 | "github.com/omec-project/util/httpwrapper" 18 | ) 19 | 20 | func HTTPNfSubscriptionStatusNotify(c *gin.Context) { 21 | var nfSubscriptionStatusNotification models.NotificationData 22 | 23 | requestBody, err := c.GetRawData() 24 | if err != nil { 25 | logger.CallbackLog.Errorf("Get Request Body error: %+v", err) 26 | problemDetail := models.ProblemDetails{ 27 | Title: "System failure", 28 | Status: http.StatusInternalServerError, 29 | Detail: err.Error(), 30 | Cause: "SYSTEM_FAILURE", 31 | } 32 | c.JSON(http.StatusInternalServerError, problemDetail) 33 | return 34 | } 35 | 36 | err = openapi.Deserialize(&nfSubscriptionStatusNotification, requestBody, "application/json") 37 | if err != nil { 38 | problemDetail := "[Request Body] " + err.Error() 39 | rsp := models.ProblemDetails{ 40 | Title: "Malformed request syntax", 41 | Status: http.StatusBadRequest, 42 | Detail: problemDetail, 43 | } 44 | logger.CallbackLog.Errorln(problemDetail) 45 | c.JSON(http.StatusBadRequest, rsp) 46 | return 47 | } 48 | 49 | req := httpwrapper.NewRequest(c.Request, nfSubscriptionStatusNotification) 50 | 51 | rsp := producer.HandleNfSubscriptionStatusNotify(c.Request.Context(), req) 52 | 53 | responseBody, err := openapi.Serialize(rsp.Body, "application/json") 54 | if err != nil { 55 | logger.CallbackLog.Errorln(err) 56 | problemDetails := models.ProblemDetails{ 57 | Status: http.StatusInternalServerError, 58 | Cause: "SYSTEM_FAILURE", 59 | Detail: err.Error(), 60 | } 61 | c.JSON(http.StatusInternalServerError, problemDetails) 62 | } else if rsp.Body != nil { 63 | c.Data(rsp.Status, "application/json", responseBody) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /httpcallback/api_sm_context_status_notify.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | package httpcallback 7 | 8 | import ( 9 | "net/http" 10 | 11 | "github.com/gin-gonic/gin" 12 | "github.com/omec-project/amf/logger" 13 | "github.com/omec-project/amf/producer" 14 | "github.com/omec-project/openapi" 15 | "github.com/omec-project/openapi/models" 16 | "github.com/omec-project/util/httpwrapper" 17 | ) 18 | 19 | func HTTPSmContextStatusNotify(c *gin.Context) { 20 | var smContextStatusNotification models.SmContextStatusNotification 21 | 22 | requestBody, err := c.GetRawData() 23 | if err != nil { 24 | logger.CallbackLog.Errorf("Get Request Body error: %+v", err) 25 | problemDetail := models.ProblemDetails{ 26 | Title: "System failure", 27 | Status: http.StatusInternalServerError, 28 | Detail: err.Error(), 29 | Cause: "SYSTEM_FAILURE", 30 | } 31 | c.JSON(http.StatusInternalServerError, problemDetail) 32 | return 33 | } 34 | 35 | err = openapi.Deserialize(&smContextStatusNotification, requestBody, "application/json") 36 | if err != nil { 37 | problemDetail := "[Request Body] " + err.Error() 38 | rsp := models.ProblemDetails{ 39 | Title: "Malformed request syntax", 40 | Status: http.StatusBadRequest, 41 | Detail: problemDetail, 42 | } 43 | logger.CallbackLog.Errorln(problemDetail) 44 | c.JSON(http.StatusBadRequest, rsp) 45 | return 46 | } 47 | 48 | req := httpwrapper.NewRequest(c.Request, smContextStatusNotification) 49 | req.Params["guti"] = c.Params.ByName("guti") 50 | req.Params["pduSessionId"] = c.Params.ByName("pduSessionId") 51 | 52 | rsp := producer.HandleSmContextStatusNotify(req) 53 | 54 | responseBody, err := openapi.Serialize(rsp.Body, "application/json") 55 | if err != nil { 56 | logger.CallbackLog.Errorln(err) 57 | problemDetails := models.ProblemDetails{ 58 | Status: http.StatusInternalServerError, 59 | Cause: "SYSTEM_FAILURE", 60 | Detail: err.Error(), 61 | } 62 | c.JSON(http.StatusInternalServerError, problemDetails) 63 | } else { 64 | c.Data(rsp.Status, "application/json", responseBody) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /httpcallback/api_dereg_notify.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Infosys Limited 2 | // Copyright 2019 free5GC.org 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // 6 | 7 | package httpcallback 8 | 9 | import ( 10 | "net/http" 11 | 12 | "github.com/gin-gonic/gin" 13 | "github.com/omec-project/amf/logger" 14 | "github.com/omec-project/amf/producer" 15 | "github.com/omec-project/openapi" 16 | "github.com/omec-project/openapi/models" 17 | "github.com/omec-project/util/httpwrapper" 18 | ) 19 | 20 | func HTTPDeregistrationNotification(c *gin.Context) { 21 | var deregistrationData models.DeregistrationData 22 | 23 | requestBody, err := c.GetRawData() 24 | if err != nil { 25 | logger.CallbackLog.Errorf("Get Request Body error: %+v", err) 26 | problemDetail := models.ProblemDetails{ 27 | Title: "System failure", 28 | Status: http.StatusInternalServerError, 29 | Detail: err.Error(), 30 | Cause: "SYSTEM_FAILURE", 31 | } 32 | c.JSON(http.StatusInternalServerError, problemDetail) 33 | return 34 | } 35 | 36 | err = openapi.Deserialize(&deregistrationData, requestBody, "application/json") 37 | if err != nil { 38 | problemDetail := "[Request Body] " + err.Error() 39 | rsp := models.ProblemDetails{ 40 | Title: "Malformed request syntax", 41 | Status: http.StatusBadRequest, 42 | Detail: problemDetail, 43 | } 44 | logger.CallbackLog.Errorln(problemDetail) 45 | c.JSON(http.StatusBadRequest, rsp) 46 | return 47 | } 48 | 49 | req := httpwrapper.NewRequest(c.Request, deregistrationData) 50 | if supi, exists := c.Params.Get("supi"); exists { 51 | req.Params["supi"] = supi 52 | } 53 | rsp := producer.HandleDeregistrationNotification(c.Request.Context(), req) 54 | 55 | responseBody, err := openapi.Serialize(rsp.Body, "application/json") 56 | if err != nil { 57 | logger.CallbackLog.Errorln(err) 58 | problemDetails := models.ProblemDetails{ 59 | Status: http.StatusInternalServerError, 60 | Cause: "SYSTEM_FAILURE", 61 | Detail: err.Error(), 62 | } 63 | c.JSON(http.StatusInternalServerError, problemDetails) 64 | } else { 65 | c.Data(rsp.Status, "application/json", responseBody) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /context/timer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | package context 7 | 8 | import ( 9 | "sync/atomic" 10 | "time" 11 | ) 12 | 13 | // Timer can be used for retransmission, it will manage retry times automatically 14 | type Timer struct { 15 | ticker *time.Ticker 16 | expireTimes int32 // accessed atomically 17 | maxRetryTimes int32 // accessed atomically 18 | done chan bool 19 | } 20 | 21 | // NewTimer will return a Timer struct and create a goroutine. Then it calls expiredFunc every time interval d until 22 | // the user call Stop(). the number of expire event is be recorded when the timer is active. When the number of expire 23 | // event is > maxRetryTimes, then the timer will call cancelFunc and turns off itself. Whether expiredFunc pass a 24 | // parameter expireTimes to tell the user that the current expireTimes. 25 | func NewTimer(d time.Duration, maxRetryTimes int, 26 | expiredFunc func(expireTimes int32), 27 | cancelFunc func(), 28 | ) *Timer { 29 | t := &Timer{} 30 | atomic.StoreInt32(&t.expireTimes, 0) 31 | atomic.StoreInt32(&t.maxRetryTimes, int32(maxRetryTimes)) 32 | t.done = make(chan bool, 1) 33 | t.ticker = time.NewTicker(d) 34 | 35 | go func(ticker *time.Ticker) { 36 | defer ticker.Stop() 37 | 38 | for { 39 | select { 40 | case <-t.done: 41 | return 42 | case <-ticker.C: 43 | atomic.AddInt32(&t.expireTimes, 1) 44 | if t.ExpireTimes() > t.MaxRetryTimes() { 45 | cancelFunc() 46 | return 47 | } else { 48 | expiredFunc(t.ExpireTimes()) 49 | } 50 | } 51 | } 52 | }(t.ticker) 53 | 54 | return t 55 | } 56 | 57 | // MaxRetryTimes return the max retry times of the timer 58 | func (t *Timer) MaxRetryTimes() int32 { 59 | return atomic.LoadInt32(&t.maxRetryTimes) 60 | } 61 | 62 | // ExpireTimes return the current expire times of the timer 63 | func (t *Timer) ExpireTimes() int32 { 64 | return atomic.LoadInt32(&t.expireTimes) 65 | } 66 | 67 | // Stop turns off the timer, after Stop, no more timeout event will be triggered. User should call Stop() only once 68 | // otherwise it may hang on writing to done channel 69 | func (t *Timer) Stop() { 70 | t.done <- true 71 | close(t.done) 72 | } 73 | -------------------------------------------------------------------------------- /util/convert.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | // Copyright 2019 free5GC.org 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // 6 | 7 | package util 8 | 9 | import ( 10 | "encoding/hex" 11 | "fmt" 12 | "strconv" 13 | 14 | "github.com/omec-project/amf/logger" 15 | "github.com/omec-project/nas/nasMessage" 16 | "github.com/omec-project/openapi/models" 17 | ) 18 | 19 | func SnssaiHexToModels(hexString string) (*models.Snssai, error) { 20 | sst, err := strconv.ParseInt(hexString[:2], 16, 32) 21 | if err != nil { 22 | return nil, err 23 | } 24 | sNssai := models.Snssai{ 25 | Sst: int32(sst), 26 | Sd: hexString[2:], 27 | } 28 | return &sNssai, nil 29 | } 30 | 31 | func SnssaiModelsToHex(snssai models.Snssai) string { 32 | sst := fmt.Sprintf("%02x", snssai.Sst) 33 | return sst + snssai.Sd 34 | } 35 | 36 | func SeparateAmfId(amfid string) (regionId, setId, ptrId string, err error) { 37 | if len(amfid) != 6 { 38 | err = fmt.Errorf("len of amfId[%s] != 6", amfid) 39 | return 40 | } 41 | // regionId: 16bits, setId: 10bits, ptrId: 6bits 42 | regionId = amfid[:2] 43 | byteArray, err1 := hex.DecodeString(amfid[2:]) 44 | if err1 != nil { 45 | err = err1 46 | return 47 | } 48 | byteSetId := []byte{byteArray[0] >> 6, byteArray[0]<<2 | byteArray[1]>>6} 49 | setId = hex.EncodeToString(byteSetId)[1:] 50 | bytePtrId := []byte{byteArray[1] & 0x3f} 51 | ptrId = hex.EncodeToString(bytePtrId) 52 | return 53 | } 54 | 55 | func PlmnIdStringToModels(plmnId string) (plmnID models.PlmnId) { 56 | plmnID.Mcc = plmnId[:3] 57 | plmnID.Mnc = plmnId[3:] 58 | return 59 | } 60 | 61 | func TACConfigToModels(intString string) (hexString string) { 62 | tmp, err := strconv.ParseUint(intString, 10, 32) 63 | if err != nil { 64 | logger.UtilLog.Errorf("ParseUint error: %+v", err) 65 | return 66 | } 67 | hexString = fmt.Sprintf("%06x", tmp) 68 | return 69 | } 70 | 71 | func AnTypeToNas(anType models.AccessType) uint8 { 72 | switch anType { 73 | case models.AccessType__3_GPP_ACCESS: 74 | return nasMessage.AccessType3GPP 75 | case models.AccessType_NON_3_GPP_ACCESS: 76 | return nasMessage.AccessTypeNon3GPP 77 | } 78 | 79 | return nasMessage.AccessTypeBoth 80 | } 81 | -------------------------------------------------------------------------------- /oam/api_registered_ue_context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | package oam 7 | 8 | import ( 9 | "net/http" 10 | 11 | "github.com/gin-gonic/gin" 12 | "github.com/omec-project/amf/logger" 13 | "github.com/omec-project/amf/producer" 14 | "github.com/omec-project/openapi" 15 | "github.com/omec-project/openapi/models" 16 | "github.com/omec-project/util/httpwrapper" 17 | ) 18 | 19 | func setCorsHeader(c *gin.Context) { 20 | c.Writer.Header().Set("Access-Control-Allow-Origin", "*") 21 | c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") 22 | c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") 23 | c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, PATCH, DELETE") 24 | } 25 | 26 | func HTTPRegisteredUEContext(c *gin.Context) { 27 | setCorsHeader(c) 28 | 29 | req := httpwrapper.NewRequest(c.Request, nil) 30 | if supi, exists := c.Params.Get("supi"); exists { 31 | req.Params["supi"] = supi 32 | } 33 | 34 | rsp := producer.HandleOAMRegisteredUEContext(req) 35 | 36 | responseBody, err := openapi.Serialize(rsp.Body, "application/json") 37 | if err != nil { 38 | logger.MtLog.Errorln(err) 39 | problemDetails := models.ProblemDetails{ 40 | Status: http.StatusInternalServerError, 41 | Cause: "SYSTEM_FAILURE", 42 | Detail: err.Error(), 43 | } 44 | c.JSON(http.StatusInternalServerError, problemDetails) 45 | } else { 46 | c.Data(rsp.Status, "application/json", responseBody) 47 | } 48 | } 49 | 50 | func HTTPGetActiveUes(c *gin.Context) { 51 | setCorsHeader(c) 52 | 53 | req := httpwrapper.NewRequest(c.Request, nil) 54 | 55 | rsp := producer.HandleOAMActiveUEContextsFromDB(req) 56 | 57 | responseBody, err := openapi.Serialize(rsp.Body, "application/json") 58 | if err != nil { 59 | logger.MtLog.Errorln(err) 60 | problemDetails := models.ProblemDetails{ 61 | Status: http.StatusInternalServerError, 62 | Cause: "SYSTEM_FAILURE", 63 | Detail: err.Error(), 64 | } 65 | c.JSON(http.StatusInternalServerError, problemDetails) 66 | } else { 67 | c.Data(rsp.Status, "application/json", responseBody) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /eventexposure/api_subscriptions_collection_document.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | /* 7 | * Namf_EventExposure 8 | * 9 | * AMF Event Exposure Service 10 | * 11 | * API version: 1.0.0 12 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 13 | */ 14 | 15 | package eventexposure 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/gin-gonic/gin" 21 | "github.com/omec-project/amf/logger" 22 | "github.com/omec-project/amf/producer" 23 | "github.com/omec-project/openapi" 24 | "github.com/omec-project/openapi/models" 25 | "github.com/omec-project/util/httpwrapper" 26 | ) 27 | 28 | // CreateSubscription - Namf_EventExposure Subscribe service Operation 29 | func HTTPCreateSubscription(c *gin.Context) { 30 | var createEventSubscription models.AmfCreateEventSubscription 31 | 32 | requestBody, err := c.GetRawData() 33 | if err != nil { 34 | logger.EeLog.Errorf("Get Request Body error: %+v", err) 35 | problemDetail := models.ProblemDetails{ 36 | Title: "System failure", 37 | Status: http.StatusInternalServerError, 38 | Detail: err.Error(), 39 | Cause: "SYSTEM_FAILURE", 40 | } 41 | c.JSON(http.StatusInternalServerError, problemDetail) 42 | return 43 | } 44 | 45 | err = openapi.Deserialize(&createEventSubscription, requestBody, "application/json") 46 | if err != nil { 47 | problemDetail := "[Request Body] " + err.Error() 48 | rsp := models.ProblemDetails{ 49 | Title: "Malformed request syntax", 50 | Status: http.StatusBadRequest, 51 | Detail: problemDetail, 52 | } 53 | logger.EeLog.Errorln(problemDetail) 54 | c.JSON(http.StatusBadRequest, rsp) 55 | return 56 | } 57 | 58 | req := httpwrapper.NewRequest(c.Request, createEventSubscription) 59 | 60 | rsp := producer.HandleCreateAMFEventSubscription(req) 61 | 62 | responseBody, err := openapi.Serialize(rsp.Body, "application/json") 63 | if err != nil { 64 | logger.EeLog.Errorln(err) 65 | problemDetails := models.ProblemDetails{ 66 | Status: http.StatusInternalServerError, 67 | Cause: "SYSTEM_FAILURE", 68 | Detail: err.Error(), 69 | } 70 | c.JSON(http.StatusInternalServerError, problemDetails) 71 | } else { 72 | c.Data(rsp.Status, "application/json", responseBody) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /location/routers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | /* 7 | * Namf_Location 8 | * 9 | * AMF Location Service 10 | * 11 | * API version: 1.0.0 12 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 13 | */ 14 | 15 | package location 16 | 17 | import ( 18 | "net/http" 19 | "strings" 20 | 21 | "github.com/gin-gonic/gin" 22 | "github.com/omec-project/amf/logger" 23 | utilLogger "github.com/omec-project/util/logger" 24 | ) 25 | 26 | // Route is the information for every URI. 27 | type Route struct { 28 | // Name is the name of this Route. 29 | Name string 30 | // Method is the string for the HTTP method. ex) GET, POST etc.. 31 | Method string 32 | // Pattern is the pattern of the URI. 33 | Pattern string 34 | // HandlerFunc is the handler function of this route. 35 | HandlerFunc gin.HandlerFunc 36 | } 37 | 38 | // Routes is the list of the generated Route. 39 | type Routes []Route 40 | 41 | // NewRouter returns a new router. 42 | func NewRouter() *gin.Engine { 43 | router := utilLogger.NewGinWithZap(logger.GinLog) 44 | AddService(router) 45 | return router 46 | } 47 | 48 | func AddService(engine *gin.Engine) *gin.RouterGroup { 49 | group := engine.Group("/namf-loc/v1") 50 | 51 | for _, route := range routes { 52 | switch route.Method { 53 | case "GET": 54 | group.GET(route.Pattern, route.HandlerFunc) 55 | case "POST": 56 | group.POST(route.Pattern, route.HandlerFunc) 57 | case "PUT": 58 | group.PUT(route.Pattern, route.HandlerFunc) 59 | case "DELETE": 60 | group.DELETE(route.Pattern, route.HandlerFunc) 61 | case "PATCH": 62 | group.PATCH(route.Pattern, route.HandlerFunc) 63 | } 64 | } 65 | return group 66 | } 67 | 68 | // Index is the index handler. 69 | func Index(c *gin.Context) { 70 | c.String(http.StatusOK, "Hello World!") 71 | } 72 | 73 | var routes = Routes{ 74 | { 75 | "Index", 76 | "GET", 77 | "/", 78 | Index, 79 | }, 80 | 81 | { 82 | "ProvideLocationInfo", 83 | strings.ToUpper("Post"), 84 | "/:ueContextId/provide-loc-info", 85 | HTTPProvideLocationInfo, 86 | }, 87 | 88 | { 89 | "ProvidePositioningInfo", 90 | strings.ToUpper("Post"), 91 | "/:ueContextId/provide-pos-info", 92 | HTTPProvidePositioningInfo, 93 | }, 94 | } 95 | -------------------------------------------------------------------------------- /mt/routers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | /* 7 | * Namf_MT 8 | * 9 | * AMF Mobile Termination Service 10 | * 11 | * API version: 1.0.0 12 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 13 | */ 14 | 15 | package mt 16 | 17 | import ( 18 | "net/http" 19 | "strings" 20 | 21 | "github.com/gin-gonic/gin" 22 | "github.com/omec-project/amf/logger" 23 | utilLogger "github.com/omec-project/util/logger" 24 | ) 25 | 26 | // Route is the information for every URI. 27 | type Route struct { 28 | // Name is the name of this Route. 29 | Name string 30 | // Method is the string for the HTTP method. ex) GET, POST etc.. 31 | Method string 32 | // Pattern is the pattern of the URI. 33 | Pattern string 34 | // HandlerFunc is the handler function of this route. 35 | HandlerFunc gin.HandlerFunc 36 | } 37 | 38 | // Routes is the list of the generated Route. 39 | type Routes []Route 40 | 41 | // NewRouter returns a new router. 42 | func NewRouter() *gin.Engine { 43 | router := utilLogger.NewGinWithZap(logger.GinLog) 44 | AddService(router) 45 | return router 46 | } 47 | 48 | func AddService(engine *gin.Engine) *gin.RouterGroup { 49 | group := engine.Group("/namf-mt/v1") 50 | 51 | for _, route := range routes { 52 | switch route.Method { 53 | case "GET": 54 | group.GET(route.Pattern, route.HandlerFunc) 55 | case "POST": 56 | group.POST(route.Pattern, route.HandlerFunc) 57 | case "PUT": 58 | group.PUT(route.Pattern, route.HandlerFunc) 59 | case "DELETE": 60 | group.DELETE(route.Pattern, route.HandlerFunc) 61 | case "PATCH": 62 | group.PATCH(route.Pattern, route.HandlerFunc) 63 | } 64 | } 65 | return group 66 | } 67 | 68 | // Index is the index handler. 69 | func Index(c *gin.Context) { 70 | c.String(http.StatusOK, "Hello World!") 71 | } 72 | 73 | var routes = Routes{ 74 | { 75 | "Index", 76 | "GET", 77 | "/", 78 | Index, 79 | }, 80 | 81 | { 82 | "ProvideDomainSelectionInfo", 83 | strings.ToUpper("Get"), 84 | "/ue-contexts/:ueContextId", 85 | HTTPProvideDomainSelectionInfo, 86 | }, 87 | 88 | { 89 | "EnableUeReachability", 90 | strings.ToUpper("Post"), 91 | "/ue-contexts/:ueContextId/ue-reachind", 92 | HTTPEnableUeReachability, 93 | }, 94 | } 95 | -------------------------------------------------------------------------------- /context/transaction.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | // Copyright 2019 free5GC.org 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // 6 | 7 | package context 8 | 9 | import ( 10 | "context" 11 | ) 12 | 13 | type EventChannel struct { 14 | Message chan any 15 | Event chan string 16 | AmfUe *AmfUe 17 | NasHandler func(*AmfUe, NasMsg) 18 | NgapHandler func(*AmfUe, NgapMsg) 19 | SbiHandler func(ctx context.Context, s1, s2 string, msg any) (any, string, any, any) 20 | ConfigHandler func(ctx context.Context, s1, s2, s3 string, msg any) 21 | } 22 | 23 | func (tx *EventChannel) UpdateNgapHandler(handler func(*AmfUe, NgapMsg)) { 24 | tx.AmfUe.TxLog.Infof("updated ngaphandler") 25 | tx.NgapHandler = handler 26 | } 27 | 28 | func (tx *EventChannel) UpdateNasHandler(handler func(*AmfUe, NasMsg)) { 29 | tx.AmfUe.TxLog.Infof("updated nashandler") 30 | tx.NasHandler = handler 31 | } 32 | 33 | func (tx *EventChannel) UpdateSbiHandler(handler func(ctx context.Context, s1, s2 string, msg any) (any, string, any, any)) { 34 | tx.AmfUe.TxLog.Infof("updated sbihandler") 35 | tx.SbiHandler = handler 36 | } 37 | 38 | func (tx *EventChannel) UpdateConfigHandler(handler func(ctx context.Context, s1, s2, s3 string, msg any)) { 39 | tx.AmfUe.TxLog.Infof("updated confighandler") 40 | tx.ConfigHandler = handler 41 | } 42 | 43 | func (tx *EventChannel) Start(ctx context.Context) { 44 | for { 45 | select { 46 | case msg := <-tx.Message: 47 | switch msg := msg.(type) { 48 | case NasMsg: 49 | tx.NasHandler(tx.AmfUe, msg) 50 | case NgapMsg: 51 | tx.NgapHandler(tx.AmfUe, msg) 52 | case SbiMsg: 53 | p_1, p_2, p_3, p_4 := tx.SbiHandler(ctx, msg.UeContextId, msg.ReqUri, msg.Msg) 54 | res := SbiResponseMsg{ 55 | RespData: p_1, 56 | LocationHeader: p_2, 57 | ProblemDetails: p_3, 58 | TransferErr: p_4, 59 | } 60 | msg.Result <- res 61 | case ConfigMsg: 62 | tx.ConfigHandler(ctx, msg.Supi, msg.Sst, msg.Sd, msg.Msg) 63 | } 64 | case event := <-tx.Event: 65 | if event == "quit" { 66 | tx.AmfUe.TxLog.Infof("closed ue goroutine") 67 | return 68 | } 69 | } 70 | } 71 | } 72 | 73 | func (tx *EventChannel) SubmitMessage(msg any) { 74 | tx.Message <- msg 75 | } 76 | -------------------------------------------------------------------------------- /communication/api_n1_n2_subscriptions_collection_for_individual_ue_contexts_document.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | /* 7 | * Namf_Communication 8 | * 9 | * AMF Communication Service 10 | * 11 | * API version: 1.0.0 12 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 13 | */ 14 | 15 | package communication 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/gin-gonic/gin" 21 | "github.com/omec-project/amf/logger" 22 | "github.com/omec-project/amf/producer" 23 | "github.com/omec-project/openapi" 24 | "github.com/omec-project/openapi/models" 25 | "github.com/omec-project/util/httpwrapper" 26 | ) 27 | 28 | func HTTPN1N2MessageSubscribe(c *gin.Context) { 29 | var ueN1N2InfoSubscriptionCreateData models.UeN1N2InfoSubscriptionCreateData 30 | 31 | requestBody, err := c.GetRawData() 32 | if err != nil { 33 | logger.CommLog.Errorf("Get Request Body error: %+v", err) 34 | problemDetail := models.ProblemDetails{ 35 | Title: "System failure", 36 | Status: http.StatusInternalServerError, 37 | Detail: err.Error(), 38 | Cause: "SYSTEM_FAILURE", 39 | } 40 | c.JSON(http.StatusInternalServerError, problemDetail) 41 | return 42 | } 43 | 44 | err = openapi.Deserialize(&ueN1N2InfoSubscriptionCreateData, requestBody, "application/json") 45 | if err != nil { 46 | problemDetail := "[Request Body] " + err.Error() 47 | rsp := models.ProblemDetails{ 48 | Title: "Malformed request syntax", 49 | Status: http.StatusBadRequest, 50 | Detail: problemDetail, 51 | } 52 | logger.CommLog.Errorln(problemDetail) 53 | c.JSON(http.StatusBadRequest, rsp) 54 | return 55 | } 56 | 57 | req := httpwrapper.NewRequest(c.Request, ueN1N2InfoSubscriptionCreateData) 58 | req.Params["ueContextId"] = c.Params.ByName("ueContextId") 59 | 60 | rsp := producer.HandleN1N2MessageSubscirbeRequest(req) 61 | 62 | responseBody, err := openapi.Serialize(rsp.Body, "application/json") 63 | if err != nil { 64 | logger.CommLog.Errorln(err) 65 | problemDetails := models.ProblemDetails{ 66 | Status: http.StatusInternalServerError, 67 | Cause: "SYSTEM_FAILURE", 68 | Detail: err.Error(), 69 | } 70 | c.JSON(http.StatusInternalServerError, problemDetails) 71 | } else { 72 | c.Data(rsp.Status, "application/json", responseBody) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /communication/api_subscriptions_collection_document.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | /* 7 | * Namf_Communication 8 | * 9 | * AMF Communication Service 10 | * 11 | * API version: 1.0.0 12 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 13 | */ 14 | 15 | package communication 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/gin-gonic/gin" 21 | "github.com/omec-project/amf/logger" 22 | "github.com/omec-project/amf/producer" 23 | "github.com/omec-project/openapi" 24 | "github.com/omec-project/openapi/models" 25 | "github.com/omec-project/util/httpwrapper" 26 | ) 27 | 28 | // AMFStatusChangeSubscribe - Namf_Communication AMF Status Change Subscribe service Operation 29 | func HTTPAMFStatusChangeSubscribe(c *gin.Context) { 30 | var subscriptionData models.SubscriptionData 31 | 32 | requestBody, err := c.GetRawData() 33 | if err != nil { 34 | logger.CommLog.Errorf("Get Request Body error: %+v", err) 35 | problemDetail := models.ProblemDetails{ 36 | Title: "System failure", 37 | Status: http.StatusInternalServerError, 38 | Detail: err.Error(), 39 | Cause: "SYSTEM_FAILURE", 40 | } 41 | c.JSON(http.StatusInternalServerError, problemDetail) 42 | return 43 | } 44 | 45 | err = openapi.Deserialize(&subscriptionData, requestBody, "application/json") 46 | if err != nil { 47 | problemDetail := "[Request Body] " + err.Error() 48 | rsp := models.ProblemDetails{ 49 | Title: "Malformed request syntax", 50 | Status: http.StatusBadRequest, 51 | Detail: problemDetail, 52 | } 53 | logger.CommLog.Errorln(problemDetail) 54 | c.JSON(http.StatusBadRequest, rsp) 55 | return 56 | } 57 | 58 | req := httpwrapper.NewRequest(c.Request, subscriptionData) 59 | rsp := producer.HandleAMFStatusChangeSubscribeRequest(req) 60 | 61 | for key, val := range rsp.Header { 62 | c.Header(key, val[0]) 63 | } 64 | responseBody, err := openapi.Serialize(rsp.Body, "application/json") 65 | if err != nil { 66 | logger.CommLog.Errorln(err) 67 | problemDetails := models.ProblemDetails{ 68 | Status: http.StatusInternalServerError, 69 | Cause: "SYSTEM_FAILURE", 70 | Detail: err.Error(), 71 | } 72 | c.JSON(http.StatusInternalServerError, problemDetails) 73 | } else { 74 | c.Data(rsp.Status, "application/json", responseBody) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /metrics/telemetry.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | // Copyright 2019 free5GC.org 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // 6 | 7 | /* 8 | * AMF Statistics exposing to promethus 9 | * 10 | */ 11 | 12 | package metrics 13 | 14 | import ( 15 | "net/http" 16 | 17 | "github.com/omec-project/amf/logger" 18 | "github.com/prometheus/client_golang/prometheus" 19 | "github.com/prometheus/client_golang/prometheus/promhttp" 20 | ) 21 | 22 | // AmfStats captures AMF level stats 23 | type AmfStats struct { 24 | ngapMsg *prometheus.CounterVec 25 | gnbSessionProfile *prometheus.GaugeVec 26 | } 27 | 28 | var amfStats *AmfStats 29 | 30 | func initAmfStats() *AmfStats { 31 | return &AmfStats{ 32 | ngapMsg: prometheus.NewCounterVec(prometheus.CounterOpts{ 33 | Name: "ngap_messages_total", 34 | Help: "ngap interface counters", 35 | }, []string{"amf_id", "msg_type", "direction", "result", "reason"}), 36 | 37 | gnbSessionProfile: prometheus.NewGaugeVec(prometheus.GaugeOpts{ 38 | Name: "gnb_session_profile", 39 | Help: "gNB session Profile", 40 | }, []string{"id", "ip", "state", "tac"}), 41 | } 42 | } 43 | 44 | func (ps *AmfStats) register() error { 45 | prometheus.Unregister(ps.ngapMsg) 46 | 47 | if err := prometheus.Register(ps.ngapMsg); err != nil { 48 | return err 49 | } 50 | if err := prometheus.Register(ps.gnbSessionProfile); err != nil { 51 | return err 52 | } 53 | return nil 54 | } 55 | 56 | func init() { 57 | amfStats = initAmfStats() 58 | 59 | if err := amfStats.register(); err != nil { 60 | logger.AppLog.Errorln("AMF Stats register failed", err) 61 | } 62 | } 63 | 64 | // InitMetrics initialises AMF stats 65 | func InitMetrics() { 66 | http.Handle("/metrics", promhttp.Handler()) 67 | if err := http.ListenAndServe(":9089", nil); err != nil { 68 | logger.InitLog.Errorf("could not open metrics port: %v", err) 69 | } 70 | } 71 | 72 | // IncrementNgapMsgStats increments message level stats 73 | func IncrementNgapMsgStats(amfID, msgType, direction, result, reason string) { 74 | amfStats.ngapMsg.WithLabelValues(amfID, msgType, direction, result, reason).Inc() 75 | } 76 | 77 | // SetGnbSessProfileStats maintains Session profile info 78 | func SetGnbSessProfileStats(id, ip, state, tac string, count uint64) { 79 | amfStats.gnbSessionProfile.WithLabelValues(id, ip, state, tac).Set(float64(count)) 80 | } 81 | -------------------------------------------------------------------------------- /eventexposure/routers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | /* 7 | * Namf_EventExposure 8 | * 9 | * AMF Event Exposure Service 10 | * 11 | * API version: 1.0.0 12 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 13 | */ 14 | 15 | package eventexposure 16 | 17 | import ( 18 | "net/http" 19 | "strings" 20 | 21 | "github.com/gin-gonic/gin" 22 | "github.com/omec-project/amf/logger" 23 | utilLogger "github.com/omec-project/util/logger" 24 | ) 25 | 26 | // Route is the information for every URI. 27 | type Route struct { 28 | // Name is the name of this Route. 29 | Name string 30 | // Method is the string for the HTTP method. ex) GET, POST etc.. 31 | Method string 32 | // Pattern is the pattern of the URI. 33 | Pattern string 34 | // HandlerFunc is the handler function of this route. 35 | HandlerFunc gin.HandlerFunc 36 | } 37 | 38 | // Routes is the list of the generated Route. 39 | type Routes []Route 40 | 41 | // NewRouter returns a new router. 42 | func NewRouter() *gin.Engine { 43 | router := utilLogger.NewGinWithZap(logger.GinLog) 44 | AddService(router) 45 | return router 46 | } 47 | 48 | func AddService(engine *gin.Engine) *gin.RouterGroup { 49 | group := engine.Group("/namf-evts/v1") 50 | 51 | for _, route := range routes { 52 | switch route.Method { 53 | case "GET": 54 | group.GET(route.Pattern, route.HandlerFunc) 55 | case "POST": 56 | group.POST(route.Pattern, route.HandlerFunc) 57 | case "PUT": 58 | group.PUT(route.Pattern, route.HandlerFunc) 59 | case "DELETE": 60 | group.DELETE(route.Pattern, route.HandlerFunc) 61 | case "PATCH": 62 | group.PATCH(route.Pattern, route.HandlerFunc) 63 | } 64 | } 65 | return group 66 | } 67 | 68 | // Index is the index handler. 69 | func Index(c *gin.Context) { 70 | c.String(http.StatusOK, "Hello World!") 71 | } 72 | 73 | var routes = Routes{ 74 | { 75 | "Index", 76 | "GET", 77 | "/", 78 | Index, 79 | }, 80 | 81 | { 82 | "HTTPDeleteSubscription", 83 | strings.ToUpper("Delete"), 84 | "/subscriptions/:subscriptionId", 85 | HTTPDeleteSubscription, 86 | }, 87 | 88 | { 89 | "HTTPModifySubscription", 90 | strings.ToUpper("Patch"), 91 | "/subscriptions/:subscriptionId", 92 | HTTPModifySubscription, 93 | }, 94 | 95 | { 96 | "HTTPCreateSubscription", 97 | strings.ToUpper("Post"), 98 | "/subscriptions", 99 | HTTPCreateSubscription, 100 | }, 101 | } 102 | -------------------------------------------------------------------------------- /gmm/mock_gmm.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Open Networking Foundation 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | package gmm 7 | 8 | import ( 9 | ctxt "context" 10 | 11 | "github.com/omec-project/amf/context" 12 | "github.com/omec-project/amf/logger" 13 | "github.com/omec-project/util/fsm" 14 | ) 15 | 16 | var ( 17 | MockRegisteredCallCount uint32 = 0 18 | MockDeregisteredInitiatedCallCount uint32 = 0 19 | MockContextSetupCallCount uint32 = 0 20 | MockDeRegisteredCallCount uint32 = 0 21 | MockSecurityModeCallCount uint32 = 0 22 | MockAuthenticationCallCount uint32 = 0 23 | ) 24 | 25 | var mockCallbacks = fsm.Callbacks{ 26 | context.Deregistered: MockDeRegistered, 27 | context.Authentication: MockAuthentication, 28 | context.SecurityMode: MockSecurityMode, 29 | context.ContextSetup: MockContextSetup, 30 | context.Registered: MockRegistered, 31 | context.DeregistrationInitiated: MockDeregisteredInitiated, 32 | } 33 | 34 | func Mockinit() { 35 | if f, err := fsm.NewFSM(transitions, mockCallbacks); err != nil { 36 | logger.GmmLog.Errorf("Initialize Gmm FSM Error: %+v", err) 37 | } else { 38 | GmmFSM = f 39 | } 40 | } 41 | 42 | func MockDeRegistered(ctx ctxt.Context, state *fsm.State, event fsm.EventType, args fsm.ArgsType) { 43 | logger.GmmLog.Info("MockDeRegistered") 44 | MockDeRegisteredCallCount++ 45 | } 46 | 47 | func MockAuthentication(ctx ctxt.Context, state *fsm.State, event fsm.EventType, args fsm.ArgsType) { 48 | logger.GmmLog.Info("MockAuthentication") 49 | MockAuthenticationCallCount++ 50 | } 51 | 52 | func MockSecurityMode(ctx ctxt.Context, state *fsm.State, event fsm.EventType, args fsm.ArgsType) { 53 | logger.GmmLog.Info("MockSecurityMode") 54 | MockSecurityModeCallCount++ 55 | } 56 | 57 | func MockContextSetup(ctx ctxt.Context, state *fsm.State, event fsm.EventType, args fsm.ArgsType) { 58 | logger.GmmLog.Info("MockContextSetup") 59 | MockContextSetupCallCount++ 60 | } 61 | 62 | func MockRegistered(ctx ctxt.Context, state *fsm.State, event fsm.EventType, args fsm.ArgsType) { 63 | logger.GmmLog.Info(event) 64 | logger.GmmLog.Info("MockRegistered") 65 | MockRegisteredCallCount++ 66 | } 67 | 68 | func MockDeregisteredInitiated(ctx ctxt.Context, state *fsm.State, event fsm.EventType, args fsm.ArgsType) { 69 | logger.GmmLog.Info("MockDeregisteredInitiated") 70 | MockDeregisteredInitiatedCallCount++ 71 | 72 | amfUe := args[ArgAmfUe].(*context.AmfUe) 73 | amfUe.Remove() 74 | } 75 | -------------------------------------------------------------------------------- /location/api_individual_ue_context_document.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | /* 7 | * Namf_Location 8 | * 9 | * AMF Location Service 10 | * 11 | * API version: 1.0.0 12 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 13 | */ 14 | 15 | package location 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/gin-gonic/gin" 21 | "github.com/omec-project/amf/logger" 22 | "github.com/omec-project/amf/producer" 23 | "github.com/omec-project/openapi" 24 | "github.com/omec-project/openapi/models" 25 | "github.com/omec-project/util/httpwrapper" 26 | ) 27 | 28 | // ProvideLocationInfo - Namf_Location ProvideLocationInfo service Operation 29 | func HTTPProvideLocationInfo(c *gin.Context) { 30 | var requestLocInfo models.RequestLocInfo 31 | 32 | requestBody, err := c.GetRawData() 33 | if err != nil { 34 | problemDetail := models.ProblemDetails{ 35 | Title: "System failure", 36 | Status: http.StatusInternalServerError, 37 | Detail: err.Error(), 38 | Cause: "SYSTEM_FAILURE", 39 | } 40 | logger.LocationLog.Errorf("Get Request Body error: %+v", err) 41 | c.JSON(http.StatusInternalServerError, problemDetail) 42 | return 43 | } 44 | 45 | err = openapi.Deserialize(&requestLocInfo, requestBody, "application/json") 46 | if err != nil { 47 | problemDetail := "[Request Body] " + err.Error() 48 | rsp := models.ProblemDetails{ 49 | Title: "Malformed request syntax", 50 | Status: http.StatusBadRequest, 51 | Detail: problemDetail, 52 | } 53 | logger.LocationLog.Errorln(problemDetail) 54 | c.JSON(http.StatusBadRequest, rsp) 55 | return 56 | } 57 | 58 | req := httpwrapper.NewRequest(c.Request, requestLocInfo) 59 | req.Params["ueContextId"] = c.Params.ByName("ueContextId") 60 | 61 | rsp := producer.HandleProvideLocationInfoRequest(req) 62 | 63 | responseBody, err := openapi.Serialize(rsp.Body, "application/json") 64 | if err != nil { 65 | logger.CommLog.Errorln(err) 66 | problemDetails := models.ProblemDetails{ 67 | Status: http.StatusInternalServerError, 68 | Cause: "SYSTEM_FAILURE", 69 | Detail: err.Error(), 70 | } 71 | c.JSON(http.StatusInternalServerError, problemDetails) 72 | } else { 73 | c.Data(rsp.Status, "application/json", responseBody) 74 | } 75 | } 76 | 77 | // ProvidePositioningInfo - Namf_Location ProvidePositioningInfo service Operation 78 | func HTTPProvidePositioningInfo(c *gin.Context) { 79 | logger.LocationLog.Warnf("Handle Provide Positioning Info is not implemented.") 80 | c.JSON(http.StatusOK, gin.H{}) 81 | } 82 | -------------------------------------------------------------------------------- /context/common_function.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | package context 7 | 8 | import ( 9 | "reflect" 10 | 11 | "github.com/mohae/deepcopy" 12 | "github.com/omec-project/amf/logger" 13 | "github.com/omec-project/openapi/models" 14 | ) 15 | 16 | func CompareUserLocation(loc1 models.UserLocation, loc2 models.UserLocation) bool { 17 | if loc1.EutraLocation != nil && loc2.EutraLocation != nil { 18 | eutraloc1 := deepcopy.Copy(*loc1.EutraLocation).(models.EutraLocation) 19 | eutraloc2 := deepcopy.Copy(*loc2.EutraLocation).(models.EutraLocation) 20 | eutraloc1.UeLocationTimestamp = nil 21 | eutraloc2.UeLocationTimestamp = nil 22 | return reflect.DeepEqual(eutraloc1, eutraloc2) 23 | } 24 | if loc1.N3gaLocation != nil && loc2.N3gaLocation != nil { 25 | return reflect.DeepEqual(loc1, loc2) 26 | } 27 | if loc1.NrLocation != nil && loc2.NrLocation != nil { 28 | nrloc1 := deepcopy.Copy(*loc1.NrLocation).(models.NrLocation) 29 | nrloc2 := deepcopy.Copy(*loc2.NrLocation).(models.NrLocation) 30 | nrloc1.UeLocationTimestamp = nil 31 | nrloc2.UeLocationTimestamp = nil 32 | return reflect.DeepEqual(nrloc1, nrloc2) 33 | } 34 | 35 | return false 36 | } 37 | 38 | func InTaiList(servedTai models.Tai, taiList []models.Tai) bool { 39 | for _, tai := range taiList { 40 | if reflect.DeepEqual(tai, servedTai) { 41 | return true 42 | } 43 | } 44 | return false 45 | } 46 | 47 | func IsTaiEqual(servedTai models.Tai, targetTai models.Tai) bool { 48 | return servedTai.PlmnId.Mcc == targetTai.PlmnId.Mcc && servedTai.PlmnId.Mnc == targetTai.PlmnId.Mnc && servedTai.Tac == targetTai.Tac 49 | } 50 | 51 | func TacInAreas(targetTac string, areas []models.Area) bool { 52 | for _, area := range areas { 53 | for _, tac := range area.Tacs { 54 | if targetTac == tac { 55 | return true 56 | } 57 | } 58 | } 59 | return false 60 | } 61 | 62 | func AttachSourceUeTargetUe(sourceUe, targetUe *RanUe) { 63 | if sourceUe == nil { 64 | logger.ContextLog.Error("Source Ue is Nil") 65 | return 66 | } 67 | if targetUe == nil { 68 | logger.ContextLog.Error("Target Ue is Nil") 69 | return 70 | } 71 | amfUe := sourceUe.AmfUe 72 | if amfUe == nil { 73 | logger.ContextLog.Error("AmfUe is Nil") 74 | return 75 | } 76 | targetUe.AmfUe = amfUe 77 | targetUe.SourceUe = sourceUe 78 | sourceUe.TargetUe = targetUe 79 | } 80 | 81 | func DetachSourceUeTargetUe(ranUe *RanUe) { 82 | if ranUe == nil { 83 | logger.ContextLog.Error("ranUe is Nil") 84 | return 85 | } 86 | if ranUe.TargetUe != nil { 87 | targetUe := ranUe.TargetUe 88 | 89 | ranUe.TargetUe = nil 90 | targetUe.SourceUe = nil 91 | } else if ranUe.SourceUe != nil { 92 | source := ranUe.SourceUe 93 | 94 | ranUe.SourceUe = nil 95 | source.TargetUe = nil 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /oam/routers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | package oam 7 | 8 | import ( 9 | "net/http" 10 | "strings" 11 | 12 | "github.com/gin-contrib/cors" 13 | "github.com/gin-gonic/gin" 14 | "github.com/omec-project/amf/logger" 15 | utilLogger "github.com/omec-project/util/logger" 16 | ) 17 | 18 | // Route is the information for every URI. 19 | type Route struct { 20 | // Name is the name of this Route. 21 | Name string 22 | // Method is the string for the HTTP method. ex) GET, POST etc.. 23 | Method string 24 | // Pattern is the pattern of the URI. 25 | Pattern string 26 | // HandlerFunc is the handler function of this route. 27 | HandlerFunc gin.HandlerFunc 28 | } 29 | 30 | // Routes is the list of the generated Route. 31 | type Routes []Route 32 | 33 | // NewRouter returns a new router. 34 | func NewRouter() *gin.Engine { 35 | router := utilLogger.NewGinWithZap(logger.GinLog) 36 | AddService(router) 37 | 38 | router.Use(cors.New(cors.Config{ 39 | AllowMethods: []string{"GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE"}, 40 | AllowHeaders: []string{"Origin", "Content-Length", "Content-Type", "User-Agent", "Referrer", "Host", "Token", "X-Requested-With"}, 41 | ExposeHeaders: []string{"Content-Length"}, 42 | AllowCredentials: true, 43 | AllowAllOrigins: true, 44 | MaxAge: 86400, 45 | })) 46 | 47 | return router 48 | } 49 | 50 | func AddService(engine *gin.Engine) *gin.RouterGroup { 51 | group := engine.Group("/namf-oam/v1") 52 | 53 | for _, route := range routes { 54 | switch route.Method { 55 | case "GET": 56 | group.GET(route.Pattern, route.HandlerFunc) 57 | case "DELETE": 58 | group.DELETE(route.Pattern, route.HandlerFunc) 59 | case "POST": 60 | group.POST(route.Pattern, route.HandlerFunc) 61 | } 62 | } 63 | return group 64 | } 65 | 66 | // Index is the index handler. 67 | func Index(c *gin.Context) { 68 | c.String(http.StatusOK, "Hello World!") 69 | } 70 | 71 | var routes = Routes{ 72 | { 73 | "Index", 74 | "GET", 75 | "/", 76 | Index, 77 | }, 78 | { 79 | "Registered UE Context", 80 | "GET", 81 | "/registered-ue-context", 82 | HTTPRegisteredUEContext, 83 | }, 84 | 85 | { 86 | "Individual Registered UE Context", 87 | "GET", 88 | "/registered-ue-context/:supi", 89 | HTTPRegisteredUEContext, 90 | }, 91 | 92 | { 93 | "Purge UE Context", 94 | strings.ToUpper("Delete"), 95 | "/purge-ue-context/:supi", 96 | HTTPPurgeUEContext, 97 | }, 98 | { 99 | "Active UE List", 100 | strings.ToUpper("get"), 101 | "/active-ues", 102 | HTTPGetActiveUes, 103 | }, 104 | { 105 | "Amf Instance Down Notification", 106 | strings.ToUpper("post"), 107 | "/amfInstanceDown/:nfid", 108 | HTTPAmfInstanceDown, 109 | }, 110 | } 111 | -------------------------------------------------------------------------------- /httpcallback/router.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Intel Corporation 2 | // Copyright 2019 free5GC.org 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // 6 | 7 | package httpcallback 8 | 9 | import ( 10 | "net/http" 11 | "strings" 12 | 13 | "github.com/gin-gonic/gin" 14 | "github.com/omec-project/amf/logger" 15 | utilLogger "github.com/omec-project/util/logger" 16 | ) 17 | 18 | // Route is the information for every URI. 19 | type Route struct { 20 | // Name is the name of this Route. 21 | Name string 22 | // Method is the string for the HTTP method. ex) GET, POST etc.. 23 | Method string 24 | // Pattern is the pattern of the URI. 25 | Pattern string 26 | // HandlerFunc is the handler function of this route. 27 | HandlerFunc gin.HandlerFunc 28 | } 29 | 30 | // Routes is the list of the generated Route. 31 | type Routes []Route 32 | 33 | // NewRouter returns a new router. 34 | func NewRouter() *gin.Engine { 35 | router := utilLogger.NewGinWithZap(logger.GinLog) 36 | AddService(router) 37 | return router 38 | } 39 | 40 | func AddService(engine *gin.Engine) *gin.RouterGroup { 41 | group := engine.Group("/namf-callback/v1") 42 | 43 | for _, route := range routes { 44 | switch route.Method { 45 | case "GET": 46 | group.GET(route.Pattern, route.HandlerFunc) 47 | case "POST": 48 | group.POST(route.Pattern, route.HandlerFunc) 49 | case "PUT": 50 | group.PUT(route.Pattern, route.HandlerFunc) 51 | case "PATCH": 52 | group.PATCH(route.Pattern, route.HandlerFunc) 53 | case "DELETE": 54 | group.DELETE(route.Pattern, route.HandlerFunc) 55 | } 56 | } 57 | return group 58 | } 59 | 60 | // Index is the index handler. 61 | func Index(c *gin.Context) { 62 | c.String(http.StatusOK, "Hello World!") 63 | } 64 | 65 | var routes = Routes{ 66 | { 67 | "Index", 68 | "GET", 69 | "/", 70 | Index, 71 | }, 72 | 73 | { 74 | "SmContextStatusNotify", 75 | strings.ToUpper("Post"), 76 | "/smContextStatus/:guti/:pduSessionId", 77 | HTTPSmContextStatusNotify, 78 | }, 79 | 80 | { 81 | "AmPolicyControlUpdateNotifyUpdate", 82 | strings.ToUpper("Post"), 83 | "/am-policy/:polAssoId/update", 84 | HTTPAmPolicyControlUpdateNotifyUpdate, 85 | }, 86 | 87 | { 88 | "AmPolicyControlUpdateNotifyTerminate", 89 | strings.ToUpper("Post"), 90 | "/am-policy/:polAssoId/terminate", 91 | HTTPAmPolicyControlUpdateNotifyTerminate, 92 | }, 93 | 94 | { 95 | "N1MessageNotify", 96 | strings.ToUpper("Post"), 97 | "/n1-message-notify", 98 | HTTPN1MessageNotify, 99 | }, 100 | { 101 | "NfStatusNotify", 102 | strings.ToUpper("Post"), 103 | "/nf-status-notify", 104 | HTTPNfSubscriptionStatusNotify, 105 | }, 106 | { 107 | "DeregistrationNotify", 108 | strings.ToUpper("Post"), 109 | ":supi/deregistration-notify", 110 | HTTPDeregistrationNotification, 111 | }, 112 | } 113 | -------------------------------------------------------------------------------- /communication/api_individual_subscription_document.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | /* 7 | * Namf_Communication 8 | * 9 | * AMF Communication Service 10 | * 11 | * API version: 1.0.0 12 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 13 | */ 14 | 15 | package communication 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/gin-gonic/gin" 21 | "github.com/omec-project/amf/logger" 22 | "github.com/omec-project/amf/producer" 23 | "github.com/omec-project/openapi" 24 | "github.com/omec-project/openapi/models" 25 | "github.com/omec-project/util/httpwrapper" 26 | ) 27 | 28 | // AMFStatusChangeSubscribeModify - Namf_Communication AMF Status Change Subscribe Modify service Operation 29 | func HTTPAMFStatusChangeSubscribeModify(c *gin.Context) { 30 | var subscriptionData models.SubscriptionData 31 | 32 | requestBody, err := c.GetRawData() 33 | if err != nil { 34 | logger.CommLog.Errorf("Get Request Body error: %+v", err) 35 | problemDetail := models.ProblemDetails{ 36 | Title: "System failure", 37 | Status: http.StatusInternalServerError, 38 | Detail: err.Error(), 39 | Cause: "SYSTEM_FAILURE", 40 | } 41 | c.JSON(http.StatusInternalServerError, problemDetail) 42 | return 43 | } 44 | 45 | err = openapi.Deserialize(&subscriptionData, requestBody, "application/json") 46 | if err != nil { 47 | problemDetail := "[Request Body] " + err.Error() 48 | rsp := models.ProblemDetails{ 49 | Title: "Malformed request syntax", 50 | Status: http.StatusBadRequest, 51 | Detail: problemDetail, 52 | } 53 | logger.CommLog.Errorln(problemDetail) 54 | c.JSON(http.StatusBadRequest, rsp) 55 | return 56 | } 57 | 58 | req := httpwrapper.NewRequest(c.Request, subscriptionData) 59 | req.Params["subscriptionId"] = c.Params.ByName("subscriptionId") 60 | 61 | rsp := producer.HandleAMFStatusChangeSubscribeModify(req) 62 | 63 | responseBody, err := openapi.Serialize(rsp.Body, "application/json") 64 | if err != nil { 65 | logger.CommLog.Errorln(err) 66 | problemDetails := models.ProblemDetails{ 67 | Status: http.StatusInternalServerError, 68 | Cause: "SYSTEM_FAILURE", 69 | Detail: err.Error(), 70 | } 71 | c.JSON(http.StatusInternalServerError, problemDetails) 72 | } else { 73 | c.Data(rsp.Status, "application/json", responseBody) 74 | } 75 | } 76 | 77 | // AMFStatusChangeUnSubscribe - Namf_Communication AMF Status Change UnSubscribe service Operation 78 | func HTTPAMFStatusChangeUnSubscribe(c *gin.Context) { 79 | req := httpwrapper.NewRequest(c.Request, nil) 80 | req.Params["subscriptionId"] = c.Params.ByName("subscriptionId") 81 | 82 | rsp := producer.HandleAMFStatusChangeUnSubscribeRequest(req) 83 | 84 | responseBody, err := openapi.Serialize(rsp.Body, "application/json") 85 | if err != nil { 86 | logger.CommLog.Errorln(err) 87 | problemDetails := models.ProblemDetails{ 88 | Status: http.StatusInternalServerError, 89 | Cause: "SYSTEM_FAILURE", 90 | Detail: err.Error(), 91 | } 92 | c.JSON(http.StatusInternalServerError, problemDetails) 93 | } else { 94 | c.Data(rsp.Status, "application/json", responseBody) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /factory/factory.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | // Copyright 2019 free5GC.org 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // 6 | 7 | /* 8 | * AMF Configuration Factory 9 | */ 10 | 11 | package factory 12 | 13 | import ( 14 | "fmt" 15 | "net/url" 16 | "os" 17 | "regexp" 18 | 19 | "github.com/omec-project/amf/logger" 20 | "go.yaml.in/yaml/v4" 21 | ) 22 | 23 | var AmfConfig Config 24 | 25 | const AMFID_PATTERN = "^[A-Fa-f0-9]{6}$" 26 | 27 | // TODO: Support configuration update from REST api 28 | func InitConfigFactory(f string) error { 29 | content, err := os.ReadFile(f) 30 | if err != nil { 31 | return err 32 | } 33 | if err = yaml.Unmarshal(content, &AmfConfig); err != nil { 34 | return err 35 | } 36 | if AmfConfig.Configuration.AmfId == "" { 37 | AmfConfig.Configuration.AmfId = "cafe00" 38 | logger.CfgLog.Infof("amfId not set in configuration file. Using %s", AmfConfig.Configuration.AmfId) 39 | } 40 | if AmfConfig.Configuration.WebuiUri == "" { 41 | AmfConfig.Configuration.WebuiUri = "http://webui:5001" 42 | logger.CfgLog.Infof("webuiUri not set in configuration file. Using %s", AmfConfig.Configuration.WebuiUri) 43 | } 44 | if AmfConfig.Configuration.KafkaInfo.EnableKafka == nil { 45 | enableKafka := true 46 | AmfConfig.Configuration.KafkaInfo.EnableKafka = &enableKafka 47 | } 48 | if AmfConfig.Configuration.Telemetry != nil && AmfConfig.Configuration.Telemetry.Enabled { 49 | if AmfConfig.Configuration.Telemetry.Ratio == nil { 50 | defaultRatio := 1.0 51 | AmfConfig.Configuration.Telemetry.Ratio = &defaultRatio 52 | } 53 | 54 | if AmfConfig.Configuration.Telemetry.OtlpEndpoint == "" { 55 | return fmt.Errorf("OTLP endpoint is not set in the configuration") 56 | } 57 | } 58 | if err = validateWebuiUri(AmfConfig.Configuration.WebuiUri); err != nil { 59 | return err 60 | } 61 | err = validateAmfId(AmfConfig.Configuration.AmfId) 62 | return err 63 | } 64 | 65 | func CheckConfigVersion() error { 66 | currentVersion := AmfConfig.GetVersion() 67 | 68 | if currentVersion != AMF_EXPECTED_CONFIG_VERSION { 69 | return fmt.Errorf("config version is [%s], but expected is [%s]", 70 | currentVersion, AMF_EXPECTED_CONFIG_VERSION) 71 | } 72 | 73 | logger.CfgLog.Infof("config version [%s]", currentVersion) 74 | 75 | return nil 76 | } 77 | 78 | func validateWebuiUri(uri string) error { 79 | parsedUrl, err := url.ParseRequestURI(uri) 80 | if err != nil { 81 | return err 82 | } 83 | if parsedUrl.Scheme != "http" && parsedUrl.Scheme != "https" { 84 | return fmt.Errorf("unsupported scheme for webuiUri: %s", parsedUrl.Scheme) 85 | } 86 | if parsedUrl.Hostname() == "" { 87 | return fmt.Errorf("missing host in webuiUri") 88 | } 89 | return nil 90 | } 91 | 92 | func validateAmfId(amfId string) error { 93 | amfIdMatch, err := regexp.MatchString(AMFID_PATTERN, amfId) 94 | if err != nil { 95 | return fmt.Errorf("invalid amfId: %s. It should match the following pattern: `%s`", amfId, AMFID_PATTERN) 96 | } 97 | if !amfIdMatch { 98 | return fmt.Errorf("invalid amfId: %s. It should match the following pattern: `%s`", amfId, AMFID_PATTERN) 99 | } 100 | return nil 101 | } 102 | -------------------------------------------------------------------------------- /eventexposure/api_individual_subscription_document.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | /* 7 | * Namf_EventExposure 8 | * 9 | * AMF Event Exposure Service 10 | * 11 | * API version: 1.0.0 12 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 13 | */ 14 | 15 | package eventexposure 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/gin-gonic/gin" 21 | "github.com/omec-project/amf/logger" 22 | "github.com/omec-project/amf/producer" 23 | "github.com/omec-project/openapi" 24 | "github.com/omec-project/openapi/models" 25 | "github.com/omec-project/util/httpwrapper" 26 | ) 27 | 28 | // DeleteSubscription - Namf_EventExposure Unsubscribe service Operation 29 | func HTTPDeleteSubscription(c *gin.Context) { 30 | req := httpwrapper.NewRequest(c.Request, nil) 31 | req.Params["subscriptionId"] = c.Param("subscriptionId") 32 | 33 | rsp := producer.HandleDeleteAMFEventSubscription(req) 34 | 35 | if rsp.Status == http.StatusOK { 36 | c.JSON(http.StatusOK, gin.H{}) 37 | } else { 38 | responseBody, err := openapi.Serialize(rsp.Body, "application/json") 39 | if err != nil { 40 | logger.EeLog.Errorln(err) 41 | problemDetails := models.ProblemDetails{ 42 | Status: http.StatusInternalServerError, 43 | Cause: "SYSTEM_FAILURE", 44 | Detail: err.Error(), 45 | } 46 | c.JSON(http.StatusInternalServerError, problemDetails) 47 | } else { 48 | c.Data(rsp.Status, "application/json", responseBody) 49 | } 50 | } 51 | } 52 | 53 | // ModifySubscription - Namf_EventExposure Subscribe Modify service Operation 54 | func HTTPModifySubscription(c *gin.Context) { 55 | var modifySubscriptionRequest models.ModifySubscriptionRequest 56 | 57 | requestBody, err := c.GetRawData() 58 | if err != nil { 59 | logger.EeLog.Errorf("Get Request Body error: %+v", err) 60 | problemDetail := models.ProblemDetails{ 61 | Title: "System failure", 62 | Status: http.StatusInternalServerError, 63 | Detail: err.Error(), 64 | Cause: "SYSTEM_FAILURE", 65 | } 66 | c.JSON(http.StatusInternalServerError, problemDetail) 67 | return 68 | } 69 | 70 | err = openapi.Deserialize(&modifySubscriptionRequest, requestBody, "application/json") 71 | if err != nil { 72 | problemDetail := "[Request Body] " + err.Error() 73 | rsp := models.ProblemDetails{ 74 | Title: "Malformed request syntax", 75 | Status: http.StatusBadRequest, 76 | Detail: problemDetail, 77 | } 78 | logger.EeLog.Errorln(problemDetail) 79 | c.JSON(http.StatusBadRequest, rsp) 80 | return 81 | } 82 | 83 | req := httpwrapper.NewRequest(c.Request, modifySubscriptionRequest) 84 | req.Params["subscriptionId"] = c.Param("subscriptionId") 85 | 86 | rsp := producer.HandleModifyAMFEventSubscription(req) 87 | 88 | responseBody, err := openapi.Serialize(rsp.Body, "application/json") 89 | if err != nil { 90 | logger.EeLog.Errorln(err) 91 | problemDetails := models.ProblemDetails{ 92 | Status: http.StatusInternalServerError, 93 | Cause: "SYSTEM_FAILURE", 94 | Detail: err.Error(), 95 | } 96 | c.JSON(http.StatusInternalServerError, problemDetails) 97 | } else { 98 | c.Data(rsp.Status, "application/json", responseBody) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /metrics/kafka.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022-present Intel Corporation 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package metrics 6 | 7 | import ( 8 | "context" 9 | "encoding/json" 10 | "fmt" 11 | "time" 12 | 13 | "github.com/omec-project/amf/factory" 14 | "github.com/omec-project/amf/logger" 15 | mi "github.com/omec-project/util/metricinfo" 16 | "github.com/segmentio/kafka-go" 17 | ) 18 | 19 | type Writer struct { 20 | kafkaWriter *kafka.Writer 21 | } 22 | 23 | var StatWriter Writer 24 | 25 | func InitialiseKafkaStream(config *factory.Configuration) error { 26 | if !*config.KafkaInfo.EnableKafka { 27 | logger.KafkaLog.Warnln("Kafka is disabled") 28 | return nil 29 | } 30 | 31 | brokerUrl := "kafka:9092" 32 | topicName := "sdcore-data-source-amf" 33 | 34 | if config.KafkaInfo.BrokerUri != "" && config.KafkaInfo.BrokerPort != 0 { 35 | brokerUrl = fmt.Sprintf("%s:%d", config.KafkaInfo.BrokerUri, config.KafkaInfo.BrokerPort) 36 | } 37 | 38 | logger.KafkaLog.Debugf("initialise kafka broker url: %s", brokerUrl) 39 | 40 | if config.KafkaInfo.Topic != "" { 41 | topicName = config.KafkaInfo.Topic 42 | } 43 | 44 | logger.KafkaLog.Debugf("initialise kafka Topic: %s", topicName) 45 | 46 | producer := kafka.Writer{ 47 | Addr: kafka.TCP(brokerUrl), 48 | Topic: topicName, 49 | AllowAutoTopicCreation: true, 50 | Balancer: &kafka.LeastBytes{}, 51 | BatchTimeout: 10 * time.Millisecond, 52 | } 53 | 54 | StatWriter = Writer{ 55 | kafkaWriter: &producer, 56 | } 57 | 58 | logger.KafkaLog.Debugf("initialising kafka stream with url[%s], topic[%s]", brokerUrl, topicName) 59 | return nil 60 | } 61 | 62 | func GetWriter() Writer { 63 | return StatWriter 64 | } 65 | 66 | func (writer Writer) SendMessage(message []byte) error { 67 | msg := kafka.Message{Value: message} 68 | if err := writer.kafkaWriter.WriteMessages(context.Background(), msg); err != nil { 69 | logger.KafkaLog.Errorf("kafka send message write error: %s", err.Error()) 70 | return err 71 | } 72 | return nil 73 | } 74 | 75 | func (writer Writer) PublishUeCtxtEvent(ctxt mi.CoreSubscriber, op mi.SubscriberOp) error { 76 | smKafkaEvt := mi.MetricEvent{ 77 | EventType: mi.CSubscriberEvt, 78 | SubscriberData: mi.CoreSubscriberData{Subscriber: ctxt, Operation: op}, 79 | } 80 | msg, err := json.Marshal(smKafkaEvt) 81 | if err != nil { 82 | logger.KafkaLog.Errorf("publishing ue context event error %s", err.Error()) 83 | return err 84 | } 85 | logger.KafkaLog.Debugf("publishing ue context event: %s", string(msg)) 86 | if err := StatWriter.SendMessage(msg); err != nil { 87 | logger.KafkaLog.Errorf("could not publish ue context event, error %s", err.Error()) 88 | } 89 | return nil 90 | } 91 | 92 | func (writer Writer) PublishNfStatusEvent(msgEvent mi.MetricEvent) error { 93 | if msg, err := json.Marshal(msgEvent); err != nil { 94 | logger.KafkaLog.Errorf("publishing nf status marshal error: %s", err.Error()) 95 | return err 96 | } else { 97 | logger.KafkaLog.Debugf("publishing nf status event: %s", string(msg)) 98 | if err := StatWriter.SendMessage(msg); err != nil { 99 | logger.KafkaLog.Errorf("publishing nf status event error: %s", err.Error()) 100 | } 101 | } 102 | return nil 103 | } 104 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | version: "2" 5 | run: 6 | concurrency: 4 7 | issues-exit-code: 1 8 | tests: true 9 | allow-parallel-runners: true 10 | output: 11 | formats: 12 | text: 13 | path: stdout 14 | print-linter-name: true 15 | print-issued-lines: true 16 | linters: 17 | enable: 18 | - asciicheck 19 | - dogsled 20 | - govet 21 | - goconst 22 | - godox 23 | - gomodguard 24 | - misspell 25 | - nakedret 26 | - noctx 27 | - predeclared 28 | - staticcheck 29 | - unconvert 30 | - whitespace 31 | settings: 32 | errcheck: 33 | check-type-assertions: false 34 | check-blank: true 35 | funlen: 36 | lines: 60 37 | statements: 40 38 | gocognit: 39 | min-complexity: 10 40 | goconst: 41 | min-len: 3 42 | min-occurrences: 3 43 | gocritic: 44 | disabled-checks: 45 | - regexpMust 46 | enabled-tags: 47 | - performance 48 | disabled-tags: 49 | - experimental 50 | settings: 51 | captLocal: 52 | paramsOnly: true 53 | rangeValCopy: 54 | sizeThreshold: 32 55 | gocyclo: 56 | min-complexity: 10 57 | godox: 58 | keywords: 59 | - FIXME 60 | - BUG 61 | - XXX 62 | govet: 63 | enable-all: true 64 | disable: 65 | - fieldalignment 66 | settings: 67 | printf: 68 | funcs: 69 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof 70 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf 71 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf 72 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf 73 | lll: 74 | line-length: 120 75 | tab-width: 1 76 | nakedret: 77 | max-func-lines: 30 78 | nestif: 79 | min-complexity: 4 80 | testpackage: 81 | skip-regexp: (export|internal)_test\.go 82 | whitespace: 83 | multi-if: false 84 | multi-func: false 85 | wsl: 86 | strict-append: true 87 | allow-assign-and-call: true 88 | allow-multiline-assign: true 89 | force-case-trailing-whitespace: 0 90 | allow-trailing-comment: true 91 | allow-separated-leading-comment: false 92 | allow-cuddle-declarations: false 93 | force-err-cuddling: false 94 | exclusions: 95 | generated: lax 96 | presets: 97 | - comments 98 | - common-false-positives 99 | - legacy 100 | - std-error-handling 101 | paths: 102 | - third_party$ 103 | - builtin$ 104 | - examples$ 105 | issues: 106 | uniq-by-line: true 107 | new-from-rev: "" 108 | new: false 109 | severity: 110 | default: error 111 | rules: 112 | - linters: 113 | - mnd 114 | severity: ignore 115 | formatters: 116 | enable: 117 | - gci 118 | - gofmt 119 | - gofumpt 120 | settings: 121 | gofmt: 122 | simplify: true 123 | goimports: 124 | local-prefixes: 125 | - github.com/omec-project 126 | exclusions: 127 | generated: lax 128 | paths: 129 | - third_party$ 130 | - builtin$ 131 | - examples$ 132 | -------------------------------------------------------------------------------- /ngap/ngap_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | package ngap_test 5 | 6 | import ( 7 | "testing" 8 | "time" 9 | 10 | "github.com/omec-project/amf/context" 11 | "github.com/omec-project/amf/factory" 12 | "github.com/omec-project/amf/logger" 13 | "github.com/omec-project/amf/metrics" 14 | "github.com/omec-project/amf/ngap" 15 | ngaputil "github.com/omec-project/amf/ngap/util" 16 | "github.com/omec-project/amf/util" 17 | "github.com/omec-project/openapi/models" 18 | ) 19 | 20 | func init() { 21 | // Initializing AMF Context from config. 22 | testAmfConfig := "../util/testdata/amfcfg.yaml" 23 | if err := factory.InitConfigFactory(testAmfConfig); err != nil { 24 | logger.NgapLog.Fatalln("failed to initialize Factory Config") 25 | } 26 | if err := metrics.InitialiseKafkaStream(factory.AmfConfig.Configuration); err != nil { 27 | logger.NgapLog.Fatalln("failed to initialize Kafka Stream") 28 | } 29 | 30 | self := context.AMF_Self() 31 | util.InitAmfContext(self) 32 | self.ServedGuamiList = []models.Guami{ 33 | { 34 | PlmnId: &models.PlmnId{Mcc: "208", Mnc: "93"}, 35 | AmfId: "cafe00", 36 | }, 37 | } 38 | self.SupportTaiLists = []models.Tai{ 39 | { 40 | PlmnId: &models.PlmnId{Mcc: "208", Mnc: "93"}, 41 | Tac: "1", 42 | }, 43 | } 44 | self.PlmnSupportList = []models.PlmnSnssai{ 45 | { 46 | PlmnId: &models.PlmnId{Mcc: "208", Mnc: "93"}, 47 | SNssaiList: []models.Snssai{ 48 | { 49 | Sst: 1, Sd: "010203", 50 | }, 51 | { 52 | Sst: 1, Sd: "112233", 53 | }, 54 | }, 55 | }, 56 | } 57 | } 58 | 59 | // TestHandleNGSetupRequest validates package ngap's handling for NGSetupRequest 60 | func TestHandleNGSetupRequest(t *testing.T) { 61 | // test cases 62 | testTable := []struct { 63 | gnbName, tac string 64 | gnbId []byte 65 | bitLength uint64 66 | want, testId byte 67 | }{ 68 | // expecting SuccessfulOutcome 69 | { 70 | testId: 1, 71 | gnbName: "GNB2", 72 | tac: "\x00\x00\x01", 73 | gnbId: []byte{0x00, 0x00, 0x08}, 74 | bitLength: 22, 75 | want: ngaputil.NgapPDUSuccessfulOutcome, 76 | }, 77 | // expecting UnsuccessfulOutcome due to unsupported TA 78 | { 79 | testId: 2, 80 | gnbName: "GNB2", 81 | tac: "\x00\x00\x04", 82 | gnbId: []byte{0x00, 0x00, 0x08}, 83 | bitLength: 22, 84 | want: ngaputil.NgapPDUUnSuccessfulOutcome, 85 | }, 86 | } 87 | 88 | conn := &ngaputil.TestConn{} 89 | for _, test := range testTable { 90 | testNGSetupReq, err := ngaputil.GetNGSetupRequest(test.gnbId, test.bitLength, test.gnbName, test.tac) 91 | if err != nil { 92 | t.Log("Failed to to create NGSetupRequest") 93 | return 94 | } 95 | ngap.Dispatch(conn, testNGSetupReq) 96 | time.Sleep(2 * time.Second) 97 | // conn.data holds the NGAP response message 98 | if len(conn.Data) == 0 { 99 | t.Error("Unexpected message drop") 100 | return 101 | } 102 | 103 | // The first byte of the NGAPPDU indicates the type of NGAP Message 104 | if conn.Data[0] != test.want { 105 | t.Error("Test case", test.testId, "failed. Want:", 106 | ngaputil.MessageTypeMap[test.want], ", Got:", ngaputil.MessageTypeMap[conn.Data[0]]) 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /producer/mt.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | // Copyright 2019 free5GC.org 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // 6 | 7 | package producer 8 | 9 | import ( 10 | ctxt "context" 11 | "net/http" 12 | 13 | "github.com/omec-project/amf/context" 14 | "github.com/omec-project/amf/logger" 15 | "github.com/omec-project/openapi/models" 16 | "github.com/omec-project/util/httpwrapper" 17 | ) 18 | 19 | func MtHandler(ctx ctxt.Context, s1, s2 string, msg interface{}) (interface{}, string, interface{}, interface{}) { 20 | switch msg := msg.(type) { 21 | case string: 22 | r1, r2 := ProvideDomainSelectionInfoProcedure(s1, s2, msg) 23 | return r1, "", r2, nil 24 | } 25 | 26 | return nil, "", nil, nil 27 | } 28 | 29 | func HandleProvideDomainSelectionInfoRequest(request *httpwrapper.Request) *httpwrapper.Response { 30 | var ue *context.AmfUe 31 | var ok bool 32 | logger.MtLog.Info("Handle Provide Domain Selection Info Request") 33 | 34 | ueContextID := request.Params["ueContextId"] 35 | infoClassQuery := request.Query.Get("info-class") 36 | supportedFeaturesQuery := request.Query.Get("supported-features") 37 | 38 | amfSelf := context.AMF_Self() 39 | 40 | if ue, ok = amfSelf.AmfUeFindByUeContextID(ueContextID); !ok { 41 | problemDetails := &models.ProblemDetails{ 42 | Status: http.StatusNotFound, 43 | Cause: "CONTEXT_NOT_FOUND", 44 | } 45 | return httpwrapper.NewResponse(http.StatusForbidden, nil, problemDetails) 46 | } 47 | sbiMsg := context.SbiMsg{ 48 | UeContextId: ueContextID, 49 | ReqUri: infoClassQuery, 50 | Msg: supportedFeaturesQuery, 51 | Result: make(chan context.SbiResponseMsg, 10), 52 | } 53 | var ueContextInfo *models.UeContextInfo 54 | ue.EventChannel.UpdateSbiHandler(MtHandler) 55 | ue.EventChannel.SubmitMessage(sbiMsg) 56 | msg := <-sbiMsg.Result 57 | if msg.RespData != nil { 58 | ueContextInfo = msg.RespData.(*models.UeContextInfo) 59 | } 60 | // ueContextInfo, problemDetails := ProvideDomainSelectionInfoProcedure(ueContextID, 61 | // infoClassQuery, supportedFeaturesQuery) 62 | if msg.ProblemDetails != nil { 63 | return httpwrapper.NewResponse(int(msg.ProblemDetails.(models.ProblemDetails).Status), nil, msg.ProblemDetails.(models.ProblemDetails)) 64 | } else { 65 | return httpwrapper.NewResponse(http.StatusOK, nil, ueContextInfo) 66 | } 67 | } 68 | 69 | func ProvideDomainSelectionInfoProcedure(ueContextID string, infoClassQuery string, supportedFeaturesQuery string) ( 70 | *models.UeContextInfo, *models.ProblemDetails, 71 | ) { 72 | amfSelf := context.AMF_Self() 73 | 74 | ue, ok := amfSelf.AmfUeFindByUeContextID(ueContextID) 75 | if !ok { 76 | problemDetails := &models.ProblemDetails{ 77 | Status: http.StatusNotFound, 78 | Cause: "CONTEXT_NOT_FOUND", 79 | } 80 | return nil, problemDetails 81 | } 82 | 83 | ueContextInfo := new(models.UeContextInfo) 84 | 85 | // TODO: Error Status 307, 403 in TS29.518 Table 6.3.3.3.3.1-3 86 | anType := ue.GetAnType() 87 | if anType != "" && infoClassQuery != "" { 88 | ranUe := ue.RanUe[anType] 89 | ueContextInfo.AccessType = anType 90 | ueContextInfo.LastActTime = ranUe.LastActTime 91 | ueContextInfo.RatType = ue.RatType 92 | ueContextInfo.SupportedFeatures = ranUe.SupportedFeatures 93 | ueContextInfo.SupportVoPS = ranUe.SupportVoPS 94 | ueContextInfo.SupportVoPSn3gpp = ranUe.SupportVoPSn3gpp 95 | } 96 | 97 | return ueContextInfo, nil 98 | } 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 8 | [![Go Report Card](https://goreportcard.com/badge/github.com/omec-project/amf)](https://goreportcard.com/report/github.com/omec-project/amf) 9 | [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/omec-project/amf/badge)](https://scorecard.dev/viewer/?uri=github.com/omec-project/amf) 10 | 11 | # amf 12 | It is a control plane function in the 5G core network. AMF supports termination 13 | of NAS signalling, NAS ciphering & integrity protection, registration 14 | management, connection management, mobility management, access authentication 15 | and authorization, security context management. 16 | 17 | ## AMF flow Diagram 18 | ![AMF Flow Diagram](/docs/images/README-AMF.png) 19 | 20 | AMF takes configuration from NFConfig Service. Configuration is handled at 21 | the Network Slice level. Configuration (Network Slices) can be added, removed and 22 | deleted. AMF has a prometheus interface to export metrics. Metrics include 23 | connected gNodeB's and its status. 24 | 25 | ## Dynamic Network configuration (via webconsole) 26 | 27 | AMF polls the webconsole every 5 seconds to fetch the latest Access and Mobility (PLMN, SNssai, TACs) configuration. 28 | 29 | ### Setting Up Polling 30 | 31 | Include the `webuiUri` of the webconsole in the configuration file 32 | ``` 33 | configuration: 34 | ... 35 | webuiUri: https://webui:5001 # or http://webui:5001 36 | ... 37 | ``` 38 | The scheme (http:// or https://) must be explicitly specified. If no parameter is specified, 39 | AMF will use `http://webui:5001` by default. 40 | 41 | ### HTTPS Support 42 | 43 | If the webconsole is served over HTTPS and uses a custom or self-signed certificate, 44 | you must install the root CA certificate into the trust store of the AMF environment. 45 | 46 | Check the official guide for installing root CA certificates on Ubuntu: 47 | [Install a Root CA Certificate in the Trust Store](https://documentation.ubuntu.com/server/how-to/security/install-a-root-ca-certificate-in-the-trust-store/index.html) 48 | 49 | ## The SD-Core AMF currently supports the following functionalities: 50 | - Termination of RAN CP interface (N2) 51 | - Termination of NAS (N1), NAS ciphering and integrity protection 52 | - Registration management 53 | - Connection management 54 | - Reachability management 55 | - Mobility Management 56 | - Provide transport for SM messages between UE and SMF 57 | - Transparent proxy for routing SM messages 58 | - Access Authentication 59 | - Access Authorization 60 | - Tracing with OpenTelemetry 61 | 62 | ## Supported Procedures: 63 | - Registration/Deregistration 64 | - Registration update 65 | - UE initiated Service request 66 | - N2 Handover 67 | - Xn handover 68 | - PDU Establishment Request/Release 69 | - Paging 70 | - CN High Availibilty and Stateless session support 71 | - AMF metrics are available via metricfunc on the 5g Grafana dashboard 72 | 73 | ## Upcoming Changes in AMF 74 | 75 | Compliance of the 5G Network functions can be found at [5G Compliance](https://docs.sd-core.opennetworking.org/main/overview/3gpp-compliance-5g.html) 76 | 77 | ## Reach out to us through 78 | 79 | 1. #sdcore-dev channel in [Aether Community Slack](https://aether5g-project.slack.com) 80 | 2. Raise Github [issues](https://github.com/omec-project/amf/issues/new) 81 | -------------------------------------------------------------------------------- /producer/location_info.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | // Copyright 2019 free5GC.org 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // 6 | 7 | package producer 8 | 9 | import ( 10 | ctxt "context" 11 | "net/http" 12 | 13 | "github.com/omec-project/amf/context" 14 | "github.com/omec-project/amf/logger" 15 | "github.com/omec-project/openapi/models" 16 | "github.com/omec-project/util/httpwrapper" 17 | ) 18 | 19 | func LocationInfoHandler(ctx ctxt.Context, s1, s2 string, msg interface{}) (interface{}, string, interface{}, interface{}) { 20 | switch msg := msg.(type) { 21 | case models.RequestLocInfo: 22 | r1, r2 := ProvideLocationInfoProcedure(msg, s1) 23 | return r1, "", r2, nil 24 | } 25 | 26 | return nil, "", nil, nil 27 | } 28 | 29 | func HandleProvideLocationInfoRequest(request *httpwrapper.Request) *httpwrapper.Response { 30 | var ue *context.AmfUe 31 | var ok bool 32 | logger.ProducerLog.Info("Handle Provide Location Info Request") 33 | 34 | requestLocInfo := request.Body.(models.RequestLocInfo) 35 | ueContextID := request.Params["ueContextId"] 36 | 37 | amfSelf := context.AMF_Self() 38 | if ue, ok = amfSelf.AmfUeFindByUeContextID(ueContextID); !ok { 39 | problemDetails := &models.ProblemDetails{ 40 | Status: http.StatusNotFound, 41 | Cause: "CONTEXT_NOT_FOUND", 42 | } 43 | return httpwrapper.NewResponse(http.StatusForbidden, nil, problemDetails) 44 | } 45 | 46 | sbiMsg := context.SbiMsg{ 47 | UeContextId: ueContextID, 48 | ReqUri: "", 49 | Msg: requestLocInfo, 50 | Result: make(chan context.SbiResponseMsg, 10), 51 | } 52 | var provideLocInfo *models.ProvideLocInfo 53 | ue.EventChannel.UpdateSbiHandler(LocationInfoHandler) 54 | ue.EventChannel.SubmitMessage(sbiMsg) 55 | msg := <-sbiMsg.Result 56 | if msg.RespData != nil { 57 | provideLocInfo = msg.RespData.(*models.ProvideLocInfo) 58 | } 59 | // provideLocInfo, problemDetails := ProvideLocationInfoProcedure(requestLocInfo, ueContextID) 60 | if msg.ProblemDetails != nil { 61 | return httpwrapper.NewResponse(int(msg.ProblemDetails.(*models.ProblemDetails).Status), nil, msg.ProblemDetails.(*models.ProblemDetails)) 62 | } else { 63 | return httpwrapper.NewResponse(http.StatusOK, nil, provideLocInfo) 64 | } 65 | } 66 | 67 | func ProvideLocationInfoProcedure(requestLocInfo models.RequestLocInfo, ueContextID string) ( 68 | *models.ProvideLocInfo, *models.ProblemDetails, 69 | ) { 70 | amfSelf := context.AMF_Self() 71 | 72 | ue, ok := amfSelf.AmfUeFindByUeContextID(ueContextID) 73 | if !ok { 74 | problemDetails := &models.ProblemDetails{ 75 | Status: http.StatusNotFound, 76 | Cause: "CONTEXT_NOT_FOUND", 77 | } 78 | return nil, problemDetails 79 | } 80 | 81 | anType := ue.GetAnType() 82 | if anType == "" { 83 | problemDetails := &models.ProblemDetails{ 84 | Status: http.StatusNotFound, 85 | Cause: "CONTEXT_NOT_FOUND", 86 | } 87 | return nil, problemDetails 88 | } 89 | 90 | provideLocInfo := new(models.ProvideLocInfo) 91 | 92 | ranUe := ue.RanUe[anType] 93 | if requestLocInfo.Req5gsLoc || requestLocInfo.ReqCurrentLoc { 94 | provideLocInfo.CurrentLoc = true 95 | provideLocInfo.Location = &ue.Location 96 | } 97 | 98 | if requestLocInfo.ReqRatType { 99 | provideLocInfo.RatType = ue.RatType 100 | } 101 | 102 | if requestLocInfo.ReqTimeZone { 103 | provideLocInfo.Timezone = ue.TimeZone 104 | } 105 | 106 | if requestLocInfo.SupportedFeatures != "" { 107 | provideLocInfo.SupportedFeatures = ranUe.SupportedFeatures 108 | } 109 | return provideLocInfo, nil 110 | } 111 | -------------------------------------------------------------------------------- /communication/api_n1_n2_message_collection_document.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | /* 7 | * Namf_Communication 8 | * 9 | * AMF Communication Service 10 | * 11 | * API version: 1.0.0 12 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 13 | */ 14 | 15 | package communication 16 | 17 | import ( 18 | "fmt" 19 | "net/http" 20 | "strings" 21 | 22 | "github.com/gin-gonic/gin" 23 | "github.com/omec-project/amf/logger" 24 | "github.com/omec-project/amf/producer" 25 | "github.com/omec-project/openapi" 26 | "github.com/omec-project/openapi/models" 27 | "github.com/omec-project/util/httpwrapper" 28 | ) 29 | 30 | // N1N2MessageTransfer - Namf_Communication N1N2 Message Transfer (UE Specific) service Operation 31 | func HTTPN1N2MessageTransfer(c *gin.Context) { 32 | var n1n2MessageTransferRequest models.N1N2MessageTransferRequest 33 | n1n2MessageTransferRequest.JsonData = new(models.N1N2MessageTransferReqData) 34 | 35 | requestBody, err := c.GetRawData() 36 | if err != nil { 37 | problemDetail := models.ProblemDetails{ 38 | Title: "System failure", 39 | Status: http.StatusInternalServerError, 40 | Detail: err.Error(), 41 | Cause: "SYSTEM_FAILURE", 42 | } 43 | logger.CommLog.Errorf("Get Request Body error: %+v", err) 44 | c.JSON(http.StatusInternalServerError, problemDetail) 45 | return 46 | } 47 | 48 | contentType := c.GetHeader("Content-Type") 49 | s := strings.Split(contentType, ";") 50 | switch s[0] { 51 | case "application/json": 52 | err = fmt.Errorf("N1 and N2 datas are both Empty in N1N2MessgeTransfer") 53 | case "multipart/related": 54 | err = openapi.Deserialize(&n1n2MessageTransferRequest, requestBody, contentType) 55 | default: 56 | err = fmt.Errorf("wrong content type") 57 | } 58 | 59 | if err != nil { 60 | problemDetail := "[Request Body] " + err.Error() 61 | rsp := models.ProblemDetails{ 62 | Title: "Malformed request syntax", 63 | Status: http.StatusBadRequest, 64 | Detail: problemDetail, 65 | } 66 | logger.CommLog.Errorln(problemDetail) 67 | c.JSON(http.StatusBadRequest, rsp) 68 | return 69 | } 70 | 71 | req := httpwrapper.NewRequest(c.Request, n1n2MessageTransferRequest) 72 | req.Params["ueContextId"] = c.Params.ByName("ueContextId") 73 | req.Params["reqUri"] = c.Request.RequestURI 74 | 75 | rsp := producer.HandleN1N2MessageTransferRequest(req) 76 | 77 | for key, val := range rsp.Header { 78 | c.Header(key, val[0]) 79 | } 80 | responseBody, err := openapi.Serialize(rsp.Body, "application/json") 81 | if err != nil { 82 | logger.CommLog.Errorln(err) 83 | problemDetails := models.ProblemDetails{ 84 | Status: http.StatusInternalServerError, 85 | Cause: "SYSTEM_FAILURE", 86 | Detail: err.Error(), 87 | } 88 | c.JSON(http.StatusInternalServerError, problemDetails) 89 | } else { 90 | c.Data(rsp.Status, "application/json", responseBody) 91 | } 92 | } 93 | 94 | func HTTPN1N2MessageTransferStatus(c *gin.Context) { 95 | req := httpwrapper.NewRequest(c.Request, nil) 96 | req.Params["ueContextId"] = c.Params.ByName("ueContextId") 97 | req.Params["reqUri"] = c.Request.RequestURI 98 | 99 | rsp := producer.HandleN1N2MessageTransferStatusRequest(req) 100 | 101 | responseBody, err := openapi.Serialize(rsp.Body, "application/json") 102 | if err != nil { 103 | logger.CommLog.Errorln(err) 104 | problemDetails := models.ProblemDetails{ 105 | Status: http.StatusInternalServerError, 106 | Cause: "SYSTEM_FAILURE", 107 | Detail: err.Error(), 108 | } 109 | c.JSON(http.StatusInternalServerError, problemDetails) 110 | } else { 111 | c.Data(rsp.Status, "application/json", responseBody) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /consumer/ue_context_management.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | // Copyright 2019 free5GC.org 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // 6 | 7 | package consumer 8 | 9 | import ( 10 | "context" 11 | "time" 12 | 13 | amf_context "github.com/omec-project/amf/context" 14 | "github.com/omec-project/openapi" 15 | "github.com/omec-project/openapi/Nudm_UEContextManagement" 16 | "github.com/omec-project/openapi/models" 17 | "go.opentelemetry.io/otel/attribute" 18 | ) 19 | 20 | func UeCmRegistration(ctx context.Context, ue *amf_context.AmfUe, accessType models.AccessType, initialRegistrationInd bool) ( 21 | *models.ProblemDetails, error, 22 | ) { 23 | configuration := Nudm_UEContextManagement.NewConfiguration() 24 | configuration.SetBasePath(ue.NudmUECMUri) 25 | client := Nudm_UEContextManagement.NewAPIClient(configuration) 26 | 27 | amfSelf := amf_context.AMF_Self() 28 | 29 | switch accessType { 30 | case models.AccessType__3_GPP_ACCESS: 31 | registrationData := models.Amf3GppAccessRegistration{ 32 | AmfInstanceId: amfSelf.NfId, 33 | InitialRegistrationInd: initialRegistrationInd, 34 | Guami: &amfSelf.ServedGuamiList[0], 35 | RatType: ue.RatType, 36 | ImsVoPs: models.ImsVoPs_HOMOGENEOUS_NON_SUPPORT, 37 | } 38 | gppAccessCtx, cancel := context.WithTimeout(ctx, 30*time.Second) 39 | defer cancel() 40 | 41 | gppAccessCtx, span := tracer.Start(gppAccessCtx, "HTTP PUT udm/{ueId}/registrations/amf-3gpp-access") 42 | defer span.End() 43 | 44 | span.SetAttributes( 45 | attribute.String("http.method", "PUT"), 46 | attribute.String("nf.target", "udm"), 47 | attribute.String("net.peer.name", ue.NudmUECMUri), 48 | attribute.String("udm.supi", ue.Supi), 49 | attribute.String("plmn.id", ue.PlmnId.Mcc+ue.PlmnId.Mnc), 50 | ) 51 | 52 | _, httpResp, localErr := client.AMFRegistrationFor3GPPAccessApi.Registration(gppAccessCtx, ue.Supi, registrationData) 53 | if localErr == nil { 54 | return nil, nil 55 | } else if httpResp != nil { 56 | if httpResp.Status != localErr.Error() { 57 | return nil, localErr 58 | } 59 | problem := localErr.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) 60 | return &problem, nil 61 | } else { 62 | return nil, openapi.ReportError("server no response") 63 | } 64 | 65 | case models.AccessType_NON_3_GPP_ACCESS: 66 | registrationData := models.AmfNon3GppAccessRegistration{ 67 | AmfInstanceId: amfSelf.NfId, 68 | Guami: &amfSelf.ServedGuamiList[0], 69 | RatType: ue.RatType, 70 | } 71 | 72 | non3gppAccessCtx, cancel := context.WithTimeout(ctx, 30*time.Second) 73 | defer cancel() 74 | 75 | non3gppAccessCtx, span := tracer.Start(non3gppAccessCtx, "HTTP PUT udm/{ueId}/registrations/amf-non-3gpp-access") 76 | defer span.End() 77 | 78 | span.SetAttributes( 79 | attribute.String("http.method", "PUT"), 80 | attribute.String("nf.target", "udm"), 81 | attribute.String("net.peer.name", ue.NudmUECMUri), 82 | attribute.String("udm.supi", ue.Supi), 83 | attribute.String("plmn.id", ue.PlmnId.Mcc+ue.PlmnId.Mnc), 84 | ) 85 | 86 | _, httpResp, localErr := client.AMFRegistrationForNon3GPPAccessApi.Register(non3gppAccessCtx, ue.Supi, registrationData) 87 | if localErr == nil { 88 | return nil, nil 89 | } else if httpResp != nil { 90 | if httpResp.Status != localErr.Error() { 91 | return nil, localErr 92 | } 93 | problem := localErr.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) 94 | return &problem, nil 95 | } else { 96 | return nil, openapi.ReportError("server no response") 97 | } 98 | } 99 | 100 | return nil, nil 101 | } 102 | -------------------------------------------------------------------------------- /httpcallback/api_am_policy_control_update_notify.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | package httpcallback 7 | 8 | import ( 9 | "net/http" 10 | 11 | "github.com/gin-gonic/gin" 12 | "github.com/omec-project/amf/logger" 13 | "github.com/omec-project/amf/producer" 14 | "github.com/omec-project/openapi" 15 | "github.com/omec-project/openapi/models" 16 | "github.com/omec-project/util/httpwrapper" 17 | ) 18 | 19 | func HTTPAmPolicyControlUpdateNotifyUpdate(c *gin.Context) { 20 | var policyUpdate models.PolicyUpdate 21 | 22 | requestBody, err := c.GetRawData() 23 | if err != nil { 24 | logger.CallbackLog.Errorf("Get Request Body error: %+v", err) 25 | problemDetail := models.ProblemDetails{ 26 | Title: "System failure", 27 | Status: http.StatusInternalServerError, 28 | Detail: err.Error(), 29 | Cause: "SYSTEM_FAILURE", 30 | } 31 | c.JSON(http.StatusInternalServerError, problemDetail) 32 | return 33 | } 34 | 35 | err = openapi.Deserialize(&policyUpdate, requestBody, "application/json") 36 | if err != nil { 37 | problemDetail := "[Request Body] " + err.Error() 38 | rsp := models.ProblemDetails{ 39 | Title: "Malformed request syntax", 40 | Status: http.StatusBadRequest, 41 | Detail: problemDetail, 42 | } 43 | logger.CallbackLog.Errorln(problemDetail) 44 | c.JSON(http.StatusBadRequest, rsp) 45 | return 46 | } 47 | 48 | req := httpwrapper.NewRequest(c.Request, policyUpdate) 49 | req.Params["polAssoId"] = c.Params.ByName("polAssoId") 50 | 51 | rsp := producer.HandleAmPolicyControlUpdateNotifyUpdate(req) 52 | 53 | responseBody, err := openapi.Serialize(rsp.Body, "application/json") 54 | if err != nil { 55 | logger.CallbackLog.Errorln(err) 56 | problemDetails := models.ProblemDetails{ 57 | Status: http.StatusInternalServerError, 58 | Cause: "SYSTEM_FAILURE", 59 | Detail: err.Error(), 60 | } 61 | c.JSON(http.StatusInternalServerError, problemDetails) 62 | } else { 63 | c.Data(rsp.Status, "application/json", responseBody) 64 | } 65 | } 66 | 67 | func HTTPAmPolicyControlUpdateNotifyTerminate(c *gin.Context) { 68 | var terminationNotification models.TerminationNotification 69 | 70 | requestBody, err := c.GetRawData() 71 | if err != nil { 72 | logger.CallbackLog.Errorf("Get Request Body error: %+v", err) 73 | problemDetail := models.ProblemDetails{ 74 | Title: "System failure", 75 | Status: http.StatusInternalServerError, 76 | Detail: err.Error(), 77 | Cause: "SYSTEM_FAILURE", 78 | } 79 | c.JSON(http.StatusInternalServerError, problemDetail) 80 | return 81 | } 82 | 83 | err = openapi.Deserialize(&terminationNotification, requestBody, "application/json") 84 | if err != nil { 85 | problemDetail := "[Request Body] " + err.Error() 86 | rsp := models.ProblemDetails{ 87 | Title: "Malformed request syntax", 88 | Status: http.StatusBadRequest, 89 | Detail: problemDetail, 90 | } 91 | logger.CallbackLog.Errorln(problemDetail) 92 | c.JSON(http.StatusBadRequest, rsp) 93 | return 94 | } 95 | 96 | req := httpwrapper.NewRequest(c.Request, terminationNotification) 97 | req.Params["polAssoId"] = c.Params.ByName("polAssoId") 98 | 99 | rsp := producer.HandleAmPolicyControlUpdateNotifyTerminate(req) 100 | 101 | responseBody, err := openapi.Serialize(rsp.Body, "application/json") 102 | if err != nil { 103 | logger.CallbackLog.Errorln(err) 104 | problemDetails := models.ProblemDetails{ 105 | Status: http.StatusInternalServerError, 106 | Cause: "SYSTEM_FAILURE", 107 | Detail: err.Error(), 108 | } 109 | c.JSON(http.StatusInternalServerError, problemDetails) 110 | } else { 111 | c.Data(rsp.Status, "application/json", responseBody) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /nas/handler.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | // Copyright 2019 free5GC.org 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // 6 | 7 | package nas 8 | 9 | import ( 10 | ctxt "context" 11 | "os" 12 | 13 | "github.com/omec-project/amf/context" 14 | "github.com/omec-project/amf/logger" 15 | "github.com/omec-project/amf/nas/nas_security" 16 | "github.com/omec-project/amf/protos/sdcoreAmfServer" 17 | "github.com/omec-project/openapi/models" 18 | ) 19 | 20 | func HandleNAS(ctx ctxt.Context, ue *context.RanUe, procedureCode int64, nasPdu []byte) { 21 | amfSelf := context.AMF_Self() 22 | 23 | if ue == nil { 24 | logger.NasLog.Errorln("RanUe is nil") 25 | return 26 | } 27 | 28 | if nasPdu == nil { 29 | ue.Log.Errorln("nasPdu is nil") 30 | return 31 | } 32 | 33 | if ue.AmfUe == nil { 34 | ue.AmfUe = nas_security.FetchUeContextWithMobileIdentity(nasPdu) 35 | if ue.AmfUe == nil { 36 | ue.AmfUe = amfSelf.NewAmfUe("") 37 | } else { 38 | if amfSelf.EnableSctpLb && amfSelf.EnableDbStore { 39 | /* checking the guti-ue belongs to this amf instance */ 40 | id, err := amfSelf.Drsm.FindOwnerInt32ID(ue.AmfUe.Tmsi) 41 | if err != nil { 42 | logger.NasLog.Errorf("error checking guti-ue: %v", err) 43 | } 44 | if id != nil && id.PodName != os.Getenv("HOSTNAME") { 45 | rsp := &sdcoreAmfServer.AmfMessage{} 46 | rsp.VerboseMsg = "Redirecting Msg From AMF Pod !" 47 | rsp.Msgtype = sdcoreAmfServer.MsgType_REDIRECT_MSG 48 | rsp.AmfId = os.Getenv("HOSTNAME") 49 | /* TODO for this release setting pod ip to simplify logic in sctplb */ 50 | rsp.RedirectId = id.PodIp 51 | rsp.GnbId = ue.Ran.GnbId 52 | rsp.Msg = ue.SctplbMsg 53 | if ue.AmfUe != nil { 54 | ue.AmfUe.Remove() 55 | } else { 56 | if err := ue.Remove(); err != nil { 57 | logger.NasLog.Errorf("error removing ue: %v", err) 58 | } 59 | } 60 | ue.Ran.Amf2RanMsgChan <- rsp 61 | return 62 | } 63 | } 64 | } 65 | 66 | ue.AmfUe.Mutex.Lock() 67 | defer ue.AmfUe.Mutex.Unlock() 68 | 69 | ue.Log.Infoln("Antype from new RanUe:", ue.Ran.AnType) 70 | // AnType is set in SetRanId function. This is called 71 | // when we handle NGSetup. In case of sctplb enabled, 72 | // we dont call this function when AMF restarts. So we 73 | // need to set the AnType from stored Information. 74 | if amfSelf.EnableSctpLb { 75 | ue.Ran.AnType = models.AccessType__3_GPP_ACCESS 76 | } 77 | ue.AmfUe.AttachRanUe(ue) 78 | 79 | if ue.AmfUe.EventChannel == nil { 80 | ue.AmfUe.EventChannel = ue.AmfUe.NewEventChannel() 81 | ue.AmfUe.EventChannel.UpdateNasHandler(DispatchMsg) 82 | go ue.AmfUe.EventChannel.Start(ctx) 83 | } 84 | ue.AmfUe.EventChannel.UpdateNasHandler(DispatchMsg) 85 | 86 | nasMsg := context.NasMsg{ 87 | Context: ctx, 88 | AnType: ue.Ran.AnType, 89 | NasMsg: nasPdu, 90 | ProcedureCode: procedureCode, 91 | } 92 | ue.AmfUe.EventChannel.SubmitMessage(nasMsg) 93 | 94 | return 95 | } 96 | if amfSelf.EnableSctpLb { 97 | ue.Ran.AnType = models.AccessType__3_GPP_ACCESS 98 | } 99 | 100 | msg, err := nas_security.Decode(ue.AmfUe, ue.Ran.AnType, nasPdu) 101 | if err != nil { 102 | ue.AmfUe.NASLog.Errorln(err) 103 | return 104 | } 105 | if err := Dispatch(ctx, ue.AmfUe, ue.Ran.AnType, procedureCode, msg); err != nil { 106 | ue.AmfUe.NASLog.Errorf("handle NAS Error: %v", err) 107 | } 108 | } 109 | 110 | func DispatchMsg(amfUe *context.AmfUe, transInfo context.NasMsg) { 111 | amfUe.NASLog.Infoln("handle Nas Message") 112 | msg, err := nas_security.Decode(amfUe, transInfo.AnType, transInfo.NasMsg) 113 | if err != nil { 114 | amfUe.NASLog.Errorln(err) 115 | return 116 | } 117 | 118 | if err := Dispatch(transInfo.Context, amfUe, transInfo.AnType, transInfo.ProcedureCode, msg); err != nil { 119 | amfUe.NASLog.Errorf("handle NAS Error: %v", err) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | # Copyright 2019 free5GC.org 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | # 6 | # 7 | 8 | PROJECT_NAME := sdcore 9 | DOCKER_VERSION ?= $(shell cat ./VERSION) 10 | 11 | ## Docker related 12 | DOCKER_REGISTRY ?= 13 | DOCKER_REPOSITORY ?= 14 | DOCKER_TAG ?= ${DOCKER_VERSION} 15 | DOCKER_IMAGENAME := ${DOCKER_REGISTRY}${DOCKER_REPOSITORY}${PROJECT_NAME}:${DOCKER_TAG} 16 | DOCKER_BUILDKIT ?= 1 17 | DOCKER_BUILD_ARGS ?= 18 | 19 | ## Docker labels. Only set ref and commit date if committed 20 | DOCKER_LABEL_VCS_URL ?= $(shell git remote get-url $(shell git remote)) 21 | DOCKER_LABEL_VCS_REF ?= $(shell git diff-index --quiet HEAD -- && git rev-parse HEAD || echo "unknown") 22 | DOCKER_LABEL_COMMIT_DATE ?= $(shell git diff-index --quiet HEAD -- && git show -s --format=%cd --date=iso-strict HEAD || echo "unknown" ) 23 | DOCKER_LABEL_BUILD_DATE ?= $(shell date -u "+%Y-%m-%dT%H:%M:%SZ") 24 | 25 | DOCKER_TARGETS ?= amf 26 | 27 | GO_BIN_PATH = bin 28 | GO_SRC_PATH = ./ 29 | C_BUILD_PATH = build 30 | ROOT_PATH = $(shell pwd) 31 | 32 | NF = $(GO_NF) 33 | GO_NF = amf 34 | 35 | NF_GO_FILES = $(shell find $(GO_SRC_PATH)/$(%) -name "*.go" ! -name "*_test.go") 36 | NF_GO_FILES_ALL = $(shell find $(GO_SRC_PATH)/$(%) -name "*.go") 37 | 38 | VERSION = $(shell git describe --tags) 39 | BUILD_TIME = $(shell date -u +"%Y-%m-%dT%H:%M:%SZ") 40 | COMMIT_HASH = $(shell git submodule status | grep $(GO_SRC_PATH)/$(@F) | awk '{print $$(1)}' | cut -c1-8) 41 | COMMIT_TIME = $(shell cd $(GO_SRC_PATH) && git log --pretty="%ai" -1 | awk '{time=$$(1)"T"$$(2)"Z"; print time}') 42 | 43 | .PHONY: $(NF) clean docker-build docker-push 44 | 45 | .DEFAULT_GOAL: nfs 46 | 47 | nfs: $(NF) 48 | 49 | all: $(NF) 50 | 51 | $(GO_NF): % : $(GO_BIN_PATH)/% 52 | 53 | $(GO_BIN_PATH)/%: %.go $(NF_GO_FILES) 54 | # $(@F): The file-within-directory part of the file name of the target. 55 | @echo "Start building $(@F)...." 56 | cd $(GO_SRC_PATH)/ && \ 57 | CGO_ENABLED=0 go build -o $(ROOT_PATH)/$@ $(@F).go 58 | 59 | vpath %.go $(addprefix $(GO_SRC_PATH)/, $(GO_NF)) 60 | 61 | #test: $(NF_GO_FILES_ALL) 62 | # @echo "Start building $(@F)...." 63 | # cd $(GO_SRC_PATH)/ && \ 64 | # CGO_ENABLED=0 go test -o $(ROOT_PATH)/$@ 65 | 66 | clean: 67 | rm -rf $(addprefix $(GO_BIN_PATH)/, $(GO_NF)) 68 | rm -rf $(addprefix $(GO_SRC_PATH)/, $(addsuffix /$(C_BUILD_PATH), $(C_NF))) 69 | 70 | docker-build: 71 | @go mod vendor 72 | for target in $(DOCKER_TARGETS); do \ 73 | DOCKER_BUILDKIT=$(DOCKER_BUILDKIT) docker build $(DOCKER_BUILD_ARGS) \ 74 | --target $$target \ 75 | --tag ${DOCKER_REGISTRY}${DOCKER_REPOSITORY}5gc-$$target:${DOCKER_TAG} \ 76 | --build-arg org_label_schema_version="${DOCKER_VERSION}" \ 77 | --build-arg org_label_schema_vcs_url="${DOCKER_LABEL_VCS_URL}" \ 78 | --build-arg org_label_schema_vcs_ref="${DOCKER_LABEL_VCS_REF}" \ 79 | --build-arg org_label_schema_build_date="${DOCKER_LABEL_BUILD_DATE}" \ 80 | --build-arg org_opencord_vcs_commit_date="${DOCKER_LABEL_COMMIT_DATE}" \ 81 | . \ 82 | || exit 1; \ 83 | done 84 | rm -rf vendor 85 | 86 | docker-push: 87 | for target in $(DOCKER_TARGETS); do \ 88 | docker push ${DOCKER_REGISTRY}${DOCKER_REPOSITORY}5gc-$$target:${DOCKER_TAG}; \ 89 | done 90 | 91 | .coverage: 92 | rm -rf $(CURDIR)/.coverage 93 | mkdir -p $(CURDIR)/.coverage 94 | 95 | test: .coverage 96 | docker run --rm -v $(CURDIR):/amf -w /amf golang:latest \ 97 | go test \ 98 | -failfast \ 99 | -coverprofile=.coverage/coverage-unit.txt \ 100 | -covermode=atomic \ 101 | -v \ 102 | ./ ./... 103 | 104 | fmt: 105 | @go fmt ./... 106 | 107 | golint: 108 | @docker run --rm -v $(CURDIR):/app -w /app golangci/golangci-lint:latest golangci-lint run -v --config /app/.golangci.yml 109 | 110 | check-reuse: 111 | @docker run --rm -v $(CURDIR):/amf -w /amf omecproject/reuse-verify:latest reuse lint 112 | -------------------------------------------------------------------------------- /producer/oam_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Open Networking Foundation 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | package producer 7 | 8 | import ( 9 | ctxt "context" 10 | "testing" 11 | 12 | "github.com/omec-project/amf/context" 13 | "github.com/omec-project/amf/factory" 14 | "github.com/omec-project/amf/gmm" 15 | "github.com/omec-project/amf/logger" 16 | "github.com/omec-project/amf/util" 17 | "github.com/omec-project/openapi/models" 18 | "github.com/omec-project/util/fsm" 19 | ) 20 | 21 | func init() { 22 | if err := factory.InitConfigFactory("../util/testdata/amfcfg.yaml"); err != nil { 23 | logger.ProducerLog.Errorf("error in InitConfigFactory: %v", err) 24 | } 25 | 26 | self := context.AMF_Self() 27 | util.InitAmfContext(self) 28 | self.ServedGuamiList = []models.Guami{ 29 | { 30 | PlmnId: &models.PlmnId{Mcc: "208", Mnc: "93"}, 31 | AmfId: "cafe00", 32 | }, 33 | } 34 | self.SupportTaiLists = []models.Tai{ 35 | { 36 | PlmnId: &models.PlmnId{Mcc: "208", Mnc: "93"}, 37 | Tac: "1", 38 | }, 39 | } 40 | self.PlmnSupportList = []models.PlmnSnssai{ 41 | { 42 | PlmnId: &models.PlmnId{Mcc: "208", Mnc: "93"}, 43 | SNssaiList: []models.Snssai{ 44 | { 45 | Sst: 1, Sd: "010203", 46 | }, 47 | { 48 | Sst: 1, Sd: "112233", 49 | }, 50 | }, 51 | }, 52 | } 53 | 54 | gmm.Mockinit() 55 | } 56 | 57 | func TestHandleOAMPurgeUEContextRequest(t *testing.T) { 58 | tests := []struct { 59 | name string 60 | setupUE func(*context.AMFContext) *context.AmfUe 61 | expectedDeregisteredInitiatedCount uint32 62 | expectedRegisteredCount uint32 63 | description string 64 | }{ 65 | { 66 | name: "UE_Deregistered", 67 | setupUE: func(self *context.AMFContext) *context.AmfUe { 68 | // UE is created but not in registered state (default deregistered) 69 | return self.NewAmfUe("imsi-208930100007497") 70 | }, 71 | expectedDeregisteredInitiatedCount: 0, 72 | expectedRegisteredCount: 0, 73 | description: "UE in deregistered state should be purged without state transitions", 74 | }, 75 | { 76 | name: "UE_Registered", 77 | setupUE: func(self *context.AMFContext) *context.AmfUe { 78 | amfUe := self.NewAmfUe("imsi-208930100007497") 79 | // Set UE to registered state 80 | amfUe.State[models.AccessType__3_GPP_ACCESS] = fsm.NewState(context.Registered) 81 | return amfUe 82 | }, 83 | expectedDeregisteredInitiatedCount: 1, 84 | expectedRegisteredCount: 2, 85 | description: "UE in registered state should trigger deregistration before purge", 86 | }, 87 | } 88 | 89 | for _, tt := range tests { 90 | t.Run(tt.name, func(t *testing.T) { 91 | self := context.AMF_Self() 92 | var err error 93 | self.Drsm, err = util.MockDrsmInit() 94 | if err != nil { 95 | t.Fatalf("error in MockDrsmInit: %v", err) 96 | } 97 | 98 | // Reset mock counters 99 | gmm.MockDeregisteredInitiatedCallCount = 0 100 | gmm.MockRegisteredCallCount = 0 101 | 102 | amfUe := tt.setupUE(self) 103 | HandleOAMPurgeUEContextRequest(ctxt.Background(), amfUe.Supi, "", nil) 104 | if _, ok := self.AmfUeFindBySupi(amfUe.Supi); ok { 105 | t.Errorf("UE should have been purged from context but still exists") 106 | } 107 | 108 | if gmm.MockDeregisteredInitiatedCallCount != tt.expectedDeregisteredInitiatedCount { 109 | t.Errorf("MockDeregisteredInitiatedCallCount: got = %d, want = %d", 110 | gmm.MockDeregisteredInitiatedCallCount, tt.expectedDeregisteredInitiatedCount) 111 | } 112 | 113 | if gmm.MockRegisteredCallCount != tt.expectedRegisteredCount { 114 | t.Errorf("MockRegisteredCallCount: got = %d, want = %d", 115 | gmm.MockRegisteredCallCount, tt.expectedRegisteredCount) 116 | } 117 | 118 | t.Logf("Test passed: %s", tt.description) 119 | }) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /util/testdata/no_telemetry.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Intel Corporation 2 | # SPDX-FileCopyrightText: 2021 Open Networking Foundation 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | # 6 | 7 | info: 8 | version: 1.0.0 9 | description: AMF initial local configuration 10 | 11 | configuration: 12 | amfName: AMF # the name of this AMF 13 | ngapIpList: # the IP list of N2 interfaces on this AMF 14 | - 127.0.0.1 15 | sbi: # Service-based interface information 16 | scheme: http # the protocol for sbi (http or https) 17 | registerIPv4: 127.0.0.18 # IP used to register to NRF 18 | bindingIPv4: 127.0.0.18 # IP used to bind the service 19 | port: 8000 # port used to bind the service 20 | tls: # the local path of TLS key 21 | key: /support/TLS/amf.pem # AMF TLS Certificate 22 | pem: /support/TLS/amf.pem # AMF TLS Private key 23 | serviceNameList: # the SBI services provided by this AMF, refer to TS 29.518 24 | - namf-comm # Namf_Communication service 25 | - namf-evts # Namf_EventExposure service 26 | - namf-mt # Namf_MT service 27 | - namf-loc # Namf_Location service 28 | - namf-oam # OAM service 29 | supportDnnList: # the DNN (Data Network Name) list supported by this AMF 30 | - internet 31 | nrfUri: http://127.0.0.10:8000 # a valid URI of NRF 32 | security: # NAS security parameters 33 | integrityOrder: # the priority of integrity algorithms 34 | - NIA2 35 | # - NIA0 36 | cipheringOrder: # the priority of ciphering algorithms 37 | - NEA0 38 | # - NEA2 39 | networkName: # the name of this core network 40 | full: Aether 41 | short: Aether 42 | networkFeatureSupport5GS: # 5gs Network Feature Support IE, refer to TS 24.501 43 | enable: true # append this IE in Registration accept or not 44 | imsVoPS: 0 # IMS voice over PS session indicator (uinteger, range: 0~1) 45 | emc: 0 # Emergency service support indicator for 3GPP access (uinteger, range: 0~3) 46 | emf: 0 # Emergency service fallback indicator for 3GPP access (uinteger, range: 0~3) 47 | iwkN26: 0 # Interworking without N26 interface indicator (uinteger, range: 0~1) 48 | mpsi: 0 # MPS indicator (uinteger, range: 0~1) 49 | emcN3: 0 # Emergency service support indicator for Non-3GPP access (uinteger, range: 0~1) 50 | mcsi: 0 # MCS indicator (uinteger, range: 0~1) 51 | t3502Value: 720 # timer value (seconds) at UE side 52 | t3512Value: 3600 # timer value (seconds) at UE side 53 | non3gppDeregistrationTimerValue: 3240 # timer value (seconds) at UE side 54 | # retransmission timer for paging message 55 | t3513: 56 | enable: true # true or false 57 | expireTime: 6s # default is 6 seconds 58 | maxRetryTimes: 4 # the max number of retransmission 59 | # retransmission timer for NAS Deregistration Request message 60 | t3522: 61 | enable: true # true or false 62 | expireTime: 6s # default is 6 seconds 63 | maxRetryTimes: 4 # the max number of retransmission 64 | # retransmission timer for NAS Registration Accept message 65 | t3550: 66 | enable: true # true or false 67 | expireTime: 6s # default is 6 seconds 68 | maxRetryTimes: 4 # the max number of retransmission 69 | # retransmission timer for NAS Authentication Request/Security Mode Command message 70 | t3560: 71 | enable: true # true or false 72 | expireTime: 6s # default is 6 seconds 73 | maxRetryTimes: 4 # the max number of retransmission 74 | # retransmission timer for NAS Notification message 75 | t3565: 76 | enable: true # true or false 77 | expireTime: 6s # default is 6 seconds 78 | maxRetryTimes: 4 # the max number of retransmission 79 | # the kind of log output 80 | # debugLevel: how detailed to output, value: trace, debug, info, warn, error, fatal, panic 81 | # ReportCaller: enable the caller report or not, value: true or false 82 | logger: 83 | AMF: 84 | debugLevel: info 85 | NAS: 86 | debugLevel: info 87 | FSM: 88 | debugLevel: info 89 | NGAP: 90 | debugLevel: info 91 | Aper: 92 | debugLevel: info 93 | OpenApi: 94 | debugLevel: info 95 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # Copyright 2023 Canonical Ltd. 3 | # Copyright 2024-Present Intel Corporation 4 | name: CI Pipeline 5 | 6 | on: 7 | pull_request: 8 | branches: 9 | - main 10 | push: 11 | branches: 12 | - main 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | build: 19 | permissions: 20 | contents: read 21 | actions: read 22 | security-events: write 23 | id-token: write 24 | attestations: write 25 | uses: omec-project/.github/.github/workflows/build.yml@76c248f1621bfe102956c558ea8cecfe5df143bf # v0.0.3 26 | with: 27 | branch_name: ${{ github.ref }} 28 | 29 | docker-build: 30 | permissions: 31 | contents: read 32 | packages: write 33 | id-token: write 34 | attestations: write 35 | uses: omec-project/.github/.github/workflows/docker-build.yml@76c248f1621bfe102956c558ea8cecfe5df143bf # v0.0.3 36 | with: 37 | branch_name: ${{ github.ref }} 38 | 39 | static-analysis: 40 | permissions: 41 | contents: read 42 | security-events: write 43 | actions: read 44 | id-token: write 45 | attestations: write 46 | uses: omec-project/.github/.github/workflows/static-analysis.yml@76c248f1621bfe102956c558ea8cecfe5df143bf # v0.0.3 47 | with: 48 | branch_name: ${{ github.ref }} 49 | 50 | lint: 51 | permissions: 52 | contents: read 53 | checks: write 54 | id-token: write 55 | attestations: write 56 | uses: omec-project/.github/.github/workflows/lint.yml@76c248f1621bfe102956c558ea8cecfe5df143bf # v0.0.3 57 | with: 58 | branch_name: ${{ github.ref }} 59 | 60 | hadolint: 61 | permissions: 62 | contents: read 63 | security-events: write 64 | id-token: write 65 | attestations: write 66 | uses: omec-project/.github/.github/workflows/hadolint.yml@76c248f1621bfe102956c558ea8cecfe5df143bf # v0.0.3 67 | with: 68 | branch_name: ${{ github.ref }} 69 | 70 | license-check: 71 | permissions: 72 | contents: read 73 | id-token: write 74 | attestations: write 75 | uses: omec-project/.github/.github/workflows/license-check.yml@76c248f1621bfe102956c558ea8cecfe5df143bf # v0.0.3 76 | with: 77 | branch_name: ${{ github.ref }} 78 | 79 | fossa-scan: 80 | permissions: 81 | contents: read 82 | security-events: write 83 | id-token: write 84 | attestations: write 85 | uses: omec-project/.github/.github/workflows/fossa-scan.yml@76c248f1621bfe102956c558ea8cecfe5df143bf # v0.0.3 86 | with: 87 | branch_name: ${{ github.ref }} 88 | 89 | unit-tests: 90 | permissions: 91 | contents: read 92 | checks: write 93 | id-token: write 94 | attestations: write 95 | uses: omec-project/.github/.github/workflows/unit-test.yml@76c248f1621bfe102956c558ea8cecfe5df143bf # v0.0.3 96 | with: 97 | branch_name: ${{ github.ref }} 98 | 99 | analysis: 100 | if: github.repository_owner == 'omec-project' 101 | permissions: 102 | actions: read 103 | artifact-metadata: read 104 | attestations: read 105 | checks: read 106 | contents: read 107 | deployments: read 108 | discussions: read 109 | id-token: write 110 | issues: read 111 | models: read 112 | packages: read 113 | pages: read 114 | pull-requests: read 115 | repository-projects: read 116 | security-events: write 117 | statuses: read 118 | uses: omec-project/.github/.github/workflows/scorecard-analysis.yml@76c248f1621bfe102956c558ea8cecfe5df143bf # v0.0.3 119 | with: 120 | branch_name: ${{ github.ref }} 121 | 122 | e2e-tests: 123 | if: github.event_name == 'pull_request' 124 | permissions: 125 | contents: read 126 | pull-requests: write 127 | checks: write 128 | id-token: write 129 | attestations: write 130 | uses: omec-project/.github/.github/workflows/e2e-test.yml@76c248f1621bfe102956c558ea8cecfe5df143bf # v0.0.3 131 | with: 132 | branch_name: ${{ github.ref }} 133 | -------------------------------------------------------------------------------- /util/testdata/telemetry_no_endpoint.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Intel Corporation 2 | # SPDX-FileCopyrightText: 2021 Open Networking Foundation 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | # 6 | 7 | info: 8 | version: 1.0.0 9 | description: AMF initial local configuration 10 | 11 | configuration: 12 | amfName: AMF # the name of this AMF 13 | ngapIpList: # the IP list of N2 interfaces on this AMF 14 | - 127.0.0.1 15 | sbi: # Service-based interface information 16 | scheme: http # the protocol for sbi (http or https) 17 | registerIPv4: 127.0.0.18 # IP used to register to NRF 18 | bindingIPv4: 127.0.0.18 # IP used to bind the service 19 | port: 8000 # port used to bind the service 20 | tls: # the local path of TLS key 21 | key: /support/TLS/amf.pem # AMF TLS Certificate 22 | pem: /support/TLS/amf.pem # AMF TLS Private key 23 | serviceNameList: # the SBI services provided by this AMF, refer to TS 29.518 24 | - namf-comm # Namf_Communication service 25 | - namf-evts # Namf_EventExposure service 26 | - namf-mt # Namf_MT service 27 | - namf-loc # Namf_Location service 28 | - namf-oam # OAM service 29 | supportDnnList: # the DNN (Data Network Name) list supported by this AMF 30 | - internet 31 | nrfUri: http://127.0.0.10:8000 # a valid URI of NRF 32 | security: # NAS security parameters 33 | integrityOrder: # the priority of integrity algorithms 34 | - NIA2 35 | # - NIA0 36 | cipheringOrder: # the priority of ciphering algorithms 37 | - NEA0 38 | # - NEA2 39 | networkName: # the name of this core network 40 | full: Aether 41 | short: Aether 42 | networkFeatureSupport5GS: # 5gs Network Feature Support IE, refer to TS 24.501 43 | enable: true # append this IE in Registration accept or not 44 | imsVoPS: 0 # IMS voice over PS session indicator (uinteger, range: 0~1) 45 | emc: 0 # Emergency service support indicator for 3GPP access (uinteger, range: 0~3) 46 | emf: 0 # Emergency service fallback indicator for 3GPP access (uinteger, range: 0~3) 47 | iwkN26: 0 # Interworking without N26 interface indicator (uinteger, range: 0~1) 48 | mpsi: 0 # MPS indicator (uinteger, range: 0~1) 49 | emcN3: 0 # Emergency service support indicator for Non-3GPP access (uinteger, range: 0~1) 50 | mcsi: 0 # MCS indicator (uinteger, range: 0~1) 51 | t3502Value: 720 # timer value (seconds) at UE side 52 | t3512Value: 3600 # timer value (seconds) at UE side 53 | non3gppDeregistrationTimerValue: 3240 # timer value (seconds) at UE side 54 | # retransmission timer for paging message 55 | t3513: 56 | enable: true # true or false 57 | expireTime: 6s # default is 6 seconds 58 | maxRetryTimes: 4 # the max number of retransmission 59 | # retransmission timer for NAS Deregistration Request message 60 | t3522: 61 | enable: true # true or false 62 | expireTime: 6s # default is 6 seconds 63 | maxRetryTimes: 4 # the max number of retransmission 64 | # retransmission timer for NAS Registration Accept message 65 | t3550: 66 | enable: true # true or false 67 | expireTime: 6s # default is 6 seconds 68 | maxRetryTimes: 4 # the max number of retransmission 69 | # retransmission timer for NAS Authentication Request/Security Mode Command message 70 | t3560: 71 | enable: true # true or false 72 | expireTime: 6s # default is 6 seconds 73 | maxRetryTimes: 4 # the max number of retransmission 74 | # retransmission timer for NAS Notification message 75 | t3565: 76 | enable: true # true or false 77 | expireTime: 6s # default is 6 seconds 78 | maxRetryTimes: 4 # the max number of retransmission 79 | telemetry: # telemetry configuration 80 | enabled: true # Optional; defaults to false 81 | ratio: 0.4 82 | # the kind of log output 83 | # debugLevel: how detailed to output, value: trace, debug, info, warn, error, fatal, panic 84 | # ReportCaller: enable the caller report or not, value: true or false 85 | logger: 86 | AMF: 87 | debugLevel: info 88 | NAS: 89 | debugLevel: info 90 | FSM: 91 | debugLevel: info 92 | NGAP: 93 | debugLevel: info 94 | Aper: 95 | debugLevel: info 96 | OpenApi: 97 | debugLevel: info 98 | -------------------------------------------------------------------------------- /util/testdata/telemetry_no_ratio.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Intel Corporation 2 | # SPDX-FileCopyrightText: 2021 Open Networking Foundation 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | # 6 | 7 | info: 8 | version: 1.0.0 9 | description: AMF initial local configuration 10 | 11 | configuration: 12 | amfName: AMF # the name of this AMF 13 | ngapIpList: # the IP list of N2 interfaces on this AMF 14 | - 127.0.0.1 15 | sbi: # Service-based interface information 16 | scheme: http # the protocol for sbi (http or https) 17 | registerIPv4: 127.0.0.18 # IP used to register to NRF 18 | bindingIPv4: 127.0.0.18 # IP used to bind the service 19 | port: 8000 # port used to bind the service 20 | tls: # the local path of TLS key 21 | key: /support/TLS/amf.pem # AMF TLS Certificate 22 | pem: /support/TLS/amf.pem # AMF TLS Private key 23 | serviceNameList: # the SBI services provided by this AMF, refer to TS 29.518 24 | - namf-comm # Namf_Communication service 25 | - namf-evts # Namf_EventExposure service 26 | - namf-mt # Namf_MT service 27 | - namf-loc # Namf_Location service 28 | - namf-oam # OAM service 29 | supportDnnList: # the DNN (Data Network Name) list supported by this AMF 30 | - internet 31 | nrfUri: http://127.0.0.10:8000 # a valid URI of NRF 32 | security: # NAS security parameters 33 | integrityOrder: # the priority of integrity algorithms 34 | - NIA2 35 | # - NIA0 36 | cipheringOrder: # the priority of ciphering algorithms 37 | - NEA0 38 | # - NEA2 39 | networkName: # the name of this core network 40 | full: Aether 41 | short: Aether 42 | networkFeatureSupport5GS: # 5gs Network Feature Support IE, refer to TS 24.501 43 | enable: true # append this IE in Registration accept or not 44 | imsVoPS: 0 # IMS voice over PS session indicator (uinteger, range: 0~1) 45 | emc: 0 # Emergency service support indicator for 3GPP access (uinteger, range: 0~3) 46 | emf: 0 # Emergency service fallback indicator for 3GPP access (uinteger, range: 0~3) 47 | iwkN26: 0 # Interworking without N26 interface indicator (uinteger, range: 0~1) 48 | mpsi: 0 # MPS indicator (uinteger, range: 0~1) 49 | emcN3: 0 # Emergency service support indicator for Non-3GPP access (uinteger, range: 0~1) 50 | mcsi: 0 # MCS indicator (uinteger, range: 0~1) 51 | t3502Value: 720 # timer value (seconds) at UE side 52 | t3512Value: 3600 # timer value (seconds) at UE side 53 | non3gppDeregistrationTimerValue: 3240 # timer value (seconds) at UE side 54 | # retransmission timer for paging message 55 | t3513: 56 | enable: true # true or false 57 | expireTime: 6s # default is 6 seconds 58 | maxRetryTimes: 4 # the max number of retransmission 59 | # retransmission timer for NAS Deregistration Request message 60 | t3522: 61 | enable: true # true or false 62 | expireTime: 6s # default is 6 seconds 63 | maxRetryTimes: 4 # the max number of retransmission 64 | # retransmission timer for NAS Registration Accept message 65 | t3550: 66 | enable: true # true or false 67 | expireTime: 6s # default is 6 seconds 68 | maxRetryTimes: 4 # the max number of retransmission 69 | # retransmission timer for NAS Authentication Request/Security Mode Command message 70 | t3560: 71 | enable: true # true or false 72 | expireTime: 6s # default is 6 seconds 73 | maxRetryTimes: 4 # the max number of retransmission 74 | # retransmission timer for NAS Notification message 75 | t3565: 76 | enable: true # true or false 77 | expireTime: 6s # default is 6 seconds 78 | maxRetryTimes: 4 # the max number of retransmission 79 | telemetry: # telemetry configuration 80 | enabled: true # Optional; defaults to false 81 | otlp_endpoint: "otel-collector.svc:4317" # Mandatory if enabled=true 82 | # the kind of log output 83 | # debugLevel: how detailed to output, value: trace, debug, info, warn, error, fatal, panic 84 | # ReportCaller: enable the caller report or not, value: true or false 85 | logger: 86 | AMF: 87 | debugLevel: info 88 | NAS: 89 | debugLevel: info 90 | FSM: 91 | debugLevel: info 92 | NGAP: 93 | debugLevel: info 94 | Aper: 95 | debugLevel: info 96 | OpenApi: 97 | debugLevel: info 98 | -------------------------------------------------------------------------------- /util/testdata/telemetry_zero_ratio.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Intel Corporation 2 | # SPDX-FileCopyrightText: 2021 Open Networking Foundation 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | # 6 | 7 | info: 8 | version: 1.0.0 9 | description: AMF initial local configuration 10 | 11 | configuration: 12 | amfName: AMF # the name of this AMF 13 | ngapIpList: # the IP list of N2 interfaces on this AMF 14 | - 127.0.0.1 15 | sbi: # Service-based interface information 16 | scheme: http # the protocol for sbi (http or https) 17 | registerIPv4: 127.0.0.18 # IP used to register to NRF 18 | bindingIPv4: 127.0.0.18 # IP used to bind the service 19 | port: 8000 # port used to bind the service 20 | tls: # the local path of TLS key 21 | key: /support/TLS/amf.pem # AMF TLS Certificate 22 | pem: /support/TLS/amf.pem # AMF TLS Private key 23 | serviceNameList: # the SBI services provided by this AMF, refer to TS 29.518 24 | - namf-comm # Namf_Communication service 25 | - namf-evts # Namf_EventExposure service 26 | - namf-mt # Namf_MT service 27 | - namf-loc # Namf_Location service 28 | - namf-oam # OAM service 29 | supportDnnList: # the DNN (Data Network Name) list supported by this AMF 30 | - internet 31 | nrfUri: http://127.0.0.10:8000 # a valid URI of NRF 32 | security: # NAS security parameters 33 | integrityOrder: # the priority of integrity algorithms 34 | - NIA2 35 | # - NIA0 36 | cipheringOrder: # the priority of ciphering algorithms 37 | - NEA0 38 | # - NEA2 39 | networkName: # the name of this core network 40 | full: Aether 41 | short: Aether 42 | networkFeatureSupport5GS: # 5gs Network Feature Support IE, refer to TS 24.501 43 | enable: true # append this IE in Registration accept or not 44 | imsVoPS: 0 # IMS voice over PS session indicator (uinteger, range: 0~1) 45 | emc: 0 # Emergency service support indicator for 3GPP access (uinteger, range: 0~3) 46 | emf: 0 # Emergency service fallback indicator for 3GPP access (uinteger, range: 0~3) 47 | iwkN26: 0 # Interworking without N26 interface indicator (uinteger, range: 0~1) 48 | mpsi: 0 # MPS indicator (uinteger, range: 0~1) 49 | emcN3: 0 # Emergency service support indicator for Non-3GPP access (uinteger, range: 0~1) 50 | mcsi: 0 # MCS indicator (uinteger, range: 0~1) 51 | t3502Value: 720 # timer value (seconds) at UE side 52 | t3512Value: 3600 # timer value (seconds) at UE side 53 | non3gppDeregistrationTimerValue: 3240 # timer value (seconds) at UE side 54 | # retransmission timer for paging message 55 | t3513: 56 | enable: true # true or false 57 | expireTime: 6s # default is 6 seconds 58 | maxRetryTimes: 4 # the max number of retransmission 59 | # retransmission timer for NAS Deregistration Request message 60 | t3522: 61 | enable: true # true or false 62 | expireTime: 6s # default is 6 seconds 63 | maxRetryTimes: 4 # the max number of retransmission 64 | # retransmission timer for NAS Registration Accept message 65 | t3550: 66 | enable: true # true or false 67 | expireTime: 6s # default is 6 seconds 68 | maxRetryTimes: 4 # the max number of retransmission 69 | # retransmission timer for NAS Authentication Request/Security Mode Command message 70 | t3560: 71 | enable: true # true or false 72 | expireTime: 6s # default is 6 seconds 73 | maxRetryTimes: 4 # the max number of retransmission 74 | # retransmission timer for NAS Notification message 75 | t3565: 76 | enable: true # true or false 77 | expireTime: 6s # default is 6 seconds 78 | maxRetryTimes: 4 # the max number of retransmission 79 | telemetry: # telemetry configuration 80 | enabled: true # Optional; defaults to false 81 | otlp_endpoint: "otel-collector.svc:4317" # Mandatory if enabled=true 82 | ratio: 0 83 | # the kind of log output 84 | # debugLevel: how detailed to output, value: trace, debug, info, warn, error, fatal, panic 85 | # ReportCaller: enable the caller report or not, value: true or false 86 | logger: 87 | AMF: 88 | debugLevel: info 89 | NAS: 90 | debugLevel: info 91 | FSM: 92 | debugLevel: info 93 | NGAP: 94 | debugLevel: info 95 | Aper: 96 | debugLevel: info 97 | OpenApi: 98 | debugLevel: info 99 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/omec-project/amf 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/antihax/optional v1.0.0 7 | github.com/gin-contrib/cors v1.7.6 8 | github.com/gin-gonic/gin v1.11.0 9 | github.com/go-viper/mapstructure/v2 v2.4.0 10 | github.com/google/uuid v1.6.0 11 | github.com/ishidawataru/sctp v0.0.0-20251114114122-19ddcbc6aae2 12 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 13 | github.com/omec-project/nas v1.6.4 14 | github.com/omec-project/ngap v1.6.1 15 | github.com/omec-project/openapi v1.6.5 16 | github.com/omec-project/util v1.5.7 17 | github.com/prometheus/client_golang v1.23.2 18 | github.com/urfave/cli/v3 v3.6.1 19 | go.mongodb.org/mongo-driver v1.17.6 20 | go.opentelemetry.io/otel v1.39.0 21 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 22 | go.opentelemetry.io/otel/sdk v1.39.0 23 | go.opentelemetry.io/otel/trace v1.39.0 24 | go.yaml.in/yaml/v4 v4.0.0-rc.3 25 | google.golang.org/grpc v1.77.0 26 | google.golang.org/protobuf v1.36.10 27 | ) 28 | 29 | require ( 30 | github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1 // indirect 31 | github.com/beorn7/perks v1.0.1 // indirect 32 | github.com/bytedance/sonic v1.14.0 // indirect 33 | github.com/bytedance/sonic/loader v0.3.0 // indirect 34 | github.com/cenkalti/backoff/v5 v5.0.3 // indirect 35 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 36 | github.com/cloudwego/base64x v0.1.6 // indirect 37 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 38 | github.com/evanphx/json-patch/v5 v5.9.11 // indirect 39 | github.com/gabriel-vasile/mimetype v1.4.9 // indirect 40 | github.com/gin-contrib/sse v1.1.0 // indirect 41 | github.com/go-logr/logr v1.4.3 // indirect 42 | github.com/go-logr/stdr v1.2.2 // indirect 43 | github.com/go-playground/locales v0.14.1 // indirect 44 | github.com/go-playground/universal-translator v0.18.1 // indirect 45 | github.com/go-playground/validator/v10 v10.27.0 // indirect 46 | github.com/goccy/go-json v0.10.5 // indirect 47 | github.com/goccy/go-yaml v1.18.0 // indirect 48 | github.com/golang-jwt/jwt/v5 v5.3.0 // indirect 49 | github.com/golang/snappy v0.0.4 // indirect 50 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect 51 | github.com/json-iterator/go v1.1.12 // indirect 52 | github.com/klauspost/compress v1.18.0 // indirect 53 | github.com/klauspost/cpuid/v2 v2.3.0 // indirect 54 | github.com/leodido/go-urn v1.4.0 // indirect 55 | github.com/mattn/go-isatty v0.0.20 // indirect 56 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 57 | github.com/modern-go/reflect2 v1.0.2 // indirect 58 | github.com/montanaflynn/stats v0.7.1 // indirect 59 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 60 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect 61 | github.com/pierrec/lz4/v4 v4.1.15 // indirect 62 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 63 | github.com/prometheus/client_model v0.6.2 // indirect 64 | github.com/prometheus/common v0.66.1 // indirect 65 | github.com/prometheus/procfs v0.16.1 // indirect 66 | github.com/quic-go/qpack v0.6.0 // indirect 67 | github.com/quic-go/quic-go v0.57.0 // indirect 68 | github.com/segmentio/kafka-go v0.4.49 69 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 70 | github.com/ugorji/go/codec v1.3.0 // indirect 71 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 72 | github.com/xdg-go/scram v1.1.2 // indirect 73 | github.com/xdg-go/stringprep v1.0.4 // indirect 74 | github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect 75 | go.opentelemetry.io/auto/sdk v1.2.1 // indirect 76 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect 77 | go.opentelemetry.io/otel/metric v1.39.0 // indirect 78 | go.opentelemetry.io/proto/otlp v1.9.0 // indirect 79 | go.uber.org/multierr v1.11.0 // indirect 80 | go.uber.org/zap v1.27.1 81 | go.yaml.in/yaml/v2 v2.4.2 // indirect 82 | golang.org/x/arch v0.20.0 // indirect 83 | golang.org/x/crypto v0.45.0 // indirect 84 | golang.org/x/net v0.47.0 // indirect 85 | golang.org/x/oauth2 v0.32.0 // indirect 86 | golang.org/x/sync v0.18.0 // indirect 87 | golang.org/x/sys v0.39.0 // indirect 88 | golang.org/x/text v0.31.0 // indirect 89 | google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect 90 | google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect 91 | ) 92 | -------------------------------------------------------------------------------- /util/testdata/telemetry.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Intel Corporation 2 | # SPDX-FileCopyrightText: 2021 Open Networking Foundation 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | # 6 | 7 | info: 8 | version: 1.0.0 9 | description: AMF initial local configuration 10 | 11 | configuration: 12 | amfName: AMF # the name of this AMF 13 | ngapIpList: # the IP list of N2 interfaces on this AMF 14 | - 127.0.0.1 15 | sbi: # Service-based interface information 16 | scheme: http # the protocol for sbi (http or https) 17 | registerIPv4: 127.0.0.18 # IP used to register to NRF 18 | bindingIPv4: 127.0.0.18 # IP used to bind the service 19 | port: 8000 # port used to bind the service 20 | tls: # the local path of TLS key 21 | key: /support/TLS/amf.pem # AMF TLS Certificate 22 | pem: /support/TLS/amf.pem # AMF TLS Private key 23 | serviceNameList: # the SBI services provided by this AMF, refer to TS 29.518 24 | - namf-comm # Namf_Communication service 25 | - namf-evts # Namf_EventExposure service 26 | - namf-mt # Namf_MT service 27 | - namf-loc # Namf_Location service 28 | - namf-oam # OAM service 29 | supportDnnList: # the DNN (Data Network Name) list supported by this AMF 30 | - internet 31 | nrfUri: http://127.0.0.10:8000 # a valid URI of NRF 32 | security: # NAS security parameters 33 | integrityOrder: # the priority of integrity algorithms 34 | - NIA2 35 | # - NIA0 36 | cipheringOrder: # the priority of ciphering algorithms 37 | - NEA0 38 | # - NEA2 39 | networkName: # the name of this core network 40 | full: Aether 41 | short: Aether 42 | networkFeatureSupport5GS: # 5gs Network Feature Support IE, refer to TS 24.501 43 | enable: true # append this IE in Registration accept or not 44 | imsVoPS: 0 # IMS voice over PS session indicator (uinteger, range: 0~1) 45 | emc: 0 # Emergency service support indicator for 3GPP access (uinteger, range: 0~3) 46 | emf: 0 # Emergency service fallback indicator for 3GPP access (uinteger, range: 0~3) 47 | iwkN26: 0 # Interworking without N26 interface indicator (uinteger, range: 0~1) 48 | mpsi: 0 # MPS indicator (uinteger, range: 0~1) 49 | emcN3: 0 # Emergency service support indicator for Non-3GPP access (uinteger, range: 0~1) 50 | mcsi: 0 # MCS indicator (uinteger, range: 0~1) 51 | t3502Value: 720 # timer value (seconds) at UE side 52 | t3512Value: 3600 # timer value (seconds) at UE side 53 | non3gppDeregistrationTimerValue: 3240 # timer value (seconds) at UE side 54 | # retransmission timer for paging message 55 | t3513: 56 | enable: true # true or false 57 | expireTime: 6s # default is 6 seconds 58 | maxRetryTimes: 4 # the max number of retransmission 59 | # retransmission timer for NAS Deregistration Request message 60 | t3522: 61 | enable: true # true or false 62 | expireTime: 6s # default is 6 seconds 63 | maxRetryTimes: 4 # the max number of retransmission 64 | # retransmission timer for NAS Registration Accept message 65 | t3550: 66 | enable: true # true or false 67 | expireTime: 6s # default is 6 seconds 68 | maxRetryTimes: 4 # the max number of retransmission 69 | # retransmission timer for NAS Authentication Request/Security Mode Command message 70 | t3560: 71 | enable: true # true or false 72 | expireTime: 6s # default is 6 seconds 73 | maxRetryTimes: 4 # the max number of retransmission 74 | # retransmission timer for NAS Notification message 75 | t3565: 76 | enable: true # true or false 77 | expireTime: 6s # default is 6 seconds 78 | maxRetryTimes: 4 # the max number of retransmission 79 | telemetry: # telemetry configuration 80 | enabled: true # Optional; defaults to false 81 | otlp_endpoint: "otel-collector.svc:4317" # Mandatory if enabled=true 82 | ratio: 0.4 # Optional; defaults to 1.0 83 | # the kind of log output 84 | # debugLevel: how detailed to output, value: trace, debug, info, warn, error, fatal, panic 85 | # ReportCaller: enable the caller report or not, value: true or false 86 | logger: 87 | AMF: 88 | debugLevel: info 89 | NAS: 90 | debugLevel: info 91 | FSM: 92 | debugLevel: info 93 | NGAP: 94 | debugLevel: info 95 | Aper: 96 | debugLevel: info 97 | OpenApi: 98 | debugLevel: info 99 | -------------------------------------------------------------------------------- /util/testdata/amfcfg.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Intel Corporation 2 | # SPDX-FileCopyrightText: 2021 Open Networking Foundation 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | # 6 | 7 | info: 8 | version: 1.0.0 9 | description: AMF initial local configuration 10 | 11 | configuration: 12 | amfName: AMF # the name of this AMF 13 | ngapIpList: # the IP list of N2 interfaces on this AMF 14 | - 127.0.0.1 15 | sbi: # Service-based interface information 16 | scheme: http # the protocol for sbi (http or https) 17 | registerIPv4: 127.0.0.18 # IP used to register to NRF 18 | bindingIPv4: 127.0.0.18 # IP used to bind the service 19 | port: 8000 # port used to bind the service 20 | tls: # the local path of TLS key 21 | key: /support/TLS/amf.pem # AMF TLS Certificate 22 | pem: /support/TLS/amf.pem # AMF TLS Private key 23 | serviceNameList: # the SBI services provided by this AMF, refer to TS 29.518 24 | - namf-comm # Namf_Communication service 25 | - namf-evts # Namf_EventExposure service 26 | - namf-mt # Namf_MT service 27 | - namf-loc # Namf_Location service 28 | - namf-oam # OAM service 29 | supportDnnList: # the DNN (Data Network Name) list supported by this AMF 30 | - internet 31 | nrfUri: http://127.0.0.10:8000 # a valid URI of NRF 32 | security: # NAS security parameters 33 | integrityOrder: # the priority of integrity algorithms 34 | - NIA2 35 | # - NIA0 36 | cipheringOrder: # the priority of ciphering algorithms 37 | - NEA0 38 | # - NEA2 39 | networkName: # the name of this core network 40 | full: Aether 41 | short: Aether 42 | networkFeatureSupport5GS: # 5gs Network Feature Support IE, refer to TS 24.501 43 | enable: true # append this IE in Registration accept or not 44 | imsVoPS: 0 # IMS voice over PS session indicator (uinteger, range: 0~1) 45 | emc: 0 # Emergency service support indicator for 3GPP access (uinteger, range: 0~3) 46 | emf: 0 # Emergency service fallback indicator for 3GPP access (uinteger, range: 0~3) 47 | iwkN26: 0 # Interworking without N26 interface indicator (uinteger, range: 0~1) 48 | mpsi: 0 # MPS indicator (uinteger, range: 0~1) 49 | emcN3: 0 # Emergency service support indicator for Non-3GPP access (uinteger, range: 0~1) 50 | mcsi: 0 # MCS indicator (uinteger, range: 0~1) 51 | t3502Value: 720 # timer value (seconds) at UE side 52 | t3512Value: 3600 # timer value (seconds) at UE side 53 | non3gppDeregistrationTimerValue: 3240 # timer value (seconds) at UE side 54 | # retransmission timer for paging message 55 | t3513: 56 | enable: true # true or false 57 | expireTime: 6s # default is 6 seconds 58 | maxRetryTimes: 4 # the max number of retransmission 59 | # retransmission timer for NAS Deregistration Request message 60 | t3522: 61 | enable: true # true or false 62 | expireTime: 6s # default is 6 seconds 63 | maxRetryTimes: 4 # the max number of retransmission 64 | # retransmission timer for NAS Registration Accept message 65 | t3550: 66 | enable: true # true or false 67 | expireTime: 6s # default is 6 seconds 68 | maxRetryTimes: 4 # the max number of retransmission 69 | # retransmission timer for NAS Authentication Request/Security Mode Command message 70 | t3560: 71 | enable: true # true or false 72 | expireTime: 6s # default is 6 seconds 73 | maxRetryTimes: 4 # the max number of retransmission 74 | # retransmission timer for NAS Notification message 75 | t3565: 76 | enable: true # true or false 77 | expireTime: 6s # default is 6 seconds 78 | maxRetryTimes: 4 # the max number of retransmission 79 | telemetry: # telemetry configuration 80 | enabled: true # Optional; defaults to false (i.e., telemetry disabled). 81 | otlp_endpoint: "otel-collector.svc:4317" # Mandatory if enabled=true 82 | ratio: 0.4 # Optional; defaults to 1.0. 83 | # the kind of log output 84 | # debugLevel: how detailed to output, value: trace, debug, info, warn, error, fatal, panic 85 | # ReportCaller: enable the caller report or not, value: true or false 86 | logger: 87 | AMF: 88 | debugLevel: info 89 | NAS: 90 | debugLevel: info 91 | FSM: 92 | debugLevel: info 93 | NGAP: 94 | debugLevel: info 95 | Aper: 96 | debugLevel: info 97 | OpenApi: 98 | debugLevel: info 99 | -------------------------------------------------------------------------------- /communication/routers.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Intel Corporation 2 | // Copyright 2019 free5GC.org 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // 6 | 7 | /* 8 | * Namf_Communication 9 | * 10 | * AMF Communication Service 11 | * 12 | * API version: 1.0.0 13 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 14 | */ 15 | 16 | package communication 17 | 18 | import ( 19 | "net/http" 20 | "strings" 21 | 22 | "github.com/gin-gonic/gin" 23 | "github.com/omec-project/amf/logger" 24 | utilLogger "github.com/omec-project/util/logger" 25 | ) 26 | 27 | // Route is the information for every URI. 28 | type Route struct { 29 | // Name is the name of this Route. 30 | Name string 31 | // Method is the string for the HTTP method. ex) GET, POST etc.. 32 | Method string 33 | // Pattern is the pattern of the URI. 34 | Pattern string 35 | // HandlerFunc is the handler function of this route. 36 | HandlerFunc gin.HandlerFunc 37 | } 38 | 39 | // Routes is the list of the generated Route. 40 | type Routes []Route 41 | 42 | // NewRouter returns a new router. 43 | func NewRouter() *gin.Engine { 44 | router := utilLogger.NewGinWithZap(logger.GinLog) 45 | AddService(router) 46 | return router 47 | } 48 | 49 | func AddService(engine *gin.Engine) *gin.RouterGroup { 50 | group := engine.Group("/namf-comm/v1") 51 | 52 | for _, route := range routes { 53 | switch route.Method { 54 | case "GET": 55 | group.GET(route.Pattern, route.HandlerFunc) 56 | case "POST": 57 | group.POST(route.Pattern, route.HandlerFunc) 58 | case "PUT": 59 | group.PUT(route.Pattern, route.HandlerFunc) 60 | case "DELETE": 61 | group.DELETE(route.Pattern, route.HandlerFunc) 62 | } 63 | } 64 | return group 65 | } 66 | 67 | // Index is the index handler. 68 | func Index(c *gin.Context) { 69 | c.String(http.StatusOK, "Hello World!") 70 | } 71 | 72 | var routes = Routes{ 73 | { 74 | "Index", 75 | "GET", 76 | "/", 77 | Index, 78 | }, 79 | 80 | { 81 | "AMFStatusChangeSubscribeModify", 82 | strings.ToUpper("Put"), 83 | "/subscriptions/:subscriptionId", 84 | HTTPAMFStatusChangeSubscribeModify, 85 | }, 86 | 87 | { 88 | "AMFStatusChangeUnSubscribe", 89 | strings.ToUpper("Delete"), 90 | "/subscriptions/:subscriptionId", 91 | HTTPAMFStatusChangeUnSubscribe, 92 | }, 93 | 94 | { 95 | "CreateUEContext", 96 | strings.ToUpper("Put"), 97 | "/ue-contexts/:ueContextId", 98 | HTTPCreateUEContext, 99 | }, 100 | 101 | { 102 | "EBIAssignment", 103 | strings.ToUpper("Post"), 104 | "/ue-contexts/:ueContextId/assign-ebi", 105 | HTTPEBIAssignment, 106 | }, 107 | 108 | { 109 | "RegistrationStatusUpdate", 110 | strings.ToUpper("Post"), 111 | "/ue-contexts/:ueContextId/transfer-update", 112 | HTTPRegistrationStatusUpdate, 113 | }, 114 | 115 | { 116 | "ReleaseUEContext", 117 | strings.ToUpper("Post"), 118 | "/ue-contexts/:ueContextId/release", 119 | HTTPReleaseUEContext, 120 | }, 121 | 122 | { 123 | "UEContextTransfer", 124 | strings.ToUpper("Post"), 125 | "/ue-contexts/:ueContextId/transfer", 126 | HTTPUEContextTransfer, 127 | }, 128 | 129 | { 130 | "N1N2MessageUnSubscribe", 131 | strings.ToUpper("Delete"), 132 | "/ue-contexts/:ueContextId/n1-n2-messages/subscriptions/:subscriptionId", 133 | HTTPN1N2MessageUnSubscribe, 134 | }, 135 | 136 | { 137 | "N1N2MessageTransfer", 138 | strings.ToUpper("Post"), 139 | "/ue-contexts/:ueContextId/n1-n2-messages", 140 | HTTPN1N2MessageTransfer, 141 | }, 142 | 143 | { 144 | "N1N2MessageTransferStatus", 145 | strings.ToUpper("Get"), 146 | "/ue-contexts/:ueContextId/n1-n2-messages/:n1N2MessageId", 147 | HTTPN1N2MessageTransferStatus, 148 | }, 149 | 150 | { 151 | "N1N2MessageSubscribe", 152 | strings.ToUpper("Post"), 153 | "/ue-contexts/:ueContextId/n1-n2-messages/subscriptions", 154 | HTTPN1N2MessageSubscribe, 155 | }, 156 | 157 | { 158 | "NonUeN2InfoUnSubscribe", 159 | strings.ToUpper("Delete"), 160 | "/non-ue-n2-messages/subscriptions/:n2NotifySubscriptionId", 161 | HTTPNonUeN2InfoUnSubscribe, 162 | }, 163 | 164 | { 165 | "NonUeN2MessageTransfer", 166 | strings.ToUpper("Post"), 167 | "/non-ue-n2-messages/transfer", 168 | HTTPNonUeN2MessageTransfer, 169 | }, 170 | 171 | { 172 | "NonUeN2InfoSubscribe", 173 | strings.ToUpper("Post"), 174 | "/non-ue-n2-messages/subscriptions", 175 | HTTPNonUeN2InfoSubscribe, 176 | }, 177 | 178 | { 179 | "AMFStatusChangeSubscribe", 180 | strings.ToUpper("Post"), 181 | "/subscriptions", 182 | HTTPAMFStatusChangeSubscribe, 183 | }, 184 | } 185 | -------------------------------------------------------------------------------- /logger/logger.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Intel Corporation 2 | // SPDX-FileCopyrightText: 2021 Open Networking Foundation 3 | // Copyright 2019 free5GC.org 4 | // 5 | // SPDX-License-Identifier: Apache-2.0 6 | 7 | package logger 8 | 9 | import ( 10 | "go.uber.org/zap" 11 | "go.uber.org/zap/zapcore" 12 | ) 13 | 14 | var ( 15 | log *zap.Logger 16 | AppLog *zap.SugaredLogger 17 | InitLog *zap.SugaredLogger 18 | CfgLog *zap.SugaredLogger 19 | ContextLog *zap.SugaredLogger 20 | DataRepoLog *zap.SugaredLogger 21 | NgapLog *zap.SugaredLogger 22 | HandlerLog *zap.SugaredLogger 23 | HttpLog *zap.SugaredLogger 24 | GmmLog *zap.SugaredLogger 25 | MtLog *zap.SugaredLogger 26 | ProducerLog *zap.SugaredLogger 27 | LocationLog *zap.SugaredLogger 28 | CommLog *zap.SugaredLogger 29 | CallbackLog *zap.SugaredLogger 30 | UtilLog *zap.SugaredLogger 31 | NasLog *zap.SugaredLogger 32 | ConsumerLog *zap.SugaredLogger 33 | EeLog *zap.SugaredLogger 34 | GinLog *zap.SugaredLogger 35 | GrpcLog *zap.SugaredLogger 36 | KafkaLog *zap.SugaredLogger 37 | NrfRegistrationLog *zap.SugaredLogger 38 | PollConfigLog *zap.SugaredLogger 39 | atomicLevel zap.AtomicLevel 40 | ) 41 | 42 | const ( 43 | FieldRanAddr string = "ran_addr" 44 | FieldRanId string = "ran_id" 45 | FieldAmfUeNgapID string = "amf_ue_ngap_id" 46 | FieldSupi string = "supi" 47 | FieldSuci string = "suci" 48 | ) 49 | 50 | func init() { 51 | atomicLevel = zap.NewAtomicLevelAt(zap.InfoLevel) 52 | config := zap.Config{ 53 | Level: atomicLevel, 54 | Development: false, 55 | Encoding: "console", 56 | EncoderConfig: zap.NewProductionEncoderConfig(), 57 | OutputPaths: []string{"stdout"}, 58 | ErrorOutputPaths: []string{"stderr"}, 59 | } 60 | 61 | config.EncoderConfig.TimeKey = "timestamp" 62 | config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder 63 | config.EncoderConfig.LevelKey = "level" 64 | config.EncoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder 65 | config.EncoderConfig.CallerKey = "caller" 66 | config.EncoderConfig.EncodeCaller = zapcore.ShortCallerEncoder 67 | config.EncoderConfig.MessageKey = "message" 68 | config.EncoderConfig.StacktraceKey = "" 69 | 70 | var err error 71 | log, err = config.Build() 72 | if err != nil { 73 | panic(err) 74 | } 75 | 76 | AppLog = log.Sugar().With("component", "AMF", "category", "App") 77 | InitLog = log.Sugar().With("component", "AMF", "category", "Init") 78 | CfgLog = log.Sugar().With("component", "AMF", "category", "CFG") 79 | ContextLog = log.Sugar().With("component", "AMF", "category", "Context") 80 | DataRepoLog = log.Sugar().With("component", "AMF", "category", "DBRepo") 81 | NgapLog = log.Sugar().With("component", "AMF", "category", "NGAP") 82 | HandlerLog = log.Sugar().With("component", "AMF", "category", "Handler") 83 | HttpLog = log.Sugar().With("component", "AMF", "category", "HTTP") 84 | GmmLog = log.Sugar().With("component", "AMF", "category", "GMM") 85 | MtLog = log.Sugar().With("component", "AMF", "category", "MT") 86 | ProducerLog = log.Sugar().With("component", "AMF", "category", "Producer") 87 | LocationLog = log.Sugar().With("component", "AMF", "category", "LocInfo") 88 | CommLog = log.Sugar().With("component", "AMF", "category", "Comm") 89 | CallbackLog = log.Sugar().With("component", "AMF", "category", "Callback") 90 | UtilLog = log.Sugar().With("component", "AMF", "category", "Util") 91 | NasLog = log.Sugar().With("component", "AMF", "category", "NAS") 92 | ConsumerLog = log.Sugar().With("component", "AMF", "category", "Consumer") 93 | EeLog = log.Sugar().With("component", "AMF", "category", "EventExposure") 94 | GinLog = log.Sugar().With("component", "AMF", "category", "GIN") 95 | GrpcLog = log.Sugar().With("component", "AMF", "category", "GRPC") 96 | KafkaLog = log.Sugar().With("component", "AMF", "category", "Kafka") 97 | NrfRegistrationLog = log.Sugar().With("component", "AMF", "category", "NrfRegistration") 98 | PollConfigLog = log.Sugar().With("component", "AMF", "category", "PollConfig") 99 | } 100 | 101 | func GetLogger() *zap.Logger { 102 | return log 103 | } 104 | 105 | // SetLogLevel: set the log level (panic|fatal|error|warn|info|debug) 106 | func SetLogLevel(level zapcore.Level) { 107 | CfgLog.Infoln("set log level:", level) 108 | atomicLevel.SetLevel(level) 109 | } 110 | -------------------------------------------------------------------------------- /polling/nf_configuration.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 Canonical Ltd 2 | 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | package polling 7 | 8 | import ( 9 | "context" 10 | "encoding/json" 11 | "fmt" 12 | "io" 13 | "net/http" 14 | "reflect" 15 | "strings" 16 | "time" 17 | 18 | "github.com/mohae/deepcopy" 19 | "github.com/omec-project/amf/logger" 20 | "github.com/omec-project/openapi/nfConfigApi" 21 | ) 22 | 23 | const ( 24 | initialPollingInterval = 5 * time.Second 25 | pollingMaxBackoff = 40 * time.Second 26 | pollingBackoffFactor = 2 27 | pollingPath = "/nfconfig/access-mobility" 28 | ) 29 | 30 | type nfConfigPoller struct { 31 | currentAccessAndMobilityConfig []nfConfigApi.AccessAndMobility 32 | client *http.Client 33 | } 34 | 35 | // StartPollingService initializes the polling service and starts it. The polling service 36 | // continuously makes a HTTP GET request to the webconsole and updates the network configuration 37 | func StartPollingService(ctx context.Context, webuiUri string, registrationChannel, contextUpdateChannel chan []nfConfigApi.AccessAndMobility) { 38 | poller := nfConfigPoller{ 39 | currentAccessAndMobilityConfig: []nfConfigApi.AccessAndMobility{}, 40 | client: &http.Client{Timeout: initialPollingInterval}, 41 | } 42 | interval := initialPollingInterval 43 | pollingEndpoint := webuiUri + pollingPath 44 | logger.PollConfigLog.Infof("started polling service on %s every %v", pollingEndpoint, initialPollingInterval) 45 | for { 46 | select { 47 | case <-ctx.Done(): 48 | logger.PollConfigLog.Infoln("polling service shutting down") 49 | return 50 | case <-time.After(interval): 51 | newAccessMobilityConfig, err := fetchAccessAndMobilityConfig(&poller, pollingEndpoint) 52 | if err != nil { 53 | interval = minDuration(interval*time.Duration(pollingBackoffFactor), pollingMaxBackoff) 54 | logger.PollConfigLog.Errorf("polling error. Retrying in %v: %+v", interval, err) 55 | continue 56 | } 57 | interval = initialPollingInterval 58 | if !reflect.DeepEqual(newAccessMobilityConfig, poller.currentAccessAndMobilityConfig) { 59 | logger.PollConfigLog.Infof("Access and Mobility config changed. New Access and Mobility: %+v", newAccessMobilityConfig) 60 | registrationChannel <- newAccessMobilityConfig 61 | poller.currentAccessAndMobilityConfig = deepcopy.Copy(newAccessMobilityConfig).([]nfConfigApi.AccessAndMobility) 62 | contextUpdateChannel <- newAccessMobilityConfig 63 | } else { 64 | logger.PollConfigLog.Debugf("Access and Mobility config did not change %+v", newAccessMobilityConfig) 65 | } 66 | } 67 | } 68 | } 69 | 70 | var fetchAccessAndMobilityConfig = func(p *nfConfigPoller, endpoint string) ([]nfConfigApi.AccessAndMobility, error) { 71 | return p.fetchAccessAndMobilityConfig(endpoint) 72 | } 73 | 74 | func (p *nfConfigPoller) fetchAccessAndMobilityConfig(pollingEndpoint string) ([]nfConfigApi.AccessAndMobility, error) { 75 | ctx, cancel := context.WithTimeout(context.Background(), initialPollingInterval) 76 | defer cancel() 77 | 78 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, pollingEndpoint, nil) 79 | if err != nil { 80 | return nil, fmt.Errorf("failed to create HTTP request: %w", err) 81 | } 82 | req.Header.Set("Accept", "application/json") 83 | 84 | resp, err := p.client.Do(req) 85 | if err != nil { 86 | return nil, fmt.Errorf("HTTP GET %v failed: %w", pollingEndpoint, err) 87 | } 88 | defer resp.Body.Close() 89 | 90 | contentType := resp.Header.Get("Content-Type") 91 | if !strings.Contains(contentType, "application/json") { 92 | return nil, fmt.Errorf("unexpected Content-Type: got %s, want application/json", contentType) 93 | } 94 | 95 | switch resp.StatusCode { 96 | case http.StatusOK: 97 | body, err := io.ReadAll(resp.Body) 98 | if err != nil { 99 | return nil, fmt.Errorf("failed to read response body: %w", err) 100 | } 101 | 102 | var config []nfConfigApi.AccessAndMobility 103 | if err := json.Unmarshal(body, &config); err != nil { 104 | return nil, fmt.Errorf("failed to parse JSON response: %w", err) 105 | } 106 | return config, nil 107 | 108 | case http.StatusBadRequest, http.StatusInternalServerError: 109 | return nil, fmt.Errorf("server returned %d error code", resp.StatusCode) 110 | 111 | default: 112 | return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) 113 | } 114 | } 115 | 116 | func minDuration(a, b time.Duration) time.Duration { 117 | if a < b { 118 | return a 119 | } 120 | return b 121 | } 122 | -------------------------------------------------------------------------------- /gmm/init.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | // Copyright 2019 free5GC.org 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // 6 | 7 | package gmm 8 | 9 | import ( 10 | "github.com/omec-project/amf/context" 11 | "github.com/omec-project/amf/logger" 12 | "github.com/omec-project/util/fsm" 13 | ) 14 | 15 | const ( 16 | GmmMessageEvent fsm.EventType = "Gmm Message" 17 | StartAuthEvent fsm.EventType = "Start Authentication" 18 | AuthSuccessEvent fsm.EventType = "Authentication Success" 19 | AuthRestartEvent fsm.EventType = "Authentication Restart" 20 | AuthFailEvent fsm.EventType = "Authentication Fail" 21 | AuthErrorEvent fsm.EventType = "Authentication Error" 22 | SecurityModeSuccessEvent fsm.EventType = "SecurityMode Success" 23 | SecurityModeFailEvent fsm.EventType = "SecurityMode Fail" 24 | SecuritySkipEvent fsm.EventType = "Security Skip" 25 | SecurityModeAbortEvent fsm.EventType = "SecurityMode Abort" 26 | ContextSetupSuccessEvent fsm.EventType = "ContextSetup Success" 27 | ContextSetupFailEvent fsm.EventType = "ContextSetup Fail" 28 | InitDeregistrationEvent fsm.EventType = "Initialize Deregistration" 29 | NwInitiatedDeregistrationEvent fsm.EventType = "Network Initiated Deregistration Event" 30 | SliceInfoDeleteEvent fsm.EventType = "Slice Info Delete Event" 31 | SliceInfoAddEvent fsm.EventType = "Slice Info Add Event" 32 | DeregistrationAcceptEvent fsm.EventType = "Deregistration Accept" 33 | ) 34 | 35 | const ( 36 | ArgAmfUe string = "AMF Ue" 37 | ArgNASMessage string = "NAS Message" 38 | ArgProcedureCode string = "Procedure Code" 39 | ArgAccessType string = "Access Type" 40 | ArgEAPSuccess string = "EAP Success" 41 | ArgEAPMessage string = "EAP Message" 42 | Arg3GPPDeregistered string = "3GPP Deregistered" 43 | ArgNon3GPPDeregistered string = "Non3GPP Deregistered" 44 | ArgNssai string = "Nssai" 45 | ) 46 | 47 | var transitions = fsm.Transitions{ 48 | {Event: GmmMessageEvent, From: context.Deregistered, To: context.Deregistered}, 49 | {Event: GmmMessageEvent, From: context.Authentication, To: context.Authentication}, 50 | {Event: GmmMessageEvent, From: context.SecurityMode, To: context.SecurityMode}, 51 | {Event: GmmMessageEvent, From: context.ContextSetup, To: context.ContextSetup}, 52 | {Event: GmmMessageEvent, From: context.Registered, To: context.Registered}, 53 | {Event: GmmMessageEvent, From: context.DeregistrationInitiated, To: context.DeregistrationInitiated}, 54 | {Event: StartAuthEvent, From: context.Deregistered, To: context.Authentication}, 55 | {Event: StartAuthEvent, From: context.Registered, To: context.Authentication}, 56 | {Event: AuthRestartEvent, From: context.Authentication, To: context.Authentication}, 57 | {Event: AuthSuccessEvent, From: context.Authentication, To: context.SecurityMode}, 58 | {Event: AuthFailEvent, From: context.Authentication, To: context.Deregistered}, 59 | {Event: AuthErrorEvent, From: context.Authentication, To: context.Deregistered}, 60 | {Event: SecurityModeSuccessEvent, From: context.SecurityMode, To: context.ContextSetup}, 61 | {Event: SecuritySkipEvent, From: context.SecurityMode, To: context.ContextSetup}, 62 | {Event: SecurityModeFailEvent, From: context.SecurityMode, To: context.Deregistered}, 63 | {Event: SecurityModeAbortEvent, From: context.SecurityMode, To: context.Deregistered}, 64 | {Event: ContextSetupSuccessEvent, From: context.ContextSetup, To: context.Registered}, 65 | {Event: ContextSetupFailEvent, From: context.ContextSetup, To: context.Deregistered}, 66 | {Event: InitDeregistrationEvent, From: context.Registered, To: context.DeregistrationInitiated}, 67 | {Event: NwInitiatedDeregistrationEvent, From: context.Registered, To: context.DeregistrationInitiated}, 68 | {Event: DeregistrationAcceptEvent, From: context.DeregistrationInitiated, To: context.Deregistered}, 69 | } 70 | 71 | var callbacks = fsm.Callbacks{ 72 | context.Deregistered: DeRegistered, 73 | context.Authentication: Authentication, 74 | context.SecurityMode: SecurityMode, 75 | context.ContextSetup: ContextSetup, 76 | context.Registered: Registered, 77 | context.DeregistrationInitiated: DeregisteredInitiated, 78 | } 79 | 80 | var GmmFSM *fsm.FSM 81 | 82 | func init() { 83 | if f, err := fsm.NewFSM(transitions, callbacks); err != nil { 84 | logger.GmmLog.Errorf("initialize Gmm FSM error: %+v", err) 85 | } else { 86 | GmmFSM = f 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /msgtypes/ngapmsgtypes/ngapmsgtypes.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | // Copyright 2019 free5GC.org 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // 6 | 7 | package ngapmsgtypes 8 | 9 | import ( 10 | "github.com/omec-project/ngap/ngapType" 11 | ) 12 | 13 | var NgapMsg map[int64]string 14 | 15 | func init() { 16 | BuildProcedureCodeToMsgMap() 17 | } 18 | 19 | func BuildProcedureCodeToMsgMap() { 20 | NgapMsg = make(map[int64]string, 255) 21 | NgapMsg[ngapType.ProcedureCodeAMFConfigurationUpdate] = "AMFConfigurationUpdate" 22 | NgapMsg[ngapType.ProcedureCodeAMFStatusIndication] = "AMFStatusIndication" 23 | NgapMsg[ngapType.ProcedureCodeCellTrafficTrace] = "CellTrafficTrace" 24 | NgapMsg[ngapType.ProcedureCodeDeactivateTrace] = "DeactivateTrace" 25 | NgapMsg[ngapType.ProcedureCodeDownlinkNASTransport] = "DownlinkNASTransport" 26 | NgapMsg[ngapType.ProcedureCodeDownlinkNonUEAssociatedNRPPaTransport] = "DownlinkNonUEAssociatedNRPPaTransport" 27 | NgapMsg[ngapType.ProcedureCodeDownlinkRANConfigurationTransfer] = "DownlinkRANConfigurationTransfer" 28 | NgapMsg[ngapType.ProcedureCodeDownlinkRANStatusTransfer] = "DownlinkRANStatusTransfer" 29 | NgapMsg[ngapType.ProcedureCodeDownlinkUEAssociatedNRPPaTransport] = "DownlinkUEAssociatedNRPPaTransport" 30 | NgapMsg[ngapType.ProcedureCodeErrorIndication] = "ErrorIndication" 31 | NgapMsg[ngapType.ProcedureCodeHandoverCancel] = "HandoverCancel" 32 | NgapMsg[ngapType.ProcedureCodeHandoverNotification] = "HandoverNotification" 33 | NgapMsg[ngapType.ProcedureCodeHandoverPreparation] = "HandoverPreparation" 34 | NgapMsg[ngapType.ProcedureCodeHandoverResourceAllocation] = "HandoverResourceAllocation" 35 | NgapMsg[ngapType.ProcedureCodeInitialContextSetup] = "InitialContextSetup" 36 | NgapMsg[ngapType.ProcedureCodeInitialUEMessage] = "InitialUEMessage" 37 | NgapMsg[ngapType.ProcedureCodeLocationReportingControl] = "LocationReportingControl" 38 | NgapMsg[ngapType.ProcedureCodeLocationReportingFailureIndication] = "LocationReportingFailureIndication" 39 | NgapMsg[ngapType.ProcedureCodeLocationReport] = "LocationReport" 40 | NgapMsg[ngapType.ProcedureCodeNASNonDeliveryIndication] = "NASNonDeliveryIndication" 41 | NgapMsg[ngapType.ProcedureCodeNGReset] = "NGReset" 42 | NgapMsg[ngapType.ProcedureCodeNGSetup] = "NGSetup" 43 | NgapMsg[ngapType.ProcedureCodeOverloadStart] = "OverloadStart" 44 | NgapMsg[ngapType.ProcedureCodeOverloadStop] = "OverloadStop" 45 | NgapMsg[ngapType.ProcedureCodePaging] = "Paging" 46 | NgapMsg[ngapType.ProcedureCodePathSwitchRequest] = "PathSwitchRequest" 47 | NgapMsg[ngapType.ProcedureCodePDUSessionResourceModify] = "PDUSessionResourceModify" 48 | NgapMsg[ngapType.ProcedureCodePDUSessionResourceModifyIndication] = "PDUSessionResourceModifyIndication" 49 | NgapMsg[ngapType.ProcedureCodePDUSessionResourceRelease] = "PDUSessionResourceRelease" 50 | NgapMsg[ngapType.ProcedureCodePDUSessionResourceSetup] = "PDUSessionResourceSetup" 51 | NgapMsg[ngapType.ProcedureCodePDUSessionResourceNotify] = "PDUSessionResourceNotify" 52 | NgapMsg[ngapType.ProcedureCodePrivateMessage] = "PrivateMessage" 53 | NgapMsg[ngapType.ProcedureCodePWSCancel] = "PWSCancel" 54 | NgapMsg[ngapType.ProcedureCodePWSFailureIndication] = "PWSFailureIndication" 55 | NgapMsg[ngapType.ProcedureCodePWSRestartIndication] = "PWSRestartIndication" 56 | NgapMsg[ngapType.ProcedureCodeRANConfigurationUpdate] = "RANConfigurationUpdate" 57 | NgapMsg[ngapType.ProcedureCodeRerouteNASRequest] = "RerouteNASRequest" 58 | NgapMsg[ngapType.ProcedureCodeRRCInactiveTransitionReport] = "RRCInactiveTransitionReport" 59 | NgapMsg[ngapType.ProcedureCodeTraceFailureIndication] = "TraceFailureIndication" 60 | NgapMsg[ngapType.ProcedureCodeTraceStart] = "TraceStart" 61 | NgapMsg[ngapType.ProcedureCodeUEContextModification] = "UEContextModification" 62 | NgapMsg[ngapType.ProcedureCodeUEContextRelease] = "UEContextRelease" 63 | NgapMsg[ngapType.ProcedureCodeUEContextReleaseRequest] = "UEContextReleaseRequest" 64 | NgapMsg[ngapType.ProcedureCodeUERadioCapabilityCheck] = "UERadioCapabilityCheck" 65 | NgapMsg[ngapType.ProcedureCodeUERadioCapabilityInfoIndication] = "UERadioCapabilityInfoIndication" 66 | NgapMsg[ngapType.ProcedureCodeUETNLABindingRelease] = "UETNLABindingRelease" 67 | NgapMsg[ngapType.ProcedureCodeUplinkNASTransport] = "UplinkNASTransport" 68 | NgapMsg[ngapType.ProcedureCodeUplinkNonUEAssociatedNRPPaTransport] = "NonUEAssociatedNRPPaTransport" 69 | NgapMsg[ngapType.ProcedureCodeUplinkRANConfigurationTransfer] = "RANConfigurationTransfer" 70 | NgapMsg[ngapType.ProcedureCodeUplinkRANStatusTransfer] = "RANStatusTransfer" 71 | NgapMsg[ngapType.ProcedureCodeUplinkUEAssociatedNRPPaTransport] = "UplinkUEAssociatedNRPPaTransport" 72 | NgapMsg[ngapType.ProcedureCodeWriteReplaceWarning] = "WriteReplaceWarning" 73 | NgapMsg[ngapType.ProcedureCodeSecondaryRATDataUsageReport] = "SecondaryRATDataUsageReport" 74 | } 75 | -------------------------------------------------------------------------------- /consumer/nsselection.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | // Copyright 2019 free5GC.org 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | // 6 | 7 | package consumer 8 | 9 | import ( 10 | "context" 11 | "encoding/json" 12 | "time" 13 | 14 | "github.com/antihax/optional" 15 | amf_context "github.com/omec-project/amf/context" 16 | "github.com/omec-project/amf/logger" 17 | "github.com/omec-project/openapi" 18 | "github.com/omec-project/openapi/Nnssf_NSSelection" 19 | "github.com/omec-project/openapi/models" 20 | "go.opentelemetry.io/otel/attribute" 21 | ) 22 | 23 | func NSSelectionGetForRegistration(ctx context.Context, ue *amf_context.AmfUe, requestedNssai []models.MappingOfSnssai) ( 24 | *models.ProblemDetails, error, 25 | ) { 26 | ctx, span := tracer.Start(ctx, "HTTP GET nssf/network-slice-information") 27 | defer span.End() 28 | 29 | span.SetAttributes( 30 | attribute.String("http.method", "GET"), 31 | attribute.String("nf.target", "nssf"), 32 | attribute.String("net.peer.name", ue.NssfUri), 33 | attribute.String("amf.nf.id", amf_context.AMF_Self().NfId), 34 | ) 35 | 36 | configuration := Nnssf_NSSelection.NewConfiguration() 37 | configuration.SetBasePath(ue.NssfUri) 38 | client := Nnssf_NSSelection.NewAPIClient(configuration) 39 | 40 | amfSelf := amf_context.AMF_Self() 41 | sliceInfo := models.SliceInfoForRegistration{ 42 | SubscribedNssai: ue.SubscribedNssai, 43 | } 44 | 45 | for _, snssai := range requestedNssai { 46 | sliceInfo.RequestedNssai = append(sliceInfo.RequestedNssai, *snssai.ServingSnssai) 47 | if snssai.HomeSnssai != nil { 48 | sliceInfo.MappingOfNssai = append(sliceInfo.MappingOfNssai, snssai) 49 | } 50 | } 51 | 52 | var paramOpt Nnssf_NSSelection.NSSelectionGetParamOpts 53 | if e, err := json.Marshal(sliceInfo); err != nil { 54 | logger.ConsumerLog.Warnf("json marshal failed: %+v", err) 55 | } else { 56 | paramOpt = Nnssf_NSSelection.NSSelectionGetParamOpts{ 57 | SliceInfoRequestForRegistration: optional.NewInterface(string(e)), 58 | } 59 | } 60 | ctx, cancel := context.WithTimeout(ctx, 30*time.Second) 61 | defer cancel() 62 | 63 | res, httpResp, localErr := client.NetworkSliceInformationDocumentApi.NSSelectionGet(ctx, 64 | models.NfType_AMF, amfSelf.NfId, ¶mOpt) 65 | if localErr == nil { 66 | ue.NetworkSliceInfo = &res 67 | for _, allowedNssai := range res.AllowedNssaiList { 68 | ue.AllowedNssai[allowedNssai.AccessType] = allowedNssai.AllowedSnssaiList 69 | } 70 | ue.ConfiguredNssai = res.ConfiguredNssai 71 | } else if httpResp != nil { 72 | if httpResp.Status != localErr.Error() { 73 | err := localErr 74 | return nil, err 75 | } 76 | problem := localErr.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) 77 | return &problem, nil 78 | } else { 79 | return nil, openapi.ReportError("NSSF No Response") 80 | } 81 | 82 | return nil, nil 83 | } 84 | 85 | func NSSelectionGetForPduSession(ctx context.Context, ue *amf_context.AmfUe, snssai models.Snssai) ( 86 | *models.AuthorizedNetworkSliceInfo, *models.ProblemDetails, error, 87 | ) { 88 | ctx, span := tracer.Start(ctx, "HTTP GET nssf/network-slice-information") 89 | defer span.End() 90 | 91 | span.SetAttributes( 92 | attribute.String("http.method", "GET"), 93 | attribute.String("nf.target", "nssf"), 94 | attribute.String("net.peer.name", ue.NssfUri), 95 | attribute.String("amf.nf.id", amf_context.AMF_Self().NfId), 96 | attribute.Int("snssai.sst", int(snssai.Sst)), 97 | attribute.String("snssai.sd", snssai.Sd), 98 | ) 99 | 100 | configuration := Nnssf_NSSelection.NewConfiguration() 101 | configuration.SetBasePath(ue.NssfUri) 102 | client := Nnssf_NSSelection.NewAPIClient(configuration) 103 | 104 | amfSelf := amf_context.AMF_Self() 105 | sliceInfoForPduSession := models.SliceInfoForPduSession{ 106 | SNssai: &snssai, 107 | RoamingIndication: models.RoamingIndication_NON_ROAMING, // not support roaming 108 | } 109 | 110 | e, err := json.Marshal(sliceInfoForPduSession) 111 | if err != nil { 112 | logger.ConsumerLog.Warnf("json marshal failed: %+v", err) 113 | } 114 | paramOpt := Nnssf_NSSelection.NSSelectionGetParamOpts{ 115 | SliceInfoRequestForPduSession: optional.NewInterface(string(e)), 116 | } 117 | ctx, cancel := context.WithTimeout(ctx, 30*time.Second) 118 | defer cancel() 119 | 120 | res, httpResp, localErr := client.NetworkSliceInformationDocumentApi.NSSelectionGet(ctx, 121 | models.NfType_AMF, amfSelf.NfId, ¶mOpt) 122 | if localErr == nil { 123 | return &res, nil, nil 124 | } else if httpResp != nil { 125 | if httpResp.Status != localErr.Error() { 126 | return nil, nil, localErr 127 | } 128 | problem := localErr.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) 129 | return nil, &problem, nil 130 | } else { 131 | return nil, nil, openapi.ReportError("NSSF No Response") 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /producer/subscription.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | package producer 7 | 8 | import ( 9 | "net/http" 10 | "reflect" 11 | 12 | "github.com/omec-project/amf/context" 13 | "github.com/omec-project/amf/logger" 14 | "github.com/omec-project/openapi/models" 15 | "github.com/omec-project/util/httpwrapper" 16 | ) 17 | 18 | // TS 29.518 5.2.2.5.1 19 | func HandleAMFStatusChangeSubscribeRequest(request *httpwrapper.Request) *httpwrapper.Response { 20 | logger.CommLog.Info("Handle AMF Status Change Subscribe Request") 21 | 22 | subscriptionDataReq := request.Body.(models.SubscriptionData) 23 | 24 | subscriptionDataRsp, locationHeader, problemDetails := AMFStatusChangeSubscribeProcedure(subscriptionDataReq) 25 | if problemDetails != nil { 26 | return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) 27 | } 28 | 29 | headers := http.Header{ 30 | "Location": {locationHeader}, 31 | } 32 | return httpwrapper.NewResponse(http.StatusCreated, headers, subscriptionDataRsp) 33 | } 34 | 35 | func AMFStatusChangeSubscribeProcedure(subscriptionDataReq models.SubscriptionData) ( 36 | subscriptionDataRsp models.SubscriptionData, locationHeader string, problemDetails *models.ProblemDetails, 37 | ) { 38 | amfSelf := context.AMF_Self() 39 | 40 | for _, guami := range subscriptionDataReq.GuamiList { 41 | for _, servedGumi := range amfSelf.ServedGuamiList { 42 | if reflect.DeepEqual(guami, servedGumi) { 43 | // AMF status is available 44 | subscriptionDataRsp.GuamiList = append(subscriptionDataRsp.GuamiList, guami) 45 | } 46 | } 47 | } 48 | 49 | if subscriptionDataRsp.GuamiList != nil { 50 | newSubscriptionID := amfSelf.NewAMFStatusSubscription(subscriptionDataReq) 51 | locationHeader = subscriptionDataReq.AmfStatusUri + "/" + newSubscriptionID 52 | logger.CommLog.Infof("new AMF Status Subscription[%s]", newSubscriptionID) 53 | return 54 | } else { 55 | problemDetails = &models.ProblemDetails{ 56 | Status: http.StatusForbidden, 57 | Cause: "UNSPECIFIED", 58 | } 59 | return 60 | } 61 | } 62 | 63 | // TS 29.518 5.2.2.5.2 64 | func HandleAMFStatusChangeUnSubscribeRequest(request *httpwrapper.Request) *httpwrapper.Response { 65 | logger.CommLog.Info("Handle AMF Status Change UnSubscribe Request") 66 | 67 | subscriptionID := request.Params["subscriptionId"] 68 | 69 | problemDetails := AMFStatusChangeUnSubscribeProcedure(subscriptionID) 70 | if problemDetails != nil { 71 | return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) 72 | } else { 73 | return httpwrapper.NewResponse(http.StatusNoContent, nil, nil) 74 | } 75 | } 76 | 77 | func AMFStatusChangeUnSubscribeProcedure(subscriptionID string) (problemDetails *models.ProblemDetails) { 78 | amfSelf := context.AMF_Self() 79 | 80 | if _, ok := amfSelf.FindAMFStatusSubscription(subscriptionID); !ok { 81 | problemDetails = &models.ProblemDetails{ 82 | Status: http.StatusNotFound, 83 | Cause: "SUBSCRIPTION_NOT_FOUND", 84 | } 85 | } else { 86 | logger.CommLog.Debugf("Delete AMF status subscription[%s]", subscriptionID) 87 | amfSelf.DeleteAMFStatusSubscription(subscriptionID) 88 | } 89 | return 90 | } 91 | 92 | // TS 29.518 5.2.2.5.1.3 93 | func HandleAMFStatusChangeSubscribeModify(request *httpwrapper.Request) *httpwrapper.Response { 94 | logger.CommLog.Info("Handle AMF Status Change Subscribe Modify Request") 95 | 96 | updateSubscriptionData := request.Body.(models.SubscriptionData) 97 | subscriptionID := request.Params["subscriptionId"] 98 | 99 | updatedSubscriptionData, problemDetails := AMFStatusChangeSubscribeModifyProcedure(subscriptionID, 100 | updateSubscriptionData) 101 | if problemDetails != nil { 102 | return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) 103 | } else { 104 | return httpwrapper.NewResponse(http.StatusAccepted, nil, updatedSubscriptionData) 105 | } 106 | } 107 | 108 | func AMFStatusChangeSubscribeModifyProcedure(subscriptionID string, subscriptionData models.SubscriptionData) ( 109 | *models.SubscriptionData, *models.ProblemDetails, 110 | ) { 111 | amfSelf := context.AMF_Self() 112 | 113 | if currentSubscriptionData, ok := amfSelf.FindAMFStatusSubscription(subscriptionID); !ok { 114 | problemDetails := &models.ProblemDetails{ 115 | Status: http.StatusForbidden, 116 | Cause: "Forbidden", 117 | } 118 | return nil, problemDetails 119 | } else { 120 | logger.CommLog.Debugf("Modify AMF status subscription[%s]", subscriptionID) 121 | 122 | currentSubscriptionData.GuamiList = currentSubscriptionData.GuamiList[:0] 123 | 124 | currentSubscriptionData.GuamiList = append(currentSubscriptionData.GuamiList, subscriptionData.GuamiList...) 125 | currentSubscriptionData.AmfStatusUri = subscriptionData.AmfStatusUri 126 | 127 | amfSelf.AMFStatusSubscriptions.Store(subscriptionID, currentSubscriptionData) 128 | return currentSubscriptionData, nil 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /protos/sdcoreAmfServer/server_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022-present Intel Corporation 2 | // SPDX-FileCopyrightText: 2021 Open Networking Foundation 3 | // Copyright 2019 free5GC.org 4 | // 5 | // SPDX-License-Identifier: Apache-2.0 6 | // 7 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 8 | // versions: 9 | // - protoc-gen-go-grpc v1.2.0 10 | // - protoc v3.21.5 11 | // source: server.proto 12 | 13 | package sdcoreAmfServer 14 | 15 | import ( 16 | context "context" 17 | 18 | grpc "google.golang.org/grpc" 19 | codes "google.golang.org/grpc/codes" 20 | status "google.golang.org/grpc/status" 21 | ) 22 | 23 | // This is a compile-time assertion to ensure that this generated file 24 | // is compatible with the grpc package it is being compiled against. 25 | // Requires gRPC-Go v1.32.0 or later. 26 | const _ = grpc.SupportPackageIsVersion7 27 | 28 | // NgapServiceClient is the client API for NgapService service. 29 | // 30 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 31 | type NgapServiceClient interface { 32 | HandleMessage(ctx context.Context, opts ...grpc.CallOption) (NgapService_HandleMessageClient, error) 33 | } 34 | 35 | type ngapServiceClient struct { 36 | cc grpc.ClientConnInterface 37 | } 38 | 39 | func NewNgapServiceClient(cc grpc.ClientConnInterface) NgapServiceClient { 40 | return &ngapServiceClient{cc} 41 | } 42 | 43 | func (c *ngapServiceClient) HandleMessage(ctx context.Context, opts ...grpc.CallOption) (NgapService_HandleMessageClient, error) { 44 | stream, err := c.cc.NewStream(ctx, &NgapService_ServiceDesc.Streams[0], "/sdcoreAmfServer.NgapService/HandleMessage", opts...) 45 | if err != nil { 46 | return nil, err 47 | } 48 | x := &ngapServiceHandleMessageClient{stream} 49 | return x, nil 50 | } 51 | 52 | type NgapService_HandleMessageClient interface { 53 | Send(*SctplbMessage) error 54 | Recv() (*AmfMessage, error) 55 | grpc.ClientStream 56 | } 57 | 58 | type ngapServiceHandleMessageClient struct { 59 | grpc.ClientStream 60 | } 61 | 62 | func (x *ngapServiceHandleMessageClient) Send(m *SctplbMessage) error { 63 | return x.ClientStream.SendMsg(m) 64 | } 65 | 66 | func (x *ngapServiceHandleMessageClient) Recv() (*AmfMessage, error) { 67 | m := new(AmfMessage) 68 | if err := x.ClientStream.RecvMsg(m); err != nil { 69 | return nil, err 70 | } 71 | return m, nil 72 | } 73 | 74 | // NgapServiceServer is the server API for NgapService service. 75 | // All implementations must embed UnimplementedNgapServiceServer 76 | // for forward compatibility 77 | type NgapServiceServer interface { 78 | HandleMessage(NgapService_HandleMessageServer) error 79 | mustEmbedUnimplementedNgapServiceServer() 80 | } 81 | 82 | // UnimplementedNgapServiceServer must be embedded to have forward compatible implementations. 83 | type UnimplementedNgapServiceServer struct { 84 | } 85 | 86 | func (UnimplementedNgapServiceServer) HandleMessage(NgapService_HandleMessageServer) error { 87 | return status.Errorf(codes.Unimplemented, "method HandleMessage not implemented") 88 | } 89 | func (UnimplementedNgapServiceServer) mustEmbedUnimplementedNgapServiceServer() {} 90 | 91 | // UnsafeNgapServiceServer may be embedded to opt out of forward compatibility for this service. 92 | // Use of this interface is not recommended, as added methods to NgapServiceServer will 93 | // result in compilation errors. 94 | type UnsafeNgapServiceServer interface { 95 | mustEmbedUnimplementedNgapServiceServer() 96 | } 97 | 98 | func RegisterNgapServiceServer(s grpc.ServiceRegistrar, srv NgapServiceServer) { 99 | s.RegisterService(&NgapService_ServiceDesc, srv) 100 | } 101 | 102 | func _NgapService_HandleMessage_Handler(srv interface{}, stream grpc.ServerStream) error { 103 | return srv.(NgapServiceServer).HandleMessage(&ngapServiceHandleMessageServer{stream}) 104 | } 105 | 106 | type NgapService_HandleMessageServer interface { 107 | Send(*AmfMessage) error 108 | Recv() (*SctplbMessage, error) 109 | grpc.ServerStream 110 | } 111 | 112 | type ngapServiceHandleMessageServer struct { 113 | grpc.ServerStream 114 | } 115 | 116 | func (x *ngapServiceHandleMessageServer) Send(m *AmfMessage) error { 117 | return x.ServerStream.SendMsg(m) 118 | } 119 | 120 | func (x *ngapServiceHandleMessageServer) Recv() (*SctplbMessage, error) { 121 | m := new(SctplbMessage) 122 | if err := x.ServerStream.RecvMsg(m); err != nil { 123 | return nil, err 124 | } 125 | return m, nil 126 | } 127 | 128 | // NgapService_ServiceDesc is the grpc.ServiceDesc for NgapService service. 129 | // It's only intended for direct use with grpc.RegisterService, 130 | // and not to be introspected or modified (even as a copy) 131 | var NgapService_ServiceDesc = grpc.ServiceDesc{ 132 | ServiceName: "sdcoreAmfServer.NgapService", 133 | HandlerType: (*NgapServiceServer)(nil), 134 | Methods: []grpc.MethodDesc{}, 135 | Streams: []grpc.StreamDesc{ 136 | { 137 | StreamName: "HandleMessage", 138 | Handler: _NgapService_HandleMessage_Handler, 139 | ServerStreams: true, 140 | ClientStreams: true, 141 | }, 142 | }, 143 | Metadata: "server.proto", 144 | } 145 | -------------------------------------------------------------------------------- /service/amf_server.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Open Networking Foundation 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | package service 7 | 8 | import ( 9 | "context" 10 | "fmt" 11 | "net" 12 | "os" 13 | 14 | amfContext "github.com/omec-project/amf/context" 15 | "github.com/omec-project/amf/factory" 16 | "github.com/omec-project/amf/logger" 17 | "github.com/omec-project/amf/metrics" 18 | "github.com/omec-project/amf/ngap" 19 | "github.com/omec-project/amf/protos/sdcoreAmfServer" 20 | mi "github.com/omec-project/util/metricinfo" 21 | "google.golang.org/grpc" 22 | ) 23 | 24 | type Server struct { 25 | sdcoreAmfServer.UnimplementedNgapServiceServer 26 | } 27 | 28 | func (s *Server) HandleMessage(srv sdcoreAmfServer.NgapService_HandleMessageServer) error { 29 | ctx := srv.Context() 30 | Amf2RanMsgChan := make(chan *sdcoreAmfServer.AmfMessage, 100) 31 | 32 | go func() { 33 | for { 34 | msg1 := <-Amf2RanMsgChan 35 | logger.GrpcLog.Infof("send Response message body from client (%s): Verbose - %s, MsgType %v GnbId: %v", msg1.AmfId, msg1.VerboseMsg, msg1.Msgtype, msg1.GnbId) 36 | if err := srv.Send(msg1); err != nil { 37 | logger.GrpcLog.Errorln("error in sending response") 38 | } 39 | } 40 | }() 41 | 42 | for { 43 | req, err := srv.Recv() /* TODO : handle errors */ 44 | if err != nil { 45 | logger.GrpcLog.Errorln("error in SCTPLB stream", err) 46 | break 47 | } else { 48 | logger.GrpcLog.Debugf("receive message body from client (%s): GnbIp: %v, GnbId: %v, Verbose - %s, MsgType %v", req.SctplbId, req.GnbIpAddr, req.GnbId, req.VerboseMsg, req.Msgtype) 49 | switch req.Msgtype { 50 | case sdcoreAmfServer.MsgType_INIT_MSG: 51 | rsp := &sdcoreAmfServer.AmfMessage{} 52 | rsp.VerboseMsg = "Hello From AMF Pod !" 53 | rsp.Msgtype = sdcoreAmfServer.MsgType_INIT_MSG 54 | rsp.AmfId = os.Getenv("HOSTNAME") 55 | logger.GrpcLog.Debugf("send Response message body from client (%s): Verbose - %s, MsgType %v", rsp.AmfId, rsp.VerboseMsg, rsp.Msgtype) 56 | amfSelf := amfContext.AMF_Self() 57 | var ran *amfContext.AmfRan 58 | var ok bool 59 | if ran, ok = amfSelf.AmfRanFindByGnbId(req.GnbId); !ok { 60 | ran = amfSelf.NewAmfRanId(req.GnbId) 61 | if req.GnbId != "" { 62 | ran.GnbId = req.GnbId 63 | ran.RanId = ran.ConvertGnbIdToRanId(ran.GnbId) 64 | logger.GrpcLog.Debugf("RanID: %v for GnbId: %v", ran.RanID(), req.GnbId) 65 | rsp.GnbId = req.GnbId 66 | 67 | // send nf(gnb) status notification 68 | gnbStatus := mi.MetricEvent{ 69 | EventType: mi.CNfStatusEvt, 70 | NfStatusData: mi.CNfStatus{ 71 | NfType: mi.NfTypeGnb, 72 | NfStatus: mi.NfStatusConnected, NfName: req.GnbId, 73 | }, 74 | } 75 | 76 | if *factory.AmfConfig.Configuration.KafkaInfo.EnableKafka { 77 | if err := metrics.StatWriter.PublishNfStatusEvent(gnbStatus); err != nil { 78 | logger.GrpcLog.Errorf("error publishing NfStatusEvent: %v", err) 79 | } 80 | } 81 | } 82 | } 83 | ran.Amf2RanMsgChan = Amf2RanMsgChan 84 | if err := srv.Send(rsp); err != nil { 85 | logger.GrpcLog.Errorln("error in sending response") 86 | } 87 | case sdcoreAmfServer.MsgType_GNB_DISC: 88 | logger.GrpcLog.Infoln("gNB disconnected") 89 | ngap.HandleSCTPNotificationLb(req.GnbId) 90 | // send nf(gnb) status notification 91 | gnbStatus := mi.MetricEvent{ 92 | EventType: mi.CNfStatusEvt, 93 | NfStatusData: mi.CNfStatus{ 94 | NfType: mi.NfTypeGnb, 95 | NfStatus: mi.NfStatusDisconnected, NfName: req.GnbId, 96 | }, 97 | } 98 | if *factory.AmfConfig.Configuration.KafkaInfo.EnableKafka { 99 | if err := metrics.StatWriter.PublishNfStatusEvent(gnbStatus); err != nil { 100 | logger.GrpcLog.Errorf("error publishing NfStatusEvent: %v", err) 101 | } 102 | } 103 | case sdcoreAmfServer.MsgType_GNB_CONN: 104 | logger.GrpcLog.Infoln("new gNB Connected") 105 | // send nf(gnb) status notification 106 | gnbStatus := mi.MetricEvent{ 107 | EventType: mi.CNfStatusEvt, 108 | NfStatusData: mi.CNfStatus{ 109 | NfType: mi.NfTypeGnb, 110 | NfStatus: mi.NfStatusConnected, NfName: req.GnbId, 111 | }, 112 | } 113 | if *factory.AmfConfig.Configuration.KafkaInfo.EnableKafka { 114 | if err := metrics.StatWriter.PublishNfStatusEvent(gnbStatus); err != nil { 115 | logger.GrpcLog.Errorf("error publishing NfStatusEvent: %v", err) 116 | } 117 | } 118 | default: 119 | ngap.DispatchLb(ctx, req, Amf2RanMsgChan) 120 | } 121 | } 122 | } 123 | return nil 124 | } 125 | 126 | func StartGrpcServer(ctx context.Context, port int) { 127 | endpt := fmt.Sprintf(":%d", port) 128 | logger.GrpcLog.Infof("AMF gRPC server is starting on port %s", endpt) 129 | lisCfg := net.ListenConfig{} 130 | lis, err := lisCfg.Listen(ctx, "tcp", endpt) 131 | if err != nil { 132 | logger.GrpcLog.Errorf("failed to listen: %v", err) 133 | } 134 | 135 | s := Server{} 136 | 137 | grpcServer := grpc.NewServer() 138 | 139 | sdcoreAmfServer.RegisterNgapServiceServer(grpcServer, &s) 140 | 141 | if err := grpcServer.Serve(lis); err != nil { 142 | logger.GrpcLog.Errorf("failed to serve: %v", err) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /context/sm_context.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022-present Intel Corporation 2 | // SPDX-FileCopyrightText: 2021 Open Networking Foundation 3 | // Copyright 2019 free5GC.org 4 | // 5 | // SPDX-License-Identifier: Apache-2.0 6 | // 7 | 8 | package context 9 | 10 | import ( 11 | "sync" 12 | 13 | "github.com/omec-project/nas/nasMessage" 14 | "github.com/omec-project/openapi/models" 15 | ) 16 | 17 | type SmContext struct { 18 | Mu *sync.RWMutex // protect the following fields 19 | 20 | // pdu session information 21 | PduSessionIDVal int32 22 | SmContextRefVal string 23 | SnssaiVal models.Snssai 24 | DnnVal string 25 | AccessTypeVal models.AccessType 26 | NsInstanceVal string 27 | UserLocationVal models.UserLocation 28 | PlmnIDVal models.PlmnId 29 | 30 | // SMF information 31 | SmfIDVal string 32 | SmfUriVal string 33 | HSmfIDVal string 34 | VSmfIDVal string 35 | 36 | // status of pdusession 37 | PduSessionInactiveVal bool 38 | 39 | // for duplicate pdu session id handling 40 | UlNASTransportVal *nasMessage.ULNASTransport 41 | DuplicatedVal bool 42 | 43 | SmfProfiles []models.NfProfile 44 | } 45 | 46 | func NewSmContext(pduSessionID int32) *SmContext { 47 | c := &SmContext{ 48 | PduSessionIDVal: pduSessionID, 49 | Mu: new(sync.RWMutex), 50 | } 51 | return c 52 | } 53 | 54 | func (c *SmContext) IsPduSessionActive() bool { 55 | return !c.PduSessionInactiveVal 56 | } 57 | 58 | func (c *SmContext) SetPduSessionInActive(s bool) { 59 | c.PduSessionInactiveVal = s 60 | } 61 | 62 | func (c *SmContext) PduSessionID() int32 { 63 | c.Mu.RLock() 64 | defer c.Mu.RUnlock() 65 | return c.PduSessionIDVal 66 | } 67 | 68 | func (c *SmContext) SetPduSessionID(id int32) { 69 | c.Mu.Lock() 70 | defer c.Mu.Unlock() 71 | c.PduSessionIDVal = id 72 | } 73 | 74 | func (c *SmContext) SmContextRef() string { 75 | c.Mu.RLock() 76 | defer c.Mu.RUnlock() 77 | return c.SmContextRefVal 78 | } 79 | 80 | func (c *SmContext) SetSmContextRef(ref string) { 81 | c.Mu.Lock() 82 | defer c.Mu.Unlock() 83 | c.SmContextRefVal = ref 84 | } 85 | 86 | func (c *SmContext) AccessType() models.AccessType { 87 | c.Mu.RLock() 88 | defer c.Mu.RUnlock() 89 | return c.AccessTypeVal 90 | } 91 | 92 | func (c *SmContext) SetAccessType(accessType models.AccessType) { 93 | c.Mu.Lock() 94 | defer c.Mu.Unlock() 95 | c.AccessTypeVal = accessType 96 | } 97 | 98 | func (c *SmContext) Snssai() models.Snssai { 99 | c.Mu.RLock() 100 | defer c.Mu.RUnlock() 101 | return c.SnssaiVal 102 | } 103 | 104 | func (c *SmContext) SetSnssai(snssai models.Snssai) { 105 | c.Mu.Lock() 106 | defer c.Mu.Unlock() 107 | c.SnssaiVal = snssai 108 | } 109 | 110 | func (c *SmContext) Dnn() string { 111 | c.Mu.RLock() 112 | defer c.Mu.RUnlock() 113 | return c.DnnVal 114 | } 115 | 116 | func (c *SmContext) SetDnn(dnn string) { 117 | c.Mu.Lock() 118 | defer c.Mu.Unlock() 119 | c.DnnVal = dnn 120 | } 121 | 122 | func (c *SmContext) NsInstance() string { 123 | c.Mu.RLock() 124 | defer c.Mu.RUnlock() 125 | return c.NsInstanceVal 126 | } 127 | 128 | func (c *SmContext) SetNsInstance(nsInstanceID string) { 129 | c.Mu.Lock() 130 | defer c.Mu.Unlock() 131 | c.NsInstanceVal = nsInstanceID 132 | } 133 | 134 | func (c *SmContext) UserLocation() models.UserLocation { 135 | c.Mu.RLock() 136 | defer c.Mu.RUnlock() 137 | return c.UserLocationVal 138 | } 139 | 140 | func (c *SmContext) SetUserLocation(userLocation models.UserLocation) { 141 | c.Mu.Lock() 142 | defer c.Mu.Unlock() 143 | c.UserLocationVal = userLocation 144 | } 145 | 146 | func (c *SmContext) PlmnID() models.PlmnId { 147 | c.Mu.RLock() 148 | defer c.Mu.RUnlock() 149 | return c.PlmnIDVal 150 | } 151 | 152 | func (c *SmContext) SetPlmnID(plmnID models.PlmnId) { 153 | c.Mu.Lock() 154 | defer c.Mu.Unlock() 155 | c.PlmnIDVal = plmnID 156 | } 157 | 158 | func (c *SmContext) SmfID() string { 159 | c.Mu.RLock() 160 | defer c.Mu.RUnlock() 161 | return c.SmfIDVal 162 | } 163 | 164 | func (c *SmContext) SetSmfID(smfID string) { 165 | c.Mu.Lock() 166 | defer c.Mu.Unlock() 167 | c.SmfIDVal = smfID 168 | } 169 | 170 | func (c *SmContext) SmfUri() string { 171 | c.Mu.RLock() 172 | defer c.Mu.RUnlock() 173 | return c.SmfUriVal 174 | } 175 | 176 | func (c *SmContext) SetSmfUri(smfUri string) { 177 | c.Mu.Lock() 178 | defer c.Mu.Unlock() 179 | c.SmfUriVal = smfUri 180 | } 181 | 182 | func (c *SmContext) HSmfID() string { 183 | c.Mu.RLock() 184 | defer c.Mu.RUnlock() 185 | return c.HSmfIDVal 186 | } 187 | 188 | func (c *SmContext) SetHSmfID(hsmfID string) { 189 | c.Mu.Lock() 190 | defer c.Mu.Unlock() 191 | c.HSmfIDVal = hsmfID 192 | } 193 | 194 | func (c *SmContext) VSmfID() string { 195 | c.Mu.RLock() 196 | defer c.Mu.RUnlock() 197 | return c.VSmfIDVal 198 | } 199 | 200 | func (c *SmContext) SetVSmfID(vsmfID string) { 201 | c.Mu.Lock() 202 | defer c.Mu.Unlock() 203 | c.VSmfIDVal = vsmfID 204 | } 205 | 206 | func (c *SmContext) PduSessionIDDuplicated() bool { 207 | c.Mu.RLock() 208 | defer c.Mu.RUnlock() 209 | return c.DuplicatedVal 210 | } 211 | 212 | func (c *SmContext) SetDuplicatedPduSessionID(duplicated bool) { 213 | c.Mu.Lock() 214 | defer c.Mu.Unlock() 215 | c.DuplicatedVal = duplicated 216 | } 217 | 218 | func (c *SmContext) ULNASTransport() *nasMessage.ULNASTransport { 219 | c.Mu.RLock() 220 | defer c.Mu.RUnlock() 221 | return c.UlNASTransportVal 222 | } 223 | 224 | func (c *SmContext) StoreULNASTransport(msg *nasMessage.ULNASTransport) { 225 | c.Mu.Lock() 226 | defer c.Mu.Unlock() 227 | c.UlNASTransportVal = msg 228 | } 229 | 230 | func (c *SmContext) DeleteULNASTransport() { 231 | c.Mu.Lock() 232 | defer c.Mu.Unlock() 233 | c.UlNASTransportVal = nil 234 | } 235 | -------------------------------------------------------------------------------- /consumer/nf_discovery.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 free5GC.org 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | package consumer 7 | 8 | import ( 9 | "context" 10 | "fmt" 11 | "net/http" 12 | 13 | amf_context "github.com/omec-project/amf/context" 14 | "github.com/omec-project/amf/logger" 15 | "github.com/omec-project/amf/util" 16 | "github.com/omec-project/openapi/Nnrf_NFDiscovery" 17 | "github.com/omec-project/openapi/models" 18 | nrfCache "github.com/omec-project/openapi/nrfcache" 19 | "go.opentelemetry.io/otel/attribute" 20 | ) 21 | 22 | func SendSearchNFInstances(ctx context.Context, nrfUri string, targetNfType, requestNfType models.NfType, 23 | param *Nnrf_NFDiscovery.SearchNFInstancesParamOpts, 24 | ) (models.SearchResult, error) { 25 | if amf_context.AMF_Self().EnableNrfCaching { 26 | return nrfCache.SearchNFInstances(ctx, nrfUri, targetNfType, requestNfType, param) 27 | } else { 28 | return SendNfDiscoveryToNrf(ctx, nrfUri, targetNfType, requestNfType, param) 29 | } 30 | } 31 | 32 | func SendNfDiscoveryToNrf(ctx context.Context, nrfUri string, targetNfType, requestNfType models.NfType, 33 | param *Nnrf_NFDiscovery.SearchNFInstancesParamOpts, 34 | ) (models.SearchResult, error) { 35 | ctx, span := tracer.Start(ctx, "HTTP GET nrf/nf-instances") 36 | defer span.End() 37 | 38 | span.SetAttributes( 39 | attribute.String("http.method", "GET"), 40 | attribute.String("nf.target", "nrf"), 41 | attribute.String("net.peer.name", nrfUri), 42 | attribute.String("amf.nf.id", amf_context.AMF_Self().NfId), 43 | attribute.String("request.nf.type", string(requestNfType)), 44 | ) 45 | 46 | // Set client and set url 47 | configuration := Nnrf_NFDiscovery.NewConfiguration() 48 | configuration.SetBasePath(nrfUri) 49 | client := Nnrf_NFDiscovery.NewAPIClient(configuration) 50 | 51 | result, res, err := client.NFInstancesStoreApi.SearchNFInstances(ctx, targetNfType, requestNfType, param) 52 | if res != nil && res.StatusCode == http.StatusTemporaryRedirect { 53 | err = fmt.Errorf("temporary Redirect For Non NRF Consumer") 54 | } 55 | defer func() { 56 | if bodyCloseErr := res.Body.Close(); bodyCloseErr != nil { 57 | err = fmt.Errorf("SearchNFInstances' response body cannot close: %+w", bodyCloseErr) 58 | } 59 | }() 60 | 61 | amfSelf := amf_context.AMF_Self() 62 | 63 | var nrfSubData models.NrfSubscriptionData 64 | var problemDetails *models.ProblemDetails 65 | for _, nfProfile := range result.NfInstances { 66 | // checking whether the AMF subscribed to this target nfinstanceid or not 67 | if _, ok := amfSelf.NfStatusSubscriptions.Load(nfProfile.NfInstanceId); !ok { 68 | nrfSubscriptionData := models.NrfSubscriptionData{ 69 | NfStatusNotificationUri: fmt.Sprintf("%s/namf-callback/v1/nf-status-notify", amfSelf.GetIPv4Uri()), 70 | SubscrCond: &models.NfInstanceIdCond{NfInstanceId: nfProfile.NfInstanceId}, 71 | ReqNfType: requestNfType, 72 | } 73 | nrfSubData, problemDetails, err = SendCreateSubscription(ctx, nrfUri, nrfSubscriptionData) 74 | if problemDetails != nil { 75 | logger.ConsumerLog.Errorf("SendCreateSubscription to NRF, Problem[%+v]", problemDetails) 76 | } else if err != nil { 77 | logger.ConsumerLog.Errorf("SendCreateSubscription Error[%+v]", err) 78 | } 79 | amfSelf.NfStatusSubscriptions.Store(nfProfile.NfInstanceId, nrfSubData.SubscriptionId) 80 | } 81 | } 82 | 83 | return result, err 84 | } 85 | 86 | func SearchUdmSdmInstance(ctx context.Context, ue *amf_context.AmfUe, nrfUri string, targetNfType, requestNfType models.NfType, 87 | param *Nnrf_NFDiscovery.SearchNFInstancesParamOpts, 88 | ) error { 89 | resp, localErr := SendSearchNFInstances(ctx, nrfUri, targetNfType, requestNfType, param) 90 | if localErr != nil { 91 | return localErr 92 | } 93 | 94 | // select the first UDM_SDM, TODO: select base on other info 95 | var sdmUri string 96 | for _, nfProfile := range resp.NfInstances { 97 | ue.UdmId = nfProfile.NfInstanceId 98 | sdmUri = util.SearchNFServiceUri(nfProfile, models.ServiceName_NUDM_SDM, models.NfServiceStatus_REGISTERED) 99 | if sdmUri != "" { 100 | break 101 | } 102 | } 103 | ue.NudmSDMUri = sdmUri 104 | if ue.NudmSDMUri == "" { 105 | err := fmt.Errorf("AMF can not select an UDM by NRF") 106 | logger.ConsumerLog.Errorln(err.Error()) 107 | return err 108 | } 109 | return nil 110 | } 111 | 112 | func SearchNssfNSSelectionInstance(ctx context.Context, ue *amf_context.AmfUe, nrfUri string, targetNfType, requestNfType models.NfType, 113 | param *Nnrf_NFDiscovery.SearchNFInstancesParamOpts, 114 | ) error { 115 | resp, localErr := SendSearchNFInstances(ctx, nrfUri, targetNfType, requestNfType, param) 116 | if localErr != nil { 117 | return localErr 118 | } 119 | 120 | // select the first NSSF, TODO: select base on other info 121 | var nssfUri string 122 | for _, nfProfile := range resp.NfInstances { 123 | ue.NssfId = nfProfile.NfInstanceId 124 | nssfUri = util.SearchNFServiceUri(nfProfile, models.ServiceName_NNSSF_NSSELECTION, models.NfServiceStatus_REGISTERED) 125 | if nssfUri != "" { 126 | break 127 | } 128 | } 129 | ue.NssfUri = nssfUri 130 | if ue.NssfUri == "" { 131 | return fmt.Errorf("AMF can not select an NSSF by NRF") 132 | } 133 | return nil 134 | } 135 | 136 | func SearchAmfCommunicationInstance(ctx context.Context, ue *amf_context.AmfUe, nrfUri string, targetNfType, 137 | requestNfType models.NfType, param *Nnrf_NFDiscovery.SearchNFInstancesParamOpts, 138 | ) (err error) { 139 | resp, localErr := SendSearchNFInstances(ctx, nrfUri, targetNfType, requestNfType, param) 140 | if localErr != nil { 141 | err = localErr 142 | return 143 | } 144 | 145 | // select the first AMF, TODO: select base on other info 146 | var amfUri string 147 | for _, nfProfile := range resp.NfInstances { 148 | ue.TargetAmfProfile = &nfProfile 149 | amfUri = util.SearchNFServiceUri(nfProfile, models.ServiceName_NAMF_COMM, models.NfServiceStatus_REGISTERED) 150 | if amfUri != "" { 151 | break 152 | } 153 | } 154 | ue.TargetAmfUri = amfUri 155 | if ue.TargetAmfUri == "" { 156 | err = fmt.Errorf("AMF can not select an target AMF by NRF") 157 | } 158 | return 159 | } 160 | --------------------------------------------------------------------------------