diff --git a/.wordlist.txt b/.wordlist.txt index 46f50c53c..107e1cdb9 100644 --- a/.wordlist.txt +++ b/.wordlist.txt @@ -123,6 +123,7 @@ sudo tc TCP tcpdump +TEID TRex UDP UE diff --git a/VERSION b/VERSION index ec802c662..227cea215 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.1-dev +2.0.0 diff --git a/go.mod b/go.mod index 04b6cc748..bac5f63cc 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/pfcpiface/fteid.go b/pfcpiface/fteid.go new file mode 100644 index 000000000..8d8c49d44 --- /dev/null +++ b/pfcpiface/fteid.go @@ -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 { + 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 +} diff --git a/pfcpiface/fteid_test.go b/pfcpiface/fteid_test.go new file mode 100644 index 000000000..d663948db --- /dev/null +++ b/pfcpiface/fteid_test.go @@ -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") + } +} diff --git a/pfcpiface/messages_conn.go b/pfcpiface/messages_conn.go index 7300ddf4d..ebed1366f 100644 --- a/pfcpiface/messages_conn.go +++ b/pfcpiface/messages_conn.go @@ -107,6 +107,8 @@ func (pConn *PFCPConn) associationIEs() []*ie.IE { setUeipFeature(features...) } + setFTUPFeature(features...) + if upf.enableEndMarker { setEndMarkerFeature(features...) } diff --git a/pfcpiface/messages_session.go b/pfcpiface/messages_session.go index f5a60bb0c..f5344f15f 100644 --- a/pfcpiface/messages_session.go +++ b/pfcpiface/messages_session.go @@ -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) @@ -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 } @@ -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 diff --git a/pfcpiface/parse_pdr.go b/pfcpiface/parse_pdr.go index 601fba496..d29b6dc38 100644 --- a/pfcpiface/parse_pdr.go +++ b/pfcpiface/parse_pdr.go @@ -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 @@ -390,13 +391,14 @@ func (p *pdr) parseFTEID(teidIE *ie.IE) error { } teid := fteid.TEID - tunnelIPv4Address := fteid.IPv4Address - - if teid != 0 { + if fteid.HasCh() { + 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 diff --git a/pfcpiface/session_pdr.go b/pfcpiface/session_pdr.go index afcf71d23..1b2b80a9b 100644 --- a/pfcpiface/session_pdr.go +++ b/pfcpiface/session_pdr.go @@ -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( diff --git a/pfcpiface/upf.go b/pfcpiface/upf.go index 33003eaeb..82e22bc41 100644 --- a/pfcpiface/upf.go +++ b/pfcpiface/upf.go @@ -51,6 +51,7 @@ type upf struct { reportNotifyChan chan uint64 sliceInfo *SliceInfo readTimeout time.Duration + fteidGenerator *FTEIDGenerator datapath maxReqRetries uint8 @@ -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, } diff --git a/pfcpiface/utils.go b/pfcpiface/utils.go index f340d8d32..5b715305d 100644 --- a/pfcpiface/utils.go +++ b/pfcpiface/utils.go @@ -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 diff --git a/pfcpiface/utils_test.go b/pfcpiface/utils_test.go index a5df2e659..8032a9f03 100644 --- a/pfcpiface/utils_test.go +++ b/pfcpiface/utils_test.go @@ -5,6 +5,7 @@ package pfcpiface import ( "github.com/stretchr/testify/require" + "github.com/wmnsk/go-pfcp/ie" "net" "reflect" @@ -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") + } +}