Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add F-TEID allocation support #840

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ sudo
tc
TCP
tcpdump
TEID
TRex
UDP
UE
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.5.1-dev
2.0.0
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/omec-project/upf-epc

go 1.21
go 1.23

require (
github.com/Showmax/go-fqdn v1.0.0
Expand Down
75 changes: 75 additions & 0 deletions pfcpiface/fteid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Canonical Ltd.

package pfcpiface

import (
"errors"
"math"
"sync"
)

const (
minValue = 1
maxValue = math.MaxUint32
)

type FTEIDGenerator struct {
lock sync.Mutex
offset uint32
usedMap map[uint32]bool
}

func NewFTEIDGenerator() *FTEIDGenerator {
return &FTEIDGenerator{
offset: 0,
usedMap: make(map[uint32]bool),
}
}

// Allocate and return an id in range [minValue, maxValue]
func (idGenerator *FTEIDGenerator) Allocate() (uint32, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not efficient.. In case we establish few hundred or thousand tunnels then this will become slow. Anything better we can do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is using the exact same algorithm as was used in the SMF. While we may want to change it for something efficient, I would decouple this performance improvement from this change here which is focused on moving f-teid allocation from the SMF to the UPF.

Reference:

idGenerator.lock.Lock()
defer idGenerator.lock.Unlock()

offsetBegin := idGenerator.offset
for {
if _, ok := idGenerator.usedMap[idGenerator.offset]; ok {
idGenerator.updateOffset()

if idGenerator.offset == offsetBegin {
return 0, errors.New("no available value range to allocate id")
}
} else {
break
}
}
idGenerator.usedMap[idGenerator.offset] = true
id := idGenerator.offset + minValue
idGenerator.updateOffset()
return id, nil
}

func (idGenerator *FTEIDGenerator) FreeID(id uint32) {
if id < minValue {
return
}
idGenerator.lock.Lock()
defer idGenerator.lock.Unlock()
delete(idGenerator.usedMap, id-minValue)
}

func (idGenerator *FTEIDGenerator) IsAllocated(id uint32) bool {
if id < minValue {
return false
}
idGenerator.lock.Lock()
defer idGenerator.lock.Unlock()
_, ok := idGenerator.usedMap[id-minValue]
return ok
}

func (idGenerator *FTEIDGenerator) updateOffset() {
idGenerator.offset++
idGenerator.offset = idGenerator.offset % maxValue
}
39 changes: 39 additions & 0 deletions pfcpiface/fteid_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Canonical Ltd.

package pfcpiface_test

import (
"testing"

"github.com/omec-project/upf-epc/pfcpiface"
)

func TestFTEIDAllocate(t *testing.T) {
fteidGenerator := pfcpiface.NewFTEIDGenerator()

fteid, err := fteidGenerator.Allocate()
if err != nil {
t.Errorf("FTEID allocation failed: %v", err)
}
if fteid < 1 {
t.Errorf("FTEID allocation failed, value is too small: %v", fteid)
}
if !fteidGenerator.IsAllocated(fteid) {
t.Errorf("FTEID was not allocated")
}
}

func TestFTEIDFree(t *testing.T) {
fteidGenerator := pfcpiface.NewFTEIDGenerator()
fteid, err := fteidGenerator.Allocate()
if err != nil {
t.Errorf("FTEID allocation failed: %v", err)
}

fteidGenerator.FreeID(fteid)

if fteidGenerator.IsAllocated(fteid) {
t.Errorf("FTEID was not freed")
}
}
2 changes: 2 additions & 0 deletions pfcpiface/messages_conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ func (pConn *PFCPConn) associationIEs() []*ie.IE {
setUeipFeature(features...)
}

setFTUPFeature(features...)

if upf.enableEndMarker {
setEndMarkerFeature(features...)
}
Expand Down
16 changes: 14 additions & 2 deletions pfcpiface/messages_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,18 @@ func (pConn *PFCPConn) handleSessionEstablishmentRequest(msg message.Message) (m
return errProcessReply(err, ie.CauseRequestRejected)
}

if p.UPAllocateFteid {
var fteid uint32
fteid, err = pConn.upf.fteidGenerator.Allocate()
if err != nil {
return errProcessReply(err, ie.CauseNoResourcesAvailable)
}
p.tunnelTEID = fteid
p.tunnelTEIDMask = 0xFFFFFFFF
p.tunnelIP4Dst = ip2int(upf.accessIP)
p.tunnelIP4DstMask = 0xFFFFFFFF
}

p.fseidIP = fseidIP
session.CreatePDR(p)
addPDRs = append(addPDRs, p)
Expand Down Expand Up @@ -164,8 +176,7 @@ func (pConn *PFCPConn) handleSessionEstablishmentRequest(msg message.Message) (m
ie.NewCause(ie.CauseRequestAccepted), /* accept it blindly for the time being */
localFSEID,
)

addPdrInfo(seres, &session)
addPdrInfo(seres, addPDRs)

return seres, nil
}
Expand Down Expand Up @@ -231,6 +242,7 @@ func (pConn *PFCPConn) handleSessionModificationRequest(msg message.Message) (me
session.CreatePDR(p)
addPDRs = append(addPDRs, p)
}
logger.PfcpLog.Debugln("PDRs added:", addPDRs)

