本文目標:
本篇文章以 free5GC v3.2.0 介紹 Network Functions 的通用軟體架構。
除了 UPF 以外,所有的 Network Function 的資料夾結構都是一致的,這句話的意思是:如果想要快速找到 free5GC 中任意一個 Network Function 的 Context、SBI 實現邏輯,那麼你可以參考這篇文章!
注意!本篇文章指的架構為 “free5GC NF 的軟體架構”而非 ”NF 的邏輯架構“!
App 即 Network Function 本身,它會把所有的子模組彙整起來,參考 AMF 的原始程式碼:
// Source: https://github.com/free5gc/amf/blob/main/cmd/main.go
package main
import (
"fmt"
"os"
"path/filepath"
"runtime/debug"
"github.com/asaskevich/govalidator"
"github.com/urfave/cli"
"github.com/free5gc/amf/internal/logger"
"github.com/free5gc/amf/internal/util"
"github.com/free5gc/amf/pkg/service"
"github.com/free5gc/util/version"
)
var AMF = &service.AMF{}
func main() {
defer func() {
if p := recover(); p != nil {
// Print stack for panic to log. Fatalf() will let program exit.
logger.AppLog.Fatalf("panic: %v\n%s", p, string(debug.Stack()))
}
}()
app := cli.NewApp()
app.Name = "amf"
app.Usage = "5G Access and Mobility Management Function (AMF)"
app.Action = action
app.Flags = AMF.GetCliCmd()
if err := app.Run(os.Args); err != nil {
logger.AppLog.Errorf("AMF Run error: %v\n", err)
return
}
}
func action(c *cli.Context) error {
if err := initLogFile(c.String("log"), c.String("log5gc")); err != nil {
logger.AppLog.Errorf("%+v", err)
return err
}
if err := AMF.Initialize(c); err != nil {
switch err1 := err.(type) {
case govalidator.Errors:
errs := err1.Errors()
for _, e := range errs {
logger.CfgLog.Errorf("%+v", e)
}
default:
logger.CfgLog.Errorf("%+v", err)
}
logger.CfgLog.Errorf("[-- PLEASE REFER TO SAMPLE CONFIG FILE COMMENTS --]")
return fmt.Errorf("Failed to initialize !!")
}
logger.AppLog.Infoln(c.App.Name)
logger.AppLog.Infoln("AMF version: ", version.GetVersion())
AMF.Start()
return nil
}
func initLogFile(logNfPath, log5gcPath string) error {
AMF.KeyLogPath = util.AmfDefaultKeyLogPath
if err := logger.LogFileHook(logNfPath, log5gcPath); err != nil {
return err
}
if logNfPath != "" {
nfDir, _ := filepath.Split(logNfPath)
tmpDir := filepath.Join(nfDir, "key")
if err := os.MkdirAll(tmpDir, 0o775); err != nil {
logger.InitLog.Errorf("Make directory %s failed: %+v", tmpDir, err)
return err
}
_, name := filepath.Split(util.AmfDefaultKeyLogPath)
AMF.KeyLogPath = filepath.Join(tmpDir, name)
}
return nil
}
每一個 Network Function 在不同的 Reference Point 上都有不同的 Protocol Stack,以 AMF 為例,它必須要有能力解析 NGAP & NAS protocol 的訊息。
至於下層的 SCTP 我們可以交給外部已經實作的 package 處理(如果沒有的話仍須自己實作)。
一般來說,Network Function 的實作可以分成兩種:
Statefull
若一個 Process 在運作時有屬於自己的 Context,並且如果遺失了這些 Context 會造成非預期行為,那麼我們可以說它是 Stateful。
以 AMF 來看,它至少會需要紀錄 Amf-UE 與 Ran-UE 之間的 Context,如果 AMF 遺失了這些資料就有可能會影響終端使用者的服務。
Stateless
Stateless NF 不會有自己的上下文,所以在處理 failover 上是最容易的,因為它只需要重新啟動或是交由 Backup NF 處理外部流量即可。
3GPP 有定義一個特殊的 Network Function,它叫做 Unstructured Data Store Function(UDSF),透過將 Context 存放在 UDSF 可以將原本 Stateful NF 的設計轉移成 Stateless NF。
一個追求穩定的服務都應該要有日誌紀錄與管理的機制,核心網路也不例外!
NF 的工作日誌可以幫助我們進行除錯、數據統計與分析。
情境:
每個 NF 在啟動時都需要向 NRF 進行註冊,這樣其他 NF 才能夠過詢問 NRF 知道每個 NF 的網路位址,例如:
PCF 需要向 UDR 存取 Policies Data,在進行這個動作之前 PCF 會先向 NRF 詢問 UDR 的資訊。
Sbi module 又可以再分成兩類,分別是:
1. Producer
以上述情境來看,我們可以參考 PCF 原始程式碼中的 createSMPolicyProcedure()
。
2. Consumer
以上述情境來看,註冊與詢問NRF 的邏輯都需要實作在 Consumer:
// Source: https://github.com/free5gc/amf/blob/a3bd5358ec55215e2b2f86f7744609f17bbc6991/internal/sbi/consumer/nf_discovery.go
func SendSearchNFInstances(nrfUri string, targetNfType, requestNfType models.NfType,
param *Nnrf_NFDiscovery.SearchNFInstancesParamOpts) (models.SearchResult, error) {
// Set client and set url
configuration := Nnrf_NFDiscovery.NewConfiguration()
configuration.SetBasePath(nrfUri)
client := Nnrf_NFDiscovery.NewAPIClient(configuration)
result, res, err := client.NFInstancesStoreApi.SearchNFInstances(context.TODO(), targetNfType, requestNfType, param)
if res != nil && res.StatusCode == http.StatusTemporaryRedirect {
err = fmt.Errorf("Temporary Redirect For Non NRF Consumer")
}
defer func() {
if bodyCloseErr := res.Body.Close(); bodyCloseErr != nil {
err = fmt.Errorf("SearchNFInstances' response body cannot close: %+w", bodyCloseErr)
}
}()
return result, err
}
Pkg module 負責:
Util 一般用來存放一些通用邏輯,例如:
本篇文章簡單的介紹每個 Network Function 都會出現的 package,並且說明這些 package 的作用與重要性。
在明天的文章中,筆者將會以此為基礎帶大家追蹤 UPF(僅介紹 UPF 的 control plane)的原始程式碼。