本文研究 Network Repository Function (NRF) 主要實現的功能
NRF的概念
NRF在5G網路架構中有著中心意義——所有NF在剛上線時就要向網路中的NRF報告自己的存在,告知NRF自己的基本資訊,同時在即將下線時也要像NRF報告自己的不存在;而某個NF想要呼叫其他NF的功能時,需要向NRF詢問網路中存在哪些能滿足需求的NF,拿到這些NF的API介面然後才能訪問它們的服務。所以,NRF就是5G網路裡的中介,誰有需求的話就去問NRF,NRF會給它介紹能為它解決問題的服務。當然,這就要求所有的NF在最初就知道NRF的API埠,這一般需要人工配置。比如,AUSF的配置檔案就有一項nrfUri
具體而言,5G標準TS29.510定義了NRF的應該提供的API服務
在著幾個服務裡,Nnrf_NFManagement
負責網路中NF資訊的管理,實際上就是資料的增刪改查;Nnrf_NFDiscovery
則為NF提供“諮詢服務”,給NF介紹能為其解決問題的其他NF;Nnrf_AccessToken
則是為NF提供鑑權機制(AUSF是鑑別某個使用者是否具備許可權訪問網路,NRF是鑑別某個NF是否具備許可權訪問其他NF);Nnrf_Bootstrapping
則是一個更高層的服務,用來告知其他NF本NRF能提供什麼服務,以及對應的API介面是什麼(然而現階段的free5gc/nrf並沒有實現這個服務)。
NRF的實現
Context
考察NF的原始碼,最重要的NFContext資料型別自然不能忽略,下面是NRF的Context定義:
https://github.com/free5gc/nrf/blob/v1.2.5/internal/context/context.go#L21
type NRFContext struct {
NrfNfProfile models.NfProfile
Nrf_NfInstanceID string
RootPrivKey *rsa.PrivateKey
RootCert *x509.Certificate
NrfPrivKey *rsa.PrivateKey
NrfPubKey *rsa.PublicKey
NrfCert *x509.Certificate
NfRegistNum int
nfRegistNumLock sync.RWMutex
}
可見這個NRFContext
只是儲存了一些很基本的資訊,比如其ID和Profile,NfRegistNum
用來跟蹤網路中有多少個活躍的NF,其他都是各種加密解密用的key。前文我們說到NRF有管理網路中各個NF基本資訊的功能,然而它的NFContext中卻看不到儲存大批次資料的容器結構。相比之下,AUSFCOontext可是用了兩個Map
型別來儲存使用者裝置的資料。NRF不在Context中儲存NF的資料,那就肯定儲存在其他地方了。也許是考慮到了持久化和資料一致性問題,尤其是NRF需要提供查詢服務,所以要free5gc選擇把NRF的資料儲存到資料庫中,這樣一來NRF可以利用資料庫的查詢引擎來提供查詢服務了。
具體來說,NRF選擇使用MongoDB,儲存在名為free5gc
的資料庫的NfProfile
的集合中:
# https://github.com/free5gc/free5gc/blob/main/config/nrfcfg.yaml#L5
configuration:
MongoDBName: free5gc # database name in MongoDB
MongoDBUrl: mongodb://127.0.0.1:27017 # a valid URL of the mongodb
// https://github.com/free5gc/nrf/blob/v1.2.5/internal/context/context.go#L33
const (
NfProfileCollName string = "NfProfile"
)
NF_Management以及MongoDB
對網路中各NF的資訊的管理是NRF所有功能的基礎,而這所謂的資料管理就是對資料庫裡的資訊做增刪改查。free5gc選擇的資料庫是MongoDB,還把對其的各種操作封裝在了free5gc/util裡。下面是NRF根據某個NF例項的ID在資料庫做查詢的簡化版程式碼:
// https://github.com/free5gc/nrf/blob/v1.2.5/internal/sbi/processor/nf_management.go#L370
import (
"github.com/free5gc/util/mongoapi"
"github.com/gin-gonic/gin"
)
func (p *Processor) GetNFInstanceProcedure(c *gin.Context, nfInstanceID string) {
collName := nrf_context.NfProfileCollName
filter := bson.M{"nfInstanceId": nfInstanceID}
response, err := mongoapi.RestfulAPIGetOne(collName, filter)
c.JSON(http.StatusOK, response)
}
著急哈簡化版程式碼顯示了NF_Management裡各種服務的核心邏輯:獲取MongoDB中對應的Collection Name(相當於SQL資料庫裡的Table),構建一個filter(相當於SQL資料庫裡的query),然後呼叫封裝好的函式對資料庫進行操作(在上面的函式里做的事查詢),最後返回相應的結果。除了查詢之外,當然還可以對資料做新增、修改、刪除等,這些操作對應的mongoapi函式如下
// https://github.com/free5gc/util/blob/v1.0.6/mongoapi/mongoapi.go
func RestfulAPIGetMany(collName string, filter bson.M, argOpt ...interface{}) ([]map[string]interface{}, error)
func RestfulAPIGetOne(collName string, filter bson.M, argOpt ...interface{}) (result map[string]interface{}, err error)
func RestfulAPIDeleteMany(collName string, filter bson.M, argOpt ...interface{}) error
func RestfulAPIDeleteOne(collName string, filter bson.M, argOpt ...interface{}) error
func RestfulAPIPutOne(collName string, filter bson.M, putData map[string]interface{}, ...) (bool, error)
func RestfulAPIPutOneNotUpdate(collName string, filter bson.M, putData map[string]interface{}, ...) (bool, error)
func RestfulAPIPost(collName string, filter bson.M, postData map[string]interface{}, argOpt ...interface{}) (bool, error)
func RestfulAPIPostMany(collName string, filter bson.M, postDataArray []interface{}) error
func RestfulAPIJSONPatch(collName string, filter bson.M, patchJSON []byte, argOpt ...interface{}) error
// ...
增刪改的邏輯比查的邏輯相對來說複雜一點,這裡以NF的新增為例:
// https://github.com/free5gc/nrf/blob/v1.2.5/internal/sbi/processor/nf_management.go#L390
func (p *Processor) NFRegisterProcedure(c *gin.Context, nfProfile models.NfProfile) {
var nf models.NfProfile
nrf_context.NnrfNFManagementDataModel(&nf, nfProfile)
// Marshal nf to bson
tmp, err := json.Marshal(nf)
putData := bson.M{}
json.Unmarshal(tmp, &putData)
// set db info
collName := nrf_context.NfProfileCollName
nfInstanceId := nf.NfInstanceId
filter := bson.M{"nfInstanceId": nfInstanceId}
// Update NF Profile case
mongoapi.RestfulAPIPutOne(collName, filter, putData)
// notify related NFs
uriList := nrf_context.GetNofificationUri(nf)
Notification_event := models.NotificationEventType_REGISTERED
for _, uri := range uriList {
p.Consumer().SendNFStatusNotify(Notification_event, nfInstanceUri, uri, &nfProfile)
}
c.JSON(http.StatusCreated, putData)
}
NF的修改UpdateNFInstanceProcedure(c *gin.Context, nfProfile models.NfProfile)
和刪除NFDeregisterProcedure(nfInstanceID string)
也都是相似的邏輯,只不過對應的mongoapi操作不一樣而已。在上面的程式碼我們可以看到在對資料庫操作以後,還要通知相關NF,最後才是返回。這些“相關的NF”是哪裡來的?這就是NF_Management要管理的另一類資料subscription
(注意是NF的subscription,不是使用者裝置的subscription):如果某個nf_a很關心另一類NF的動向(比如AUSF的例項都很關心UDM的動向,因為他們的鑑權服務依賴UDM的資料服務),那麼它可以向NRF訂閱關於這類NF的訊息,每當有變動(被增、刪、改),NRF就會告知nf_a這些變動。那麼對應的,就會有關於subscription
的增刪改查,比如下面的簡化版程式碼,就把一個subscription
存到了資料庫中
// https://github.com/free5gc/nrf/blob/v1.2.5/internal/sbi/processor/nf_management.go#L127
func (p *Processor) CreateSubscriptionProcedure(
subscription models.NrfSubscriptionData,
) (bson.M, *models.ProblemDetails) {
subscriptionID, err := nrf_context.SetsubscriptionId()
subscription.SubscriptionId = subscriptionID
tmp, err := json.Marshal(subscription)
putData := bson.M{}
json.Unmarshal(tmp, &putData)
mongoapi.RestfulAPIPost("Subscriptions", bson.M{"subscriptionId": subscription.SubscriptionId}, putData)
return putData, nil
}
注意這個函式接受的引數型別是一個models.NrfSubscriptionData
,裡面就包含了訂閱者留下的URINfStatusNotificationUri
,需要發訊息時NRF就會構建相應的filter,把所有的這些URI找出來,挨個發一遍訊息。
// https://github.com/free5gc/nrf/blob/v1.2.5/internal/context/management_data.go#L452
func setUriListByFilter(filter bson.M, uriList *[]string) {
filterNfTypeResultsRaw, err := mongoapi.RestfulAPIGetMany("Subscriptions", filter)
var filterNfTypeResults []models.NrfSubscriptionData
for _, subscr := range filterNfTypeResults {
*uriList = append(*uriList, subscr.NfStatusNotificationUri)
}
}
點選檢視models.NrfSubscriptionData
package models
import (
"time"
)
type NrfSubscriptionData struct {
NfStatusNotificationUri string `json:"nfStatusNotificationUri" yaml:"nfStatusNotificationUri" bson:"nfStatusNotificationUri" mapstructure:"NfStatusNotificationUri"`
SubscrCond interface{} `json:"subscrCond,omitempty" yaml:"subscrCond" bson:"subscrCond" mapstructure:"SubscrCond"`
SubscriptionId string `json:"subscriptionId" yaml:"subscriptionId" bson:"subscriptionId" mapstructure:"SubscriptionId"`
ValidityTime *time.Time `json:"validityTime,omitempty" yaml:"validityTime" bson:"validityTime" mapstructure:"ValidityTime"`
ReqNotifEvents []NotificationEventType `json:"reqNotifEvents,omitempty" yaml:"reqNotifEvents" bson:"reqNotifEvents" mapstructure:"ReqNotifEvents"`
PlmnId *PlmnId `json:"plmnId,omitempty" yaml:"plmnId" bson:"plmnId" mapstructure:"PlmnId"`
NotifCondition *NotifCondition `json:"notifCondition,omitempty" yaml:"notifCondition" bson:"notifCondition" mapstructure:"NotifCondition"`
ReqNfType NfType `json:"reqNfType,omitempty" yaml:"reqNfType" bson:"reqNfType" mapstructure:"ReqNfType"`
ReqNfFqdn string `json:"reqNfFqdn,omitempty" yaml:"reqNfFqdn" bson:"reqNfFqdn" mapstructure:"ReqNfFqdn"`
}
點選檢視subscription的修改和刪除
// https://github.com/free5gc/nrf/blob/v1.2.5/internal/sbi/processor/nf_management.go#L174
func (p *Processor) UpdateSubscriptionProcedure(subscriptionID string, patchJSON []byte) map[string]interface{} {
collName := "Subscriptions"
filter := bson.M{"subscriptionId": subscriptionID}
if err := mongoapi.RestfulAPIJSONPatch(collName, filter, patchJSON); err != nil {
return nil
} else {
if response, err1 := mongoapi.RestfulAPIGetOne(collName, filter); err1 == nil {
return response
}
return nil
}
}
func (p *Processor) RemoveSubscriptionProcedure(subscriptionID string) {
collName := "Subscriptions"
filter := bson.M{"subscriptionId": subscriptionID}
if err := mongoapi.RestfulAPIDeleteMany(collName, filter); err != nil {
logger.NfmLog.Errorf("RemoveSubscriptionProcedure err: %+v", err)
}
}
Nnrf_AccessToken以及OAuth
由於5G網路中每一個NF本質上其實就是一個網路服務,理論上誰都可以訪問,我們在命令列使用curl都可以。那麼這些NF就需要一個鑑權機制,確保只會回應被授權的實體,而來自其他實體的請求則不回應或直接返回一個403 Forbidden
。5G標準選擇的鑑權機制是OAuth加JWT。
+--------+ +---------------+
| |--(A)------- Authorization Grant --------->| |
| | | |
| |<-(B)----------- Access Token -------------| |
| | & Refresh Token | |
| | | |
| | +----------+ | |
| |--(C)---- Access Token ---->| | | |
| | | | | |
| |<-(D)- Protected Resource --| Resource | | Authorization |
| Client | | Server | | Server |
| |--(E)---- Access Token ---->| | | |
| | | | | |
| |<-(F)- Invalid Token Error -| | | |
| | +----------+ | |
| | | |
| |--(G)----------- Refresh Token ----------->| |
| | | |
| |<-(H)----------- Access Token -------------| |
+--------+ & Optional Refresh Token +---------------+
每次NF向另一個NF請求服務,比如說AUSF向UDM請求服務時,AUSF就向NRF要一個JWT,然後用自己的私鑰加密這個JWT,設定在請求頭中。UDM接收到請求後,使用AUSF的公鑰嘗試解密請求頭中的JWT,成功的話就能確認該請求來自AUSF。在free5gc中,這些公鑰和私鑰都存放在cert/目錄下。對應上圖,client就是AUSF,UDM就是Resource Server,而NRF則是Authorization Server。如此,每個收到來自網路中其他NF的請求時,先解碼請求頭中的JWT,若能解碼成功則可以判斷這個請求來自網路內部的其他NF,然後根據解碼後的JWT資訊判斷該請求是否有足夠的許可權(例如一些第三方NF雖然也算是網路內部NF,但無權訪問使用者隱私資料)。具體來說,每一個NFService都有一個AllowedNfTypes
切片,如果它為空,則代表所有NF都可以訪問;否則,只有被指定的NF才有權訪問。
以下是NRF檢查許可權並生成JWT的簡化版程式碼,主要是略缺了JWT裡具體編碼的資料,以及驗證NF的網路證書的過程。
// https://github.com/free5gc/nrf/blob/v1.2.5/internal/sbi/processor/access_token.go
import (
"github.com/free5gc/openapi/oauth"
)
// https://github.com/free5gc/nrf/blob/v1.2.5/internal/sbi/processor/access_token.go
func (p *Processor) AccessTokenProcedure(request models.AccessTokenReq) (
*models.AccessTokenRsp, *models.AccessTokenErr,
) {
errResponse := p.AccessTokenScopeCheck(request)
if errResponse != nil {
logger.AccTokenLog.Errorf("AccessTokenScopeCheck error: %v", errResponse.Error)
return nil, errResponse
}
// Create AccessToken
nrfCtx := nrf_context.GetSelf()
accessTokenClaims := models.AccessTokenClaims{...} // omitted
// Use NRF private key to sign AccessToken
token := jwt.NewWithClaims(jwt.GetSigningMethod("RS512"), accessTokenClaims)
accessToken, err := token.SignedString(nrfCtx.NrfPrivKey)
response := &models.AccessTokenRsp{
AccessToken: accessToken,
// ...
}
return response, nil
}
func (p *Processor) AccessTokenScopeCheck(req models.AccessTokenReq) *models.AccessTokenErr {
// Check with nf profile
collName := nrf_context.NfProfileCollName
filter := bson.M{"nfInstanceId": reqNfInstanceId}
consumerNfInfo, err := mongoapi.RestfulAPIGetOne(collName, filter)
nfProfile := models.NfProfile{}
mapstruct.Decode(consumerNfInfo, &nfProfile)
// Verify NF's certificate with root certificate to avoid Man in the Middle Attack
// more code ...
// Check scope
if reqTargetNfType == "NRF" { // Any NF can access all services of NRF
return nil
}
filter = bson.M{"nfType": reqTargetNfType}
producerNfInfo, err := mongoapi.RestfulAPIGetOne(collName, filter)
nfProfile = models.NfProfile{}
err = mapstruct.Decode(producerNfInfo, &nfProfile)
nfServices := *nfProfile.NfServices
scopes := strings.Split(req.Scope, " ")
for _, reqNfService := range scopes {
found := false
for _, nfService := range nfServices {
if string(nfService.ServiceName) == reqNfService {
if len(nfService.AllowedNfTypes) == 0 { // if not specified, any NF can access the reqNfService
found = true
break
} else {
for _, nfType := range nfService.AllowedNfTypes { // otherwise only the specified NF can access
if string(nfType) == reqNfType {
found = true
break
}
}
break
}
}
}
if !found {
logger.AccTokenLog.Errorln("Certificate verify error: Request out of scope (" + reqNfService + ")")
return &models.AccessTokenErr{
Error: "invalid_scope",
}
}
}
return nil
}
點選檢視更多資料型別
// https://github.com/free5gc/openapi/blob/449098e08462/models/model_access_token_req.go
type AccessTokenReq struct {
GrantType string `json:"grant_type" yaml:"grant_type" bson:"grant_type" mapstructure:"GrantType"`
NfInstanceId string `json:"nfInstanceId" yaml:"nfInstanceId" bson:"nfInstanceId" mapstructure:"NfInstanceId"`
NfType NfType `json:"nfType,omitempty" yaml:"nfType" bson:"nfType" mapstructure:"NfType"`
TargetNfType NfType `json:"targetNfType,omitempty" yaml:"targetNfType" bson:"targetNfType" mapstructure:"TargetNfType"`
Scope string `json:"scope" yaml:"scope" bson:"scope" mapstructure:"Scope"`
TargetNfInstanceId string `json:"targetNfInstanceId,omitempty" yaml:"targetNfInstanceId" bson:"targetNfInstanceId" mapstructure:"TargetNfInstanceId"`
RequesterPlmn *PlmnId `json:"requesterPlmn,omitempty" yaml:"requesterPlmn" bson:"requesterPlmn" mapstructure:"RequesterPlmn"`
RequesterPlmnList []PlmnId `json:"requesterPlmnList,omitempty" yaml:"requesterPlmnList" bson:"requesterPlmnList" mapstructure:"RequesterPlmnList"`
RequesterSnssaiList []Snssai `json:"requesterSnssaiList,omitempty" yaml:"requesterSnssaiList" bson:"requesterSnssaiList" mapstructure:"RequesterSnssaiList"`
// Fully Qualified Domain Name
RequesterFqdn string `json:"requesterFqdn,omitempty" yaml:"requesterFqdn" bson:"requesterFqdn" mapstructure:"RequesterFqdn"`
RequesterSnpnList []PlmnIdNid `json:"requesterSnpnList,omitempty" yaml:"requesterSnpnList" bson:"requesterSnpnList" mapstructure:"RequesterSnpnList"`
TargetPlmn *PlmnId `json:"targetPlmn,omitempty" yaml:"targetPlmn" bson:"targetPlmn" mapstructure:"TargetPlmn"`
TargetSnssaiList []Snssai `json:"targetSnssaiList,omitempty" yaml:"targetSnssaiList" bson:"targetSnssaiList" mapstructure:"TargetSnssaiList"`
TargetNsiList []string `json:"targetNsiList,omitempty" yaml:"targetNsiList" bson:"targetNsiList" mapstructure:"TargetNsiList"`
TargetNfSetId string `json:"targetNfSetId,omitempty" yaml:"targetNfSetId" bson:"targetNfSetId" mapstructure:"TargetNfSetId"`
TargetNfServiceSetId string `` /* 129-byte string literal not displayed */
}
// https://github.com/free5gc/openapi/blob/449098e08462/models/model_access_token_rsp.go
type AccessTokenRsp struct {
// JWS Compact Serialized representation of JWS signed JSON object (AccessTokenClaims)
AccessToken string `json:"access_token" yaml:"access_token" bson:"access_token" mapstructure:"AccessToken"`
TokenType string `json:"token_type" yaml:"token_type" bson:"token_type" mapstructure:"TokenType"`
ExpiresIn int32 `json:"expires_in,omitempty" yaml:"expires_in" bson:"expires_in" mapstructure:"ExpiresIn"`
Scope string `json:"scope,omitempty" yaml:"scope" bson:"scope" mapstructure:"Scope"`
}
// https://github.com/free5gc/openapi/blob/v1.0.8/models/model_access_token_claims.go
type AccessTokenClaims struct {
Iss string `json:"iss" yaml:"iss" bson:"iss" mapstructure:"Iss"`
Sub string `json:"sub" yaml:"sub" bson:"sub" mapstructure:"Sub"`
Aud interface{} `json:"aud" yaml:"aud" bson:"aud" mapstructure:"Aud"`
Scope string `json:"scope" yaml:"scope" bson:"scope" mapstructure:"Scope"`
Exp int32 `json:"exp" yaml:"exp" bson:"exp" mapstructure:"Exp"`
jwt.RegisteredClaims
}
以上是請求方向NRF獲得一個JWT的過程和相應的函式。請求方拿到一個JWT後,就把它附在請求頭上發出去。收到請求的NF呼叫free5gc/openapi/auth
包裡的函式來鑑別其中JWT的許可權
// https://github.com/free5gc/nrf/blob/v1.2.5/internal/context/context.go#L206
func (context *NRFContext) AuthorizationCheck(token string, serviceName models.ServiceName) error {
err := oauth.VerifyOAuth(token, string(serviceName), factory.NrfConfig.GetNrfCertPemPath())
if err != nil {
logger.AccTokenLog.Warningln("AuthorizationCheck:", err)
return err
}
return nil
}
雖然這個AuthorizationCheck
函式繫結在NRFContext
型別上,但在應用鑑權機制時保護的不是Context,而是Router,也就是API介面。每當我們認為訪問某一個或某一組API需要許可權時,我們就呼叫NRFContext
中的AuthorizationCheck
函式:
// https://github.com/free5gc/nrf/blob/v1.2.5/internal/util/router_auth_check.go#L13
type (
NFContextGetter func() *nrf_context.NRFContext
RouterAuthorizationCheck struct {
serviceName models.ServiceName
}
)
func NewRouterAuthorizationCheck(serviceName models.ServiceName) *RouterAuthorizationCheck {
return &RouterAuthorizationCheck{
serviceName: serviceName,
}
}
func (rac *RouterAuthorizationCheck) Check(c *gin.Context, nrfContext nrf_context.NFContext) {
token := c.Request.Header.Get("Authorization")
err := nrfContext.AuthorizationCheck(token, rac.serviceName)
if err != nil {
logger.UtilLog.Debugf("RouterAuthorizationCheck::Check Unauthorized: %s", err.Error())
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
c.Abort()
return
}
logger.UtilLog.Debugf("RouterAuthorizationCheck::Check Authorized")
}
// https://github.com/free5gc/nrf/blob/v1.2.5/internal/sbi/server.go#L90
managementRoutes := s.getNfManagementRoute()
managementGroup := s.router.Group(factory.NrfNfmResUriPrefix)
managementAuthCheck := util.NewRouterAuthorizationCheck(models.ServiceName_NNRF_NFM)
managementGroup.Use(func(c *gin.Context) {
managementAuthCheck.Check(c, s.Context())
})
applyRoutes(managementGroup, managementRoutes)
順帶一提,free5gc把很多OAuth的基本操作封裝在了free5gc/openapi中,這樣我有點迷惑,畢竟從設計上來說它更應該和mongoapi等其他工具函式一樣放在free5gc/util裡,而不是與從openapi規範文件中生成的程式碼放在一起。也許是作者有什麼我沒有想到的考量吧,但我更傾向於這時候尚未改過來的早期設計缺陷......
NF_Discovery
在free5gc中,NF_Discovery
的實現總體上邏輯與NF_Management
差不多,都是根據需求構建一個查詢,然後讓資料庫執行這個查詢,最後向外界返回查詢結果。可以認為,NF_Management
做的是NF資料的“增、刪、改”,而NF_Discovery
做的就是最後的“查”。
下面簡化版的NFDiscoveryProcedure
程式碼,整個函式做的事情就是根據使用者的需求,也就是編碼在URL的query parameter去構架一個MongoDB filter
,然後呼叫MongoDB的查詢引擎去執行這個filter
,把查詢到的結果稍加轉換後返回給使用者。
// https://github.com/free5gc/nrf/blob/v1.2.5/internal/sbi/processor/nf_discovery.go#L75
func (p *Processor) NFDiscoveryProcedure(c *gin.Context, queryParameters url.Values) {
// Build Query Filter
var filter bson.M = buildFilter(queryParameters)
// Use the filter to find documents
nfProfilesRaw, err := mongoapi.RestfulAPIGetMany(nrf_context.NfProfileCollName, filter)
// convert data for response
var nfProfilesStruct []models.NfProfile
timedecode.Decode(nfProfilesRaw, &nfProfilesStruct)
// Build and return SearchResult
searchResult := &models.SearchResult{
NfInstances: nfProfilesStruct,
}
c.JSON(http.StatusOK, searchResult)
}
這樣看起來,整個對NF做查詢的邏輯清晰簡潔。然而,internal/sbi/processor/nf_discovery.go整個原始碼檔案有兩千多行程式碼!這是因為NRF需要支援各種各樣花裡胡哨的查詢功能。TS29.510@v18.06的Table 6.2.3.2.3.1-1是個長達20頁的表格,描述了需要支援哪些URI query parameters,以及怎樣根據這些引數執行查詢。因此,“構建filter”就是一件複雜繁瑣的工作,兩千多行程式碼中的90%都是在幹buildFilter(queryParameters)
這一件事情。
// https://github.com/free5gc/nrf/blob/v1.2.5/internal/sbi/processor/nf_discovery.go#L184
func buildFilter(queryParameters url.Values) bson.M {
// build the filter
filter := bson.M{
"$and": []bson.M{},
}
// [Query-1] target-nf-type
targetNfType := queryParameters["target-nf-type"][0]
if targetNfType != "" {
targetNfTypeFilter := bson.M{
"nfType": targetNfType,
}
filter["$and"] = append(filter["$and"].([]bson.M), targetNfTypeFilter)
}
// [Query-2] request-nf-type
requesterNfType := queryParameters["requester-nf-type"][0]
if requesterNfType != "" {
requesterNfTypeFilter := bson.M{
"$or": []bson.M{
{"allowedNfTypes": requesterNfType},
{"allowedNfTypes": bson.M{
"$exists": false,
}},
},
}
filter["$and"] = append(filter["$and"].([]bson.M), requesterNfTypeFilter)
}
// =============== 2000+ more lines of code ====================
}
buildFilter
函式有1000+行程式碼,但裡面的模式也很簡單,就是一步步檢查URL的queryParameters
有沒有某個引數,有的話就處理。其處理本質上也就是把引數裡的值轉化成能被MongoDB的查詢引擎理解的語言,最後把所有這些引數拼接成一個巨大的,用"$and"連結起來的查詢語句:
filter["$and"] = append(filter["$and"].([]bson.M), XXX)
本文記錄了NRF的主要功能以及在free5gc中的實現。