Free5GC源码研究(5) - NRF研究
本文研究 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说:“当XX事件发生时就call这个NfStatusNotificationUri
通知我”。那么需要发消息时,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就需要一个鉴权机制,确保只会回应被授权的实体(基本上就是指核心网中的其他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,但无权访问用户隐私数据)。关于NRF鉴权机制的更详细介绍可以参考Free5GC团队写的博客文章。
具体来说,每一个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中的实现。