Free5GC源码研究(1) - 总体架构
我以前和以后的工作都和5G核心网有密切联系,所以今天下定决心研究一个5GC的项目源代码以增进对底层技术的理解。
5G网络背景知识
5G网络全称第五代移动网络(5th Generation Mobile Network),比起前面几代最大的特点就是更高速率、更低延迟、更大接入容量。一个部署好的5G网络,简单来说可以用下图来表示:
图中用户设备(UE)通过接入网(RAN)连接到核心网(5G Core),核心网统一对设备的请求处理后在连入到互联网。
要实现更高速率、更低延迟、更大接入容量这几个要求,5G网络在架构上使用了一些新的技术方案,最典型的是虚拟化和服务化。虚拟化的意思是把尽可能多的网络功能(Network Function,NF)用软件实现,而不是专门的硬件,如此一来在部署时只需要准备大量却少数几种的通用计算设施,而不用准备几十种不同类型的专门设施。这样的好处是更加灵活且便捷,也利于生产和采购。5G网络架构标准中定义了常用的一些网络功能,比如AMF、SMF、NRF等,这些NF就是5G核心网的基本组成部分。服务化的意思是各种功能使用微服务的形式来实现。综上所述,5G核心网在软件系统的角度就是一个容器化的微服务应用程序。
Free5GC软件架构
Free5GC的源代码在github上可以找到。整个核心网的所有NF模块都有各自的仓库,比如free5gc/amf和free5gc/smf,其主仓库free5gc/free5gc基本只起到统合的作用。要下载整个系统项目的所有代码,需要使用git的--recursive
参数。
$ git clone --recursive -b v3.4.3 https://github.com/free5gc/free5gc.git
截至目前为止,github上的仓库最早的版本是v3.0.1,最新的小版本是v3.4.3,中间还有十几个版本。那么应该选择哪一个版本研究呢?一般来说,最初的几个版本会只要包含实现其核心功能的代码,整体上会比较简洁和直接,代码量也会少一点,是最适合阅读的。但是最初的版本会缺少一些后面演化出来的重要功能。而且有时候最初的版本在架构设计上的考虑也会有所欠缺,经过及时和良好重构以后,后面的版本反而阅读体验会更好一点。Free5GC正是这“有时侯”的其中之一。综合来看,我选择直接看v3.4.3,不过我同时也会参考v3.0.1,甚至这之间的一些其他版本。
代码概览
把整一个系统项目的代码下载下来后,我们做一些观其大略式的分析。我首先做的事看看整个项目的结构(文件目录)以及规模(代码行数)。
下面是整个Free5GC的简化版代码结构
free5gc
├── cert
├── config
│ ├── multiAMF
│ └── multiUPF
├── NFs
│ ├── amf
│ ├── ausf
│ ├── n3iwf
│ ├── tngf
│ ├── chf
│ ├── nrf
│ ├── nssf
│ ├── pcf
│ ├── smf
│ ├── udm
│ ├── udr
│ └── upf
├── test
│ ├── app
│ ├── consumerTestdata
│ ├── nasTestpacket
│ ├── ngapTestpacket
│ ├── udpserver
│ └── ueRanEmulator
├── webconsole
├── test_ci.sh
├── test_ci_ulcl.sh
├── test_multiUPF.sh
├── test.sh
├── test_ulcl.sh
├── force_kill.sh
├── Makefile
├── make_gtp5gtunnel.sh
├── LICENSE
└── THIRD-PARTY-NOTICES.txt
项目根目录下只包含5个子目录,其中cert
里是CA网络证书文件、config
里是配置文件、test
里都是测试代码,这些都是相对不那么核心的文件。NFs
里面得则是核心网得所有网络功能得代码,这就是Free5GC的核心代码了,也是我们要重点阅读和研究的。webconsole
是Free5GC的一个图形化管理界面。如果只是想研究5G核心网的精髓,那么webconsole
可以暂时跳过不看。
我们使用cloc先统计一下整个free5gc的3.4.3版本大概有多少行代码
$ cloc free5gc
1187 text files.
954 unique files.
249 files ignored.
github.com/AlDanial/cloc v 2.00 T=16.78 s (56.9 files/s, 11155.2 lines/s)
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Go 811 21253 11086 132692
TypeScript 50 467 1181 5808
YAML 58 379 1827 5443
Text 1 558 0 2735
JSON 4 0 0 1724
Bourne Shell 15 237 68 1169
Markdown 8 72 5 179
make 2 26 1 83
JavaScript 2 2 0 50
CSS 2 6 0 43
HTML 1 3 20 17
-------------------------------------------------------------------------------
SUM: 954 23003 14188 149943
-------------------------------------------------------------------------------
可以看到项目总共大约15万行代码,其主要语言Golang的代码有约13万行。整体上这算一个中等规模的项目。然Open5GS当前版本v2.7.2将近150万行的规模,哪怕是最早的v0.1.0版核心C语言代码也有超20万行。相比之下,Free5GC可谓是相当“小巧”。
Free5GC的主要代码都被划分在NFs/
里,因此有必要再看看各个NF的情况:
$ cd free5gc
$ dir=$(ls "NFs"); \
for i in $dir; \
do cloc "NFs/$i" --report-file="$i.stats" --quiet; \
done && \
cloc --sum-reports *.stats --quiet && \
rm *.stats
github.com/AlDanial/cloc v 2.02
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Go 755 18039 9764 114719
YAML 44 340 2045 2498
Markdown 2 2 0 6
-------------------------------------------------------------------------------
SUM: 801 18381 11809 117223
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
File files blank comment code
-------------------------------------------------------------------------------
amf.stats 81 4074 2034 32662
smf.stats 91 2593 938 16794
n3iwf.stats 47 2532 993 13196
tngf.stats 49 2321 3594 10189
chf.stats 268 1547 796 7499
pcf.stats 38 762 767 7152
udr.stats 70 1223 777 6576
upf.stats 34 778 320 6447
udm.stats 39 908 417 5948
nrf.stats 32 710 473 5362
nssf.stats 28 531 456 2873
ausf.stats 24 402 244 2525
-------------------------------------------------------------------------------
SUM: 801 18381 11809 117223
-------------------------------------------------------------------------------
可见最大的NF是amf
,总共含有3万多行代码,其次的smf
也有近1万7千行,而最小的ausf
只有两千多行。毕竟amf
和smf
分别管理连接和会话,都是最重要和复杂的功能,所以代码量多也是正常。但我们如果要研究源代码的话,最好是先从简单的,比如ausf
读起。
前文已经观察过整个Free5GC项目的代码结构,现在我们看看每一个NF组件内部的代码结构。我们对比一下smf
和ausf
两个NF的结构
$ tree -d NFs/smf $ tree -d NFs/ausf
NFs/smf NFs/ausf
├── cmd ├── cmd
├── internal ├── internal
│ ├── context │ ├── context
│ │ └── pool │ ├── logger
│ ├── logger │ ├── sbi
│ ├── pfcp │ │ ├── consumer
│ │ ├── handler │ │ └── processor
│ │ ├── message │ └── util
│ │ └── udp └── pkg
│ ├── sbi ├── app
│ │ ├── consumer ├── factory
│ │ └── processor └── service
│ └── util
│ └── oauth
└── pkg
├── app
├── factory
├── service
└── utils
可以看到这两个NF的文件目录很相似,都包含cmd
、internal
、和pkg
三个子目录,其中
cmd
目录里面都只有一个文件main.go
,这个文件是编译这个NF时的入口,负责生成一个二进制的可执行文件internal
都有子目录context
、logger
、sbi
和util
,这些代码是本NF模块内部使用的代码,Go编译器会保证编译时其他模块不能使用这些代码pkg
都有子目录factory
和service
,与internal
相反,这些代码则是提供给其他NF模块调用的功能服务
如果逐个NF查看,就能发现所有NF都有着这样类似的代码目录。这样的代码目录其实是Go语言模块典型的代码组织形式。对于这部分感兴趣的可以去github看一下golang-standards/project-layout,里面有详细解释,甚至有中文。
编译运行项目代码
对于源代码文件的概览到此暂停一下,让我们抽身出来按照项目的安装指引编译和运行一下这个项目,看看编译这些代码需要安装哪些依赖,会产生哪些可执行文件,以及怎么跑这些可执行文件。Free5GC官方推荐使用docker来安装。使用docker的确省时省力,一行docker compose up
就能全自动安装运行所有东西(但也需要预先安装好GTP5G内核模块,这一点无论是否使用docker都要做)。docker大法好,但是对于想要深入理解的我们来说,还是按照传统法子一步步准备更能加深理解。
在安装前,需要确认一下Linux内核版本为5.0.0-23-generic
或者5.4.x
。此外Free5GC使用MongoDB来存储数据,所以对于4.4版本以后的MongoDB,CPU要支持AVX标记。然后就是安装好数据库(MongoDB)、构建工具(Go、gcc、g++、cmake、autoconf, etc.)、以及各种动态库。最后还要做一些Linux的网络配置,主要是开启Linux的IP转发功能,添加一些转发规则,以及关闭防火前以免影响IP转发。一些且准备就绪以后就可以开始编译了。
安装gtp5p
5G网络毕竟是计算机底层的事情,所以会需要Linux内核的支持。Free5GC的UPF模块需要用到一个叫gtp5g的内核模块来处理网络数据包。因此,在编译Free5GC以前,我们先安装gtp5p
$ git clone https://github.com/free5gc/gtp5g.git
$ cd gtp5g
$ make
$ sudo make install
编译free5gc
编译free5gc的命令也很简单,进入free5gc
的根目录后,make
一下就好
$ cd /path/to/free5gc
$ make
跑完make命令以后,free5gc
目录下会出现一个新的bin
目录,里面都是各个NF的二进制可执行文件:
$ ls ./bin
amf ausf chf n3iwf nrf nssf pcf smf udm udr upf
整个编译过程就是把各个NF模块力得Go语言源码编译成二进制可执行文件的过程。观察Makefile(有所省略)
GO_BIN_PATH = bin
GO_SRC_PATH = NFs
NF = $(GO_NF)
GO_NF = amf ausf nrf nssf pcf smf udm udr n3iwf upf chf
$(GO_BIN_PATH)/%: $(NF_GO_FILES)
# $(@F): The file-within-directory part of the file name of the target.
@echo "Start building $(@F)...."
cd $(GO_SRC_PATH)/$(@F)/cmd && \
CGO_ENABLED=0 go build -gcflags "$(GCFLAGS)" -ldflags "$(LDFLAGS)" -o $(ROOT_PATH)/$@ main.go
可知编译一个NF模块等价于进入这个模块的cmd/
目录,然后跑go build
对于不常使用Makefile的用户,可以看看v3.0.1版本的构建文件。在那个时候,项目的构建用的还不是make,而只是一个shell脚本build.sh
:
#!/usr/bin/env bash
NF_LIST="amf ausf nrf nssf pcf smf udm udr n3iwf"
for NF in ${NF_LIST}; do
echo "Start build ${NF}...."
go build -o bin/${NF} -x src/${NF}/${NF}.go # v3.0.1 的文件结构和v3.4.3有所不同,用`src`目录组织NF模块
done
echo "Start build UPF...." # 最早的UPF使用C写的,到v3.2.0才用Go重写了
cd src/upf # cwd is upf
rm -rf build
mkdir -p build
cd ./build
cmake ..
make -j$(nproc)
如果之前没有编译安装GTP5P,需要现在
运行
编译完成以后,我们可以运行项目。运行这个项目,只需要逐个运行编译后NF的可执行文件即可。比如说:
ubuntu@VM-0-6-ubuntu:~/free5gc$ export GIN_MODE=release # 或者`export GIN_MODE=debug`
ubuntu@VM-0-6-ubuntu:~/free5gc$ bin/nrf -c config/nrfcfg.yaml
2024-09-07T21:04:19.159089575+08:00 [INFO][NRF][Main] NRF version:
free5GC version: v3.4.3
commit hash: 21c66562
commit time: 2024-01-30T10:44:59Z
go version: go1.21.8 linux/amd64
2024-09-07T21:04:19.159904976+08:00 [INFO][NRF][CFG] Read config from [config/nrfcfg.yaml]
2024-09-07T21:04:19.160254489+08:00 [INFO][NRF][Main] Log enable is set to [true]
2024-09-07T21:04:19.160331943+08:00 [INFO][NRF][Main] Log level is set to [info]
2024-09-07T21:04:19.160389621+08:00 [INFO][NRF][Main] Report Caller is set to [false]
2024-09-07T21:04:19.160450124+08:00 [INFO][NRF][Init] nrfconfig Info: Version[1.0.2] Description[NRF initial local configuration]
2024-09-07T21:04:19.161028774+08:00 [INFO][NRF][Init] generate new NRF cert
2024-09-07T21:04:19.163205479+08:00 [INFO][NRF][Init] Server starting
2024-09-07T21:04:19.164046848+08:00 [INFO][NRF][Init] Binding addr: [127.0.0.10:8000]
每运行一个NF,都会开启一个服务器程序,监听网络端口,并且相应HTTP请求。
逐个运行编译后NF的可执行文件,这就是Free5GC官方在v3.0.1版本中提供的run.sh
脚本文件所做的所有事情。下面是有所删减的简洁版本:
#!/usr/bin/env bash
cd src/upf/build
sudo ./bin/free5gc-upfd &
sleep 1
sudo ip link set dev upfgtp0 mtu 1500
cd ../../..
NF_LIST="nrf amf smf udr pcf udm nssf ausf"
# 逐个运行编译后NF的可执行文件
for NF in ${NF_LIST}; do
./bin/${NF} & # 当时还没有使用配置文件
PID_LIST+=($!)
done
sudo ./bin/n3iwf &
当然在v3.4.3版本中还做了其他事情,比如设置环境变量,传入配置文件之类的。
- Credit: Image from this post