for _, cFAR := range smreq.CreateFAR {
var f far
Expand Down
18 changes: 10 additions & 8 deletions pfcpiface/parse_pdr.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,11 @@ type applicationFilter struct {
}

type pdr struct {
srcIface uint8
tunnelIP4Dst uint32
tunnelTEID uint32
ueAddress uint32
UPAllocateFteid bool
srcIface uint8
tunnelIP4Dst uint32
tunnelTEID uint32
ueAddress uint32

srcIfaceMask uint8
tunnelIP4DstMask uint32
Expand Down Expand Up @@ -390,13 +391,14 @@ func (p *pdr) parseFTEID(teidIE *ie.IE) error {
}

teid := fteid.TEID
tunnelIPv4Address := fteid.IPv4Address

if teid != 0 {
if fteid.HasCh() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you pls help with this. What does this do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the SMF wants the UPF to allocate the F-TEID, which is the case in 3GPP release 16, the SMF sets the CHOOSE flag in the Local F-TEID of its PFCP association setup request. Here, the UPF checks if this flag is set. If it is, it sets UPAllocateFteid to True which will result in the UPF allocating an F-TEID.

From the 3GPP spec:

The SMF shall request the UPF to allocate the F-TEID by setting the CHOOSE flag in the Local F-TEID. And if the PDR(s) is created successfully, the UPF shall return the F-TEID(s) it has assigned to the PDR(s) in the PFCP Session Establishment Response or PFCP Session Modification Response.

p.UPAllocateFteid = true
} else if teid != 0 {
p.tunnelTEID = teid
p.tunnelTEIDMask = 0xFFFFFFFF
p.tunnelIP4Dst = ip2int(tunnelIPv4Address)
p.tunnelIP4Dst = ip2int(fteid.IPv4Address)
p.tunnelIP4DstMask = 0xFFFFFFFF

}

return nil
Expand Down
19 changes: 12 additions & 7 deletions pfcpiface/session_pdr.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,28 @@ func releaseAllocatedIPs(ippool *IPPool, session *PFCPSession) error {
return ippool.DeallocIP(session.localSEID)
}
}

return nil
}

func addPdrInfo(msg *message.SessionEstablishmentResponse,
session *PFCPSession) {
func addPdrInfo(msg *message.SessionEstablishmentResponse, pdrs []pdr) {
logger.PfcpLog.Infoln("add PDRs with UPF alloc IPs to Establishment response")

for _, pdr := range session.pdrs {
logger.PfcpLog.Infoln("PDRs:", pdrs)
for _, pdr := range pdrs {
logger.PfcpLog.Infoln("pdrID:", pdr.pdrID)
if pdr.UPAllocateFteid {
logger.PfcpLog.Infoln("adding PDR with tunnel TEID:", pdr.tunnelTEID)
msg.CreatedPDR = append(msg.CreatedPDR,
ie.NewCreatedPDR(
ie.NewPDRID(uint16(pdr.pdrID)),
ie.NewFTEID(0x01, pdr.tunnelTEID, int2ip(pdr.tunnelIP4Dst), nil, 0),
))
}
if (pdr.allocIPFlag) && (pdr.srcIface == core) {
logger.PfcpLog.Debugln("pdrID:", pdr.pdrID)

var (
flags uint8 = 0x02
ueIP net.IP = int2ip(pdr.ueAddress)
)

logger.PfcpLog.Debugln("ueIP:", ueIP.String())
msg.CreatedPDR = append(msg.CreatedPDR,
ie.NewCreatedPDR(
Expand Down
2 changes: 2 additions & 0 deletions pfcpiface/upf.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type upf struct {
reportNotifyChan chan uint64
sliceInfo *SliceInfo
readTimeout time.Duration
fteidGenerator *FTEIDGenerator

datapath
maxReqRetries uint8
Expand Down Expand Up @@ -129,6 +130,7 @@ func NewUPF(conf *Conf, fp datapath) *upf {
maxReqRetries: conf.MaxReqRetries,
enableHBTimer: conf.EnableHBTimer,
readTimeout: time.Second * time.Duration(conf.ReadTimeout),
fteidGenerator: NewFTEIDGenerator(),
n4addr: conf.N4Addr,
}

Expand Down
7 changes: 7 additions & 0 deletions pfcpiface/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ func setUeipFeature(features ...uint8) {
}
}

// Set the 5th bit of the first octet to 1.
func setFTUPFeature(features ...uint8) {
if len(features) >= 1 {
features[0] = features[0] | 0x10
}
}

func setEndMarkerFeature(features ...uint8) {
if len(features) >= 2 {
features[1] = features[1] | 0x01
Expand Down
25 changes: 25 additions & 0 deletions pfcpiface/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package pfcpiface

import (
"github.com/stretchr/testify/require"
"github.com/wmnsk/go-pfcp/ie"

"net"
"reflect"
Expand Down Expand Up @@ -91,3 +92,27 @@ func TestGetSliceTcMeterIndex(t *testing.T) {
)
}
}

func TestSetUeipFeature(t *testing.T) {
features := make([]uint8, 4)

setUeipFeature(features...)

ie := ie.NewUPFunctionFeatures(features...)
hasUeIPAlloc := ie.HasUEIP()
if !hasUeIPAlloc {
t.Errorf("Expected UEIPAlloc to be set")
}
}

func TestSetFTUPFeature(t *testing.T) {
features := make([]uint8, 4)

setFTUPFeature(features...)

ie := ie.NewUPFunctionFeatures(features...)
hasFTUP := ie.HasFTUP()
if !hasFTUP {
t.Errorf("Expected FTUP to be set")
}
}