What is free5GC
free5GCは、第5世代(5G)モバイルコアネットワーク向けのオープンソースプロジェクトです。
最終的な目標は、3GPPリリース15(R15)以降で定義されている5Gコアネットワーク(5GC)を実装することだそうです。
そもそも、5GCoreの特徴は?
5GCoreを構成する機能群NF(Network Function)は、以下の図のようになっています。
そして、free5GCでは、RAN、UEを除く基本ソフトウェアモジュールを提供するものだそうです。
そして、5Gモバイル端末が、RANを経由して、5GCoreに登録する手順は、こんな感じのシーケンスになるようです。
5GCore動作を理解するには?
もちろん、実際に、free5GCが動作する環境を自前で準備して、色々と手元で動作を確認するのが早道だと思います。
そして、次のステップとしては、「どのようなfree5GCがメカニズムで動作しているのか?」を学習するのが王道でしょうか。
幸い、free5GCは、golangで実装されているので、ソースコードリーディングも行いやすいと思います。(UPFを除く)
free5GCのソースコードを理解するための事前知識
ここでは、UPFの話は、対象外とさせて頂きます。
free5GC動作については、基本的に、Webアプリ基盤をベースとした、マイクロサービス的なアプローチで各NF(Network Function)を構成しています。従って、「どのような条件で、NF(Network Function)が動作するのか?」を理解するのが、free5GCのソースコード理解の早道になるわけです。
free5GCの特徴1「OpenAPI活用」
5GCoreでのAPI仕様を、OpenAPIで定義されたドキュメントをベースとして、Webアプリ基盤をベースのコード実装を行なっています。
ちなみに、UDMモジュールが提供するサービスは、下表のように規定されています。
例えば、5GCoreでのAPI仕様をYAMLファイルで定義したOpenAPIは、こちらのWebサイトで確認できます。
そして、Nudm_UECMサービスに関わるインタフェースは、こんな感じで確認できます。
free5GCのWebアプリ基盤では、OpenAPIとして定義されたYAMLファイルを活用したOpenAPI Generatorツールを活用しています。
従って、OpenAPI Generatorでのコード自動生成ツールを習熟しておくことが、free5GCのWebアプリ基盤の理解の助けになるはずです。
ちなみに、OpenAPI Generatorツールの使い方のサンプルは、こちらのリポジトリが参考になると思います。
free5GCの特徴2「独自FSMなステート管理」
5GCoreでのRegistration Procedureをもう一度再掲します。
5GCoreでのRegistration Procedureでの、技術サイト"RAN〜AMF間でのNASメッセージのやりとりについては5G NR SA Registration/Attach Call Flow"が参考になりました。
着目すべきポイントは、「AMFが、RANからNASメッセージを受信した時に、如何に適切な処理を実装するか?」になります。
free5GCでは、「独自FSMなステート管理」を実装することによって対応しているようです。
free5GCのfsmリポジトリがその役割を担うわけですが、その活用方法については、READMEファイル含め、何の情報も公開されていないので、free5GCのソースコードリーディングに向けた乗り越えるべき最大の課題だと思います。
free5GCでのfsm機構を理解する
ちなみに、free5GCでのfsm機構のスペックとしては、
- STATE情報の状態遷移を定義して、SendEvent関数の発動条件と紐付けることができる
- 状態遷移の保持状態に応じた、適切な挙動を定義できる
- STATE情報の状態遷移には、SendEvent関数を使用する
- RANから、NASメッセージを受信した時には、SendEvent関数を起動することによって、fsm機構を動作させる
free5GCの特徴2「独自FSMなステート管理」を理解するために、サンプルアプリを別途、準備して挙動を確認しました。
まず、やりたいことは、5GCoreでのRegistration Procedureにて、AMFが、以下の4つのNASメッセージを受信した場合に、AMF内部で保持しているSTATE情報に応じて、適切な挙動を行う仕組みを理解したいという点です。
- [NAS-PDU: Registration request]
- [NAS-PDU: Authentication Response]
- [NAS-PDU: Security mode complete]
- [NAS-PDU: Registration complete]
(1)STATE情報の状態遷移を定義して、SendEvent関数の発動条件と紐付ける
package gmm
import (
"fmt"
"github.com/free5gc/fsm"
"github.com/ttsubo/free5gc-fsm-sample/context"
)
const (
GmmMessageEvent fsm.EventType = "Gmm Message"
StartAuthEvent fsm.EventType = "Start Authentication"
AuthSuccessEvent fsm.EventType = "Authentication Success"
AuthRestartEvent fsm.EventType = "Authentication Restart"
SecurityModeSuccessEvent fsm.EventType = "SecurityMode Success"
ContextSetupSuccessEvent fsm.EventType = "ContextSetup Success"
)
var transitions = fsm.Transitions{
{Event: GmmMessageEvent, From: context.Deregistered, To: context.Deregistered},
{Event: GmmMessageEvent, From: context.Authentication, To: context.Authentication},
{Event: GmmMessageEvent, From: context.SecurityMode, To: context.SecurityMode},
{Event: GmmMessageEvent, From: context.ContextSetup, To: context.ContextSetup},
{Event: GmmMessageEvent, From: context.Registered, To: context.Registered},
{Event: StartAuthEvent, From: context.Deregistered, To: context.Authentication},
{Event: AuthSuccessEvent, From: context.Authentication, To: context.SecurityMode},
{Event: SecurityModeSuccessEvent, From: context.SecurityMode, To: context.ContextSetup},
{Event: ContextSetupSuccessEvent, From: context.ContextSetup, To: context.Registered},
}
var callbacks = fsm.Callbacks{
context.Deregistered: DeRegistered,
context.Authentication: Authentication,
context.SecurityMode: SecurityMode,
context.ContextSetup: ContextSetup,
context.Registered: Registered,
}
var GmmFSM *fsm.FSM
func init() {
if f, err := fsm.NewFSM(transitions, callbacks); err != nil {
fmt.Printf("Initialize Gmm FSM Error: %+v", err)
} else {
GmmFSM = f
}
}
(2)状態遷移の保持状態に応じた、適切な挙動を定義する
package gmm
import (
"fmt"
"github.com/free5gc/fsm"
)
func DeRegistered(state *fsm.State, event fsm.EventType, args fsm.ArgsType) {
switch event {
case fsm.EntryEvent:
fmt.Printf("# Receiving Event: [%+v] on DeRegistered\n", event)
case GmmMessageEvent:
fmt.Printf("# Receiving Event: [%+v] on DeRegistered\n", event)
fmt.Println("... Send Event: StartAuthEvent")
GmmFSM.SendEvent(state, StartAuthEvent, fsm.ArgsType{})
case StartAuthEvent:
fmt.Printf("# Receiving Event: [%+v] on DeRegistered\n", event)
case fsm.ExitEvent:
fmt.Printf("# Receiving Event: [%+v] on DeRegistered\n", event)
default:
fmt.Println("Unknown event [%+v]", event)
}
}
func Authentication(state *fsm.State, event fsm.EventType, args fsm.ArgsType) {
switch event {
case fsm.EntryEvent:
fmt.Printf("## Receiving Event: [%+v] on Authentication\n", event)
fallthrough
case AuthRestartEvent:
fmt.Printf("## (Receiving Event: AuthRestartEvent on Authentication)\n")
case GmmMessageEvent:
fmt.Printf("## Receiving Event: [%+v] on Authentication\n", event)
fmt.Println("... Send Event: AuthSuccessEvent")
GmmFSM.SendEvent(state, AuthSuccessEvent, fsm.ArgsType{})
case AuthSuccessEvent:
fmt.Printf("## Receiving Event: [%+v] on Authentication\n", event)
case fsm.ExitEvent:
fmt.Printf("## Receiving Event: [%+v] on Authentication\n", event)
default:
fmt.Println("Unknown event [%+v]", event)
}
}
func SecurityMode(state *fsm.State, event fsm.EventType, args fsm.ArgsType) {
switch event {
case fsm.EntryEvent:
fmt.Printf("### Receiving Event: [%+v] on SecurityMode\n", event)
case GmmMessageEvent:
fmt.Printf("### Receiving Event: [%+v] on SecurityMode\n", event)
fmt.Println("... Send Event: SecurityModeSuccessEvent")
GmmFSM.SendEvent(state, SecurityModeSuccessEvent, fsm.ArgsType{})
case SecurityModeSuccessEvent:
fmt.Printf("### Receiving Event: [%+v] on SecurityMode\n", event)
case fsm.ExitEvent:
fmt.Printf("### Receiving Event: [%+v] on SecurityMode\n", event)
default:
fmt.Println("Unknown event [%+v]", event)
}
}
func ContextSetup(state *fsm.State, event fsm.EventType, args fsm.ArgsType) {
switch event {
case fsm.EntryEvent:
fmt.Printf("#### Receiving Event: [%+v] on ContextSetup\n", event)
case GmmMessageEvent:
fmt.Printf("#### Receiving Event: [%+v] on ContextSetup\n", event)
fmt.Println("... Send Event: ContextSetupSuccessEvent")
GmmFSM.SendEvent(state, ContextSetupSuccessEvent, fsm.ArgsType{})
case ContextSetupSuccessEvent:
fmt.Printf("#### Receiving Event: [%+v] on ContextSetup\n", event)
case fsm.ExitEvent:
fmt.Printf("#### Receiving Event: [%+v] on ContextSetup\n", event)
default:
fmt.Printf("Unknown event [%+v]", event)
}
}
func Registered(state *fsm.State, event fsm.EventType, args fsm.ArgsType) {
switch event {
case fsm.EntryEvent:
fmt.Printf("##### Receiving Event: [%+v] on Registered\n", event)
default:
fmt.Printf("Unknown event [%+v]", event)
}
}
(3)RANから、NASメッセージを受信した時には、SendEvent関数を起動することによって、fsm機構を動作させる
package main
import (
"fmt"
"github.com/free5gc/fsm"
"github.com/ttsubo/free5gc-fsm-sample/context"
"github.com/ttsubo/free5gc-fsm-sample/gmm"
"time"
)
func main() {
ue := context.NewDummyAmfUe()
time.Sleep(2 * time.Second)
fmt.Println("... Send Event: [GmmMessageEvent], after receiving [NAS-PDU: Registration request]")
gmm.GmmFSM.SendEvent(ue.State, gmm.GmmMessageEvent, fsm.ArgsType{})
fmt.Println("")
time.Sleep(5 * time.Second)
fmt.Println("... Send Event: [GmmMessageEvent], after receiving [NAS-PDU: Authentication Response]")
gmm.GmmFSM.SendEvent(ue.State, gmm.GmmMessageEvent, fsm.ArgsType{})
fmt.Println("")
time.Sleep(5 * time.Second)
fmt.Println("... Send Event: [GmmMessageEvent], after receiving [NAS-PDU: Security mode complete]")
gmm.GmmFSM.SendEvent(ue.State, gmm.GmmMessageEvent, fsm.ArgsType{})
fmt.Println("")
time.Sleep(5 * time.Second)
fmt.Println("... Send Event: [GmmMessageEvent], after receiving [NAS-PDU: Registration complete]")
gmm.GmmFSM.SendEvent(ue.State, gmm.GmmMessageEvent, fsm.ArgsType{})
}
あと、状態管理を担うコンテキストも定義します
package context
import (
"github.com/free5gc/fsm"
)
const (
Deregistered fsm.StateType = "Deregistered"
DeregistrationInitiated fsm.StateType = "DeregistrationInitiated"
Authentication fsm.StateType = "Authentication"
SecurityMode fsm.StateType = "SecurityMode"
ContextSetup fsm.StateType = "ContextSetup"
Registered fsm.StateType = "Registered"
)
type DummyAmfUe struct {
State *fsm.State
}
func (ue *DummyAmfUe) init() {
ue.State = fsm.NewState(Deregistered)
}
func NewDummyAmfUe() *DummyAmfUe {
ue := DummyAmfUe{}
ue.init()
return &ue
}
実際に、動かしてみる
コマンドの実行結果に基づき、サンプルアプリ実装をトレースすれば、fsm機構の習熟も可能だと思います。
% go run main.go
... Send Event: [GmmMessageEvent], after receiving [NAS-PDU: Registration request]
Receiving Event: [Gmm Message] on DeRegistered
... Send Event: StartAuthEvent
Receiving Event: [Exit event] on DeRegistered
Receiving Event: [Start Authentication] on DeRegistered
Receiving Event: [Entry event] on Authentication
(Receiving Event: AuthRestartEvent on Authentication)
... Send Event: [GmmMessageEvent], after receiving [NAS-PDU: Authentication Response]
Receiving Event: [Gmm Message] on Authentication
... Send Event: AuthSuccessEvent
Receiving Event: [Exit event] on Authentication
Receiving Event: [Authentication Success] on Authentication
Receiving Event: [Entry event] on SecurityMode
... Send Event: [GmmMessageEvent], after receiving [NAS-PDU: Security mode complete]
Receiving Event: [Gmm Message] on SecurityMode
... Send Event: SecurityModeSuccessEvent
Receiving Event: [Exit event] on SecurityMode
Receiving Event: [SecurityMode Success] on SecurityMode
Receiving Event: [Entry event] on ContextSetup
... Send Event: [GmmMessageEvent], after receiving [NAS-PDU: Registration complete]
Receiving Event: [Gmm Message] on ContextSetup
... Send Event: ContextSetupSuccessEvent
Receiving Event: [Exit event] on ContextSetup
Receiving Event: [ContextSetup Success] on ContextSetup
Receiving Event: [Entry event] on Registered
以上、free5GCのソースコードを理解するため、「どのようなfree5GCがメカニズムで動作しているのか?」の必要最低限なトピックを基礎知識としてまとめてみました。