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

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
Draft
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
3 changes: 3 additions & 0 deletions conf/upf.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@
// [Optional] Whether to enable End Marker Support
// "enable_end_marker": false,

// [Optional] Whether to enable TEID Allocation
"enable_ftup": false,

// [Optional] Whether to enable Notify BESS feature
// "enable_notify_bess": false,

Expand Down
1 change: 1 addition & 0 deletions docs/configuration-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ These are configurations commonly shared between P4-UPF and BESS-UPF.
| `max_req_retries` | 5 | No | Max retries for sending PFCP message towards SMF/SPGW-C |
| `resp_timeout` | 2s | No | Period to wait for a response from SMF/SPGW-C |
| `enable_end_marker` | false | No | |
| `enable_ftup` | false | No | Enable TEID Allocation |
| `enable_p4rt` | false | Yes for P4-UPF only | |
| `enable_gtpu_path_monitoring` | false | No | |
| `cpiface.enable_ue_ip_alloc` | false | No | Whether to enable UPF-based UE IP allocation |
Expand Down
1 change: 1 addition & 0 deletions pfcpiface/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Conf struct {
ReadTimeout uint32 `json:"read_timeout"` // TODO(max): convert to duration string
EnableNotifyBess bool `json:"enable_notify_bess"`
EnableEndMarker bool `json:"enable_end_marker"`
EnableFTUP bool `json:"enable_ftup"`
NotifySockAddr string `json:"notify_sockaddr"`
EndMarkerSockAddr string `json:"endmarker_sockaddr"`
LogLevel zapcore.Level `json:"log_level"`
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) {
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 || id > maxValue {
return
}
idGenerator.lock.Lock()
defer idGenerator.lock.Unlock()
delete(idGenerator.usedMap, id-minValue)
}

func (idGenerator *FTEIDGenerator) IsAllocated(id uint32) bool {
if id < minValue || id > maxValue {
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")
}
}
4 changes: 4 additions & 0 deletions pfcpiface/messages_conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ func (pConn *PFCPConn) associationIEs() []*ie.IE {
setUeipFeature(features...)
}

if upf.enableFTUP {
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
25 changes: 14 additions & 11 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,15 @@ func (p *pdr) parseFTEID(teidIE *ie.IE) error {
}

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

if teid != 0 {
p.tunnelTEID = teid
p.tunnelTEIDMask = 0xFFFFFFFF
p.tunnelIP4Dst = ip2int(tunnelIPv4Address)
p.tunnelIP4DstMask = 0xFFFFFFFF
if fteid.HasCh() {
p.UPAllocateFteid = true
} else {
if teid != 0 {
p.tunnelTEID = teid
p.tunnelTEIDMask = 0xFFFFFFFF
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
4 changes: 4 additions & 0 deletions pfcpiface/upf.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type upf struct {
enableUeIPAlloc bool
enableEndMarker bool
enableFlowMeasure bool
enableFTUP bool
accessIface string
coreIface string
ippoolCidr string
Expand All @@ -51,6 +52,7 @@ type upf struct {
reportNotifyChan chan uint64
sliceInfo *SliceInfo
readTimeout time.Duration
fteidGenerator *FTEIDGenerator

datapath
maxReqRetries uint8
Expand Down Expand Up @@ -117,6 +119,7 @@ func NewUPF(conf *Conf, fp datapath) *upf {
u := &upf{
enableUeIPAlloc: conf.CPIface.EnableUeIPAlloc,
enableEndMarker: conf.EnableEndMarker,
enableFTUP: conf.EnableFTUP,
enableFlowMeasure: conf.EnableFlowMeasure,
accessIface: conf.AccessIface.IfName,
coreIface: conf.CoreIface.IfName,
Expand All @@ -129,6 +132,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")
}
}