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/amffree5gc/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只有两千多行。毕竟amfsmf分别管理连接和会话,都是最重要和复杂的功能,所以代码量多也是正常。但我们如果要研究源代码的话,最好是先从简单的,比如ausf读起。

前文已经观察过整个Free5GC项目的代码结构,现在我们看看每一个NF组件内部的代码结构。我们对比一下smfausf两个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的文件目录很相似,都包含cmdinternal、和pkg三个子目录,其中

  • cmd目录里面都只有一个文件main.go,这个文件是编译这个NF时的入口,负责生成一个二进制的可执行文件
  • internal都有子目录 contextloggersbiutil,这些代码是本NF模块内部使用的代码,Go编译器会保证编译时其他模块不能使用这些代码
  • pkg都有子目录factoryservice,与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版本中还做了其他事情,比如设置环境变量,传入配置文件之类的。


posted @ 2024-09-13 21:09  zrq96  阅读(16)  评论(0编辑  收藏  举报