[Trace Code] Go UPF - ianchen0119/Introduce-to-5GC GitHub Wiki

UPF、AMF、SMF 是整個核心網路最為重要的 NFs,UPF 負責了所有 UE 的 Uplink & Downlink Data Packet,除了 5G 基地台傳輸效能外,UPF 的吞吐量也會影響這個 5G 網路的傳輸效率。

image

UPF 一共有 N3、N4、N6、N9 四個 Interface,它們的作用分別是:

  • N3 用於建立與 RAN 之間的聯繫,任何上下行封包都會經過 N3 (Based on GTP protocol)。
  • N4 用來負責與 SMF 溝通,任何與 PDU Session、QoS 有關的東西都由 SMF 建立 PFCP Procedure 告訴 UPF(N4 is Based on PFCP protocol)。
  • N6 用來將封包傳送給 Public Data Network(外部網路):
    • 當 UPF 收到來自 N3/N9 的 GTP 封包時,它會將封包進行解封裝與 SNAT 再轉發到外部網路。
    • 當 UPF 收到來自外部網路的 IP 封包,它需要將封包的目標 IP 轉換回正確的 UE IP。
  • N9 用於連結其他 UPF,供 ULCL 的場景使用。

Packet processing flow

任何從 UE 送來、或是要送給 UE 的資料流都會經過下面的 Packet Processing flow:

  • MAR: Multi-Access Rule
  • FAR: Forwarding Action Rule
  • PDR: Packet Detection Rule
  • PDI: Packet Detection Information
  • QER: QoS Enforcement Rule
  • URR: Usage Reporting Rules
  • SEID: Session Endpoint Identifier
  • F-SEID: Fully Qualified SEID The SEID uniquely identifies a PFCP session at an IP address of a PFCP entity. The F-SEID is the Fully Qualified SEID and it contains the SEID and IP address.
  • F-TEID: Fully Qualified TEID

簡單來說,當一個資料流進到 UPF 時,會先使用 PDR 檢查封包到底屬於哪一個 PDU Session,這時:

  • 如果檢測不到,UPF 會丟棄該封包
  • 如果 PDR matching 成功,這個封包會繼續被套用到 MAR、FAR(要轉發到哪裡?或是要先 Buffer 起來?)、QER(這個 Session 的 QoS 如何?)...

至於為什麼要有 QoS 這樣的東西出現,道理也非常簡單:就是為了流量控制,像是現在台灣最低的 4G 吃到飽方案已經下探到 200 左右,電信商不可能提供給 299、499 甚至是 999 月租費的用戶一樣的網路品質,這時候他們就可以透過 OAM 去設定像是 AMBR、MBR、GBR 等參數去限制每一個用戶能獲得什麼樣的連線品質囉。

原始程式碼解說

架構圖取自:https://github.com/khirono/aps-upf-doc

本篇文章探討的是 UPF 的 control-plane,它負責處理來自 SMF 的控制訊號,並且轉換為 data-plane 模組看得懂的 flow rule(概念有點像是 SDN)。

透過先前的“5G NF 通用框架”章節的內容,我們可以快速的鎖定 Network Function 中不同功能分別實作在何處,GO-UPF 由 5 個子模組所組成:

1. forwarder

用於控制 gtp5g kernel module,運作原理如下:

  • 在初始化時,透過 netlink 新增一個 socket。
  • 透過建立好的 netlink client 發送 rtnetlink 訊息:
    • 建立 gtp5g device
    • 新增 gtp5g device 的路由(呼叫 RouteAdd() 函式)

初始化完成後,其他模組可以呼叫 forwarder 提供的 newSdfFilter() 新增 SDF,該函式會呼叫 newFlowDesc() 函式新增 flow description。 除此之外,forwarder 還提供了其他 api 讓相關的 rule 可以傳送至 gtp5g 執行:

  • CreatePDR()UpdatePDR()RemovePDR()
  • CreateFAR()UpdateFAR()RemoveFAR()
  • CreateQER()UpdateQER()RemoveQER()
  • ...

2. gtpv1

用於構造 gtpv1 protocol 的封包,當 UPF 收到 PFCP Modification Request 更新 Flow rules 會檢查是否有 buffered data 的狀態從 BUFF 變為 FORW(Forward),如果有,會呼叫 g.WritePacket(far, qer, pkt) 將 buffered packets 送往 gtp5g 轉發。 3. logger

4. pfcp

pfcp 模組用於處理來自 SMF 的 PFCP 訊息:

func (s *PfcpServer) reqDispacher(msg message.Message, addr net.Addr) error {
	switch req := msg.(type) {
	case *message.HeartbeatRequest:
		s.handleHeartbeatRequest(req, addr)
	case *message.AssociationSetupRequest:
		s.handleAssociationSetupRequest(req, addr)
	case *message.AssociationUpdateRequest:
		s.handleAssociationUpdateRequest(req, addr)
	case *message.AssociationReleaseRequest:
		s.handleAssociationReleaseRequest(req, addr)
	case *message.SessionEstablishmentRequest:
		s.handleSessionEstablishmentRequest(req, addr)
	case *message.SessionModificationRequest:
		s.handleSessionModificationRequest(req, addr)
	case *message.SessionDeletionRequest:
		s.handleSessionDeletionRequest(req, addr)
	default:
		return errors.Errorf("pfcp reqDispacher unknown msg type: %d", msg.MessageType())
	}
	return nil
}
  • HeartBeat 是 SMF 可以用來檢查 UPF 是否保持運作狀態的方式。
  • 根據 spec 定義,UPF 和 SMF 都只會受理有向自己建立 association 的 node(UPF -> SMF 或是 SMF -> UPF)。
  • 當 SMF 收到 PDU Session 相關請求需要為 Session 建立通道時,會向 UPF 發送 PFCP Session Establishment。
    • 相對的,如果要變更或刪除 Flow rule,SMF 會向 UPF 發送 Session Modification。

以 PFCP Session Establishment 為例,UPF 會進行以下處理:

  • rnode, ok := s.rnodes[rnodeid]:檢查發送 PFCP 訊息的 SMF 是否為已經建立 association 的 PFCP node?
  • sess := rnode.NewSess(fseid.SEID):建立一個 session。
  • 建立 PDR、FAR、URR、QER、BAR 等 rules:
for _, i := range req.CreateFAR {
		err = sess.CreateFAR(i)
		if err != nil {
			sess.log.Errorf("Est CreateFAR error: %+v", err)
		}
	}

以 FAR 為例,在呼叫 sess.CreateFAR(i) 時會有兩個行為:

  1. 告訴 forwarder 要建立 FAR
  2. 將 FAR ID 存入 Session 紀錄 FAR 的 MAP

以下為一個 session 的資料結構:

type Sess struct {
	rnode    *RemoteNode
	LocalID  uint64
	RemoteID uint64
	PDRIDs   map[uint16]struct{}
	FARIDs   map[uint32]struct{}
	QERIDs   map[uint32]struct{}
	URRIDs   map[uint32]struct{}
	BARIDs   map[uint8]struct{}
	q        map[uint16]chan []byte // key: PDR_ID
	qlen     int
	log      *logrus.Entry
}

每一個 Session 會紀錄:

  • 對應的 SMF node 的資訊
  • Local 以及 Remote 的 session id
  • Rule map
  • buffered packets queue,用 PDR ID 當成鍵值
    • ServeReport() 收到來自 gtp5g 的 Downlink data report,並且有 buffered packets 需要處理,該函式會找出 report 對應到的 session 並且呼叫 sess.Push(dldr.PDRID, rp.BufPkt) 將 buffered packets 儲存到 queue。

5. report

report 模組定義了 report 相關的 handler interface:

type Handler interface {
	NotifySessReport(SessReport)
	PopBufPkt(uint64, uint16) ([]byte, bool)
}

以及 report 相關的資料結構:

const (
	DLDR = iota + 1
	USAR
	ERIR
	UPIR
	TMIR
	SESR
	UISR
)

type Report interface {
	Type() int
}

type DLDReport struct {
	PDRID uint16
}

func (r DLDReport) Type() int {
	return DLDR
}

type USAReport struct {
	URRID       uint32
	URSEQN      uint32
	USARTrigger UsageReportTrigger
}

type UsageReportTrigger struct {
	PERIO uint8
	VOLTH uint8
	TIMTH uint8
	QUHTI uint8
	START uint8
	STOPT uint8
	DROTH uint8
	IMMER uint8
	VOLQU uint8
	TIMQU uint8
	LIUSA uint8
	TERMR uint8
	MONIT uint8
	ENVCL uint8
	MACAR uint8
	EVETH uint8
	EVEQU uint8
	TEBUR uint8
	IPMJL uint8
	QUVTI uint8
	EMRRE uint8
}

func (r USAReport) Type() int {
	return USAR
}

const (
	DROP = 1 << 0
	FORW = 1 << 1
	BUFF = 1 << 2
	NOCP = 1 << 3
)

type SessReport struct {
	SEID   uint64
	Report Report
	Action uint16
	BufPkt []byte
}

type BufInfo struct {
	SEID  uint64
	PDRID uint16
}

總結

這個 Go-upf 專案相較原本使用 C 語言撰寫的 UPF 要精簡許多,只要了解 TS 29.244 所描述的 UP Function 行為後都可以非常快的搞懂整個程式架構與設計。 其中,與 netlink 有關的邏輯被獨立至一個外部的套件進行維護,所以在閱讀專案原始碼時不需要了解太多 Linux Programming 的技巧,總體來說是一個好維護、開發的專案。

References