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就需要一个鉴权机制,确保只会回应被授权的实体,而来自其他实体的请求则不回应或直接返回一个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中的实现。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY