Free5GC源码研究(3) - 单个NF的软件架构(下)
前文以一个example-nf为例研究了free5gc中单个NF的软件架构,然而
example-nf
毕竟不是真正的NF,有一些真正的NF的细节没能在其中展现,比如最重要的:各个NF之间如何交互。本文以AUSF@v1.2.3,一个真正的NF为例,继续深入研究单个NF软件架构的更多细节。
首先我们对比一下ausf
与example-nf
在目录结构上的不同:
$ tree nf-example $ tree NFs/ausf/ ├── cmd ├── cmd │ └── main.go │ └── main.go ├── config ├── internal │ └── nfcfg.yaml │ ├── context ├── internal │ │ ├── ausf_context_init.go │ ├── context │ │ └── context.go │ │ └── context.go │ ├── logger │ ├── logger │ │ └── logger.go │ │ └── logger.go │ ├── sbi │ └── sbi │ │ ├── consumer │ ├── processor │ │ │ ├── consumer.go │ │ ├── processor.go │ │ │ ├── nrf_service.go │ │ ├── processor_mock.go │ │ │ └── udm_service.go │ │ ├── spy_family.go │ │ ├── processor │ │ └── spy_family_test.go │ │ │ ├── processor.go │ ├── api_default.go │ │ │ └── ue_authentication.go │ ├── api_spyfamily.go │ │ ├── api_sorprotection.go │ ├── api_spyfamily_test.go │ │ ├── api_ueauthentication.go │ ├── router.go │ │ ├── api_upuprotection.go │ ├── server.go │ │ ├── routes.go │ └── server_mock.go │ │ └── server.go ├── pkg │ └── util │ ├── app │ ├── router_auth_check.go │ │ └── app.go │ └── router_auth_check_test.go │ ├── factory ├── pkg │ │ ├── config.go │ ├── app │ │ └── factory.go │ │ └── app.go │ └── service │ ├── factory │ └── init.go │ │ ├── config.go ├── Makefile │ │ └── factory.go ├── go.mod │ └── service ├── go.sum │ └── init.go └── README.md ├── LICENSE ├── go.mod └── go.sum
可以看到两者的差别并不大,本质的差别在于ausf/sbi/
目录下多了一个consumer/
目录以及相应的代码文件。考察AusfApp
以及ServerAusf
这两个承担主要任务的类型,会发现他们的函数定义里也多了一些与consumer
相关的代码:
/* https://github.com/free5gc/ausf/blob/v1.2.3/internal/sbi/server.go */ type ServerAusf interface { app.App Consumer() *consumer.Consumer // import "github.com/free5gc/ausf/internal/sbi/consumer" Processor() *processor.Processor } // other functions ...... func (s *Server) Run(traceCtx context.Context, wg *sync.WaitGroup) error { var err error _, s.Context().NfId, err = s.Consumer().RegisterNFInstance(context.Background()) // other code }
上面的代码中,Server
结构体里多了一个Consumer
属性,而Run
函数里也调用了consumer
的RegisterNFInstance
函数。
/* https://github.com/free5gc/ausf/blob/v1.2.3/pkg/service/init.go */ type AusfApp struct { ausfCtx *ausf_context.AUSFContext cfg *factory.Config ctx context.Context cancel context.CancelFunc wg sync.WaitGroup sbiServer *sbi.Server consumer *consumer.Consumer // import "github.com/free5gc/ausf/internal/sbi/consumer" processor *processor.Processor } // other functions ...... func (a *AusfApp) terminateProcedure() { problemDetails, err := a.Consumer().SendDeregisterNFInstance() // other code ...... a.CallServerStop() }
类似的,AusfApp
结构体里也了一个Consumer
属性,而terminateProcedure
函数里也调用了consumer
的SendDeregisterNFInstance
函数。
从字面上看,AUSF的consumer
有着帮忙“注册NF”和“注销NF”的功能。那么consumer
到底是干什么用的?
internal/sbi/consumer
深入查看,consumer
包定义了Consumer
结构体,该结构体包含两个子结构体nnrfService
和nudmService
。
/* https://github.com/free5gc/ausf/blob/v1.2.3/internal/sbi/consumer/consumer.go */ package consumer import ( "github.com/free5gc/ausf/pkg/app" "github.com/free5gc/openapi/Nnrf_NFDiscovery" "github.com/free5gc/openapi/Nnrf_NFManagement" "github.com/free5gc/openapi/Nudm_UEAuthentication" ) type ConsumerAusf interface { app.App } type Consumer struct { ConsumerAusf *nnrfService *nudmService } func NewConsumer(ausf ConsumerAusf) (*Consumer, error) { c := &Consumer{ ConsumerAusf: ausf, } c.nnrfService = &nnrfService{ consumer: c, nfMngmntClients: make(map[string]*Nnrf_NFManagement.APIClient), nfDiscClients: make(map[string]*Nnrf_NFDiscovery.APIClient), } c.nudmService = &nudmService{ consumer: c, ueauClients: make(map[string]*Nudm_UEAuthentication.APIClient), } return c, nil }
NewConsumer
函数用于创建Consumer
实例,并初始化其子结构体。其主要做的事情就是创建并初始化Consumer
的两个重要属性:ndmfService
和nnrfmService
,他们分别被定义在下面两个代码文件里:
/* https://github.com/free5gc/ausf/blob/v1.2.3/internal/sbi/consumer/udm_service.go */ import ( "sync" // other imports ... Nudm_UEAU "github.com/free5gc/openapi/Nudm_UEAuthentication" "github.com/free5gc/openapi/models" ) type nudmService struct { consumer *Consumer ueauMu sync.RWMutex ueauClients map[string]*Nudm_UEAU.APIClient }
/* https://github.com/free5gc/ausf/blob/v1.2.3/internal/sbi/consumer/nrf_service.go */ import ( "sync" // other imports ... "github.com/free5gc/openapi/Nnrf_NFDiscovery" "github.com/free5gc/openapi/Nnrf_NFManagement" "github.com/free5gc/openapi/models" ) type nnrfService struct { consumer *Consumer nfMngmntMu sync.RWMutex nfDiscMu sync.RWMutex nfMngmntClients map[string]*Nnrf_NFManagement.APIClient nfDiscClients map[string]*Nnrf_NFDiscovery.APIClient } func (s *nnrfService) SendDeregisterNFInstance() (problemDetails *models.ProblemDetails, err error) { // ... client := s.getNFManagementClient(ausfContext.NrfUri) res, err = client.NFInstanceIDDocumentApi.DeregisterNFInstance(ctx, ausfContext.NfId) // ... } func (s *nnrfService) RegisterNFInstance(ctx context.Context) ( resouceNrfUri string, retrieveNfInstanceID string, err error, ) { // ... client := s.getNFManagementClient(ausfContext.NrfUri) nf, res, err = client.NFInstanceIDDocumentApi.RegisterNFInstance(ctx, ausfContext.NfId, nfProfile) // ... } // other fucntions ...
读到这里,我们初步可以得出结论,ausf的Consumer
提供了分别用于与nrf和udm这两外两个NF进行通信的功能,其中我们在AusfApp
以及ServerAusf
里看到的RegisterNFInstance
和SendDeregisterNFInstance
函数就是与nrf通信的函数——也就是当ausf自己创建成功时,告诉网络中的nrf自己的存在,可以为其他NF提供服务了;而当ausf被终止时,也告诉nrf自己不呢个再提供服务了。
加入了consumer
后,前文的NF结构图可以进一步迭代成如下形式:

现在的问题是,ausf的consumer
具体如何与nrf和udm进行通信?在consumer
的代码文件中可以看到对free5gc/openapi的依赖,这个openapi又是什么?
OpenAPI
OpenAPI是一种描述HTTP API的规范。5G标准选择使用OpenAPI来描述每一个NF可以提供怎样的HTTP服务,以及如何使用这些服务。下面是一个经过简化的OpenAPI的例子:
# https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v3.0/uspto.yaml openapi: "3.0.0" info: title: Dataset API version: 2.0.0 paths: /{dataset}/fields: get: tags: - metadata summary: >- Provides the general information about the API and the list of fields that can be used to query the dataset. operationId: list-searchable-fields parameters: - name: dataset in: path description: 'Name of the dataset.' required: true schema: type: string responses: '200': description: >- The dataset API for the given version is found and it is accessible to consume. content: application/json: schema: type: string '404': description: >- The combination of dataset name and version is not found in the system or it is not published yet to be consumed by public. content: application/json: schema: type: string
以上yaml文档描述了这么一个HTTP API:API路径为/{dataset}/fields
,支持GET方法,并且需要传入一个名为dataset
的字符串类型参数。可能的返回值有200和404,分别对应API成功和失败,而返回的内容都是字符串,且能被解析为json对象。假设我们在本地有这么一个API服务器,那么我们就可以通过GET /{dataset}/fields
来获取这个API的元数据,包括可以查询的字段列表,例如$curl -X GET https://localhost:8080/api/userDataSet/userName
。
OpenAPI的水很深,以上的例子仅仅揭示了冰山一角。更多的内容还需研读OpenAPI规范。言归正传,5G标准为每一个NF的NFS(Network Function Service)提供了相应的OpenAPI文档,通过研究i这些文档,我们可以清楚地知道每一个NFS提供什么服务,应该怎么使用这些服务。也正因为有了这些清晰定义的文档,我们甚至可以(应该)使用代码生成工具,例如OpenAPI Generator,来生成对应的服务端和客户端的部分代码,从而大大减少开发工作量和手工编程中的bug,并提高代码的规范性。
free5gc/openapi
这个代码仓库中的代码就是使用OpenAPI Generator通过解析相应NFS的OpenAPI文档生成的。需要注意的是,free5gc团队使用的OpenAPI Generator是定制版的,所以我们使用开源版工具生成的代码与官方的代码是不相同的。
目前 free5GC 使用的 openapi generator 為團隊維護的客製版本,且暫時尚沒有公開的計畫。如果你有貢獻 free5GC 的計畫,可以告訴我們需要使用的規格書和版本,由我們來產生對應的程式碼。
那么官方的openapi代码又是怎样的?
$ls openapi CHANGELOG.md Nnrf_AccessToken Nudm_ParameterProvision json_query_builder.go LICENSE.txt Nnrf_NFDiscovery Nudm_SubscriberDataManagement media_type.go Namf_Communication Nnrf_NFManagement Nudm_UEAuthentication models Namf_EventExposure Nnssf_NSSAIAvailability Nudm_UEContextManagement models_nef Namf_Location Nnssf_NSSelection Nudr_DataRepository multipart_related.go Namf_MT Npcf_AMPolicy PfdManagement oauth Nausf_SoRProtection Npcf_BDTPolicyControl auth.go problem_details.go Nausf_UEAuthentication Npcf_PolicyAuthorization client.go serialize.go Nausf_UPUProtection Npcf_SMPolicyControl convert.go supported_feature.go Nbsf_Management Npcf_UEPolicy convert_test.go supported_feature_test.go Nchf_ConvergedCharging Nsmf_EventExposure error.go util.go Nnef_PFDmanagement Nsmf_PDUSession go.mod util_test.go Nnef_TrafficInfluence Nudm_EventExposure go.sum
可以看到,官方的openapi代码中包含了每一个NFS的客户端和服务端代码,以及一些辅助代码。这些代码都是通过OpenAPI Generator生成的。我们再哪一个NFS深入看看。就拿会与ausf以及所有其他NF通信的nrf来说,
$ tree openapi/Nnrf_NFManagement/ . ├── api │ └── openapi.yaml ├── api_nf_instance_id_document.go ├── api_nf_instances_store.go ├── api_notification.go ├── api_subscription_id_document.go ├── api_subscriptions_collection.go ├── CHANGELOG.md ├── client.go └── configuration.go
可以看到,除了api/openapi.yaml
之外,还有client.go
和configuration.go
,以及四个api_*.go
文件。这四个api_*.go
文件实现了对具体API服务的调用,而client.go
则整合了这些API服务,并提供了对外的接口。因此,我们可以看到ausf通过创建一个Nnrf_NFManagement.APIClient
对象来调用Nnrf_NFManagement
提供的所有API服务。
client.go
的代码非常简短,且每一个NFS的client.go
文件都是这样大同小异,因此我们在这里不妨仔细阅读一个。
// https://github.com/free5gc/openapi/blob/v1.0.8/Nnrf_NFManagement/client.go /* * NRF NFManagement Service * * API version: 1.0.0 * Generated by: OpenAPI Generator (https://openapi-generator.tech) */ package Nnrf_NFManagement // APIClient manages communication with the NRF NFManagement Service API v1.0.0 // In most cases there should be only one, shared, APIClient. type APIClient struct { cfg *Configuration common service // Reuse a single struct instead of allocating one for each service on the heap. // API Services NFInstanceIDDocumentApi *NFInstanceIDDocumentApiService NFInstancesStoreApi *NFInstancesStoreApiService SubscriptionIDDocumentApi *SubscriptionIDDocumentApiService SubscriptionsCollectionApi *SubscriptionsCollectionApiService NotificationApi *NotificationApiService } type service struct { client *APIClient } // NewAPIClient creates a new API client. Requires a userAgent string describing your application. // optionally a custom http.Client to allow for advanced features such as caching. func NewAPIClient(cfg *Configuration) *APIClient { c := &APIClient{} c.cfg = cfg c.common.client = c // API Services c.NFInstanceIDDocumentApi = (*NFInstanceIDDocumentApiService)(&c.common) c.NFInstancesStoreApi = (*NFInstancesStoreApiService)(&c.common) c.SubscriptionIDDocumentApi = (*SubscriptionIDDocumentApiService)(&c.common) c.SubscriptionsCollectionApi = (*SubscriptionsCollectionApiService)(&c.common) c.NotificationApi = (*NotificationApiService)(&c.common) return c }
而api_*.go
文件则实现了对具体API服务的调用,例如,api_nf_instance_id_document.go
文件实现了对curl -X PUT https://example.com/nnrf/v1/nf-instances/{nfInstanceID}
的调用,也就是往nrf注册一个新的NF,简化版的代码如下:
// https://github.com/free5gc/openapi/blob/v1.0.8/Nnrf_NFManagement/api_nf_instance_id_document.go#L260 func (a *NFInstanceIDDocumentApiService) RegisterNFInstance( ctx context.Context, nfInstanceID string, nfProfile models.NfProfile) (models.NfProfile, *http.Response, error) { // create path and map variables // set Accept header // body params r, err := openapi.PrepareRequest(ctx, a.client.cfg, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) localVarHTTPResponse, err := openapi.CallAPI(a.client.cfg, r) switch localVarHTTPResponse.StatusCode { case 200: // ...... } }
这个函数做的事情就是把各种信息整合并且构造一个http.Request
对象,然后调用openapi.CallAPI
函数发送请求并处理返回的响应。
另外值得一提的是,free5gc/openapi
里除了NFS的客户端和服务端代码,还有很大一部分是数据模型/数据结构,主要集中在models
和models_nef
目录下,例如models/model_nf_service.go
文件定义了NFService
类型。这个类型主要是会被Nnrf_NFManagement
用到,但其他的NFS也可能会使用到。
// https://github.com/free5gc/openapi/blob/v1.0.8/models/model_nf_service.go /* * NRF NFManagement Service * * API version: 1.0.1 * Generated by: OpenAPI Generator (https://openapi-generator.tech) */ package models import ( "time" ) type NfService struct { ServiceInstanceId string `json:"serviceInstanceId" yaml:"serviceInstanceId" bson:"serviceInstanceId" mapstructure:"ServiceInstanceId"` ServiceName ServiceName `json:"serviceName" yaml:"serviceName" bson:"serviceName" mapstructure:"ServiceName"` Versions *[]NfServiceVersion `json:"versions" yaml:"versions" bson:"versions" mapstructure:"Versions"` Scheme UriScheme `json:"scheme" yaml:"scheme" bson:"scheme" mapstructure:"Scheme"` NfServiceStatus NfServiceStatus `json:"nfServiceStatus" yaml:"nfServiceStatus" bson:"nfServiceStatus" mapstructure:"NfServiceStatus"` Fqdn string `json:"fqdn,omitempty" yaml:"fqdn" bson:"fqdn" mapstructure:"Fqdn"` InterPlmnFqdn string `json:"interPlmnFqdn,omitempty" yaml:"interPlmnFqdn" bson:"interPlmnFqdn" mapstructure:"InterPlmnFqdn"` IpEndPoints *[]IpEndPoint `json:"ipEndPoints,omitempty" yaml:"ipEndPoints" bson:"ipEndPoints" mapstructure:"IpEndPoints"` ApiPrefix string `json:"apiPrefix,omitempty" yaml:"apiPrefix" bson:"apiPrefix" mapstructure:"ApiPrefix"` DefaultNotificationSubscriptions []DefaultNotificationSubscription `json:"defaultNotificationSubscriptions,omitempty" yaml:"defaultNotificationSubscriptions" bson:"defaultNotificationSubscriptions" mapstructure:"DefaultNotificationSubscriptions"` AllowedPlmns *[]PlmnId `json:"allowedPlmns,omitempty" yaml:"allowedPlmns" bson:"allowedPlmns" mapstructure:"AllowedPlmns"` AllowedNfTypes []NfType `json:"allowedNfTypes,omitempty" yaml:"allowedNfTypes" bson:"allowedNfTypes" mapstructure:"AllowedNfTypes"` AllowedNfDomains []string `json:"allowedNfDomains,omitempty" yaml:"allowedNfDomains" bson:"allowedNfDomains" mapstructure:"AllowedNfDomains"` AllowedNssais *[]Snssai `json:"allowedNssais,omitempty" yaml:"allowedNssais" bson:"allowedNssais" mapstructure:"AllowedNssais"` Priority int32 `json:"priority,omitempty" yaml:"priority" bson:"priority" mapstructure:"Priority"` Capacity int32 `json:"capacity,omitempty" yaml:"capacity" bson:"capacity" mapstructure:"Capacity"` Load int32 `json:"load,omitempty" yaml:"load" bson:"load" mapstructure:"Load"` RecoveryTime *time.Time `json:"recoveryTime,omitempty" yaml:"recoveryTime" bson:"recoveryTime" mapstructure:"RecoveryTime"` ChfServiceInfo *ChfServiceInfo `json:"chfServiceInfo,omitempty" yaml:"chfServiceInfo" bson:"chfServiceInfo" mapstructure:"ChfServiceInfo"` SupportedFeatures string `json:"supportedFeatures,omitempty" yaml:"supportedFeatures" bson:"supportedFeatures" mapstructure:"SupportedFeatures"` Oauth2Required string `json:"oauth2Required,omitempty" yaml:"oauth2Required" bson:"oauth2Required" mapstructure:"oauth2Required"` }
这些数据类型全部都在TS29.571由定义。
至此,我们就完成了对单个NF的软件架构的探究。可以看到,Free5GC的软件架构非常清晰(至少在v3.4.3),每一个NF都有自己的模块,并且通过API服务进行通信。我们将开始对每个NF源码的研究,而第一个NF就是AUSF。
【推荐】国内首个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