From 353cdd70519e56d31291b34d52d38077b7b29f87 Mon Sep 17 00:00:00 2001 From: Laura Henning Date: Tue, 10 Sep 2024 14:37:24 +0000 Subject: [PATCH 01/16] refactor: add UPNodeInterface for UPF and gNB UPNodes to improve code reuse --- internal/context/datapath.go | 46 +- internal/context/pfcp_reports.go | 2 +- internal/context/sm_context.go | 10 +- internal/context/sm_context_policy_test.go | 4 +- internal/context/ue_datapath.go | 11 +- internal/context/upf.go | 208 ++++-- internal/context/upf_test.go | 52 +- internal/context/user_plane_information.go | 605 ++++++++++-------- .../context/user_plane_information_test.go | 6 +- internal/pfcp/message/send.go | 32 +- internal/sbi/api_upi.go | 12 +- internal/sbi/processor/association.go | 10 +- internal/sbi/processor/charging_trigger.go | 2 +- internal/sbi/processor/datapath.go | 9 +- internal/sbi/processor/pdu_session_test.go | 2 +- internal/sbi/processor/ulcl_procedure.go | 10 +- pkg/factory/config.go | 2 - pkg/utils/pfcp_util.go | 6 +- 18 files changed, 604 insertions(+), 425 deletions(-) diff --git a/internal/context/datapath.go b/internal/context/datapath.go index 3b540813..84bfc89a 100644 --- a/internal/context/datapath.go +++ b/internal/context/datapath.go @@ -135,7 +135,7 @@ func (node *DataPathNode) ActivateUpLinkTunnel(smContext *SMContext) error { destUPF := node.UPF if node.UpLinkTunnel.PDR, err = destUPF.AddPDR(); err != nil { - logger.CtxLog.Errorln("In ActivateUpLinkTunnel UPF IP: ", node.UPF.NodeID.ResolveNodeIdToIp().String()) + logger.CtxLog.Errorln("In ActivateUpLinkTunnel UPF IP: ", node.GetNodeIP()) logger.CtxLog.Errorln("Allocate PDR Error: ", err) return fmt.Errorf("Add PDR failed: %s", err) } @@ -159,7 +159,7 @@ func (node *DataPathNode) ActivateDownLinkTunnel(smContext *SMContext) error { destUPF := node.UPF if node.DownLinkTunnel.PDR, err = destUPF.AddPDR(); err != nil { - logger.CtxLog.Errorln("In ActivateDownLinkTunnel UPF IP: ", node.UPF.NodeID.ResolveNodeIdToIp().String()) + logger.CtxLog.Errorln("In ActivateDownLinkTunnel UPF IP: ", node.GetNodeIP()) logger.CtxLog.Errorln("Allocate PDR Error: ", err) return fmt.Errorf("Add PDR failed: %s", err) } @@ -244,20 +244,12 @@ func (node *DataPathNode) DeactivateDownLinkTunnel(smContext *SMContext) { } } -func (node *DataPathNode) GetUPFID() (id string, err error) { - node_ip := node.GetNodeIP() - var exist bool - - if id, exist = smfContext.UserPlaneInformation.UPFsIPtoID[node_ip]; !exist { - err = fmt.Errorf("UPNode IP %s doesn't exist in smfcfg.yaml", node_ip) - return "", err - } - - return id, nil +func (node *DataPathNode) GetUPFID() uuid.UUID { + return node.UPF.GetID() } func (node *DataPathNode) GetNodeIP() (ip string) { - ip = node.UPF.NodeID.ResolveNodeIdToIp().String() + ip = node.UPF.GetNodeIDString() return } @@ -319,17 +311,17 @@ func (dataPath *DataPath) String() string { for curDPNode := firstDPNode; curDPNode != nil; curDPNode = curDPNode.Next() { str += strconv.Itoa(index) + "th Node in the Path\n" str += "Current UPF IP: " + curDPNode.GetNodeIP() + "\n" - str += "Current UPF ID: " + curDPNode.UPF.GetUPFID() + "\n" + str += "Current UPF ID: " + curDPNode.UPF.GetID().String() + "\n" if curDPNode.Prev() != nil { str += "Previous UPF IP: " + curDPNode.Prev().GetNodeIP() + "\n" - str += "Previous UPF ID: " + curDPNode.Prev().UPF.GetUPFID() + "\n" + str += "Previous UPF ID: " + curDPNode.Prev().UPF.GetID().String() + "\n" } else { str += "Previous UPF IP: None\n" } if curDPNode.Next() != nil { str += "Next UPF IP: " + curDPNode.Next().GetNodeIP() + "\n" - str += "Next UPF ID: " + curDPNode.Next().UPF.GetUPFID() + "\n" + str += "Next UPF ID: " + curDPNode.Next().UPF.GetID().String() + "\n" } else { str += "Next UPF IP: None\n" } @@ -340,8 +332,8 @@ func (dataPath *DataPath) String() string { return str } -func getUrrIdKey(uuid string, urrId uint32) string { - return uuid + ":" + strconv.Itoa(int(urrId)) +func getUrrIdKey(uuid uuid.UUID, urrId uint32) string { + return uuid.String() + ":" + strconv.Itoa(int(urrId)) } func GetUpfIdFromUrrIdKey(urrIdKey string) string { @@ -352,7 +344,7 @@ func (node DataPathNode) addUrrToNode(smContext *SMContext, urrId uint32, isMeas var urr *URR var ok bool var err error - currentUUID := node.UPF.UUID() + currentUUID := node.UPF.GetID() id := getUrrIdKey(currentUUID, urrId) if urr, ok = smContext.UrrUpfMap[id]; !ok { @@ -412,7 +404,7 @@ func (dataPath *DataPath) ActivateTunnelAndPDR(smContext *SMContext, precedence logger.PduSessLog.Traceln(dataPath.String()) // Activate Tunnels for node := firstDPNode; node != nil; node = node.Next() { - logger.PduSessLog.Traceln("Current DP Node IP: ", node.UPF.NodeID.ResolveNodeIdToIp().String()) + logger.PduSessLog.Traceln("Current DP Node IP: ", node.GetNodeIP()) if err := node.ActivateUpLinkTunnel(smContext); err != nil { logger.CtxLog.Warnln(err) return @@ -438,7 +430,7 @@ func (dataPath *DataPath) ActivateTunnelAndPDR(smContext *SMContext, precedence for curDataPathNode := firstDPNode; curDataPathNode != nil; curDataPathNode = curDataPathNode.Next() { var defaultQER *QER var ambrQER *QER - currentUUID := curDataPathNode.UPF.uuid + currentUUID := curDataPathNode.UPF.GetID() if qerId, okCurrentId := smContext.AMBRQerMap[currentUUID]; !okCurrentId { if newQER, err := curDataPathNode.UPF.AddQER(); err != nil { logger.PduSessLog.Errorln("new QER failed") @@ -644,7 +636,7 @@ func (dataPath *DataPath) ActivateTunnelAndPDR(smContext *SMContext, precedence DLFAR := DLPDR.FAR - logger.PduSessLog.Traceln("Current DP Node IP: ", curDataPathNode.UPF.NodeID.ResolveNodeIdToIp().String()) + logger.PduSessLog.Traceln("Current DP Node IP: ", curDataPathNode.GetNodeIP()) logger.PduSessLog.Traceln("Before DLPDR OuterHeaderCreation") if nextDLDest := curDataPathNode.Prev(); nextDLDest != nil { logger.PduSessLog.Traceln("In DLPDR OuterHeaderCreation") @@ -768,7 +760,7 @@ func (p *DataPath) AddChargingRules(smContext *SMContext, chgLevel ChargingLevel chgInfo := &ChargingInfo{ RatingGroup: chgData.RatingGroup, ChargingLevel: chgLevel, - UpfId: node.UPF.UUID(), + UpfId: node.UPF.GetID().String(), } urrId, err := smContext.UrrIDGenerator.Allocate() @@ -777,7 +769,7 @@ func (p *DataPath) AddChargingRules(smContext *SMContext, chgLevel ChargingLevel return } - currentUUID := node.UPF.UUID() + currentUUID := node.UPF.GetID() id := getUrrIdKey(currentUUID, uint32(urrId)) if oldURR, ok := smContext.UrrUpfMap[id]; !ok { @@ -820,7 +812,7 @@ func (p *DataPath) AddChargingRules(smContext *SMContext, chgLevel ChargingLevel if !isUrrExist(node.UpLinkTunnel.PDR.URR, urr) { node.UpLinkTunnel.PDR.AppendURRs([]*URR{urr}) // nolint - nodeId, _ := node.GetUPFID() + nodeId := node.GetUPFID() logger.PduSessLog.Tracef("UpLinkTunnel add URR for node %s %+v", nodeId, node.UpLinkTunnel.PDR) } @@ -829,7 +821,7 @@ func (p *DataPath) AddChargingRules(smContext *SMContext, chgLevel ChargingLevel if !isUrrExist(node.DownLinkTunnel.PDR.URR, urr) { node.DownLinkTunnel.PDR.AppendURRs([]*URR{urr}) // nolint - nodeId, _ := node.GetUPFID() + nodeId := node.GetUPFID() logger.PduSessLog.Tracef("DownLinkTunnel add URR for node %s %+v", nodeId, node.UpLinkTunnel.PDR) } @@ -847,7 +839,7 @@ func (p *DataPath) AddQoS(smContext *SMContext, qfi uint8, qos *models.QosData) for node := p.FirstDPNode; node != nil; node = node.Next() { var qer *QER - currentUUID := node.UPF.GetUUID() + currentUUID := node.UPF.GetID() id := getQosIdKey(currentUUID, qfi) if qerId, ok := smContext.QerUpfMap[id]; !ok { diff --git a/internal/context/pfcp_reports.go b/internal/context/pfcp_reports.go index 8ca9d689..8b1ec373 100644 --- a/internal/context/pfcp_reports.go +++ b/internal/context/pfcp_reports.go @@ -15,7 +15,7 @@ func (smContext *SMContext) HandleReports( ) { var usageReport UsageReport upf := RetrieveUPFNodeByNodeID(nodeId) - upfId := upf.UUID() + upfId := upf.GetID() for _, report := range usageReportRequest { usageReport.UrrId = report.URRID.UrrIdValue diff --git a/internal/context/sm_context.go b/internal/context/sm_context.go index 8a9b5724..987af5c6 100644 --- a/internal/context/sm_context.go +++ b/internal/context/sm_context.go @@ -94,7 +94,7 @@ type EventExposureNotification struct { type UsageReport struct { UrrId uint32 - UpfId string + UpfId uuid.UUID TotalVolume uint64 UplinkVolume uint64 @@ -160,7 +160,7 @@ type SMContext struct { SmStatusNotifyUri string Tunnel *UPTunnel - SelectedUPF *UPNode + SelectedUPF *UPF BPManager *BPManager // NodeID(string form) to PFCP Session Context PFCPContext map[string]*PFCPSessionContext @@ -476,13 +476,13 @@ func (smContext *SMContext) GetNodeIDByLocalSEID(seid uint64) pfcpType.NodeID { func (smContext *SMContext) AllocateLocalSEIDForUPPath(path UPPath) { for _, upNode := range path { - NodeIDtoIP := upNode.NodeID.ResolveNodeIdToIp().String() + NodeIDtoIP := upNode.GetNodeIDString() if _, exist := smContext.PFCPContext[NodeIDtoIP]; !exist { allocatedSEID := AllocateLocalSEID() smContext.PFCPContext[NodeIDtoIP] = &PFCPSessionContext{ PDRs: make(map[uint16]*PDR), - NodeID: upNode.NodeID, + NodeID: upNode.GetNodeID(), LocalSEID: allocatedSEID, } @@ -494,7 +494,7 @@ func (smContext *SMContext) AllocateLocalSEIDForUPPath(path UPPath) { func (smContext *SMContext) AllocateLocalSEIDForDataPath(dataPath *DataPath) { logger.PduSessLog.Traceln("In AllocateLocalSEIDForDataPath") for node := dataPath.FirstDPNode; node != nil; node = node.Next() { - NodeIDtoIP := node.UPF.NodeID.ResolveNodeIdToIp().String() + NodeIDtoIP := node.GetNodeIP() logger.PduSessLog.Traceln("NodeIDtoIP: ", NodeIDtoIP) if _, exist := smContext.PFCPContext[NodeIDtoIP]; !exist { allocatedSEID := AllocateLocalSEID() diff --git a/internal/context/sm_context_policy_test.go b/internal/context/sm_context_policy_test.go index 8630c143..1961a4cd 100644 --- a/internal/context/sm_context_policy_test.go +++ b/internal/context/sm_context_policy_test.go @@ -18,7 +18,6 @@ var userPlaneConfig = factory.UserPlaneInformation{ "UPF1": { Type: "UPF", NodeID: "10.4.0.11", - Addr: "10.4.0.11", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { SNssai: &models.Snssai{ @@ -53,7 +52,6 @@ var userPlaneConfig = factory.UserPlaneInformation{ "UPF2": { Type: "UPF", NodeID: "10.4.0.12", - Addr: "10.4.0.12", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { SNssai: &models.Snssai{ @@ -622,7 +620,7 @@ func TestApplyPccRules(t *testing.T) { smfContext := context.GetSelf() smfContext.UserPlaneInformation = context.NewUserPlaneInformation(&userPlaneConfig) for _, n := range smfContext.UserPlaneInformation.UPFs { - n.UPF.UPFStatus = context.AssociatedSetUpSuccess + n.UPFStatus = context.AssociatedSetUpSuccess } smctx := context.NewSMContext("imsi-208930000000002", 10) diff --git a/internal/context/ue_datapath.go b/internal/context/ue_datapath.go index c8f918f5..7e14dc92 100644 --- a/internal/context/ue_datapath.go +++ b/internal/context/ue_datapath.go @@ -15,15 +15,20 @@ type UEPreConfigPaths struct { } func NewUEDataPathNode(name string) (node *DataPathNode, err error) { - upNodes := smfContext.UserPlaneInformation.UPNodes + var upNode UPNodeInterface - if _, exist := upNodes[name]; !exist { + if _, exist := smfContext.UserPlaneInformation.UPNodes[name]; !exist { err = fmt.Errorf("UPNode %s isn't exist in smfcfg.yaml, but in UERouting.yaml!", name) return nil, err } + if upNode.GetType() == UPNODE_AN { + err = fmt.Errorf("UPNode %s has type 'UPNODE_AN', cannot add as DataPathNode!", name) + return nil, err + } + node = &DataPathNode{ - UPF: upNodes[name].UPF, + UPF: upNode.(*UPF), UpLinkTunnel: >PTunnel{}, DownLinkTunnel: >PTunnel{}, } diff --git a/internal/context/upf.go b/internal/context/upf.go index d14a0d3f..f1a7d4a0 100644 --- a/internal/context/upf.go +++ b/internal/context/upf.go @@ -65,9 +65,8 @@ const ( ) type UPF struct { - uuid uuid.UUID - NodeID pfcpType.NodeID - Addr string + *UPNode + UPIPInfo pfcpType.UserPlaneIPResourceInformation UPFStatus UPFStatus RecoveryTimeStamp time.Time @@ -92,6 +91,96 @@ type UPF struct { qerIDGenerator *idgenerator.IDGenerator } +func (upf *UPF) String() string { + str := "UPF {\n" + prefix := " " + str += prefix + fmt.Sprintf("Name: %s\n", upf.Name) + str += prefix + fmt.Sprintf("ID: %s\n", upf.ID) + str += prefix + fmt.Sprintf("NodeID: %s\n", upf.GetNodeIDString()) + str += prefix + fmt.Sprintf("Dnn: %s\n", upf.Dnn) + str += prefix + fmt.Sprintln("Links:") + for _, link := range upf.Links { + str += prefix + fmt.Sprintf("-- %s: %s\n", link.GetName(), link.GetNodeIDString()) + } + str += prefix + fmt.Sprintln("N3 interfaces:") + for _, iface := range upf.N3Interfaces { + str += prefix + fmt.Sprintf("-- %s\n", iface) + } + if len(upf.N9Interfaces) > 0 { + str += prefix + fmt.Sprintln("N9 interfaces:") + for _, iface := range upf.N9Interfaces { + str += prefix + fmt.Sprintf("-- %s\n", iface) + } + } + str += "}" + return str +} + +// Checks the NodeID type and either returns IPv4, IPv6, or FQDN +func (upf *UPF) GetNodeIDString() string { + switch upf.NodeID.NodeIdType { + case pfcpType.NodeIdTypeIpv4Address, pfcpType.NodeIdTypeIpv6Address: + return upf.NodeID.IP.String() + case pfcpType.NodeIdTypeFqdn: + ip := upf.NodeID.ResolveNodeIdToIp() + return ip.String() + default: + logger.CtxLog.Errorf("nodeID has unknown type %d", upf.NodeID.NodeIdType) + return "" + } +} + +func (upf *UPF) GetNodeID() pfcpType.NodeID { + return upf.NodeID +} + +func (upf *UPF) GetName() string { + return upf.Name +} + +func (upf *UPF) GetID() uuid.UUID { + return upf.ID +} + +func (upf *UPF) GetType() UPNodeType { + return upf.Type +} + +func (upf *UPF) GetDnn() string { + return upf.Dnn +} + +func (upf *UPF) GetLinks() UPPath { + return upf.Links +} + +func (upf *UPF) AddLink(link UPNodeInterface) bool { + for _, existingLink := range upf.Links { + if link.GetName() == existingLink.GetName() { + logger.CfgLog.Warningf("UPLink [%s] <=> [%s] already exists, skip\n", existingLink.GetName(), link.GetName()) + return false + } + } + upf.Links = append(upf.Links, link) + return true +} + +func (upf *UPF) RemoveLink(link UPNodeInterface) bool { + for i, existingLink := range upf.Links { + if link.GetName() == existingLink.GetName() && existingLink.GetNodeIDString() == link.GetNodeIDString() { + logger.CfgLog.Warningf("Remove UPLink [%s] <=> [%s]\n", existingLink.GetName(), link.GetName()) + upf.Links = append(upf.Links[:i], upf.Links[i+1:]...) + return true + } + } + return false +} + +func (upf *UPF) RemoveLinkByIndex(index int) bool { + upf.Links[index] = upf.Links[len(upf.Links)-1] + return true +} + // UPFSelectionParams ... parameters for upf selection type UPFSelectionParams struct { Dnn string @@ -108,6 +197,14 @@ type UPFInterfaceInfo struct { EndpointFQDN string } +func (i *UPFInterfaceInfo) String() string { + str := "" + str += fmt.Sprintf("NetworkInstances: %v, ", i.NetworkInstances) + str += fmt.Sprintf("IPv4EndPointAddresses: %v, ", i.IPv4EndPointAddresses) + str += fmt.Sprintf("EndpointFQDN: %s", i.EndpointFQDN) + return str +} + func GetUpfById(uuid string) *UPF { upf, ok := upfPool.Load(uuid) if ok { @@ -178,37 +275,23 @@ func (i *UPFInterfaceInfo) IP(pduSessType uint8) (net.IP, error) { } func (upfSelectionParams *UPFSelectionParams) String() string { - str := "" - Dnn := upfSelectionParams.Dnn - if Dnn != "" { - str += fmt.Sprintf("Dnn: %s\n", Dnn) + str := "UPFSelectionParams {" + if upfSelectionParams.Dnn != "" { + str += fmt.Sprintf("Dnn: %s -- ", upfSelectionParams.Dnn) } - - SNssai := upfSelectionParams.SNssai - if SNssai != nil { - str += fmt.Sprintf("Sst: %d, Sd: %s\n", int(SNssai.Sst), SNssai.Sd) + if upfSelectionParams.SNssai != nil { + str += fmt.Sprintf("Sst: %d, Sd: %s -- ", upfSelectionParams.SNssai.Sst, upfSelectionParams.SNssai.Sd) } - - Dnai := upfSelectionParams.Dnai - if Dnai != "" { - str += fmt.Sprintf("DNAI: %s\n", Dnai) + if upfSelectionParams.Dnai != "" { + str += fmt.Sprintf("DNAI: %s -- ", upfSelectionParams.Dnai) } - - pduAddress := upfSelectionParams.PDUAddress - if pduAddress != nil { - str += fmt.Sprintf("PDUAddress: %s\n", pduAddress) + if upfSelectionParams.PDUAddress != nil { + str += fmt.Sprintf("PDUAddress: %s -- ", upfSelectionParams.PDUAddress) } - + str += " }" return str } -// UUID return this UPF UUID (allocate by SMF in this time) -// Maybe allocate by UPF in future -func (upf *UPF) UUID() string { - uuid := upf.uuid.String() - return uuid -} - func NewUPTunnel() (tunnel *UPTunnel) { tunnel = &UPTunnel{ DataPathPool: make(DataPathPool), @@ -237,15 +320,17 @@ func (t *UPTunnel) RemoveDataPath(pathID int64) { // *** add unit test ***// // NewUPF returns a new UPF context in SMF -func NewUPF(nodeID *pfcpType.NodeID, ifaces []*factory.InterfaceUpfInfoItem) (upf *UPF) { +func NewUPF( + upNode *UPNode, + ifaces []*factory.InterfaceUpfInfoItem, +) (upf *UPF) { upf = new(UPF) - upf.uuid = uuid.New() + upf.UPNode = upNode - upfPool.Store(upf.UUID(), upf) + upfPool.Store(upf.GetID(), upf) // Initialize context upf.UPFStatus = NotAssociated - upf.NodeID = *nodeID upf.pdrIDGenerator = idgenerator.NewGenerator(1, math.MaxUint16) upf.farIDGenerator = idgenerator.NewGenerator(1, math.MaxUint32) upf.barIDGenerator = idgenerator.NewGenerator(1, math.MaxUint8) @@ -365,20 +450,9 @@ func SelectUPFByDnn(dnn string) *UPF { return upf } -func (upf *UPF) GetUPFIP() string { - upfIP := upf.NodeID.ResolveNodeIdToIp().String() - return upfIP -} - -func (upf *UPF) GetUPFID() string { - upInfo := GetUserPlaneInformation() - upfIP := upf.NodeID.ResolveNodeIdToIp().String() - return upInfo.GetUPFIDByIP(upfIP) -} - func (upf *UPF) pdrID() (uint16, error) { if upf.UPFStatus != AssociatedSetUpSuccess { - err := fmt.Errorf("UPF[%s] not Associate with SMF", upf.NodeID.ResolveNodeIdToIp().String()) + err := fmt.Errorf("UPF[%s] not Associate with SMF", upf.GetNodeIDString()) return 0, err } @@ -394,7 +468,7 @@ func (upf *UPF) pdrID() (uint16, error) { func (upf *UPF) farID() (uint32, error) { if upf.UPFStatus != AssociatedSetUpSuccess { - err := fmt.Errorf("UPF[%s] not Associate with SMF", upf.NodeID.ResolveNodeIdToIp().String()) + err := fmt.Errorf("UPF[%s] not Associate with SMF", upf.GetNodeIDString()) return 0, err } @@ -410,7 +484,7 @@ func (upf *UPF) farID() (uint32, error) { func (upf *UPF) barID() (uint8, error) { if upf.UPFStatus != AssociatedSetUpSuccess { - err := fmt.Errorf("UPF[%s] not Associate with SMF", upf.NodeID.ResolveNodeIdToIp().String()) + err := fmt.Errorf("UPF[%s] not Associate with SMF", upf.GetNodeIDString()) return 0, err } @@ -426,7 +500,7 @@ func (upf *UPF) barID() (uint8, error) { func (upf *UPF) qerID() (uint32, error) { if upf.UPFStatus != AssociatedSetUpSuccess { - err := fmt.Errorf("UPF[%s] not Associate with SMF", upf.NodeID.ResolveNodeIdToIp().String()) + err := fmt.Errorf("UPF[%s] not Associate with SMF", upf.GetNodeIDString()) return 0, err } @@ -453,7 +527,7 @@ func (upf *UPF) urrID() (uint32, error) { func (upf *UPF) AddPDR() (*PDR, error) { if upf.UPFStatus != AssociatedSetUpSuccess { - err := fmt.Errorf("UPF[%s] not Associate with SMF", upf.NodeID.ResolveNodeIdToIp().String()) + err := fmt.Errorf("UPF[%s] not Associate with SMF", upf.GetNodeIDString()) return nil, err } @@ -476,7 +550,7 @@ func (upf *UPF) AddPDR() (*PDR, error) { func (upf *UPF) AddFAR() (*FAR, error) { if upf.UPFStatus != AssociatedSetUpSuccess { - err := fmt.Errorf("UPF[%s] not Associate with SMF", upf.NodeID.ResolveNodeIdToIp().String()) + err := fmt.Errorf("UPF[%s] not Associate with SMF", upf.GetNodeIDString()) return nil, err } @@ -493,7 +567,7 @@ func (upf *UPF) AddFAR() (*FAR, error) { func (upf *UPF) AddBAR() (*BAR, error) { if upf.UPFStatus != AssociatedSetUpSuccess { - err := fmt.Errorf("UPF[%s] not Associate with SMF", upf.NodeID.ResolveNodeIdToIp().String()) + err := fmt.Errorf("UPF[%s] not Associate with SMF", upf.GetNodeIDString()) return nil, err } @@ -509,7 +583,7 @@ func (upf *UPF) AddBAR() (*BAR, error) { func (upf *UPF) AddQER() (*QER, error) { if upf.UPFStatus != AssociatedSetUpSuccess { - err := fmt.Errorf("UPF[%s] not Associate with SMF", upf.NodeID.ResolveNodeIdToIp().String()) + err := fmt.Errorf("UPF[%s] not Associate with SMF", upf.GetNodeIDString()) return nil, err } @@ -525,7 +599,7 @@ func (upf *UPF) AddQER() (*QER, error) { func (upf *UPF) AddURR(urrId uint32, opts ...UrrOpt) (*URR, error) { if upf.UPFStatus != AssociatedSetUpSuccess { - err := fmt.Errorf("UPF[%s] not Associate with SMF", upf.NodeID.ResolveNodeIdToIp().String()) + err := fmt.Errorf("UPF[%s] not Associate with SMF", upf.GetNodeIDString()) return nil, err } @@ -550,10 +624,6 @@ func (upf *UPF) AddURR(urrId uint32, opts ...UrrOpt) (*URR, error) { return urr, nil } -func (upf *UPF) GetUUID() uuid.UUID { - return upf.uuid -} - func (upf *UPF) GetQERById(qerId uint32) *QER { qer, ok := upf.qerPool.Load(qerId) if ok { @@ -565,7 +635,7 @@ func (upf *UPF) GetQERById(qerId uint32) *QER { // *** add unit test ***// func (upf *UPF) RemovePDR(pdr *PDR) (err error) { if upf.UPFStatus != AssociatedSetUpSuccess { - err = fmt.Errorf("UPF[%s] not Associate with SMF", upf.NodeID.ResolveNodeIdToIp().String()) + err = fmt.Errorf("UPF[%s] not Associate with SMF", upf.GetNodeIDString()) return err } @@ -577,7 +647,7 @@ func (upf *UPF) RemovePDR(pdr *PDR) (err error) { // *** add unit test ***// func (upf *UPF) RemoveFAR(far *FAR) (err error) { if upf.UPFStatus != AssociatedSetUpSuccess { - err = fmt.Errorf("UPF[%s] not Associate with SMF", upf.NodeID.ResolveNodeIdToIp().String()) + err = fmt.Errorf("UPF[%s] not Associate with SMF", upf.GetNodeIDString()) return err } @@ -589,7 +659,7 @@ func (upf *UPF) RemoveFAR(far *FAR) (err error) { // *** add unit test ***// func (upf *UPF) RemoveBAR(bar *BAR) (err error) { if upf.UPFStatus != AssociatedSetUpSuccess { - err = fmt.Errorf("UPF[%s] not Associate with SMF", upf.NodeID.ResolveNodeIdToIp().String()) + err = fmt.Errorf("UPF[%s] not Associate with SMF", upf.GetNodeIDString()) return err } @@ -601,7 +671,7 @@ func (upf *UPF) RemoveBAR(bar *BAR) (err error) { // *** add unit test ***// func (upf *UPF) RemoveQER(qer *QER) (err error) { if upf.UPFStatus != AssociatedSetUpSuccess { - err = fmt.Errorf("UPF[%s] not Associate with SMF", upf.NodeID.ResolveNodeIdToIp().String()) + err = fmt.Errorf("UPF[%s] not Associate with SMF", upf.GetNodeIDString()) return err } @@ -622,9 +692,27 @@ func (upf *UPF) isSupportSnssai(snssai *SNssai) bool { func (upf *UPF) ProcEachSMContext(procFunc func(*SMContext)) { smContextPool.Range(func(key, value interface{}) bool { smContext := value.(*SMContext) - if smContext.SelectedUPF != nil && smContext.SelectedUPF.UPF == upf { + if smContext.SelectedUPF != nil && smContext.SelectedUPF == upf { procFunc(smContext) } return true }) } + +func (upf *UPF) MatchedSelection(selection *UPFSelectionParams) bool { + for _, snssaiInfo := range upf.SNssaiInfos { + currentSnssai := snssaiInfo.SNssai + if currentSnssai.Equal(selection.SNssai) { + for _, dnnInfo := range snssaiInfo.DnnList { + if dnnInfo.Dnn == selection.Dnn { + if selection.Dnai == "" { + return true + } else if dnnInfo.ContainsDNAI(selection.Dnai) { + return true + } + } + } + } + } + return false +} diff --git a/internal/context/upf_test.go b/internal/context/upf_test.go index d64837f4..3b0a662b 100644 --- a/internal/context/upf_test.go +++ b/internal/context/upf_test.go @@ -5,17 +5,25 @@ import ( "net" "testing" + "github.com/google/uuid" . "github.com/smartystreets/goconvey/convey" "github.com/free5gc/nas/nasMessage" "github.com/free5gc/pfcp/pfcpType" "github.com/free5gc/smf/internal/context" + smf_context "github.com/free5gc/smf/internal/context" "github.com/free5gc/smf/pkg/factory" ) -var mockIPv4NodeID = &pfcpType.NodeID{ - NodeIdType: pfcpType.NodeIdTypeIpv4Address, - IP: net.ParseIP("127.0.0.1"), +var mockUPNode = &smf_context.UPNode{ + Name: "UPF1", + Type: smf_context.UPNODE_UPF, + ID: uuid.New(), + NodeID: pfcpType.NodeID{ + NodeIdType: pfcpType.NodeIdTypeIpv4Address, + IP: net.ParseIP("127.0.0.1"), + }, + Dnn: "internet", } var mockIfaces = []*factory.InterfaceUpfInfoItem{ @@ -45,7 +53,7 @@ func convertPDUSessTypeToString(pduType uint8) string { func TestIP(t *testing.T) { testCases := []struct { - input *context.UPFInterfaceInfo + input *smf_context.UPFInterfaceInfo inputPDUSessionType uint8 paramStr string resultStr string @@ -53,7 +61,7 @@ func TestIP(t *testing.T) { expectedError error }{ { - input: &context.UPFInterfaceInfo{ + input: &smf_context.UPFInterfaceInfo{ NetworkInstances: []string{""}, IPv4EndPointAddresses: []net.IP{net.ParseIP("8.8.8.8")}, IPv6EndPointAddresses: []net.IP{net.ParseIP("2001:4860:4860::8888")}, @@ -65,7 +73,7 @@ func TestIP(t *testing.T) { expectedError: nil, }, { - input: &context.UPFInterfaceInfo{ + input: &smf_context.UPFInterfaceInfo{ NetworkInstances: []string{""}, IPv4EndPointAddresses: []net.IP{net.ParseIP("8.8.8.8")}, IPv6EndPointAddresses: []net.IP{net.ParseIP("2001:4860:4860::8888")}, @@ -101,14 +109,14 @@ func TestIP(t *testing.T) { func TestAddDataPath(t *testing.T) { // AddDataPath is simple, should only have one case testCases := []struct { - tunnel *context.UPTunnel - addedDataPath *context.DataPath + tunnel *smf_context.UPTunnel + addedDataPath *smf_context.DataPath resultStr string expectedExist bool }{ { - tunnel: context.NewUPTunnel(), - addedDataPath: context.NewDataPath(), + tunnel: smf_context.NewUPTunnel(), + addedDataPath: smf_context.NewDataPath(), resultStr: "Datapath should exist", expectedExist: true, }, @@ -138,17 +146,17 @@ func TestAddDataPath(t *testing.T) { func TestAddPDR(t *testing.T) { testCases := []struct { - upf *context.UPF + upf *smf_context.UPF resultStr string expectedError error }{ { - upf: context.NewUPF(mockIPv4NodeID, mockIfaces), + upf: smf_context.NewUPF(mockUPNode, mockIfaces), resultStr: "AddPDR should success", expectedError: nil, }, { - upf: context.NewUPF(mockIPv4NodeID, mockIfaces), + upf: smf_context.NewUPF(mockUPNode, mockIfaces), resultStr: "AddPDR should fail", expectedError: fmt.Errorf("UPF[127.0.0.1] not Associate with SMF"), }, @@ -181,17 +189,17 @@ func TestAddPDR(t *testing.T) { func TestAddFAR(t *testing.T) { testCases := []struct { - upf *context.UPF + upf *smf_context.UPF resultStr string expectedError error }{ { - upf: context.NewUPF(mockIPv4NodeID, mockIfaces), + upf: context.NewUPF(mockUPNode, mockIfaces), resultStr: "AddFAR should success", expectedError: nil, }, { - upf: context.NewUPF(mockIPv4NodeID, mockIfaces), + upf: context.NewUPF(mockUPNode, mockIfaces), resultStr: "AddFAR should fail", expectedError: fmt.Errorf("UPF[127.0.0.1] not Associate with SMF"), }, @@ -224,17 +232,17 @@ func TestAddFAR(t *testing.T) { func TestAddQER(t *testing.T) { testCases := []struct { - upf *context.UPF + upf *smf_context.UPF resultStr string expectedError error }{ { - upf: context.NewUPF(mockIPv4NodeID, mockIfaces), + upf: context.NewUPF(mockUPNode, mockIfaces), resultStr: "AddQER should success", expectedError: nil, }, { - upf: context.NewUPF(mockIPv4NodeID, mockIfaces), + upf: context.NewUPF(mockUPNode, mockIfaces), resultStr: "AddQER should fail", expectedError: fmt.Errorf("UPF[127.0.0.1] not Associate with SMF"), }, @@ -267,17 +275,17 @@ func TestAddQER(t *testing.T) { func TestAddBAR(t *testing.T) { testCases := []struct { - upf *context.UPF + upf *smf_context.UPF resultStr string expectedError error }{ { - upf: context.NewUPF(mockIPv4NodeID, mockIfaces), + upf: context.NewUPF(mockUPNode, mockIfaces), resultStr: "AddBAR should success", expectedError: nil, }, { - upf: context.NewUPF(mockIPv4NodeID, mockIfaces), + upf: context.NewUPF(mockUPNode, mockIfaces), resultStr: "AddBAR should fail", expectedError: fmt.Errorf("UPF[127.0.0.1] not Associate with SMF"), }, diff --git a/internal/context/user_plane_information.go b/internal/context/user_plane_information.go index 91f2bab3..7490e70a 100644 --- a/internal/context/user_plane_information.go +++ b/internal/context/user_plane_information.go @@ -2,6 +2,7 @@ package context import ( "errors" + "fmt" "math/rand" "net" "reflect" @@ -12,19 +13,20 @@ import ( "github.com/free5gc/pfcp/pfcpType" "github.com/free5gc/smf/internal/logger" "github.com/free5gc/smf/pkg/factory" + "github.com/google/uuid" ) // UserPlaneInformation store userplane topology type UserPlaneInformation struct { Mu sync.RWMutex // protect UPF and topology structure - UPNodes map[string]*UPNode - UPFs map[string]*UPNode - AccessNetwork map[string]*UPNode + UPNodes map[string]UPNodeInterface + UPFs map[string]*UPF + AccessNetwork map[string]*GNB UPFIPToName map[string]string - UPFsID map[string]string // name to id - UPFsIPtoID map[string]string // ip->id table, for speed optimization - DefaultUserPlanePath map[string][]*UPNode // DNN to Default Path - DefaultUserPlanePathToUPF map[string]map[string][]*UPNode // DNN and UPF to Default Path + UPFsID map[string]uuid.UUID // name to id + UPFsIPtoID map[string]uuid.UUID // ip->id table, for speed optimization + DefaultUserPlanePath map[string]UPPath // DNN to Default Path + DefaultUserPlanePathToUPF map[string]map[string]UPPath // DNN and UPF to Default Path } type UPNodeType string @@ -34,98 +36,220 @@ const ( UPNODE_AN UPNodeType = "AN" ) -// UPNode represent the user plane node topology +// UPNode represents a gNB or UPF in the user plane node topology +// UPF and gNB structs embed this ("interitance") type UPNode struct { Name string Type UPNodeType + ID uuid.UUID NodeID pfcpType.NodeID - ANIP net.IP Dnn string - Links []*UPNode - UPF *UPF + Links UPPath } -func (u *UPNode) MatchedSelection(selection *UPFSelectionParams) bool { - for _, snssaiInfo := range u.UPF.SNssaiInfos { - currentSnssai := snssaiInfo.SNssai - if currentSnssai.Equal(selection.SNssai) { - for _, dnnInfo := range snssaiInfo.DnnList { - if dnnInfo.Dnn == selection.Dnn { - if selection.Dnai == "" { - return true - } else if dnnInfo.ContainsDNAI(selection.Dnai) { - return true - } - } - } +// UPF and gNB structs implement this interface to provide common methods +// i.e., methods all UPNodes should have +type UPNodeInterface interface { + String() string + GetName() string + GetID() uuid.UUID + GetType() UPNodeType + GetDnn() string + GetLinks() UPPath + AddLink(link UPNodeInterface) bool + RemoveLink(link UPNodeInterface) bool + RemoveLinkByIndex(index int) bool + GetNodeID() pfcpType.NodeID + GetNodeIDString() string +} + +// UPPath represents the User Plane Node Sequence of this path +type UPPath []UPNodeInterface + +func (upPath UPPath) String() string { + str := "" + for i, upNode := range upPath { + str += fmt.Sprintf("Node %d: %s", i, upNode) + } + return str +} + +func (upPath UPPath) NodeInPath(upNode UPNodeInterface) int { + for i, u := range upPath { + if u == upNode { + return i + } + } + return -1 +} + +// static/ global function to convert a NodeID to a string depending on the NodeID type +func NodeIDToString(nodeID pfcpType.NodeID) string { + switch nodeID.NodeIdType { + case pfcpType.NodeIdTypeIpv4Address, pfcpType.NodeIdTypeIpv6Address: + return nodeID.IP.String() + case pfcpType.NodeIdTypeFqdn: + return nodeID.FQDN + default: + logger.CtxLog.Errorf("nodeID has unknown type %d", nodeID.NodeIdType) + return "" + } +} + +// embeds the UPNode struct ("inheritance") +// implements UPNodeInterface +type GNB struct { + UPNode + ANIP net.IP +} + +func (gNB *GNB) GetName() string { + return gNB.Name +} + +func (gNB *GNB) GetID() uuid.UUID { + return gNB.ID +} + +func (gNB *GNB) GetType() UPNodeType { + return gNB.Type +} + +func (gNB *GNB) GetDnn() string { + return gNB.Dnn +} + +func (gNB *GNB) String() string { + str := "gNB {\n" + prefix := " " + str += prefix + fmt.Sprintf("Name: %s\n", gNB.Name) + str += prefix + fmt.Sprintf("ANIP: %s\n", gNB.ANIP) + str += prefix + fmt.Sprintf("ID: %s\n", gNB.ID) + str += prefix + fmt.Sprintf("NodeID: %s\n", gNB.GetNodeIDString()) + str += prefix + fmt.Sprintf("Dnn: %s\n", gNB.Dnn) + str += prefix + fmt.Sprintln("Links:") + for _, link := range gNB.Links { + str += prefix + fmt.Sprintf("-- %s: %s\n", link.GetName(), link.GetNodeIDString()) + } + str += "}" + return str +} + +func (gNB *GNB) GetNodeIDString() string { + switch gNB.NodeID.NodeIdType { + case pfcpType.NodeIdTypeIpv4Address, pfcpType.NodeIdTypeIpv6Address: + return gNB.NodeID.IP.String() + case pfcpType.NodeIdTypeFqdn: + return gNB.NodeID.FQDN + default: + logger.CtxLog.Errorf("nodeID has unknown type %d", gNB.NodeID.NodeIdType) + return "" + } +} + +func (gNB *GNB) GetNodeID() pfcpType.NodeID { + return gNB.NodeID +} + +func (gNB *GNB) GetLinks() UPPath { + return gNB.Links +} + +func (gNB *GNB) AddLink(link UPNodeInterface) bool { + for _, existingLink := range gNB.Links { + if link.GetName() == existingLink.GetName() { + logger.CfgLog.Warningf("UPLink [%s] <=> [%s] already exists, skip\n", existingLink.GetName(), link.GetName()) + return false + } + } + gNB.Links = append(gNB.Links, link) + return true +} + +func (gNB *GNB) RemoveLink(link UPNodeInterface) bool { + for i, existingLink := range gNB.Links { + if link.GetName() == existingLink.GetName() && existingLink.GetNodeIDString() == link.GetNodeIDString() { + logger.CfgLog.Warningf("Remove UPLink [%s] <=> [%s]\n", existingLink.GetName(), link.GetName()) + gNB.Links = append(gNB.Links[:i], gNB.Links[i+1:]...) + return true } } return false } -// UPPath represent User Plane Sequence of this path -type UPPath []*UPNode +func (gNB *GNB) RemoveLinkByIndex(index int) bool { + gNB.Links[index] = gNB.Links[len(gNB.Links)-1] + return true +} func AllocateUPFID() { UPFsID := smfContext.UserPlaneInformation.UPFsID UPFsIPtoID := smfContext.UserPlaneInformation.UPFsIPtoID for upfName, upfNode := range smfContext.UserPlaneInformation.UPFs { - upfid := upfNode.UPF.UUID() - upfip := upfNode.NodeID.ResolveNodeIdToIp().String() + upfid := upfNode.GetID() + upfip := upfNode.GetNodeIDString() UPFsID[upfName] = upfid UPFsIPtoID[upfip] = upfid } } +// the config has a single string for NodeID, +// check its nature and create either IPv4, IPv6, or FQDN NodeID type +func configToNodeID(configNodeID string) pfcpType.NodeID { + var ip net.IP + if net.ParseIP(configNodeID).To4() == nil { + ip = net.ParseIP(configNodeID) + } else { + ip = net.ParseIP(configNodeID).To4() + } + switch len(configNodeID) { + case net.IPv4len: + return pfcpType.NodeID{ + NodeIdType: pfcpType.NodeIdTypeIpv4Address, + IP: ip, + } + case net.IPv6len: + return pfcpType.NodeID{ + NodeIdType: pfcpType.NodeIdTypeIpv6Address, + IP: ip, + } + default: + return pfcpType.NodeID{ + NodeIdType: pfcpType.NodeIdTypeFqdn, + FQDN: configNodeID, + } + } +} + // NewUserPlaneInformation process the configuration then returns a new instance of UserPlaneInformation func NewUserPlaneInformation(upTopology *factory.UserPlaneInformation) *UserPlaneInformation { - nodePool := make(map[string]*UPNode) - upfPool := make(map[string]*UPNode) - anPool := make(map[string]*UPNode) + nodePool := make(map[string]UPNodeInterface) + upfPool := make(map[string]*UPF) + anPool := make(map[string]*GNB) upfIPMap := make(map[string]string) allUEIPPools := []*UeIPPool{} for name, node := range upTopology.UPNodes { - upNode := new(UPNode) - upNode.Name = name - upNode.Type = UPNodeType(node.Type) + upNode := &UPNode{ + Name: name, + Type: UPNodeType(node.Type), + ID: uuid.New(), + NodeID: configToNodeID(node.NodeID), + Dnn: node.Dnn, + } switch upNode.Type { case UPNODE_AN: - upNode.ANIP = net.ParseIP(node.ANIP) - anPool[name] = upNode - case UPNODE_UPF: - // ParseIp() always return 16 bytes - // so we can't use the length of return ip to separate IPv4 and IPv6 - // This is just a work around - var ip net.IP - if net.ParseIP(node.NodeID).To4() == nil { - ip = net.ParseIP(node.NodeID) - } else { - ip = net.ParseIP(node.NodeID).To4() - } - - switch len(ip) { - case net.IPv4len: - upNode.NodeID = pfcpType.NodeID{ - NodeIdType: pfcpType.NodeIdTypeIpv4Address, - IP: ip, - } - case net.IPv6len: - upNode.NodeID = pfcpType.NodeID{ - NodeIdType: pfcpType.NodeIdTypeIpv6Address, - IP: ip, - } - default: - upNode.NodeID = pfcpType.NodeID{ - NodeIdType: pfcpType.NodeIdTypeFqdn, - FQDN: node.NodeID, - } + gNB := &GNB{ + UPNode: *upNode, + ANIP: upNode.NodeID.ResolveNodeIdToIp(), } + anPool[name] = gNB + nodePool[name] = gNB + case UPNODE_UPF: + upf := NewUPF(upNode, node.InterfaceUpfInfoList) - upNode.UPF = NewUPF(&upNode.NodeID, node.InterfaceUpfInfoList) - upNode.UPF.Addr = node.Addr snssaiInfos := make([]*SnssaiUPFInfo, 0) for _, snssaiInfoConfig := range node.SNssaiInfos { snssaiInfo := SnssaiUPFInfo{ @@ -187,72 +311,53 @@ func NewUserPlaneInformation(upTopology *factory.UserPlaneInformation) *UserPlan } snssaiInfos = append(snssaiInfos, &snssaiInfo) } - upNode.UPF.SNssaiInfos = snssaiInfos - upfPool[name] = upNode + upf.SNssaiInfos = snssaiInfos + upfPool[name] = upf + nodePool[name] = upf + upfIPMap[upf.GetNodeIDString()] = name default: logger.InitLog.Warningf("invalid UPNodeType: %s\n", upNode.Type) } - - nodePool[name] = upNode - - ipStr := upNode.NodeID.ResolveNodeIdToIp().String() - upfIPMap[ipStr] = name } if isOverlap(allUEIPPools) { logger.InitLog.Fatalf("overlap cidr value between UPFs") } - for _, link := range upTopology.Links { - nodeA := nodePool[link.A] - nodeB := nodePool[link.B] - if nodeA == nil || nodeB == nil { - logger.InitLog.Warningf("One of link edges does not exist. UPLink [%s] <=> [%s] not establish\n", link.A, link.B) - continue - } - if nodeInLink(nodeB, nodeA.Links) != -1 || nodeInLink(nodeA, nodeB.Links) != -1 { - logger.InitLog.Warningf("One of link edges already exist. UPLink [%s] <=> [%s] not establish\n", link.A, link.B) - continue - } - nodeA.Links = append(nodeA.Links, nodeB) - nodeB.Links = append(nodeB.Links, nodeA) - } - userplaneInformation := &UserPlaneInformation{ UPNodes: nodePool, UPFs: upfPool, AccessNetwork: anPool, UPFIPToName: upfIPMap, - UPFsID: make(map[string]string), - UPFsIPtoID: make(map[string]string), - DefaultUserPlanePath: make(map[string][]*UPNode), - DefaultUserPlanePathToUPF: make(map[string]map[string][]*UPNode), + UPFsID: make(map[string]uuid.UUID), + UPFsIPtoID: make(map[string]uuid.UUID), + DefaultUserPlanePath: make(map[string]UPPath), + DefaultUserPlanePathToUPF: make(map[string]map[string]UPPath), } + userplaneInformation.LinksFromConfiguration(upTopology) + return userplaneInformation } func (upi *UserPlaneInformation) UpNodesToConfiguration() map[string]*factory.UPNode { nodes := make(map[string]*factory.UPNode) for name, upNode := range upi.UPNodes { - u := new(factory.UPNode) - switch upNode.Type { - case UPNODE_UPF: - u.Type = "UPF" - case UPNODE_AN: - u.Type = "AN" - u.ANIP = upNode.ANIP.String() - default: - u.Type = "Unknown" - } - nodeIDtoIp := upNode.NodeID.ResolveNodeIdToIp() - if nodeIDtoIp != nil { - u.NodeID = nodeIDtoIp.String() + node := &factory.UPNode{ + NodeID: upNode.GetNodeIDString(), + Dnn: upNode.GetDnn(), } - if upNode.UPF != nil { - if upNode.UPF.SNssaiInfos != nil { + nodes[name] = node + + switch upNode.GetType() { + case UPNODE_AN: + node.Type = "AN" + case UPNODE_UPF: + node.Type = "UPF" + upf := upNode.(*UPF) + if upf.SNssaiInfos != nil { FsNssaiInfoList := make([]*factory.SnssaiUpfInfoItem, 0) - for _, sNssaiInfo := range upNode.UPF.SNssaiInfos { + for _, sNssaiInfo := range upf.SNssaiInfos { FDnnUpfInfoList := make([]*factory.DnnUpfInfoItem, 0) for _, dnnInfo := range sNssaiInfo.DnnList { FUEIPPools := make([]*factory.UEIPPool, 0) @@ -282,10 +387,10 @@ func (upi *UserPlaneInformation) UpNodesToConfiguration() map[string]*factory.UP } FsNssaiInfoList = append(FsNssaiInfoList, Fsnssai) } // for sNssaiInfo - u.SNssaiInfos = FsNssaiInfoList + node.SNssaiInfos = FsNssaiInfoList } // if UPF.SNssaiInfos FNxList := make([]*factory.InterfaceUpfInfoItem, 0) - for _, iface := range upNode.UPF.N3Interfaces { + for _, iface := range upf.N3Interfaces { endpoints := make([]string, 0) // upf.go L90 if iface.EndpointFQDN != "" { @@ -301,7 +406,7 @@ func (upi *UserPlaneInformation) UpNodesToConfiguration() map[string]*factory.UP }) } // for N3Interfaces - for _, iface := range upNode.UPF.N9Interfaces { + for _, iface := range upf.N9Interfaces { endpoints := make([]string, 0) // upf.go L90 if iface.EndpointFQDN != "" { @@ -316,9 +421,11 @@ func (upi *UserPlaneInformation) UpNodesToConfiguration() map[string]*factory.UP NetworkInstances: iface.NetworkInstances, }) } // N9Interfaces - u.InterfaceUpfInfoList = FNxList + node.InterfaceUpfInfoList = FNxList + default: + logger.InitLog.Warningf("invalid UPNodeType: %s\n", upNode.GetType()) + node.Type = "Unknown" } - nodes[name] = u } return nodes @@ -330,18 +437,18 @@ func (upi *UserPlaneInformation) LinksToConfiguration() []*factory.UPLink { if err != nil { logger.InitLog.Errorf("AN Node not found\n") } else { - visited := make(map[*UPNode]bool) - queue := make([]*UPNode, 0) + visited := make(map[UPNodeInterface]bool) + queue := make(UPPath, 0) queue = append(queue, source) for { node := queue[0] queue = queue[1:] visited[node] = true - for _, link := range node.Links { + for _, link := range node.GetLinks() { if !visited[link] { queue = append(queue, link) - nodeIpStr := node.NodeID.ResolveNodeIdToIp().String() - ipStr := link.NodeID.ResolveNodeIdToIp().String() + nodeIpStr := node.GetNodeIDString() + ipStr := link.GetNodeIDString() linkA := upi.UPFIPToName[nodeIpStr] linkB := upi.UPFIPToName[ipStr] links = append(links, &factory.UPLink{ @@ -359,44 +466,32 @@ func (upi *UserPlaneInformation) LinksToConfiguration() []*factory.UPLink { } func (upi *UserPlaneInformation) UpNodesFromConfiguration(upTopology *factory.UserPlaneInformation) { + allUEIPPools := []*UeIPPool{} + for name, node := range upTopology.UPNodes { if _, ok := upi.UPNodes[name]; ok { logger.InitLog.Warningf("Node [%s] already exists in SMF.\n", name) continue } - upNode := new(UPNode) - upNode.Type = UPNodeType(node.Type) + upNode := &UPNode{ + Name: name, + Type: UPNodeType(node.Type), + ID: uuid.New(), + NodeID: configToNodeID(node.NodeID), + Dnn: node.Dnn, + } switch upNode.Type { - case UPNODE_UPF: - // ParseIp() always return 16 bytes - // so we can't use the length of return ip to separate IPv4 and IPv6 - // This is just a work around - var ip net.IP - if net.ParseIP(node.NodeID).To4() == nil { - ip = net.ParseIP(node.NodeID) - } else { - ip = net.ParseIP(node.NodeID).To4() + case UPNODE_AN: + gNB := &GNB{ + UPNode: *upNode, + ANIP: upNode.NodeID.ResolveNodeIdToIp(), } - switch len(ip) { - case net.IPv4len: - upNode.NodeID = pfcpType.NodeID{ - NodeIdType: pfcpType.NodeIdTypeIpv4Address, - IP: ip, - } - case net.IPv6len: - upNode.NodeID = pfcpType.NodeID{ - NodeIdType: pfcpType.NodeIdTypeIpv6Address, - IP: ip, - } - default: - upNode.NodeID = pfcpType.NodeID{ - NodeIdType: pfcpType.NodeIdTypeFqdn, - FQDN: node.NodeID, - } - } + upi.AccessNetwork[name] = gNB + upi.UPNodes[name] = gNB - upNode.UPF = NewUPF(&upNode.NodeID, node.InterfaceUpfInfoList) + case UPNODE_UPF: + upf := NewUPF(upNode, node.InterfaceUpfInfoList) snssaiInfos := make([]*SnssaiUPFInfo, 0) for _, snssaiInfoConfig := range node.SNssaiInfos { snssaiInfo := &SnssaiUPFInfo{ @@ -444,37 +539,30 @@ func (upi *UserPlaneInformation) UpNodesFromConfiguration(upTopology *factory.Us } snssaiInfos = append(snssaiInfos, snssaiInfo) } - upNode.UPF.SNssaiInfos = snssaiInfos - upi.UPFs[name] = upNode + upf.SNssaiInfos = snssaiInfos + upi.UPFs[name] = upf // AllocateUPFID - upfid := upNode.UPF.UUID() - upfip := upNode.NodeID.ResolveNodeIdToIp().String() + upfid := upf.GetID() + upfip := upf.GetNodeIDString() upi.UPFsID[name] = upfid upi.UPFsIPtoID[upfip] = upfid - case UPNODE_AN: - upNode.ANIP = net.ParseIP(node.ANIP) - upi.AccessNetwork[name] = upNode + upi.UPNodes[name] = upf + upi.UPFIPToName[upfip] = name + + // collect IP pool of this UPF for later overlap check + for _, sNssaiInfo := range upf.SNssaiInfos { + for _, dnnUPFInfo := range sNssaiInfo.DnnList { + allUEIPPools = append(allUEIPPools, dnnUPFInfo.UeIPPools...) + } + } + default: logger.InitLog.Warningf("invalid UPNodeType: %s\n", upNode.Type) } - - upi.UPNodes[name] = upNode - - ipStr := upNode.NodeID.ResolveNodeIdToIp().String() - upi.UPFIPToName[ipStr] = name } - // overlap UE IP pool validation - allUEIPPools := []*UeIPPool{} - for _, upf := range upi.UPFs { - for _, snssaiInfo := range upf.UPF.SNssaiInfos { - for _, dnn := range snssaiInfo.DnnList { - allUEIPPools = append(allUEIPPools, dnn.UeIPPools...) - } - } - } if isOverlap(allUEIPPools) { logger.InitLog.Fatalf("overlap cidr value between UPFs") } @@ -485,15 +573,15 @@ func (upi *UserPlaneInformation) LinksFromConfiguration(upTopology *factory.User nodeA := upi.UPNodes[link.A] nodeB := upi.UPNodes[link.B] if nodeA == nil || nodeB == nil { - logger.InitLog.Warningf("One of link edges does not exist. UPLink [%s] <=> [%s] not establish\n", link.A, link.B) + logger.CfgLog.Warningf("One of link edges does not exist. UPLink [%s] <=> [%s] not established\n", link.A, link.B) continue } - if nodeInLink(nodeB, nodeA.Links) != -1 || nodeInLink(nodeA, nodeB.Links) != -1 { + if nodeInLink(nodeB, nodeA.GetLinks()) != -1 || nodeInLink(nodeA, nodeB.GetLinks()) != -1 { logger.InitLog.Warningf("One of link edges already exist. UPLink [%s] <=> [%s] not establish\n", link.A, link.B) continue } - nodeA.Links = append(nodeA.Links, nodeB) - nodeB.Links = append(nodeB.Links, nodeA) + nodeA.AddLink(nodeB) + nodeB.AddLink(nodeA) } } @@ -501,9 +589,9 @@ func (upi *UserPlaneInformation) UpNodeDelete(upNodeName string) { upNode, ok := upi.UPNodes[upNodeName] if ok { logger.InitLog.Infof("UPNode [%s] found. Deleting it.\n", upNodeName) - if upNode.Type == UPNODE_UPF { + if upNode.GetType() == UPNODE_UPF { logger.InitLog.Tracef("Delete UPF [%s] from its NodeID.\n", upNodeName) - RemoveUPFNodeByNodeID(upNode.UPF.NodeID) + RemoveUPFNodeByNodeID(upNode.GetNodeID()) if _, ok = upi.UPFs[upNodeName]; ok { logger.InitLog.Tracef("Delete UPF [%s] from upi.UPFs.\n", upNodeName) delete(upi.UPFs, upNodeName) @@ -517,7 +605,7 @@ func (upi *UserPlaneInformation) UpNodeDelete(upNodeName string) { } } } - if upNode.Type == UPNODE_AN { + if upNode.GetType() == UPNODE_AN { logger.InitLog.Tracef("Delete AN [%s] from upi.AccessNetwork.\n", upNodeName) delete(upi.AccessNetwork, upNodeName) } @@ -526,15 +614,15 @@ func (upi *UserPlaneInformation) UpNodeDelete(upNodeName string) { // update links for name, n := range upi.UPNodes { - if index := nodeInLink(upNode, n.Links); index != -1 { + if index := nodeInLink(upNode, n.GetLinks()); index != -1 { logger.InitLog.Infof("Delete UPLink [%s] <=> [%s].\n", name, upNodeName) - n.Links = removeNodeFromLink(n.Links, index) + n.RemoveLinkByIndex(index) } } } } -func nodeInPath(upNode *UPNode, path []*UPNode) int { +func nodeInPath(upNode UPNodeInterface, path UPPath) int { for i, u := range path { if u == upNode { return i @@ -543,12 +631,7 @@ func nodeInPath(upNode *UPNode, path []*UPNode) int { return -1 } -func removeNodeFromLink(links []*UPNode, index int) []*UPNode { - links[index] = links[len(links)-1] - return links[:len(links)-1] -} - -func nodeInLink(upNode *UPNode, links []*UPNode) int { +func nodeInLink(upNode UPNodeInterface, links UPPath) int { for i, n := range links { if n == upNode { return i @@ -565,22 +648,23 @@ func (upi *UserPlaneInformation) GetUPFNodeIDByName(name string) pfcpType.NodeID return upi.UPFs[name].NodeID } -func (upi *UserPlaneInformation) GetUPFNodeByIP(ip string) *UPNode { +func (upi *UserPlaneInformation) GetUPFNodeByIP(ip string) *UPF { upfName := upi.GetUPFNameByIp(ip) return upi.UPFs[upfName] } -func (upi *UserPlaneInformation) GetUPFIDByIP(ip string) string { +func (upi *UserPlaneInformation) GetUPFIDByIP(ip string) uuid.UUID { return upi.UPFsIPtoID[ip] } func (upi *UserPlaneInformation) GetDefaultUserPlanePathByDNN(selection *UPFSelectionParams) (path UPPath) { path, pathExist := upi.DefaultUserPlanePath[selection.String()] - logger.CtxLog.Traceln("In GetDefaultUserPlanePathByDNN") - logger.CtxLog.Traceln("selection: ", selection.String()) + logger.CtxLog.Tracef("[GetDefaultUserPlanePathByDNN] for %s", selection) if pathExist { + logger.CtxLog.Traceln("[GetDefaultUserPlanePathByDNN] path exists") return } else { + logger.CtxLog.Traceln("[GetDefaultUserPlanePathByDNN] path does not exist, generate default path") pathExist = upi.GenerateDefaultPath(selection) if pathExist { return upi.DefaultUserPlanePath[selection.String()] @@ -589,16 +673,15 @@ func (upi *UserPlaneInformation) GetDefaultUserPlanePathByDNN(selection *UPFSele return nil } -func (upi *UserPlaneInformation) GetDefaultUserPlanePathByDNNAndUPF(selection *UPFSelectionParams, - upf *UPNode, +func (upi *UserPlaneInformation) GetDefaultUserPlanePathByDNNAndUPF( + selection *UPFSelectionParams, + upf *UPF, ) (path UPPath) { - nodeID := upf.NodeID.ResolveNodeIdToIp().String() + nodeID := upf.GetNodeIDString() if upi.DefaultUserPlanePathToUPF[selection.String()] != nil { path, pathExist := upi.DefaultUserPlanePathToUPF[selection.String()][nodeID] - logger.CtxLog.Traceln("In GetDefaultUserPlanePathByDNNAndUPF") - logger.CtxLog.Traceln("selection: ", selection.String()) - logger.CtxLog.Traceln("pathExist: ", pathExist) + logger.CtxLog.Tracef("[GetDefaultUserPlanePathByDNNAndUPF] for %s , pathExist: %t", selection.String(), pathExist) if pathExist { return path } @@ -627,7 +710,9 @@ func GenerateDataPath(upPath UPPath) *DataPath { for idx, upNode := range upPath { node = NewDataPathNode() - node.UPF = upNode.UPF + if upNode.GetType() == UPNODE_UPF { + node.UPF = upNode.(*UPF) + } if idx == lowerBound { root = node @@ -649,8 +734,8 @@ func GenerateDataPath(upPath UPPath) *DataPath { } func (upi *UserPlaneInformation) GenerateDefaultPath(selection *UPFSelectionParams) bool { - var source *UPNode - var destinations []*UPNode + var source UPNodeInterface + var upfCandidates []*UPF for _, node := range upi.AccessNetwork { if node.Type == UPNODE_AN { @@ -664,28 +749,33 @@ func (upi *UserPlaneInformation) GenerateDefaultPath(selection *UPFSelectionPara return false } - destinations = upi.selectMatchUPF(selection) + upfCandidates = upi.selectMatchUPF(selection) - if len(destinations) == 0 { - logger.CtxLog.Errorf("Can't find UPF with DNN[%s] S-NSSAI[sst: %d sd: %s] DNAI[%s]\n", selection.Dnn, - selection.SNssai.Sst, selection.SNssai.Sd, selection.Dnai) + if len(upfCandidates) == 0 { + logger.CtxLog.Errorf("Can't find UPFs that match %s", selection) return false } else { - logger.CtxLog.Tracef("Find UPF with DNN[%s] S-NSSAI[sst: %d sd: %s] DNAI[%s]\n", selection.Dnn, - selection.SNssai.Sst, selection.SNssai.Sd, selection.Dnai) + logger.CtxLog.Tracef("Found %d UPFs that match %s", len(upfCandidates), selection) } // Run DFS - visited := make(map[*UPNode]bool) + visited := make(map[UPNodeInterface]bool) + + if len(upi.UPNodes) < 2 { + logger.CtxLog.Errorln("No UPNodes in UserPlaneInformation !?") + return false + } for _, upNode := range upi.UPNodes { + logger.CtxLog.Tracef("DFS with UPNode %s", upNode) visited[upNode] = false } - path, pathExist := getPathBetween(source, destinations[0], visited, selection) + path, pathExist := getPathBetween(source, upfCandidates[0], visited, selection) + logger.CtxLog.Tracef("After [getPathBetween]: path exists %t, path %s", pathExist, path) if pathExist { - if path[0].Type == UPNODE_AN { + if path[0].GetType() == UPNODE_AN { path = path[1:] } upi.DefaultUserPlanePath[selection.String()] = path @@ -694,8 +784,8 @@ func (upi *UserPlaneInformation) GenerateDefaultPath(selection *UPFSelectionPara return pathExist } -func (upi *UserPlaneInformation) GenerateDefaultPathToUPF(selection *UPFSelectionParams, destination *UPNode) bool { - var source *UPNode +func (upi *UserPlaneInformation) GenerateDefaultPathToUPF(selection *UPFSelectionParams, destination *UPF) bool { + var source UPNodeInterface for _, node := range upi.AccessNetwork { if node.Type == UPNODE_AN { @@ -710,7 +800,7 @@ func (upi *UserPlaneInformation) GenerateDefaultPathToUPF(selection *UPFSelectio } // Run DFS - visited := make(map[*UPNode]bool) + visited := make(map[UPNodeInterface]bool) for _, upNode := range upi.UPNodes { visited[upNode] = false @@ -719,23 +809,23 @@ func (upi *UserPlaneInformation) GenerateDefaultPathToUPF(selection *UPFSelectio path, pathExist := getPathBetween(source, destination, visited, selection) if pathExist { - if path[0].Type == UPNODE_AN { + if path[0].GetType() == UPNODE_AN { path = path[1:] } if upi.DefaultUserPlanePathToUPF[selection.String()] == nil { - upi.DefaultUserPlanePathToUPF[selection.String()] = make(map[string][]*UPNode) + upi.DefaultUserPlanePathToUPF[selection.String()] = make(map[string]UPPath) } - upi.DefaultUserPlanePathToUPF[selection.String()][destination.NodeID.ResolveNodeIdToIp().String()] = path + upi.DefaultUserPlanePathToUPF[selection.String()][destination.GetNodeIDString()] = path } return pathExist } -func (upi *UserPlaneInformation) selectMatchUPF(selection *UPFSelectionParams) []*UPNode { - upList := make([]*UPNode, 0) +func (upi *UserPlaneInformation) selectMatchUPF(selection *UPFSelectionParams) []*UPF { + upfList := make([]*UPF, 0) - for _, upNode := range upi.UPFs { - for _, snssaiInfo := range upNode.UPF.SNssaiInfos { + for _, upf := range upi.UPFs { + for _, snssaiInfo := range upf.SNssaiInfos { currentSnssai := snssaiInfo.SNssai targetSnssai := selection.SNssai @@ -747,32 +837,35 @@ func (upi *UserPlaneInformation) selectMatchUPF(selection *UPFSelectionParams) [ if selection.Dnai != "" && !dnnInfo.ContainsDNAI(selection.Dnai) { continue } - upList = append(upList, upNode) + upfList = append(upfList, upf) break } } } } - return upList + return upfList } -func getPathBetween(cur *UPNode, dest *UPNode, visited map[*UPNode]bool, +func getPathBetween( + cur UPNodeInterface, + dest UPNodeInterface, + visited map[UPNodeInterface]bool, selection *UPFSelectionParams, -) (path []*UPNode, pathExist bool) { +) (path UPPath, pathExist bool) { + logger.CtxLog.Tracef("[getPathBetween] node %s[%s] and %s[%s]", cur.GetName(), cur.GetNodeIDString(), dest.GetName(), dest.GetNodeIDString()) visited[cur] = true - if reflect.DeepEqual(*cur, *dest) { - path = make([]*UPNode, 0) + if reflect.DeepEqual(cur, dest) { + path = make(UPPath, 0, 1) path = append(path, cur) pathExist = true + logger.CtxLog.Traceln("[getPathBetween] source and destination are equal") return path, pathExist } - selectedSNssai := selection.SNssai - - for _, node := range cur.Links { + for _, node := range cur.GetLinks() { if !visited[node] { - if !node.UPF.isSupportSnssai(selectedSNssai) { + if node.GetType() == UPNODE_UPF && !node.(*UPF).isSupportSnssai(selection.SNssai) { visited[node] = true continue } @@ -780,7 +873,7 @@ func getPathBetween(cur *UPNode, dest *UPNode, visited map[*UPNode]bool, path_tail, pathExistBuf := getPathBetween(node, dest, visited, selection) pathExist = pathExistBuf if pathExist { - path = make([]*UPNode, 0) + path = make(UPPath, 0, 1+len(path_tail)) path = append(path, cur) path = append(path, path_tail...) @@ -793,11 +886,11 @@ func getPathBetween(cur *UPNode, dest *UPNode, visited map[*UPNode]bool, } // this function select PSA by SNSSAI, DNN and DNAI exlude IP -func (upi *UserPlaneInformation) selectAnchorUPF(source *UPNode, selection *UPFSelectionParams) []*UPNode { +func (upi *UserPlaneInformation) selectAnchorUPF(source UPNodeInterface, selection *UPFSelectionParams) []*UPF { // UPFSelectionParams may have static IP, but we would not match static IP in "MatchedSelection" function - upList := make([]*UPNode, 0) - visited := make(map[*UPNode]bool) - queue := make([]*UPNode, 0) + upfList := make([]*UPF, 0) + visited := make(map[UPNodeInterface]bool) + queue := make(UPPath, 0) selectionForIUPF := &UPFSelectionParams{ Dnn: selection.Dnn, SNssai: selection.SNssai, @@ -809,9 +902,9 @@ func (upi *UserPlaneInformation) selectAnchorUPF(source *UPNode, selection *UPFS queue = queue[1:] findNewNode := false visited[node] = true - for _, link := range node.Links { - if !visited[link] { - if link.MatchedSelection(selectionForIUPF) { + for _, link := range node.GetLinks() { + if link.GetType() == UPNODE_UPF && !visited[link] { + if link.(*UPF).MatchedSelection(selectionForIUPF) { queue = append(queue, link) findNewNode = true break @@ -820,8 +913,8 @@ func (upi *UserPlaneInformation) selectAnchorUPF(source *UPNode, selection *UPFS } if !findNewNode { // if new node is AN type not need to add upList - if node.Type == UPNODE_UPF && node.MatchedSelection(selection) { - upList = append(upList, node) + if node.GetType() == UPNODE_UPF && node.(*UPF).MatchedSelection(selection) { + upfList = append(upfList, node.(*UPF)) } } @@ -829,19 +922,19 @@ func (upi *UserPlaneInformation) selectAnchorUPF(source *UPNode, selection *UPFS break } } - return upList + return upfList } -func (upi *UserPlaneInformation) sortUPFListByName(upfList []*UPNode) []*UPNode { +func (upi *UserPlaneInformation) sortUPFListByName(upfList []*UPF) []*UPF { keys := make([]string, 0, len(upi.UPFs)) for k := range upi.UPFs { keys = append(keys, k) } sort.Strings(keys) - sortedUpList := make([]*UPNode, 0) + sortedUpList := make([]*UPF, 0) for _, name := range keys { for _, node := range upfList { - if name == upi.GetUPFNameByIp(node.NodeID.ResolveNodeIdToIp().String()) { + if name == upi.GetUPFNameByIp(node.GetNodeIDString()) { sortedUpList = append(sortedUpList, node) } } @@ -849,7 +942,7 @@ func (upi *UserPlaneInformation) sortUPFListByName(upfList []*UPNode) []*UPNode return sortedUpList } -func (upi *UserPlaneInformation) selectUPPathSource() (*UPNode, error) { +func (upi *UserPlaneInformation) selectUPPathSource() (UPNodeInterface, error) { // if multiple gNBs exist, select one according to some criterion for _, node := range upi.AccessNetwork { if node.Type == UPNODE_AN { @@ -860,26 +953,26 @@ func (upi *UserPlaneInformation) selectUPPathSource() (*UPNode, error) { } // SelectUPFAndAllocUEIP will return anchor UPF, allocated UE IP and use/not use static IP -func (upi *UserPlaneInformation) SelectUPFAndAllocUEIP(selection *UPFSelectionParams) (*UPNode, net.IP, bool) { +func (upi *UserPlaneInformation) SelectUPFAndAllocUEIP(selection *UPFSelectionParams) (*UPF, net.IP, bool) { source, err := upi.selectUPPathSource() if err != nil { return nil, nil, false } - UPFList := upi.selectAnchorUPF(source, selection) - listLength := len(UPFList) + upfList := upi.selectAnchorUPF(source, selection) + listLength := len(upfList) if listLength == 0 { logger.CtxLog.Warnf("Can't find UPF with DNN[%s] S-NSSAI[sst: %d sd: %s] DNAI[%s]\n", selection.Dnn, selection.SNssai.Sst, selection.SNssai.Sd, selection.Dnai) return nil, nil, false } - UPFList = upi.sortUPFListByName(UPFList) - sortedUPFList := createUPFListForSelection(UPFList) + upfList = upi.sortUPFListByName(upfList) + sortedUPFList := createUPFListForSelection(upfList) for _, upf := range sortedUPFList { logger.CtxLog.Debugf("check start UPF: %s", - upi.GetUPFNameByIp(upf.NodeID.ResolveNodeIdToIp().String())) - if upf.UPF.UPFStatus != AssociatedSetUpSuccess { + upi.GetUPFNameByIp(upf.GetNodeIDString())) + if upf.UPFStatus != AssociatedSetUpSuccess { logger.CtxLog.Infof("PFCP Association not yet Established with: %s", - upi.GetUPFNameByIp(upf.NodeID.ResolveNodeIdToIp().String())) + upi.GetUPFNameByIp(upf.GetNodeIDString())) continue } pools, useStaticIPPool := getUEIPPool(upf, selection) @@ -892,7 +985,7 @@ func (upi *UserPlaneInformation) SelectUPFAndAllocUEIP(selection *UPFSelectionPa addr := pool.Allocate(selection.PDUAddress) if addr != nil { logger.CtxLog.Infof("Selected UPF: %s", - upi.GetUPFNameByIp(upf.NodeID.ResolveNodeIdToIp().String())) + upi.GetUPFNameByIp(upf.GetNodeIDString())) return upf, addr, useStaticIPPool } // if all addresses in pool are used, search next pool @@ -907,7 +1000,7 @@ func (upi *UserPlaneInformation) SelectUPFAndAllocUEIP(selection *UPFSelectionPa return nil, nil, false } -func createUPFListForSelection(inputList []*UPNode) (outputList []*UPNode) { +func createUPFListForSelection(inputList []*UPF) (outputList []*UPF) { offset := rand.Intn(len(inputList)) return append(inputList[offset:], inputList[:offset]...) } @@ -918,8 +1011,8 @@ func createPoolListForSelection(inputList []*UeIPPool) (outputList []*UeIPPool) } // getUEIPPool will return IP pools and use/not use static IP pool -func getUEIPPool(upNode *UPNode, selection *UPFSelectionParams) ([]*UeIPPool, bool) { - for _, snssaiInfo := range upNode.UPF.SNssaiInfos { +func getUEIPPool(upf *UPF, selection *UPFSelectionParams) ([]*UeIPPool, bool) { + for _, snssaiInfo := range upf.SNssaiInfos { currentSnssai := snssaiInfo.SNssai targetSnssai := selection.SNssai @@ -959,19 +1052,19 @@ func getUEIPPool(upNode *UPNode, selection *UPFSelectionParams) ([]*UeIPPool, bo return nil, false } -func (upi *UserPlaneInformation) ReleaseUEIP(upf *UPNode, addr net.IP, static bool) { +func (upi *UserPlaneInformation) ReleaseUEIP(upf *UPF, addr net.IP, static bool) { pool := findPoolByAddr(upf, addr, static) if pool == nil { // nothing to do logger.CtxLog.Warnf("Fail to release UE IP address: %v to UPF: %s", - upi.GetUPFNameByIp(upf.NodeID.ResolveNodeIdToIp().String()), addr) + upi.GetUPFNameByIp(upf.GetNodeIDString()), addr) return } pool.Release(addr) } -func findPoolByAddr(upf *UPNode, addr net.IP, static bool) *UeIPPool { - for _, snssaiInfo := range upf.UPF.SNssaiInfos { +func findPoolByAddr(upf *UPF, addr net.IP, static bool) *UeIPPool { + for _, snssaiInfo := range upf.SNssaiInfos { for _, dnnInfo := range snssaiInfo.DnnList { if static { for _, pool := range dnnInfo.StaticIPPools { diff --git a/internal/context/user_plane_information_test.go b/internal/context/user_plane_information_test.go index d27158a8..e24b4431 100644 --- a/internal/context/user_plane_information_test.go +++ b/internal/context/user_plane_information_test.go @@ -269,7 +269,7 @@ func TestSelectUPFAndAllocUEIP(t *testing.T) { userplaneInformation := context.NewUserPlaneInformation(configuration) for _, upf := range userplaneInformation.UPFs { - upf.UPF.UPFStatus = context.AssociatedSetUpSuccess + upf.UPFStatus = context.AssociatedSetUpSuccess } for i := 0; i <= 100; i++ { @@ -486,7 +486,7 @@ var testCasesOfGetUEIPPool = []struct { func TestGetUEIPPool(t *testing.T) { userplaneInformation := context.NewUserPlaneInformation(configForIPPoolAllocate) for _, upf := range userplaneInformation.UPFs { - upf.UPF.UPFStatus = context.AssociatedSetUpSuccess + upf.UPFStatus = context.AssociatedSetUpSuccess } for ci, tc := range testCasesOfGetUEIPPool { @@ -498,7 +498,7 @@ func TestGetUEIPPool(t *testing.T) { } } - var upf *context.UPNode + var upf *context.UPF var allocatedIP net.IP var useStatic bool for times := 1; times <= tc.allocateTimes; times++ { diff --git a/internal/pfcp/message/send.go b/internal/pfcp/message/send.go index 6f9f71e2..0b083114 100644 --- a/internal/pfcp/message/send.go +++ b/internal/pfcp/message/send.go @@ -139,12 +139,12 @@ func SendPfcpSessionEstablishmentRequest( qerList []*context.QER, urrList []*context.URR, ) (resMsg *pfcpUdp.Message, err error) { - nodeIDtoIP := upf.NodeID.ResolveNodeIdToIp() + nodeIDtoIP := upf.GetNodeIDString() if upf.UPFStatus != context.AssociatedSetUpSuccess { - return nil, fmt.Errorf("Not Associated with UPF[%s]", nodeIDtoIP.String()) + return nil, fmt.Errorf("Not Associated with UPF[%s]", nodeIDtoIP) } - pfcpMsg, err := BuildPfcpSessionEstablishmentRequest(upf.NodeID, nodeIDtoIP.String(), + pfcpMsg, err := BuildPfcpSessionEstablishmentRequest(upf.NodeID, nodeIDtoIP, ctx, pdrList, farList, barList, qerList, urrList) if err != nil { logger.PfcpLog.Errorf("Build PFCP Session Establishment Request failed: %v", err) @@ -165,7 +165,7 @@ func SendPfcpSessionEstablishmentRequest( } upaddr := &net.UDPAddr{ - IP: nodeIDtoIP, + IP: net.ParseIP(nodeIDtoIP), Port: pfcpUdp.PFCP_PORT, } logger.PduSessLog.Traceln("[SMF] Send SendPfcpSessionEstablishmentRequest") @@ -180,7 +180,7 @@ func SendPfcpSessionEstablishmentRequest( return resMsg, fmt.Errorf("received unexpected type response message: %+v", resMsg.PfcpMessage.Header) } - localSEID := ctx.PFCPContext[nodeIDtoIP.String()].LocalSEID + localSEID := ctx.PFCPContext[nodeIDtoIP].LocalSEID if resMsg.PfcpMessage.Header.SEID != localSEID { return resMsg, fmt.Errorf("received unexpected SEID response message: %+v, exptcted: %d", resMsg.PfcpMessage.Header, localSEID) @@ -222,12 +222,12 @@ func SendPfcpSessionModificationRequest( qerList []*context.QER, urrList []*context.URR, ) (resMsg *pfcpUdp.Message, err error) { - nodeIDtoIP := upf.NodeID.ResolveNodeIdToIp() + nodeIDtoIP := upf.GetNodeIDString() if upf.UPFStatus != context.AssociatedSetUpSuccess { - return nil, fmt.Errorf("Not Associated with UPF[%s]", nodeIDtoIP.String()) + return nil, fmt.Errorf("Not Associated with UPF[%s]", nodeIDtoIP) } - pfcpMsg, err := BuildPfcpSessionModificationRequest(upf.NodeID, nodeIDtoIP.String(), + pfcpMsg, err := BuildPfcpSessionModificationRequest(upf.NodeID, nodeIDtoIP, ctx, pdrList, farList, barList, qerList, urrList) if err != nil { logger.PfcpLog.Errorf("Build PFCP Session Modification Request failed: %v", err) @@ -235,7 +235,7 @@ func SendPfcpSessionModificationRequest( } seqNum := getSeqNumber() - remoteSEID := ctx.PFCPContext[nodeIDtoIP.String()].RemoteSEID + remoteSEID := ctx.PFCPContext[nodeIDtoIP].RemoteSEID message := &pfcp.Message{ Header: pfcp.Header{ Version: pfcp.PfcpVersion, @@ -250,7 +250,7 @@ func SendPfcpSessionModificationRequest( } upaddr := &net.UDPAddr{ - IP: nodeIDtoIP, + IP: net.ParseIP(nodeIDtoIP), Port: pfcpUdp.PFCP_PORT, } @@ -263,7 +263,7 @@ func SendPfcpSessionModificationRequest( return resMsg, fmt.Errorf("received unexpected type response message: %+v", resMsg.PfcpMessage.Header) } - localSEID := ctx.PFCPContext[nodeIDtoIP.String()].LocalSEID + localSEID := ctx.PFCPContext[nodeIDtoIP].LocalSEID if resMsg.PfcpMessage.Header.SEID != localSEID { return resMsg, fmt.Errorf("received unexpected SEID response message: %+v, exptcted: %d", resMsg.PfcpMessage.Header, localSEID) @@ -297,9 +297,9 @@ func SendPfcpSessionModificationResponse(addr *net.UDPAddr) { } func SendPfcpSessionDeletionRequest(upf *context.UPF, ctx *context.SMContext) (resMsg *pfcpUdp.Message, err error) { - nodeIDtoIP := upf.NodeID.ResolveNodeIdToIp() + nodeIDtoIP := upf.GetNodeIDString() if upf.UPFStatus != context.AssociatedSetUpSuccess { - return nil, fmt.Errorf("Not Associated with UPF[%s]", nodeIDtoIP.String()) + return nil, fmt.Errorf("Not Associated with UPF[%s]", nodeIDtoIP) } pfcpMsg, err := BuildPfcpSessionDeletionRequest() @@ -308,7 +308,7 @@ func SendPfcpSessionDeletionRequest(upf *context.UPF, ctx *context.SMContext) (r return nil, err } seqNum := getSeqNumber() - remoteSEID := ctx.PFCPContext[nodeIDtoIP.String()].RemoteSEID + remoteSEID := ctx.PFCPContext[nodeIDtoIP].RemoteSEID message := &pfcp.Message{ Header: pfcp.Header{ Version: pfcp.PfcpVersion, @@ -323,7 +323,7 @@ func SendPfcpSessionDeletionRequest(upf *context.UPF, ctx *context.SMContext) (r } upaddr := &net.UDPAddr{ - IP: nodeIDtoIP, + IP: net.ParseIP(nodeIDtoIP), Port: pfcpUdp.PFCP_PORT, } @@ -336,7 +336,7 @@ func SendPfcpSessionDeletionRequest(upf *context.UPF, ctx *context.SMContext) (r return resMsg, fmt.Errorf("received unexpected type response message: %+v", resMsg.PfcpMessage.Header) } - localSEID := ctx.PFCPContext[nodeIDtoIP.String()].LocalSEID + localSEID := ctx.PFCPContext[nodeIDtoIP].LocalSEID if resMsg.PfcpMessage.Header.SEID != localSEID { return resMsg, fmt.Errorf("received unexpected SEID response message: %+v, exptcted: %d", resMsg.PfcpMessage.Header, localSEID) diff --git a/internal/sbi/api_upi.go b/internal/sbi/api_upi.go index 05030383..e25ba3ce 100644 --- a/internal/sbi/api_upi.go +++ b/internal/sbi/api_upi.go @@ -69,9 +69,9 @@ func (s *Server) PostUpNodesLinks(c *gin.Context) { for _, upf := range upi.UPFs { // only associate new ones - if upf.UPF.UPFStatus == smf_context.NotAssociated { - upf.UPF.Ctx, upf.UPF.CancelFunc = context.WithCancel(context.Background()) - go s.Processor().ToBeAssociatedWithUPF(smf_context.GetSelf().Ctx, upf.UPF) + if upf.UPFStatus == smf_context.NotAssociated { + upf.Ctx, upf.CancelFunc = context.WithCancel(context.Background()) + go s.Processor().ToBeAssociatedWithUPF(smf_context.GetSelf().Ctx, upf) } } c.JSON(http.StatusOK, gin.H{"status": "OK"}) @@ -87,11 +87,11 @@ func (s *Server) DeleteUpNodeLink(c *gin.Context) { upi.Mu.Lock() defer upi.Mu.Unlock() if upNode, ok := upi.UPNodes[upNodeRef]; ok { - if upNode.Type == smf_context.UPNODE_UPF { - go s.Processor().ReleaseAllResourcesOfUPF(upNode.UPF) + if upNode.GetType() == smf_context.UPNODE_UPF { + go s.Processor().ReleaseAllResourcesOfUPF(upNode.(*smf_context.UPF)) + upNode.(*smf_context.UPF).CancelFunc() } upi.UpNodeDelete(upNodeRef) - upNode.UPF.CancelFunc() c.JSON(http.StatusOK, gin.H{"status": "OK"}) } else { c.JSON(http.StatusNotFound, gin.H{}) diff --git a/internal/sbi/processor/association.go b/internal/sbi/processor/association.go index 9b60b233..52b5656d 100644 --- a/internal/sbi/processor/association.go +++ b/internal/sbi/processor/association.go @@ -18,9 +18,9 @@ import ( func (p *Processor) ToBeAssociatedWithUPF(ctx context.Context, upf *smf_context.UPF) { var upfStr string if upf.NodeID.NodeIdType == pfcpType.NodeIdTypeFqdn { - upfStr = fmt.Sprintf("[%s](%s)", upf.NodeID.FQDN, upf.NodeID.ResolveNodeIdToIp().String()) + upfStr = fmt.Sprintf("[%s](%s)", upf.NodeID.FQDN, upf.GetNodeIDString()) } else { - upfStr = fmt.Sprintf("[%s]", upf.NodeID.ResolveNodeIdToIp().String()) + upfStr = fmt.Sprintf("[%s]", upf.GetNodeIDString()) } for { @@ -49,9 +49,9 @@ func (p *Processor) ToBeAssociatedWithUPF(ctx context.Context, upf *smf_context. func (p *Processor) ReleaseAllResourcesOfUPF(upf *smf_context.UPF) { var upfStr string if upf.NodeID.NodeIdType == pfcpType.NodeIdTypeFqdn { - upfStr = fmt.Sprintf("[%s](%s)", upf.NodeID.FQDN, upf.NodeID.ResolveNodeIdToIp().String()) + upfStr = fmt.Sprintf("[%s](%s)", upf.NodeID.FQDN, upf.GetNodeIDString()) } else { - upfStr = fmt.Sprintf("[%s]", upf.NodeID.ResolveNodeIdToIp().String()) + upfStr = fmt.Sprintf("[%s]", upf.GetNodeIDString()) } p.releaseAllResourcesOfUPF(upf, upfStr) } @@ -125,7 +125,7 @@ func setupPfcpAssociation(upf *smf_context.UPF, upfStr string) error { upf.UPIPInfo = *rsp.UserPlaneIPResourceInformation logger.MainLog.Infof("UPF(%s)[%s] setup association", - upf.NodeID.ResolveNodeIdToIp().String(), upf.UPIPInfo.NetworkInstance.NetworkInstance) + upf.GetNodeIDString(), upf.UPIPInfo.NetworkInstance.NetworkInstance) } return nil diff --git a/internal/sbi/processor/charging_trigger.go b/internal/sbi/processor/charging_trigger.go index e157cf5c..788b2eba 100644 --- a/internal/sbi/processor/charging_trigger.go +++ b/internal/sbi/processor/charging_trigger.go @@ -209,7 +209,7 @@ func buildMultiUnitUsageFromUsageReport(smContext *smf_context.SMContext) []mode ratingGroupUnitUsagesMap[rg] = models.MultipleUnitUsage{ RatingGroup: rg, - UPFID: ur.UpfId, + UPFID: ur.UpfId.String(), UsedUnitContainer: []models.UsedUnitContainer{uu}, RequestedUnit: requestUnit, } diff --git a/internal/sbi/processor/datapath.go b/internal/sbi/processor/datapath.go index f089e567..6be34260 100644 --- a/internal/sbi/processor/datapath.go +++ b/internal/sbi/processor/datapath.go @@ -11,6 +11,7 @@ import ( smf_context "github.com/free5gc/smf/internal/context" "github.com/free5gc/smf/internal/logger" pfcp_message "github.com/free5gc/smf/internal/pfcp/message" + "github.com/google/uuid" ) type PFCPState struct { @@ -383,7 +384,7 @@ func (p *Processor) updateAnUpfPfcpSession( func ReleaseTunnel(smContext *smf_context.SMContext) []SendPfcpResult { resChan := make(chan SendPfcpResult) - deletedPFCPNode := make(map[string]bool) + deletedPFCPNode := make(map[uuid.UUID]bool) for _, dataPath := range smContext.Tunnel.DataPathPool { var targetNodes []*smf_context.DataPathNode for node := dataPath.FirstDPNode; node != nil; node = node.Next() { @@ -391,11 +392,7 @@ func ReleaseTunnel(smContext *smf_context.SMContext) []SendPfcpResult { } dataPath.DeactivateTunnelAndPDR(smContext) for _, node := range targetNodes { - curUPFID, err := node.GetUPFID() - if err != nil { - logger.PduSessLog.Error(err) - continue - } + curUPFID := node.GetUPFID() if _, exist := deletedPFCPNode[curUPFID]; !exist { go deletePfcpSession(node.UPF, smContext, resChan) deletedPFCPNode[curUPFID] = true diff --git a/internal/sbi/processor/pdu_session_test.go b/internal/sbi/processor/pdu_session_test.go index a8153c47..681a79bf 100644 --- a/internal/sbi/processor/pdu_session_test.go +++ b/internal/sbi/processor/pdu_session_test.go @@ -449,7 +449,7 @@ func TestHandlePDUSessionSMContextCreate(t *testing.T) { // modify associate setup status allUPFs := smf_context.GetSelf().UserPlaneInformation.UPFs for _, upfNode := range allUPFs { - upfNode.UPF.UPFStatus = smf_context.AssociatedSetUpSuccess + upfNode.UPFStatus = smf_context.AssociatedSetUpSuccess } testCases := []struct { diff --git a/internal/sbi/processor/ulcl_procedure.go b/internal/sbi/processor/ulcl_procedure.go index b721a779..bc0a9f72 100644 --- a/internal/sbi/processor/ulcl_procedure.go +++ b/internal/sbi/processor/ulcl_procedure.go @@ -65,7 +65,7 @@ func (p *Processor) EstablishPSA2(smContext *context.SMContext) { for node := activatingPath.FirstDPNode; node != nil; node = node.Next() { if nodeAfterULCL { addr := net.UDPAddr{ - IP: context.ResolveIP(node.UPF.Addr), + IP: net.ParseIP(node.GetNodeIP()), Port: pfcpUdp.PFCP_PORT, } @@ -108,7 +108,7 @@ func (p *Processor) EstablishPSA2(smContext *context.SMContext) { urrList: urrList, } - curDPNodeIP := node.UPF.NodeID.ResolveNodeIdToIp().String() + curDPNodeIP := node.GetNodeIP() pendingUPFs = append(pendingUPFs, curDPNodeIP) sessionContext, exist := smContext.PFCPContext[node.GetNodeIP()] @@ -181,7 +181,7 @@ func EstablishULCL(smContext *context.SMContext) { urrList: UPLinkPDR.URR, } - curDPNodeIP := ulcl.NodeID.ResolveNodeIdToIp().String() + curDPNodeIP := ulcl.GetNodeIDString() pendingUPFs = append(pendingUPFs, curDPNodeIP) go modifyExistingPfcpSession(smContext, pfcpState, resChan, "") break @@ -226,7 +226,7 @@ func UpdatePSA2DownLink(smContext *context.SMContext) { urrList: urrList, } - curDPNodeIP := node.UPF.NodeID.ResolveNodeIdToIp().String() + curDPNodeIP := node.GetNodeIP() pendingUPFs = append(pendingUPFs, curDPNodeIP) go modifyExistingPfcpSession(smContext, pfcpState, resChan, "") logger.PfcpLog.Info("[SMF] Update PSA2 downlink msg has been send") @@ -338,7 +338,7 @@ func UpdateRANAndIUPFUpLink(smContext *context.SMContext) { urrList: UPLinkPDR.URR, } - curDPNodeIP := curDPNode.UPF.NodeID.ResolveNodeIdToIp().String() + curDPNodeIP := curDPNode.GetNodeIP() pendingUPFs = append(pendingUPFs, curDPNodeIP) go modifyExistingPfcpSession(smContext, pfcpState, resChan, "") } diff --git a/pkg/factory/config.go b/pkg/factory/config.go index b0ee3267..7e21294b 100644 --- a/pkg/factory/config.go +++ b/pkg/factory/config.go @@ -478,8 +478,6 @@ func (u *UserPlaneInformation) validate() (bool, error) { type UPNode struct { Type string `json:"type" yaml:"type" valid:"upNodeType,required"` NodeID string `json:"nodeID" yaml:"nodeID" valid:"host,optional"` - Addr string `json:"addr" yaml:"addr" valid:"host,optional"` - ANIP string `json:"anIP" yaml:"anIP" valid:"host,optional"` Dnn string `json:"dnn" yaml:"dnn" valid:"type(string),minstringlength(1),optional"` SNssaiInfos []*SnssaiUpfInfoItem `json:"sNssaiUpfInfos" yaml:"sNssaiUpfInfos,omitempty" valid:"optional"` InterfaceUpfInfoList []*InterfaceUpfInfoItem `json:"interfaces" yaml:"interfaces,omitempty" valid:"optional"` diff --git a/pkg/utils/pfcp_util.go b/pkg/utils/pfcp_util.go index f047fdca..dfb07f4b 100644 --- a/pkg/utils/pfcp_util.go +++ b/pkg/utils/pfcp_util.go @@ -28,9 +28,9 @@ func InitPFCPFunc() (func(a *service.SmfApp), func()) { // Wait for PFCP start time.Sleep(1000 * time.Millisecond) - for _, upNode := range smf_context.GetSelf().UserPlaneInformation.UPFs { - upNode.UPF.Ctx, upNode.UPF.CancelFunc = context.WithCancel(ctx) - go a.Processor().ToBeAssociatedWithUPF(ctx, upNode.UPF) + for _, upf := range smf_context.GetSelf().UserPlaneInformation.UPFs { + upf.Ctx, upf.CancelFunc = context.WithCancel(ctx) + go a.Processor().ToBeAssociatedWithUPF(ctx, upf) } } From fef63d4fe819bd8821ba4cb099ef9478a0a70ce5 Mon Sep 17 00:00:00 2001 From: Laura Henning Date: Wed, 11 Sep 2024 15:49:54 +0000 Subject: [PATCH 02/16] fix: nil pointer --- internal/context/ue_datapath.go | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/internal/context/ue_datapath.go b/internal/context/ue_datapath.go index 7e14dc92..c21edf6b 100644 --- a/internal/context/ue_datapath.go +++ b/internal/context/ue_datapath.go @@ -15,22 +15,20 @@ type UEPreConfigPaths struct { } func NewUEDataPathNode(name string) (node *DataPathNode, err error) { - var upNode UPNodeInterface - - if _, exist := smfContext.UserPlaneInformation.UPNodes[name]; !exist { + if upNode, exist := smfContext.UserPlaneInformation.UPNodes[name]; !exist { err = fmt.Errorf("UPNode %s isn't exist in smfcfg.yaml, but in UERouting.yaml!", name) return nil, err - } - - if upNode.GetType() == UPNODE_AN { - err = fmt.Errorf("UPNode %s has type 'UPNODE_AN', cannot add as DataPathNode!", name) - return nil, err - } + } else { + if upNode.GetType() == UPNODE_AN { + err = fmt.Errorf("UPNode %s has type 'UPNODE_AN', cannot add as DataPathNode!", name) + return nil, err + } - node = &DataPathNode{ - UPF: upNode.(*UPF), - UpLinkTunnel: >PTunnel{}, - DownLinkTunnel: >PTunnel{}, + node = &DataPathNode{ + UPF: upNode.(*UPF), + UpLinkTunnel: >PTunnel{}, + DownLinkTunnel: >PTunnel{}, + } } return } From 7e9d70f9230d0a61ca8b2f2a0ce1c56b7d2393de Mon Sep 17 00:00:00 2001 From: Laura Henning Date: Wed, 18 Sep 2024 15:39:39 +0000 Subject: [PATCH 03/16] chore: linting --- internal/context/user_plane_information.go | 8 +++++--- internal/sbi/processor/datapath.go | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/internal/context/user_plane_information.go b/internal/context/user_plane_information.go index 7490e70a..5eeeaa61 100644 --- a/internal/context/user_plane_information.go +++ b/internal/context/user_plane_information.go @@ -9,11 +9,12 @@ import ( "sort" "sync" + "github.com/google/uuid" + "github.com/free5gc/openapi/models" "github.com/free5gc/pfcp/pfcpType" "github.com/free5gc/smf/internal/logger" "github.com/free5gc/smf/pkg/factory" - "github.com/google/uuid" ) // UserPlaneInformation store userplane topology @@ -676,7 +677,7 @@ func (upi *UserPlaneInformation) GetDefaultUserPlanePathByDNN(selection *UPFSele func (upi *UserPlaneInformation) GetDefaultUserPlanePathByDNNAndUPF( selection *UPFSelectionParams, upf *UPF, -) (path UPPath) { +) UPPath { nodeID := upf.GetNodeIDString() if upi.DefaultUserPlanePathToUPF[selection.String()] != nil { @@ -852,7 +853,8 @@ func getPathBetween( visited map[UPNodeInterface]bool, selection *UPFSelectionParams, ) (path UPPath, pathExist bool) { - logger.CtxLog.Tracef("[getPathBetween] node %s[%s] and %s[%s]", cur.GetName(), cur.GetNodeIDString(), dest.GetName(), dest.GetNodeIDString()) + logger.CtxLog.Tracef("[getPathBetween] node %s[%s] and %s[%s]", + cur.GetName(), cur.GetNodeIDString(), dest.GetName(), dest.GetNodeIDString()) visited[cur] = true if reflect.DeepEqual(cur, dest) { diff --git a/internal/sbi/processor/datapath.go b/internal/sbi/processor/datapath.go index 6be34260..69b59e9d 100644 --- a/internal/sbi/processor/datapath.go +++ b/internal/sbi/processor/datapath.go @@ -3,6 +3,8 @@ package processor import ( "fmt" + "github.com/google/uuid" + "github.com/free5gc/nas/nasMessage" "github.com/free5gc/openapi/models" "github.com/free5gc/pfcp" @@ -11,7 +13,6 @@ import ( smf_context "github.com/free5gc/smf/internal/context" "github.com/free5gc/smf/internal/logger" pfcp_message "github.com/free5gc/smf/internal/pfcp/message" - "github.com/google/uuid" ) type PFCPState struct { From 68ec63e89b826f70d679e38820e70a39e7be927c Mon Sep 17 00:00:00 2001 From: Laura Henning Date: Fri, 20 Sep 2024 08:28:51 +0000 Subject: [PATCH 04/16] refactor: move GNB struct and methods to separate file --- internal/context/gnb.go | 96 ++++++++++++++++++++++ internal/context/user_plane_information.go | 86 ------------------- 2 files changed, 96 insertions(+), 86 deletions(-) create mode 100644 internal/context/gnb.go diff --git a/internal/context/gnb.go b/internal/context/gnb.go new file mode 100644 index 00000000..b4c264b1 --- /dev/null +++ b/internal/context/gnb.go @@ -0,0 +1,96 @@ +package context + +import ( + "fmt" + "net" + + "github.com/free5gc/pfcp/pfcpType" + "github.com/free5gc/smf/internal/logger" + "github.com/google/uuid" +) + +// embeds the UPNode struct ("inheritance") +// implements UPNodeInterface +type GNB struct { + UPNode + ANIP net.IP +} + +func (gNB *GNB) GetName() string { + return gNB.Name +} + +func (gNB *GNB) GetID() uuid.UUID { + return gNB.ID +} + +func (gNB *GNB) GetType() UPNodeType { + return gNB.Type +} + +func (gNB *GNB) GetDnn() string { + return gNB.Dnn +} + +func (gNB *GNB) String() string { + str := "gNB {\n" + prefix := " " + str += prefix + fmt.Sprintf("Name: %s\n", gNB.Name) + str += prefix + fmt.Sprintf("ANIP: %s\n", gNB.ANIP) + str += prefix + fmt.Sprintf("ID: %s\n", gNB.ID) + str += prefix + fmt.Sprintf("NodeID: %s\n", gNB.GetNodeIDString()) + str += prefix + fmt.Sprintf("Dnn: %s\n", gNB.Dnn) + str += prefix + fmt.Sprintln("Links:") + for _, link := range gNB.Links { + str += prefix + fmt.Sprintf("-- %s: %s\n", link.GetName(), link.GetNodeIDString()) + } + str += "}" + return str +} + +func (gNB *GNB) GetNodeIDString() string { + switch gNB.NodeID.NodeIdType { + case pfcpType.NodeIdTypeIpv4Address, pfcpType.NodeIdTypeIpv6Address: + return gNB.NodeID.IP.String() + case pfcpType.NodeIdTypeFqdn: + return gNB.NodeID.FQDN + default: + logger.CtxLog.Errorf("nodeID has unknown type %d", gNB.NodeID.NodeIdType) + return "" + } +} + +func (gNB *GNB) GetNodeID() pfcpType.NodeID { + return gNB.NodeID +} + +func (gNB *GNB) GetLinks() UPPath { + return gNB.Links +} + +func (gNB *GNB) AddLink(link UPNodeInterface) bool { + for _, existingLink := range gNB.Links { + if link.GetName() == existingLink.GetName() { + logger.CfgLog.Warningf("UPLink [%s] <=> [%s] already exists, skip\n", existingLink.GetName(), link.GetName()) + return false + } + } + gNB.Links = append(gNB.Links, link) + return true +} + +func (gNB *GNB) RemoveLink(link UPNodeInterface) bool { + for i, existingLink := range gNB.Links { + if link.GetName() == existingLink.GetName() && existingLink.GetNodeIDString() == link.GetNodeIDString() { + logger.CfgLog.Warningf("Remove UPLink [%s] <=> [%s]\n", existingLink.GetName(), link.GetName()) + gNB.Links = append(gNB.Links[:i], gNB.Links[i+1:]...) + return true + } + } + return false +} + +func (gNB *GNB) RemoveLinkByIndex(index int) bool { + gNB.Links[index] = gNB.Links[len(gNB.Links)-1] + return true +} diff --git a/internal/context/user_plane_information.go b/internal/context/user_plane_information.go index 5eeeaa61..b2cacf71 100644 --- a/internal/context/user_plane_information.go +++ b/internal/context/user_plane_information.go @@ -97,92 +97,6 @@ func NodeIDToString(nodeID pfcpType.NodeID) string { } } -// embeds the UPNode struct ("inheritance") -// implements UPNodeInterface -type GNB struct { - UPNode - ANIP net.IP -} - -func (gNB *GNB) GetName() string { - return gNB.Name -} - -func (gNB *GNB) GetID() uuid.UUID { - return gNB.ID -} - -func (gNB *GNB) GetType() UPNodeType { - return gNB.Type -} - -func (gNB *GNB) GetDnn() string { - return gNB.Dnn -} - -func (gNB *GNB) String() string { - str := "gNB {\n" - prefix := " " - str += prefix + fmt.Sprintf("Name: %s\n", gNB.Name) - str += prefix + fmt.Sprintf("ANIP: %s\n", gNB.ANIP) - str += prefix + fmt.Sprintf("ID: %s\n", gNB.ID) - str += prefix + fmt.Sprintf("NodeID: %s\n", gNB.GetNodeIDString()) - str += prefix + fmt.Sprintf("Dnn: %s\n", gNB.Dnn) - str += prefix + fmt.Sprintln("Links:") - for _, link := range gNB.Links { - str += prefix + fmt.Sprintf("-- %s: %s\n", link.GetName(), link.GetNodeIDString()) - } - str += "}" - return str -} - -func (gNB *GNB) GetNodeIDString() string { - switch gNB.NodeID.NodeIdType { - case pfcpType.NodeIdTypeIpv4Address, pfcpType.NodeIdTypeIpv6Address: - return gNB.NodeID.IP.String() - case pfcpType.NodeIdTypeFqdn: - return gNB.NodeID.FQDN - default: - logger.CtxLog.Errorf("nodeID has unknown type %d", gNB.NodeID.NodeIdType) - return "" - } -} - -func (gNB *GNB) GetNodeID() pfcpType.NodeID { - return gNB.NodeID -} - -func (gNB *GNB) GetLinks() UPPath { - return gNB.Links -} - -func (gNB *GNB) AddLink(link UPNodeInterface) bool { - for _, existingLink := range gNB.Links { - if link.GetName() == existingLink.GetName() { - logger.CfgLog.Warningf("UPLink [%s] <=> [%s] already exists, skip\n", existingLink.GetName(), link.GetName()) - return false - } - } - gNB.Links = append(gNB.Links, link) - return true -} - -func (gNB *GNB) RemoveLink(link UPNodeInterface) bool { - for i, existingLink := range gNB.Links { - if link.GetName() == existingLink.GetName() && existingLink.GetNodeIDString() == link.GetNodeIDString() { - logger.CfgLog.Warningf("Remove UPLink [%s] <=> [%s]\n", existingLink.GetName(), link.GetName()) - gNB.Links = append(gNB.Links[:i], gNB.Links[i+1:]...) - return true - } - } - return false -} - -func (gNB *GNB) RemoveLinkByIndex(index int) bool { - gNB.Links[index] = gNB.Links[len(gNB.Links)-1] - return true -} - func AllocateUPFID() { UPFsID := smfContext.UserPlaneInformation.UPFsID UPFsIPtoID := smfContext.UserPlaneInformation.UPFsIPtoID From cd8145bcf8420b5d2cab611e86405fbf396297ab Mon Sep 17 00:00:00 2001 From: Laura Henning Date: Fri, 20 Sep 2024 11:23:10 +0000 Subject: [PATCH 05/16] refactor: improve config to NodeID conversion, add unit test --- internal/context/user_plane_information.go | 73 +++++--- .../context/user_plane_information_test.go | 175 ++++++++++++++---- 2 files changed, 191 insertions(+), 57 deletions(-) diff --git a/internal/context/user_plane_information.go b/internal/context/user_plane_information.go index b2cacf71..c8c39184 100644 --- a/internal/context/user_plane_information.go +++ b/internal/context/user_plane_information.go @@ -112,30 +112,49 @@ func AllocateUPFID() { // the config has a single string for NodeID, // check its nature and create either IPv4, IPv6, or FQDN NodeID type -func configToNodeID(configNodeID string) pfcpType.NodeID { - var ip net.IP - if net.ParseIP(configNodeID).To4() == nil { - ip = net.ParseIP(configNodeID) - } else { - ip = net.ParseIP(configNodeID).To4() +func ConfigToNodeID(configNodeID string) (pfcpType.NodeID, error) { + logger.CfgLog.Tracef("Converting config input %s to NodeID", configNodeID) + + ip := net.ParseIP(configNodeID) + var err error + + if ip == nil { + // might be in CIDR notation + ip, _, err = net.ParseCIDR(configNodeID) } - switch len(configNodeID) { - case net.IPv4len: - return pfcpType.NodeID{ - NodeIdType: pfcpType.NodeIdTypeIpv4Address, - IP: ip, - } - case net.IPv6len: - return pfcpType.NodeID{ - NodeIdType: pfcpType.NodeIdTypeIpv6Address, - IP: ip, - } - default: - return pfcpType.NodeID{ - NodeIdType: pfcpType.NodeIdTypeFqdn, - FQDN: configNodeID, + + if err == nil && ip != nil { + // valid IP address, check the type + if ip.To4() != nil { + logger.CfgLog.Tracef("%s is IPv4", configNodeID) + return pfcpType.NodeID{ + NodeIdType: pfcpType.NodeIdTypeIpv4Address, + IP: ip.To4(), + }, nil + } else { + logger.CfgLog.Tracef("%s is IPv6", configNodeID) + return pfcpType.NodeID{ + NodeIdType: pfcpType.NodeIdTypeIpv6Address, + IP: ip, + }, nil } } + + // might be an FQDN, try to resolve it + ips, err := net.LookupIP(configNodeID) + if err != nil { + return pfcpType.NodeID{}, fmt.Errorf("input %s is not a valid IP address or resolvable FQDN", configNodeID) + } + if len(ips) == 0 { + return pfcpType.NodeID{}, fmt.Errorf("no IP addresses found for the given FQDN %s", configNodeID) + } + + logger.CfgLog.Tracef("%s is FQDN", configNodeID) + + return pfcpType.NodeID{ + NodeIdType: pfcpType.NodeIdTypeFqdn, + FQDN: configNodeID, + }, nil } // NewUserPlaneInformation process the configuration then returns a new instance of UserPlaneInformation @@ -147,11 +166,15 @@ func NewUserPlaneInformation(upTopology *factory.UserPlaneInformation) *UserPlan allUEIPPools := []*UeIPPool{} for name, node := range upTopology.UPNodes { + nodeID, err := ConfigToNodeID(node.NodeID) + if err != nil { + logger.InitLog.Fatalf("Cannot parse NodeID from config: %+v", err) + } upNode := &UPNode{ Name: name, Type: UPNodeType(node.Type), ID: uuid.New(), - NodeID: configToNodeID(node.NodeID), + NodeID: nodeID, Dnn: node.Dnn, } switch upNode.Type { @@ -388,11 +411,15 @@ func (upi *UserPlaneInformation) UpNodesFromConfiguration(upTopology *factory.Us logger.InitLog.Warningf("Node [%s] already exists in SMF.\n", name) continue } + nodeID, err := ConfigToNodeID(node.NodeID) + if err != nil { + logger.InitLog.Fatalf("Cannot parse NodeID from config: %+v", err) + } upNode := &UPNode{ Name: name, Type: UPNodeType(node.Type), ID: uuid.New(), - NodeID: configToNodeID(node.NodeID), + NodeID: nodeID, Dnn: node.Dnn, } switch upNode.Type { diff --git a/internal/context/user_plane_information_test.go b/internal/context/user_plane_information_test.go index e24b4431..6906ae24 100644 --- a/internal/context/user_plane_information_test.go +++ b/internal/context/user_plane_information_test.go @@ -5,10 +5,12 @@ import ( "net" "testing" + . "github.com/smartystreets/goconvey/convey" "github.com/stretchr/testify/require" "github.com/free5gc/openapi/models" - "github.com/free5gc/smf/internal/context" + "github.com/free5gc/pfcp/pfcpType" + smf_context "github.com/free5gc/smf/internal/context" "github.com/free5gc/smf/pkg/factory" ) @@ -149,7 +151,7 @@ var configuration = &factory.UserPlaneInformation{ } func TestNewUserPlaneInformation(t *testing.T) { - userplaneInformation := context.NewUserPlaneInformation(configuration) + userplaneInformation := smf_context.NewUserPlaneInformation(configuration) require.NotNil(t, userplaneInformation.AccessNetwork["GNodeB"]) @@ -188,13 +190,13 @@ func TestGenerateDefaultPath(t *testing.T) { testCases := []struct { name string - param *context.UPFSelectionParams + param *smf_context.UPFSelectionParams expected bool }{ { "S-NSSAI 01112232 and DNN internet ok", - &context.UPFSelectionParams{ - SNssai: &context.SNssai{ + &smf_context.UPFSelectionParams{ + SNssai: &smf_context.SNssai{ Sst: 1, Sd: "112232", }, @@ -204,8 +206,8 @@ func TestGenerateDefaultPath(t *testing.T) { }, { "S-NSSAI 02112233 and DNN internet ok", - &context.UPFSelectionParams{ - SNssai: &context.SNssai{ + &smf_context.UPFSelectionParams{ + SNssai: &smf_context.SNssai{ Sst: 2, Sd: "112233", }, @@ -215,8 +217,8 @@ func TestGenerateDefaultPath(t *testing.T) { }, { "S-NSSAI 03112234 and DNN internet ok", - &context.UPFSelectionParams{ - SNssai: &context.SNssai{ + &smf_context.UPFSelectionParams{ + SNssai: &smf_context.SNssai{ Sst: 3, Sd: "112234", }, @@ -226,8 +228,8 @@ func TestGenerateDefaultPath(t *testing.T) { }, { "S-NSSAI 01112235 and DNN internet ok", - &context.UPFSelectionParams{ - SNssai: &context.SNssai{ + &smf_context.UPFSelectionParams{ + SNssai: &smf_context.SNssai{ Sst: 1, Sd: "112235", }, @@ -237,8 +239,8 @@ func TestGenerateDefaultPath(t *testing.T) { }, { "S-NSSAI 01010203 and DNN internet fail", - &context.UPFSelectionParams{ - SNssai: &context.SNssai{ + &smf_context.UPFSelectionParams{ + SNssai: &smf_context.SNssai{ Sst: 1, Sd: "010203", }, @@ -248,7 +250,7 @@ func TestGenerateDefaultPath(t *testing.T) { }, } - userplaneInformation := context.NewUserPlaneInformation(&config1) + userplaneInformation := smf_context.NewUserPlaneInformation(&config1) for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { pathExist := userplaneInformation.GenerateDefaultPath(tc.param) @@ -267,15 +269,15 @@ func TestSelectUPFAndAllocUEIP(t *testing.T) { expectedIPPool = append(expectedIPPool, net.ParseIP(fmt.Sprintf("10.60.0.%d", i)).To4()) } - userplaneInformation := context.NewUserPlaneInformation(configuration) + userplaneInformation := smf_context.NewUserPlaneInformation(configuration) for _, upf := range userplaneInformation.UPFs { - upf.UPFStatus = context.AssociatedSetUpSuccess + upf.UPFStatus = smf_context.AssociatedSetUpSuccess } for i := 0; i <= 100; i++ { - upf, allocatedIP, _ := userplaneInformation.SelectUPFAndAllocUEIP(&context.UPFSelectionParams{ + upf, allocatedIP, _ := userplaneInformation.SelectUPFAndAllocUEIP(&smf_context.UPFSelectionParams{ Dnn: "internet", - SNssai: &context.SNssai{ + SNssai: &smf_context.SNssai{ Sst: 1, Sd: "112232", }, @@ -393,16 +395,16 @@ var configForIPPoolAllocate = &factory.UserPlaneInformation{ var testCasesOfGetUEIPPool = []struct { name string allocateTimes int - param *context.UPFSelectionParams + param *smf_context.UPFSelectionParams subnet uint8 useStaticIP bool }{ { name: "static IP not in dynamic pool or static pool", allocateTimes: 1, - param: &context.UPFSelectionParams{ + param: &smf_context.UPFSelectionParams{ Dnn: "internet", - SNssai: &context.SNssai{ + SNssai: &smf_context.SNssai{ Sst: 1, Sd: "111111", }, @@ -414,9 +416,9 @@ var testCasesOfGetUEIPPool = []struct { { name: "static IP not in static pool but in dynamic pool", allocateTimes: 1, - param: &context.UPFSelectionParams{ + param: &smf_context.UPFSelectionParams{ Dnn: "internet", - SNssai: &context.SNssai{ + SNssai: &smf_context.SNssai{ Sst: 2, Sd: "222222", }, @@ -428,9 +430,9 @@ var testCasesOfGetUEIPPool = []struct { { name: "dynamic pool is exhausted", allocateTimes: 2, - param: &context.UPFSelectionParams{ + param: &smf_context.UPFSelectionParams{ Dnn: "internet", - SNssai: &context.SNssai{ + SNssai: &smf_context.SNssai{ Sst: 2, Sd: "222222", }, @@ -442,9 +444,9 @@ var testCasesOfGetUEIPPool = []struct { { name: "static IP is in static pool", allocateTimes: 1, - param: &context.UPFSelectionParams{ + param: &smf_context.UPFSelectionParams{ Dnn: "internet", - SNssai: &context.SNssai{ + SNssai: &smf_context.SNssai{ Sst: 3, Sd: "333333", }, @@ -456,9 +458,9 @@ var testCasesOfGetUEIPPool = []struct { { name: "static pool is exhausted", allocateTimes: 2, - param: &context.UPFSelectionParams{ + param: &smf_context.UPFSelectionParams{ Dnn: "internet", - SNssai: &context.SNssai{ + SNssai: &smf_context.SNssai{ Sst: 3, Sd: "333333", }, @@ -470,9 +472,9 @@ var testCasesOfGetUEIPPool = []struct { { name: "static IP is in static pool, and dynamic pool is exhaust(allocate twice and not release)", allocateTimes: 2, - param: &context.UPFSelectionParams{ + param: &smf_context.UPFSelectionParams{ Dnn: "internet", - SNssai: &context.SNssai{ + SNssai: &smf_context.SNssai{ Sst: 3, Sd: "333333", }, @@ -484,9 +486,9 @@ var testCasesOfGetUEIPPool = []struct { } func TestGetUEIPPool(t *testing.T) { - userplaneInformation := context.NewUserPlaneInformation(configForIPPoolAllocate) + userplaneInformation := smf_context.NewUserPlaneInformation(configForIPPoolAllocate) for _, upf := range userplaneInformation.UPFs { - upf.UPFStatus = context.AssociatedSetUpSuccess + upf.UPFStatus = smf_context.AssociatedSetUpSuccess } for ci, tc := range testCasesOfGetUEIPPool { @@ -498,7 +500,7 @@ func TestGetUEIPPool(t *testing.T) { } } - var upf *context.UPF + var upf *smf_context.UPF var allocatedIP net.IP var useStatic bool for times := 1; times <= tc.allocateTimes; times++ { @@ -517,3 +519,108 @@ func TestGetUEIPPool(t *testing.T) { }) } } + +func TestConfigToNodeID(t *testing.T) { + testCases := []struct { + name string + configNodeID string + expectedNodeID pfcpType.NodeID + expectedError error + }{ + { + name: "IPv4", + configNodeID: "192.168.179.100", + expectedNodeID: pfcpType.NodeID{ + NodeIdType: pfcpType.NodeIdTypeIpv4Address, + IP: net.ParseIP("192.168.179.100").To4(), + }, + expectedError: nil, + }, + { + name: "IPv4 CIDR", + configNodeID: "192.168.179.100/24", + expectedNodeID: pfcpType.NodeID{ + NodeIdType: pfcpType.NodeIdTypeIpv4Address, + IP: net.ParseIP("192.168.179.100").To4(), + }, + expectedError: nil, + }, + { + name: "IPv4 error", + configNodeID: "192.168.179.1111", + expectedNodeID: pfcpType.NodeID{}, + expectedError: fmt.Errorf("input %s is not a valid IP address or resolvable FQDN", "192.168.179.1111"), + }, + { + name: "IPv6", + configNodeID: "2001:41b8:810:20:df55:785b:e4ed:15b8", + expectedNodeID: pfcpType.NodeID{ + NodeIdType: pfcpType.NodeIdTypeIpv6Address, + IP: net.ParseIP("2001:41b8:810:20:df55:785b:e4ed:15b8"), + }, + expectedError: nil, + }, + { + name: "IPv6 CIDR", + configNodeID: "2001:41b8:810:20:df55:785b:e4ed:15b8/64", + expectedNodeID: pfcpType.NodeID{ + NodeIdType: pfcpType.NodeIdTypeIpv6Address, + IP: net.ParseIP("2001:41b8:810:20:df55:785b:e4ed:15b8"), + }, + expectedError: nil, + }, + { + name: "IPv6 error", + configNodeID: "2001:810:20:df55:785b:e4ed:15b8", + expectedNodeID: pfcpType.NodeID{}, + expectedError: fmt.Errorf("input %s is not a valid IP address or resolvable FQDN", + "2001:810:20:df55:785b:e4ed:15b8"), + }, + { + name: "IPv6 short", + configNodeID: "::1", + expectedNodeID: pfcpType.NodeID{ + NodeIdType: pfcpType.NodeIdTypeIpv6Address, + IP: net.ParseIP("::1"), + }, + expectedError: nil, + }, + { + name: "FQDN", + configNodeID: "example.com", + expectedNodeID: pfcpType.NodeID{ + NodeIdType: pfcpType.NodeIdTypeFqdn, + FQDN: "example.com", + }, + expectedError: nil, + }, + { + name: "FQDN error", + configNodeID: "notresolving.example.com", + expectedNodeID: pfcpType.NodeID{}, + expectedError: fmt.Errorf("input %s is not a valid IP address or resolvable FQDN", "notresolving.example.com"), + }, + } + + Convey("Should convert config input string to valid NodeID or throw error", t, func() { + for i, testcase := range testCases { + infoStr := fmt.Sprintf("testcase[%d]: %s", i, testcase.name) + + Convey(infoStr, func() { + nodeID, err := smf_context.ConfigToNodeID(testcase.configNodeID) + + if testcase.expectedError == nil { + So(err, ShouldBeNil) + So(nodeID.NodeIdType, ShouldEqual, testcase.expectedNodeID.NodeIdType) + So(nodeID.IP, ShouldEqual, testcase.expectedNodeID.IP) + So(nodeID.FQDN, ShouldEqual, testcase.expectedNodeID.FQDN) + } else { + So(err, ShouldNotBeNil) + if err != nil { + So(err.Error(), ShouldEqual, testcase.expectedError.Error()) + } + } + }) + } + }) +} From 2a401797356b76e7e40c130a779d0738014e27dd Mon Sep 17 00:00:00 2001 From: Laura Henning Date: Fri, 20 Sep 2024 12:00:26 +0000 Subject: [PATCH 06/16] chore: linting --- internal/context/gnb.go | 3 ++- internal/context/user_plane_information.go | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/internal/context/gnb.go b/internal/context/gnb.go index b4c264b1..0a2eca6f 100644 --- a/internal/context/gnb.go +++ b/internal/context/gnb.go @@ -4,9 +4,10 @@ import ( "fmt" "net" + "github.com/google/uuid" + "github.com/free5gc/pfcp/pfcpType" "github.com/free5gc/smf/internal/logger" - "github.com/google/uuid" ) // embeds the UPNode struct ("inheritance") diff --git a/internal/context/user_plane_information.go b/internal/context/user_plane_information.go index c8c39184..87a1c609 100644 --- a/internal/context/user_plane_information.go +++ b/internal/context/user_plane_information.go @@ -218,7 +218,7 @@ func NewUserPlaneInformation(upTopology *factory.UserPlaneInformation) *UserPlan staticUeIPPools = append(staticUeIPPools, staticUeIPPool) for _, dynamicUePool := range ueIPPools { if dynamicUePool.ueSubNet.Contains(staticUeIPPool.ueSubNet.IP) { - if err := dynamicUePool.Exclude(staticUeIPPool); err != nil { + if err = dynamicUePool.Exclude(staticUeIPPool); err != nil { logger.InitLog.Fatalf("exclude static Pool[%s] failed: %v", staticUeIPPool.ueSubNet, err) } @@ -228,10 +228,10 @@ func NewUserPlaneInformation(upTopology *factory.UserPlaneInformation) *UserPlan } for _, pool := range ueIPPools { if pool.pool.Min() != pool.pool.Max() { - if err := pool.pool.Reserve(pool.pool.Min(), pool.pool.Min()); err != nil { + if err = pool.pool.Reserve(pool.pool.Min(), pool.pool.Min()); err != nil { logger.InitLog.Errorf("Remove network address failed for %s: %s", pool.ueSubNet.String(), err) } - if err := pool.pool.Reserve(pool.pool.Max(), pool.pool.Max()); err != nil { + if err = pool.pool.Reserve(pool.pool.Max(), pool.pool.Max()); err != nil { logger.InitLog.Errorf("Remove network address failed for %s: %s", pool.ueSubNet.String(), err) } } @@ -463,7 +463,7 @@ func (upi *UserPlaneInformation) UpNodesFromConfiguration(upTopology *factory.Us staticUeIPPools = append(staticUeIPPools, ueIPPool) for _, dynamicUePool := range ueIPPools { if dynamicUePool.ueSubNet.Contains(ueIPPool.ueSubNet.IP) { - if err := dynamicUePool.Exclude(ueIPPool); err != nil { + if err = dynamicUePool.Exclude(ueIPPool); err != nil { logger.InitLog.Fatalf("exclude static Pool[%s] failed: %v", ueIPPool.ueSubNet, err) } From dd421cd2ffb011747777a31c34a7c796fc597e4d Mon Sep 17 00:00:00 2001 From: Laura Henning Date: Fri, 20 Sep 2024 12:11:06 +0000 Subject: [PATCH 07/16] fix: make NodeID mandatory in UPNode config --- pkg/factory/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/factory/config.go b/pkg/factory/config.go index 7e21294b..95c645c8 100644 --- a/pkg/factory/config.go +++ b/pkg/factory/config.go @@ -477,7 +477,7 @@ func (u *UserPlaneInformation) validate() (bool, error) { // UPNode represent the user plane node type UPNode struct { Type string `json:"type" yaml:"type" valid:"upNodeType,required"` - NodeID string `json:"nodeID" yaml:"nodeID" valid:"host,optional"` + NodeID string `json:"nodeID" yaml:"nodeID" valid:"host"` Dnn string `json:"dnn" yaml:"dnn" valid:"type(string),minstringlength(1),optional"` SNssaiInfos []*SnssaiUpfInfoItem `json:"sNssaiUpfInfos" yaml:"sNssaiUpfInfos,omitempty" valid:"optional"` InterfaceUpfInfoList []*InterfaceUpfInfoItem `json:"interfaces" yaml:"interfaces,omitempty" valid:"optional"` From 4d1fc68152ca9e99d51f557f6b4098f844e1aaaf Mon Sep 17 00:00:00 2001 From: Laura Henning Date: Fri, 20 Sep 2024 12:19:06 +0000 Subject: [PATCH 08/16] refactor: use ConfigToNodeID method --- internal/context/context.go | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/internal/context/context.go b/internal/context/context.go index f8ab9b63..23606636 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -176,26 +176,13 @@ func InitSmfContext(config *factory.Config) { smfContext.ListenAddr = pfcp.ListenAddr smfContext.ExternalAddr = pfcp.ExternalAddr - if ip := net.ParseIP(pfcp.NodeID); ip == nil { - smfContext.CPNodeID = pfcpType.NodeID{ - NodeIdType: pfcpType.NodeIdTypeFqdn, - FQDN: pfcp.NodeID, - } - } else { - ipv4 := ip.To4() - if ipv4 != nil { - smfContext.CPNodeID = pfcpType.NodeID{ - NodeIdType: pfcpType.NodeIdTypeIpv4Address, - IP: ipv4, - } - } else { - smfContext.CPNodeID = pfcpType.NodeID{ - NodeIdType: pfcpType.NodeIdTypeIpv6Address, - IP: ip, - } - } + nodeID, err := ConfigToNodeID(pfcp.NodeID) + if err != nil { + logger.InitLog.Fatalf("[InitSmfContext] cannot parse PFCP NodeID from config: %+v", err) } + smfContext.CPNodeID = nodeID + smfContext.PfcpHeartbeatInterval = pfcp.HeartbeatInterval var multipleOfInterval time.Duration = 5 if pfcp.AssocFailAlertInterval == 0 { From 55592717b4c8ade831160c3d1215520e529ff27e Mon Sep 17 00:00:00 2001 From: Laura Henning Date: Fri, 20 Sep 2024 12:20:47 +0000 Subject: [PATCH 09/16] fix: add gNB NodeID to failing test cases --- internal/context/sm_context_policy_test.go | 5 +++-- internal/context/user_plane_information.go | 4 ++-- internal/sbi/consumer/pcf_service_test.go | 2 +- internal/sbi/processor/pdu_session_test.go | 5 +++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/internal/context/sm_context_policy_test.go b/internal/context/sm_context_policy_test.go index 1961a4cd..5c17fa2a 100644 --- a/internal/context/sm_context_policy_test.go +++ b/internal/context/sm_context_policy_test.go @@ -13,7 +13,8 @@ import ( var userPlaneConfig = factory.UserPlaneInformation{ UPNodes: map[string]*factory.UPNode{ "GNodeB": { - Type: "AN", + Type: "AN", + NodeID: "192.168.1.1", }, "UPF1": { Type: "UPF", @@ -94,7 +95,7 @@ var userPlaneConfig = factory.UserPlaneInformation{ var testConfig = factory.Config{ Info: &factory.Info{ Version: "1.0.0", - Description: "SMF procdeure test configuration", + Description: "SMF procedure test configuration", }, Configuration: &factory.Configuration{ Sbi: &factory.Sbi{ diff --git a/internal/context/user_plane_information.go b/internal/context/user_plane_information.go index 87a1c609..f965b395 100644 --- a/internal/context/user_plane_information.go +++ b/internal/context/user_plane_information.go @@ -168,7 +168,7 @@ func NewUserPlaneInformation(upTopology *factory.UserPlaneInformation) *UserPlan for name, node := range upTopology.UPNodes { nodeID, err := ConfigToNodeID(node.NodeID) if err != nil { - logger.InitLog.Fatalf("Cannot parse NodeID from config: %+v", err) + logger.InitLog.Fatalf("[NewUserPlaneInformation] cannot parse %s NodeID from config: %+v", name, err) } upNode := &UPNode{ Name: name, @@ -413,7 +413,7 @@ func (upi *UserPlaneInformation) UpNodesFromConfiguration(upTopology *factory.Us } nodeID, err := ConfigToNodeID(node.NodeID) if err != nil { - logger.InitLog.Fatalf("Cannot parse NodeID from config: %+v", err) + logger.InitLog.Fatalf("[UpNodesFromConfiguration] cannot parse NodeID from config: %+v", err) } upNode := &UPNode{ Name: name, diff --git a/internal/sbi/consumer/pcf_service_test.go b/internal/sbi/consumer/pcf_service_test.go index 252d70aa..df119e9a 100644 --- a/internal/sbi/consumer/pcf_service_test.go +++ b/internal/sbi/consumer/pcf_service_test.go @@ -18,7 +18,7 @@ import ( var testConfig = factory.Config{ Info: &factory.Info{ Version: "1.0.0", - Description: "SMF procdeure test configuration", + Description: "SMF procedure test configuration", }, Configuration: &factory.Configuration{ Sbi: &factory.Sbi{ diff --git a/internal/sbi/processor/pdu_session_test.go b/internal/sbi/processor/pdu_session_test.go index 681a79bf..62712ee8 100644 --- a/internal/sbi/processor/pdu_session_test.go +++ b/internal/sbi/processor/pdu_session_test.go @@ -33,7 +33,8 @@ import ( var userPlaneConfig = factory.UserPlaneInformation{ UPNodes: map[string]*factory.UPNode{ "GNodeB": { - Type: "AN", + Type: "AN", + NodeID: "192.168.1.1", }, "UPF1": { Type: "UPF", @@ -76,7 +77,7 @@ var userPlaneConfig = factory.UserPlaneInformation{ var testConfig = factory.Config{ Info: &factory.Info{ Version: "1.0.0", - Description: "SMF procdeure test configuration", + Description: "SMF procedure test configuration", }, Configuration: &factory.Configuration{ SmfName: "SMF Procedure Test", From f946aa30f8ee4e92eac86633d00b0240c3610ac5 Mon Sep 17 00:00:00 2001 From: Laura Henning Date: Tue, 1 Oct 2024 13:52:00 +0000 Subject: [PATCH 10/16] Remove NodeID from gNB, only use for UPF UPNodes in config; add UPNodeConfigInterface 'inheritance' for userplaneInformation config --- go.mod | 3 + go.sum | 14 + internal/context/context.go | 2 +- internal/context/gnb.go | 32 +- internal/context/sm_context.go | 8 +- internal/context/sm_context_policy_test.go | 29 +- internal/context/upf.go | 18 +- internal/context/upf_test.go | 28 +- internal/context/user_plane_information.go | 116 +++--- .../context/user_plane_information_test.go | 60 ++- internal/sbi/processor/pdu_session_test.go | 19 +- pkg/factory/config.go | 383 ++++++++++-------- pkg/factory/config_test.go | 229 +++++++++++ 13 files changed, 623 insertions(+), 318 deletions(-) diff --git a/go.mod b/go.mod index 659c5c7f..ede5b823 100644 --- a/go.mod +++ b/go.mod @@ -60,6 +60,8 @@ require ( github.com/tim-ywliu/nested-logrus-formatter v1.3.2 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect + go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect + go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/net v0.23.0 // indirect @@ -70,4 +72,5 @@ require ( google.golang.org/protobuf v1.33.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a // indirect ) diff --git a/go.sum b/go.sum index b05b6d44..7c65e43d 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -235,6 +236,7 @@ github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -242,6 +244,11 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE= +go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 h1:WJhcL4p+YeDxmZWg141nRm7XC8IDmhz7lk5GpadO1Sg= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -308,6 +315,7 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -325,6 +333,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -351,6 +360,8 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= @@ -406,6 +417,7 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -507,6 +519,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a h1:1XCVEdxrvL6c0TGOhecLuB7U9zYNdxZEjvOqJreKZiM= +inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a/go.mod h1:e83i32mAQOW1LAqEIweALsuK2Uw4mhQadA5r7b0Wobo= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/internal/context/context.go b/internal/context/context.go index 23606636..8f29ed31 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -226,7 +226,7 @@ func InitSmfContext(config *factory.Config) { smfContext.SupportedPDUSessionType = "IPv4" - smfContext.UserPlaneInformation = NewUserPlaneInformation(&configuration.UserPlaneInformation) + smfContext.UserPlaneInformation = NewUserPlaneInformation(configuration.UserPlaneInformation) smfContext.ChargingIDGenerator = idgenerator.NewGenerator(1, math.MaxUint32) diff --git a/internal/context/gnb.go b/internal/context/gnb.go index 0a2eca6f..cb278223 100644 --- a/internal/context/gnb.go +++ b/internal/context/gnb.go @@ -2,19 +2,16 @@ package context import ( "fmt" - "net" "github.com/google/uuid" - "github.com/free5gc/pfcp/pfcpType" "github.com/free5gc/smf/internal/logger" ) // embeds the UPNode struct ("inheritance") // implements UPNodeInterface type GNB struct { - UPNode - ANIP net.IP + *UPNode } func (gNB *GNB) GetName() string { @@ -29,42 +26,19 @@ func (gNB *GNB) GetType() UPNodeType { return gNB.Type } -func (gNB *GNB) GetDnn() string { - return gNB.Dnn -} - func (gNB *GNB) String() string { str := "gNB {\n" prefix := " " str += prefix + fmt.Sprintf("Name: %s\n", gNB.Name) - str += prefix + fmt.Sprintf("ANIP: %s\n", gNB.ANIP) str += prefix + fmt.Sprintf("ID: %s\n", gNB.ID) - str += prefix + fmt.Sprintf("NodeID: %s\n", gNB.GetNodeIDString()) - str += prefix + fmt.Sprintf("Dnn: %s\n", gNB.Dnn) str += prefix + fmt.Sprintln("Links:") for _, link := range gNB.Links { - str += prefix + fmt.Sprintf("-- %s: %s\n", link.GetName(), link.GetNodeIDString()) + str += prefix + fmt.Sprintf("-- %s: %s\n", link.GetName(), link.GetName()) } str += "}" return str } -func (gNB *GNB) GetNodeIDString() string { - switch gNB.NodeID.NodeIdType { - case pfcpType.NodeIdTypeIpv4Address, pfcpType.NodeIdTypeIpv6Address: - return gNB.NodeID.IP.String() - case pfcpType.NodeIdTypeFqdn: - return gNB.NodeID.FQDN - default: - logger.CtxLog.Errorf("nodeID has unknown type %d", gNB.NodeID.NodeIdType) - return "" - } -} - -func (gNB *GNB) GetNodeID() pfcpType.NodeID { - return gNB.NodeID -} - func (gNB *GNB) GetLinks() UPPath { return gNB.Links } @@ -82,7 +56,7 @@ func (gNB *GNB) AddLink(link UPNodeInterface) bool { func (gNB *GNB) RemoveLink(link UPNodeInterface) bool { for i, existingLink := range gNB.Links { - if link.GetName() == existingLink.GetName() && existingLink.GetNodeIDString() == link.GetNodeIDString() { + if link.GetName() == existingLink.GetName() && existingLink.GetName() == link.GetName() { logger.CfgLog.Warningf("Remove UPLink [%s] <=> [%s]\n", existingLink.GetName(), link.GetName()) gNB.Links = append(gNB.Links[:i], gNB.Links[i+1:]...) return true diff --git a/internal/context/sm_context.go b/internal/context/sm_context.go index 987af5c6..17ae944c 100644 --- a/internal/context/sm_context.go +++ b/internal/context/sm_context.go @@ -474,15 +474,15 @@ func (smContext *SMContext) GetNodeIDByLocalSEID(seid uint64) pfcpType.NodeID { return pfcpType.NodeID{} } -func (smContext *SMContext) AllocateLocalSEIDForUPPath(path UPPath) { - for _, upNode := range path { - NodeIDtoIP := upNode.GetNodeIDString() +func (smContext *SMContext) AllocateLocalSEIDForUPPath(path []*UPF) { + for _, upf := range path { + NodeIDtoIP := upf.GetName() if _, exist := smContext.PFCPContext[NodeIDtoIP]; !exist { allocatedSEID := AllocateLocalSEID() smContext.PFCPContext[NodeIDtoIP] = &PFCPSessionContext{ PDRs: make(map[uint16]*PDR), - NodeID: upNode.GetNodeID(), + NodeID: upf.GetNodeID(), LocalSEID: allocatedSEID, } diff --git a/internal/context/sm_context_policy_test.go b/internal/context/sm_context_policy_test.go index 5c17fa2a..f8e1f4a7 100644 --- a/internal/context/sm_context_policy_test.go +++ b/internal/context/sm_context_policy_test.go @@ -10,14 +10,17 @@ import ( "github.com/free5gc/smf/pkg/factory" ) -var userPlaneConfig = factory.UserPlaneInformation{ - UPNodes: map[string]*factory.UPNode{ - "GNodeB": { - Type: "AN", - NodeID: "192.168.1.1", +var userPlaneConfig = &factory.UserPlaneInformation{ + UPNodes: map[string]factory.UPNodeConfigInterface{ + "GNodeB": &factory.GNBConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "AN", + }, }, - "UPF1": { - Type: "UPF", + "UPF1": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "10.4.0.11", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -33,7 +36,7 @@ var userPlaneConfig = factory.UserPlaneInformation{ }, }, }, - InterfaceUpfInfoList: []*factory.InterfaceUpfInfoItem{ + Interfaces: []*factory.Interface{ { InterfaceType: "N3", Endpoints: []string{ @@ -50,8 +53,10 @@ var userPlaneConfig = factory.UserPlaneInformation{ }, }, }, - "UPF2": { - Type: "UPF", + "UPF2": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "10.4.0.12", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -69,7 +74,7 @@ var userPlaneConfig = factory.UserPlaneInformation{ }, }, }, - InterfaceUpfInfoList: []*factory.InterfaceUpfInfoItem{ + Interfaces: []*factory.Interface{ { InterfaceType: "N9", Endpoints: []string{ @@ -619,7 +624,7 @@ func TestApplyPccRules(t *testing.T) { } smfContext := context.GetSelf() - smfContext.UserPlaneInformation = context.NewUserPlaneInformation(&userPlaneConfig) + smfContext.UserPlaneInformation = context.NewUserPlaneInformation(userPlaneConfig) for _, n := range smfContext.UserPlaneInformation.UPFs { n.UPFStatus = context.AssociatedSetUpSuccess } diff --git a/internal/context/upf.go b/internal/context/upf.go index f1a7d4a0..1ad0a73f 100644 --- a/internal/context/upf.go +++ b/internal/context/upf.go @@ -67,6 +67,8 @@ const ( type UPF struct { *UPNode + NodeID pfcpType.NodeID + UPIPInfo pfcpType.UserPlaneIPResourceInformation UPFStatus UPFStatus RecoveryTimeStamp time.Time @@ -97,10 +99,9 @@ func (upf *UPF) String() string { str += prefix + fmt.Sprintf("Name: %s\n", upf.Name) str += prefix + fmt.Sprintf("ID: %s\n", upf.ID) str += prefix + fmt.Sprintf("NodeID: %s\n", upf.GetNodeIDString()) - str += prefix + fmt.Sprintf("Dnn: %s\n", upf.Dnn) str += prefix + fmt.Sprintln("Links:") for _, link := range upf.Links { - str += prefix + fmt.Sprintf("-- %s: %s\n", link.GetName(), link.GetNodeIDString()) + str += prefix + fmt.Sprintf("-- %s: %s\n", link.GetName(), link.GetName()) } str += prefix + fmt.Sprintln("N3 interfaces:") for _, iface := range upf.N3Interfaces { @@ -135,7 +136,7 @@ func (upf *UPF) GetNodeID() pfcpType.NodeID { } func (upf *UPF) GetName() string { - return upf.Name + return fmt.Sprintf("%s[%s]", upf.Name, upf.GetNodeIDString()) } func (upf *UPF) GetID() uuid.UUID { @@ -146,10 +147,6 @@ func (upf *UPF) GetType() UPNodeType { return upf.Type } -func (upf *UPF) GetDnn() string { - return upf.Dnn -} - func (upf *UPF) GetLinks() UPPath { return upf.Links } @@ -167,7 +164,7 @@ func (upf *UPF) AddLink(link UPNodeInterface) bool { func (upf *UPF) RemoveLink(link UPNodeInterface) bool { for i, existingLink := range upf.Links { - if link.GetName() == existingLink.GetName() && existingLink.GetNodeIDString() == link.GetNodeIDString() { + if link.GetName() == existingLink.GetName() && existingLink.GetName() == link.GetName() { logger.CfgLog.Warningf("Remove UPLink [%s] <=> [%s]\n", existingLink.GetName(), link.GetName()) upf.Links = append(upf.Links[:i], upf.Links[i+1:]...) return true @@ -214,7 +211,7 @@ func GetUpfById(uuid string) *UPF { } // NewUPFInterfaceInfo parse the InterfaceUpfInfoItem to generate UPFInterfaceInfo -func NewUPFInterfaceInfo(i *factory.InterfaceUpfInfoItem) *UPFInterfaceInfo { +func NewUPFInterfaceInfo(i *factory.Interface) *UPFInterfaceInfo { interfaceInfo := new(UPFInterfaceInfo) interfaceInfo.IPv4EndPointAddresses = make([]net.IP, 0) @@ -322,7 +319,8 @@ func (t *UPTunnel) RemoveDataPath(pathID int64) { // NewUPF returns a new UPF context in SMF func NewUPF( upNode *UPNode, - ifaces []*factory.InterfaceUpfInfoItem, + nodeID *pfcpType.NodeID, + ifaces []*factory.Interface, ) (upf *UPF) { upf = new(UPF) upf.UPNode = upNode diff --git a/internal/context/upf_test.go b/internal/context/upf_test.go index 3b0a662b..c4f49d46 100644 --- a/internal/context/upf_test.go +++ b/internal/context/upf_test.go @@ -19,14 +19,14 @@ var mockUPNode = &smf_context.UPNode{ Name: "UPF1", Type: smf_context.UPNODE_UPF, ID: uuid.New(), - NodeID: pfcpType.NodeID{ - NodeIdType: pfcpType.NodeIdTypeIpv4Address, - IP: net.ParseIP("127.0.0.1"), - }, - Dnn: "internet", } -var mockIfaces = []*factory.InterfaceUpfInfoItem{ +var mockNodeID = &pfcpType.NodeID{ + NodeIdType: pfcpType.NodeIdTypeIpv4Address, + IP: net.ParseIP("127.0.0.1"), +} + +var mockIfaces = []*factory.Interface{ { InterfaceType: "N3", Endpoints: []string{"127.0.0.1"}, @@ -151,12 +151,12 @@ func TestAddPDR(t *testing.T) { expectedError error }{ { - upf: smf_context.NewUPF(mockUPNode, mockIfaces), + upf: smf_context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddPDR should success", expectedError: nil, }, { - upf: smf_context.NewUPF(mockUPNode, mockIfaces), + upf: smf_context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddPDR should fail", expectedError: fmt.Errorf("UPF[127.0.0.1] not Associate with SMF"), }, @@ -194,12 +194,12 @@ func TestAddFAR(t *testing.T) { expectedError error }{ { - upf: context.NewUPF(mockUPNode, mockIfaces), + upf: context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddFAR should success", expectedError: nil, }, { - upf: context.NewUPF(mockUPNode, mockIfaces), + upf: context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddFAR should fail", expectedError: fmt.Errorf("UPF[127.0.0.1] not Associate with SMF"), }, @@ -237,12 +237,12 @@ func TestAddQER(t *testing.T) { expectedError error }{ { - upf: context.NewUPF(mockUPNode, mockIfaces), + upf: context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddQER should success", expectedError: nil, }, { - upf: context.NewUPF(mockUPNode, mockIfaces), + upf: context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddQER should fail", expectedError: fmt.Errorf("UPF[127.0.0.1] not Associate with SMF"), }, @@ -280,12 +280,12 @@ func TestAddBAR(t *testing.T) { expectedError error }{ { - upf: context.NewUPF(mockUPNode, mockIfaces), + upf: context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddBAR should success", expectedError: nil, }, { - upf: context.NewUPF(mockUPNode, mockIfaces), + upf: context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddBAR should fail", expectedError: fmt.Errorf("UPF[127.0.0.1] not Associate with SMF"), }, diff --git a/internal/context/user_plane_information.go b/internal/context/user_plane_information.go index f965b395..55b31d70 100644 --- a/internal/context/user_plane_information.go +++ b/internal/context/user_plane_information.go @@ -40,12 +40,10 @@ const ( // UPNode represents a gNB or UPF in the user plane node topology // UPF and gNB structs embed this ("interitance") type UPNode struct { - Name string - Type UPNodeType - ID uuid.UUID - NodeID pfcpType.NodeID - Dnn string - Links UPPath + Name string + Type UPNodeType + ID uuid.UUID + Links UPPath } // UPF and gNB structs implement this interface to provide common methods @@ -55,13 +53,10 @@ type UPNodeInterface interface { GetName() string GetID() uuid.UUID GetType() UPNodeType - GetDnn() string GetLinks() UPPath AddLink(link UPNodeInterface) bool RemoveLink(link UPNodeInterface) bool RemoveLinkByIndex(index int) bool - GetNodeID() pfcpType.NodeID - GetNodeIDString() string } // UPPath represents the User Plane Node Sequence of this path @@ -166,30 +161,28 @@ func NewUserPlaneInformation(upTopology *factory.UserPlaneInformation) *UserPlan allUEIPPools := []*UeIPPool{} for name, node := range upTopology.UPNodes { - nodeID, err := ConfigToNodeID(node.NodeID) - if err != nil { - logger.InitLog.Fatalf("[NewUserPlaneInformation] cannot parse %s NodeID from config: %+v", name, err) - } upNode := &UPNode{ - Name: name, - Type: UPNodeType(node.Type), - ID: uuid.New(), - NodeID: nodeID, - Dnn: node.Dnn, + Name: name, + Type: UPNodeType(node.GetType()), + ID: uuid.New(), } switch upNode.Type { case UPNODE_AN: gNB := &GNB{ - UPNode: *upNode, - ANIP: upNode.NodeID.ResolveNodeIdToIp(), + UPNode: upNode, } anPool[name] = gNB nodePool[name] = gNB case UPNODE_UPF: - upf := NewUPF(upNode, node.InterfaceUpfInfoList) + upfConfig := node.(*factory.UPFConfig) + nodeID, err := ConfigToNodeID(upfConfig.GetNodeID()) + if err != nil { + logger.InitLog.Fatalf("[NewUserPlaneInformation] cannot parse %s NodeID from config: %+v", name, err) + } + upf := NewUPF(upNode, &nodeID, upfConfig.Interfaces) snssaiInfos := make([]*SnssaiUPFInfo, 0) - for _, snssaiInfoConfig := range node.SNssaiInfos { + for _, snssaiInfoConfig := range upfConfig.SNssaiInfos { snssaiInfo := SnssaiUPFInfo{ SNssai: &SNssai{ Sst: snssaiInfoConfig.SNssai.Sst, @@ -229,10 +222,12 @@ func NewUserPlaneInformation(upTopology *factory.UserPlaneInformation) *UserPlan for _, pool := range ueIPPools { if pool.pool.Min() != pool.pool.Max() { if err = pool.pool.Reserve(pool.pool.Min(), pool.pool.Min()); err != nil { - logger.InitLog.Errorf("Remove network address failed for %s: %s", pool.ueSubNet.String(), err) + logger.InitLog.Errorf("Remove network address failed for %s: %s", + pool.ueSubNet.String(), err) } if err = pool.pool.Reserve(pool.pool.Max(), pool.pool.Max()); err != nil { - logger.InitLog.Errorf("Remove network address failed for %s: %s", pool.ueSubNet.String(), err) + logger.InitLog.Errorf("Remove network address failed for %s: %s", + pool.ueSubNet.String(), err) } } logger.InitLog.Debugf("%d-%s %s %s", @@ -278,21 +273,27 @@ func NewUserPlaneInformation(upTopology *factory.UserPlaneInformation) *UserPlan return userplaneInformation } -func (upi *UserPlaneInformation) UpNodesToConfiguration() map[string]*factory.UPNode { - nodes := make(map[string]*factory.UPNode) +func (upi *UserPlaneInformation) UpNodesToConfiguration() map[string]factory.UPNodeConfigInterface { + nodes := make(map[string]factory.UPNodeConfigInterface) for name, upNode := range upi.UPNodes { - node := &factory.UPNode{ - NodeID: upNode.GetNodeIDString(), - Dnn: upNode.GetDnn(), - } - nodes[name] = node - switch upNode.GetType() { case UPNODE_AN: - node.Type = "AN" + gNBConfig := &factory.GNBConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "AN", + }, + } + nodes[name] = gNBConfig case UPNODE_UPF: - node.Type = "UPF" upf := upNode.(*UPF) + upfConfig := &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, + NodeID: upf.GetNodeIDString(), + } + nodes[name] = upfConfig + if upf.SNssaiInfos != nil { FsNssaiInfoList := make([]*factory.SnssaiUpfInfoItem, 0) for _, sNssaiInfo := range upf.SNssaiInfos { @@ -325,9 +326,9 @@ func (upi *UserPlaneInformation) UpNodesToConfiguration() map[string]*factory.UP } FsNssaiInfoList = append(FsNssaiInfoList, Fsnssai) } // for sNssaiInfo - node.SNssaiInfos = FsNssaiInfoList + upfConfig.SNssaiInfos = FsNssaiInfoList } // if UPF.SNssaiInfos - FNxList := make([]*factory.InterfaceUpfInfoItem, 0) + FNxList := make([]*factory.Interface, 0) for _, iface := range upf.N3Interfaces { endpoints := make([]string, 0) // upf.go L90 @@ -337,7 +338,7 @@ func (upi *UserPlaneInformation) UpNodesToConfiguration() map[string]*factory.UP for _, eIP := range iface.IPv4EndPointAddresses { endpoints = append(endpoints, eIP.String()) } - FNxList = append(FNxList, &factory.InterfaceUpfInfoItem{ + FNxList = append(FNxList, &factory.Interface{ InterfaceType: models.UpInterfaceType_N3, Endpoints: endpoints, NetworkInstances: iface.NetworkInstances, @@ -353,16 +354,15 @@ func (upi *UserPlaneInformation) UpNodesToConfiguration() map[string]*factory.UP for _, eIP := range iface.IPv4EndPointAddresses { endpoints = append(endpoints, eIP.String()) } - FNxList = append(FNxList, &factory.InterfaceUpfInfoItem{ + FNxList = append(FNxList, &factory.Interface{ InterfaceType: models.UpInterfaceType_N9, Endpoints: endpoints, NetworkInstances: iface.NetworkInstances, }) } // N9Interfaces - node.InterfaceUpfInfoList = FNxList + upfConfig.Interfaces = FNxList default: - logger.InitLog.Warningf("invalid UPNodeType: %s\n", upNode.GetType()) - node.Type = "Unknown" + logger.InitLog.Fatalf("invalid UPNodeType: %s\n", upNode.GetType()) } } @@ -385,8 +385,8 @@ func (upi *UserPlaneInformation) LinksToConfiguration() []*factory.UPLink { for _, link := range node.GetLinks() { if !visited[link] { queue = append(queue, link) - nodeIpStr := node.GetNodeIDString() - ipStr := link.GetNodeIDString() + nodeIpStr := node.(*UPF).GetNodeIDString() + ipStr := link.(*UPF).GetNodeIDString() linkA := upi.UPFIPToName[nodeIpStr] linkB := upi.UPFIPToName[ipStr] links = append(links, &factory.UPLink{ @@ -411,31 +411,31 @@ func (upi *UserPlaneInformation) UpNodesFromConfiguration(upTopology *factory.Us logger.InitLog.Warningf("Node [%s] already exists in SMF.\n", name) continue } - nodeID, err := ConfigToNodeID(node.NodeID) - if err != nil { - logger.InitLog.Fatalf("[UpNodesFromConfiguration] cannot parse NodeID from config: %+v", err) - } upNode := &UPNode{ - Name: name, - Type: UPNodeType(node.Type), - ID: uuid.New(), - NodeID: nodeID, - Dnn: node.Dnn, + Name: name, + Type: UPNodeType(node.GetType()), + ID: uuid.New(), } switch upNode.Type { case UPNODE_AN: gNB := &GNB{ - UPNode: *upNode, - ANIP: upNode.NodeID.ResolveNodeIdToIp(), + UPNode: upNode, } upi.AccessNetwork[name] = gNB upi.UPNodes[name] = gNB case UPNODE_UPF: - upf := NewUPF(upNode, node.InterfaceUpfInfoList) + upfConfig := node.(*factory.UPFConfig) + nodeID, err := ConfigToNodeID(upfConfig.GetNodeID()) + if err != nil { + logger.InitLog.Fatalf("[UpNodesFromConfiguration] cannot parse NodeID from config: %+v", err) + } + + upf := NewUPF(upNode, &nodeID, upfConfig.Interfaces) + snssaiInfos := make([]*SnssaiUPFInfo, 0) - for _, snssaiInfoConfig := range node.SNssaiInfos { + for _, snssaiInfoConfig := range upfConfig.SNssaiInfos { snssaiInfo := &SnssaiUPFInfo{ SNssai: &SNssai{ Sst: snssaiInfoConfig.SNssai.Sst, @@ -533,7 +533,7 @@ func (upi *UserPlaneInformation) UpNodeDelete(upNodeName string) { logger.InitLog.Infof("UPNode [%s] found. Deleting it.\n", upNodeName) if upNode.GetType() == UPNODE_UPF { logger.InitLog.Tracef("Delete UPF [%s] from its NodeID.\n", upNodeName) - RemoveUPFNodeByNodeID(upNode.GetNodeID()) + RemoveUPFNodeByNodeID(upNode.(*UPF).GetNodeID()) if _, ok = upi.UPFs[upNodeName]; ok { logger.InitLog.Tracef("Delete UPF [%s] from upi.UPFs.\n", upNodeName) delete(upi.UPFs, upNodeName) @@ -795,7 +795,7 @@ func getPathBetween( selection *UPFSelectionParams, ) (path UPPath, pathExist bool) { logger.CtxLog.Tracef("[getPathBetween] node %s[%s] and %s[%s]", - cur.GetName(), cur.GetNodeIDString(), dest.GetName(), dest.GetNodeIDString()) + cur.GetName(), cur.GetName(), dest.GetName(), dest.GetName()) visited[cur] = true if reflect.DeepEqual(cur, dest) { diff --git a/internal/context/user_plane_information_test.go b/internal/context/user_plane_information_test.go index 6906ae24..1564653b 100644 --- a/internal/context/user_plane_information_test.go +++ b/internal/context/user_plane_information_test.go @@ -15,13 +15,16 @@ import ( ) var configuration = &factory.UserPlaneInformation{ - UPNodes: map[string]*factory.UPNode{ - "GNodeB": { - Type: "AN", - NodeID: "192.168.179.100", + UPNodes: map[string]factory.UPNodeConfigInterface{ + "GNodeB": &factory.GNBConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "AN", + }, }, - "UPF1": { - Type: "UPF", + "UPF1": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.1", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -63,8 +66,10 @@ var configuration = &factory.UserPlaneInformation{ }, }, }, - "UPF2": { - Type: "UPF", + "UPF2": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.2", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -85,8 +90,10 @@ var configuration = &factory.UserPlaneInformation{ }, }, }, - "UPF3": { - Type: "UPF", + "UPF3": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.3", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -107,8 +114,10 @@ var configuration = &factory.UserPlaneInformation{ }, }, }, - "UPF4": { - Type: "UPF", + "UPF4": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.4", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -289,13 +298,16 @@ func TestSelectUPFAndAllocUEIP(t *testing.T) { } var configForIPPoolAllocate = &factory.UserPlaneInformation{ - UPNodes: map[string]*factory.UPNode{ - "GNodeB": { - Type: "AN", - NodeID: "192.168.179.100", + UPNodes: map[string]factory.UPNodeConfigInterface{ + "GNodeB": &factory.GNBConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "AN", + }, }, - "UPF1": { - Type: "UPF", + "UPF1": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.1", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -321,8 +333,10 @@ var configForIPPoolAllocate = &factory.UserPlaneInformation{ }, }, }, - "UPF2": { - Type: "UPF", + "UPF2": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.2", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -348,8 +362,10 @@ var configForIPPoolAllocate = &factory.UserPlaneInformation{ }, }, }, - "UPF3": { - Type: "UPF", + "UPF3": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.3", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { diff --git a/internal/sbi/processor/pdu_session_test.go b/internal/sbi/processor/pdu_session_test.go index 62712ee8..738fc8cd 100644 --- a/internal/sbi/processor/pdu_session_test.go +++ b/internal/sbi/processor/pdu_session_test.go @@ -30,14 +30,17 @@ import ( "github.com/free5gc/util/httpwrapper" ) -var userPlaneConfig = factory.UserPlaneInformation{ - UPNodes: map[string]*factory.UPNode{ - "GNodeB": { - Type: "AN", - NodeID: "192.168.1.1", +var userPlaneConfig = &factory.UserPlaneInformation{ + UPNodes: map[string]factory.UPNodeConfigInterface{ + "GNodeB": &factory.GNBConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "AN", + }, }, - "UPF1": { - Type: "UPF", + "UPF1": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.1", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -55,7 +58,7 @@ var userPlaneConfig = factory.UserPlaneInformation{ }, }, }, - InterfaceUpfInfoList: []*factory.InterfaceUpfInfoItem{ + Interfaces: []*factory.Interface{ { InterfaceType: "N3", Endpoints: []string{ diff --git a/pkg/factory/config.go b/pkg/factory/config.go index 95c645c8..73fa7b94 100644 --- a/pkg/factory/config.go +++ b/pkg/factory/config.go @@ -7,12 +7,14 @@ package factory import ( "errors" "fmt" + "slices" "strconv" "sync" "time" "github.com/asaskevich/govalidator" "github.com/davecgh/go-spew/spew" + "inet.af/netaddr" "github.com/free5gc/openapi/models" "github.com/free5gc/smf/internal/logger" @@ -47,7 +49,7 @@ type Config struct { func (c *Config) Validate() (bool, error) { if configuration := c.Configuration; configuration != nil { - if result, err := configuration.validate(); err != nil { + if result, err := configuration.Validate(); err != nil { return result, err } } @@ -69,29 +71,29 @@ type Info struct { Description string `yaml:"description,omitempty" valid:"type(string)"` } -func (i *Info) validate() (bool, error) { +func (i *Info) Validate() (bool, error) { result, err := govalidator.ValidateStruct(i) return result, appendInvalid(err) } type Configuration struct { - SmfName string `yaml:"smfName" valid:"type(string),required"` - Sbi *Sbi `yaml:"sbi" valid:"required"` - PFCP *PFCP `yaml:"pfcp" valid:"required"` - NrfUri string `yaml:"nrfUri" valid:"url,required"` - NrfCertPem string `yaml:"nrfCertPem,omitempty" valid:"optional"` - UserPlaneInformation UserPlaneInformation `yaml:"userplaneInformation" valid:"required"` - ServiceNameList []string `yaml:"serviceNameList" valid:"required"` - SNssaiInfo []*SnssaiInfoItem `yaml:"snssaiInfos" valid:"required"` - ULCL bool `yaml:"ulcl" valid:"type(bool),optional"` - PLMNList []PlmnID `yaml:"plmnList" valid:"optional"` - Locality string `yaml:"locality" valid:"type(string),optional"` - UrrPeriod uint16 `yaml:"urrPeriod,omitempty" valid:"optional"` - UrrThreshold uint64 `yaml:"urrThreshold,omitempty" valid:"optional"` - T3591 *TimerValue `yaml:"t3591" valid:"required"` - T3592 *TimerValue `yaml:"t3592" valid:"required"` - NwInstFqdnEncoding bool `yaml:"nwInstFqdnEncoding" valid:"type(bool),optional"` - RequestedUnit int32 `yaml:"requestedUnit,omitempty" valid:"optional"` + SmfName string `yaml:"smfName" valid:"type(string),required"` + Sbi *Sbi `yaml:"sbi" valid:"required"` + PFCP *PFCP `yaml:"pfcp" valid:"required"` + NrfUri string `yaml:"nrfUri" valid:"url,required"` + NrfCertPem string `yaml:"nrfCertPem,omitempty" valid:"optional"` + UserPlaneInformation *UserPlaneInformation `yaml:"userplaneInformation" valid:"required"` + ServiceNameList []string `yaml:"serviceNameList" valid:"required"` + SNssaiInfo []*SnssaiInfoItem `yaml:"snssaiInfos" valid:"required"` + ULCL bool `yaml:"ulcl" valid:"type(bool),optional"` + PLMNList []PlmnID `yaml:"plmnList" valid:"optional"` + Locality string `yaml:"locality" valid:"type(string),optional"` + UrrPeriod uint16 `yaml:"urrPeriod,omitempty" valid:"optional"` + UrrThreshold uint64 `yaml:"urrThreshold,omitempty" valid:"optional"` + T3591 *TimerValue `yaml:"t3591" valid:"required"` + T3592 *TimerValue `yaml:"t3592" valid:"required"` + NwInstFqdnEncoding bool `yaml:"nwInstFqdnEncoding" valid:"type(bool),optional"` + RequestedUnit int32 `yaml:"requestedUnit,omitempty" valid:"optional"` } type Logger struct { @@ -100,21 +102,21 @@ type Logger struct { ReportCaller bool `yaml:"reportCaller" valid:"type(bool)"` } -func (c *Configuration) validate() (bool, error) { +func (c *Configuration) Validate() (bool, error) { if sbi := c.Sbi; sbi != nil { - if result, err := sbi.validate(); err != nil { + if result, err := sbi.Validate(); err != nil { return result, err } } if pfcp := c.PFCP; pfcp != nil { - if result, err := pfcp.validate(); err != nil { + if result, err := pfcp.Validate(); err != nil { return result, err } } - if userPlaneInformation := &c.UserPlaneInformation; userPlaneInformation != nil { - if result, err := userPlaneInformation.validate(); err != nil { + if userPlaneInformation := c.UserPlaneInformation; userPlaneInformation != nil { + if result, err := userPlaneInformation.Validate(); err != nil { return result, err } } @@ -139,20 +141,20 @@ func (c *Configuration) validate() (bool, error) { if c.PLMNList != nil { for _, plmnId := range c.PLMNList { - if result, err := plmnId.validate(); err != nil { + if result, err := plmnId.Validate(); err != nil { return result, err } } } if t3591 := c.T3591; t3591 != nil { - if result, err := t3591.validate(); err != nil { + if result, err := t3591.Validate(); err != nil { return result, err } } if t3592 := c.T3592; t3592 != nil { - if result, err := t3592.validate(); err != nil { + if result, err := t3592.Validate(); err != nil { return result, err } } @@ -183,7 +185,7 @@ func (s *SnssaiInfoItem) Validate() (bool, error) { } for _, dnnInfo := range s.DnnInfos { - if result, err := dnnInfo.validate(); err != nil { + if result, err := dnnInfo.Validate(); err != nil { return result, err } } @@ -197,15 +199,15 @@ type SnssaiDnnInfoItem struct { PCSCF *PCSCF `yaml:"pcscf,omitempty" valid:"optional"` } -func (s *SnssaiDnnInfoItem) validate() (bool, error) { +func (s *SnssaiDnnInfoItem) Validate() (bool, error) { if dns := s.DNS; dns != nil { - if result, err := dns.validate(); err != nil { + if result, err := dns.Validate(); err != nil { return result, err } } if pcscf := s.PCSCF; pcscf != nil { - if result, err := pcscf.validate(); err != nil { + if result, err := pcscf.Validate(); err != nil { return result, err } } @@ -223,13 +225,13 @@ type Sbi struct { Port int `yaml:"port,omitempty" valid:"port,optional"` } -func (s *Sbi) validate() (bool, error) { +func (s *Sbi) Validate() (bool, error) { govalidator.TagMap["scheme"] = govalidator.Validator(func(str string) bool { return str == "https" || str == "http" }) if tls := s.Tls; tls != nil { - if result, err := tls.validate(); err != nil { + if result, err := tls.Validate(); err != nil { return result, err } } @@ -243,7 +245,7 @@ type Tls struct { Key string `yaml:"key,omitempty" valid:"type(string),minstringlength(1),required"` } -func (t *Tls) validate() (bool, error) { +func (t *Tls) Validate() (bool, error) { result, err := govalidator.ValidateStruct(t) return result, appendInvalid(err) } @@ -258,7 +260,7 @@ type PFCP struct { HeartbeatInterval time.Duration `yaml:"heartbeatInterval,omitempty" valid:"type(time.Duration),optional"` } -func (p *PFCP) validate() (bool, error) { +func (p *PFCP) Validate() (bool, error) { result, err := govalidator.ValidateStruct(p) return result, appendInvalid(err) } @@ -268,7 +270,7 @@ type DNS struct { IPv6Addr string `yaml:"ipv6,omitempty" valid:"ipv6,optional"` } -func (d *DNS) validate() (bool, error) { +func (d *DNS) Validate() (bool, error) { result, err := govalidator.ValidateStruct(d) return result, appendInvalid(err) } @@ -277,7 +279,7 @@ type PCSCF struct { IPv4Addr string `yaml:"ipv4,omitempty" valid:"ipv4,required"` } -func (p *PCSCF) validate() (bool, error) { +func (p *PCSCF) Validate() (bool, error) { result, err := govalidator.ValidateStruct(p) return result, appendInvalid(err) } @@ -288,7 +290,7 @@ type Path struct { UPF []string `yaml:"UPF,omitempty" valid:"required"` } -func (p *Path) validate() (bool, error) { +func (p *Path) Validate() (bool, error) { for _, upf := range p.UPF { if result := len(upf); result == 0 { err := errors.New("Invalid UPF: " + upf + ", should not be empty") @@ -308,7 +310,7 @@ type UERoutingInfo struct { SpecificPaths []SpecificPath `yaml:"specificPath,omitempty" valid:"optional"` } -func (u *UERoutingInfo) validate() (bool, error) { +func (u *UERoutingInfo) Validate() (bool, error) { for _, member := range u.Members { if result := govalidator.StringMatches(member, "imsi-[0-9]{5,15}$"); !result { err := errors.New("Invalid member (SUPI): " + member) @@ -317,19 +319,13 @@ func (u *UERoutingInfo) validate() (bool, error) { } for _, path := range u.PathList { - if result, err := path.validate(); err != nil { - return result, err - } - } - - for _, link := range u.Topology { - if result, err := link.validate(); err != nil { + if result, err := path.Validate(); err != nil { return result, err } } for _, path := range u.SpecificPaths { - if result, err := path.validate(); err != nil { + if result, err := path.Validate(); err != nil { return result, err } } @@ -347,7 +343,7 @@ type RouteProfile struct { ForwardingPolicyID string `yaml:"forwardingPolicyID,omitempty" valid:"type(string),stringlength(1|255),required"` } -func (r *RouteProfile) validate() (bool, error) { +func (r *RouteProfile) Validate() (bool, error) { result, err := govalidator.ValidateStruct(r) return result, appendInvalid(err) } @@ -367,7 +363,7 @@ type PfdContent struct { DomainNames []string `yaml:"domainNames,omitempty" valid:"optional"` } -func (p *PfdContent) validate() (bool, error) { +func (p *PfdContent) Validate() (bool, error) { for _, flowDescription := range p.FlowDescriptions { if result := len(flowDescription) > 0; !result { err := errors.New("Invalid FlowDescription: " + flowDescription + ", should not be empty.") @@ -403,9 +399,9 @@ type PfdDataForApp struct { CachingTime *time.Time `yaml:"cachingTime,omitempty" valid:"optional"` } -func (p *PfdDataForApp) validate() (bool, error) { +func (p *PfdDataForApp) Validate() (bool, error) { for _, pfd := range p.Pfds { - if result, err := pfd.validate(); err != nil { + if result, err := pfd.Validate(); err != nil { return result, err } } @@ -424,25 +420,25 @@ type RoutingConfig struct { func (r *RoutingConfig) Validate() (bool, error) { if info := r.Info; info != nil { - if result, err := info.validate(); err != nil { + if result, err := info.Validate(); err != nil { return result, err } } for _, ueRoutingInfo := range r.UERoutingInfo { - if result, err := ueRoutingInfo.validate(); err != nil { + if result, err := ueRoutingInfo.Validate(); err != nil { return result, err } } for _, routeProf := range r.RouteProf { - if result, err := routeProf.validate(); err != nil { + if result, err := routeProf.Validate(); err != nil { return result, err } } for _, pfdData := range r.PfdDatas { - if result, err := pfdData.validate(); err != nil { + if result, err := pfdData.Validate(); err != nil { return result, err } } @@ -453,93 +449,151 @@ func (r *RoutingConfig) Validate() (bool, error) { // UserPlaneInformation describe core network userplane information type UserPlaneInformation struct { - UPNodes map[string]*UPNode `json:"upNodes" yaml:"upNodes" valid:"required"` - Links []*UPLink `json:"links" yaml:"links" valid:"required"` + UPNodes map[string]UPNodeConfigInterface `json:"upNodes" yaml:"upNodes" valid:"required"` + Links []*UPLink `json:"links" yaml:"links" valid:"required"` } -func (u *UserPlaneInformation) validate() (bool, error) { - for _, upNode := range u.UPNodes { - if result, err := upNode.validate(); err != nil { - return result, err +func (u *UserPlaneInformation) Validate() (result bool, err error) { + // register valid upNodeTypes to govalidator + govalidator.TagMap["upNodeType"] = govalidator.Validator(func(str string) bool { + return str == "AN" || str == "UPF" + }) + + // register valid interfaceTypes to govalidator + govalidator.TagMap["interfaceType"] = govalidator.Validator(func(str string) bool { + return str == "N3" || str == "N9" + }) + + // register CIDR to govalidator + govalidator.TagMap["cidr"] = govalidator.Validator(func(str string) bool { + return govalidator.IsCIDR(str) + }) + + result = true + + // validate struct field correctness recursively + if ok, errStruct := govalidator.ValidateStruct(u); !ok { + result = false + err = appendInvalid(errStruct) + } + + var upNodeNames []string + for name, upNode := range u.UPNodes { + upNodeNames = append(upNodeNames, name) + + // call custom validation function (semantic validation) + if ok, errSemantic := upNode.Validate(); !ok { + result = false + err = appendInvalid(errSemantic) } } for _, link := range u.Links { - if result, err := link.validate(); err != nil { - return result, err + if !slices.Contains(upNodeNames, link.A) || !slices.Contains(upNodeNames, link.B) { + result = false + err = appendInvalid(fmt.Errorf("Link %s--%s contains unknown node name, check 'userplaneInformation'", + link.A, link.B)) } } - result, err := govalidator.ValidateStruct(u) - return result, appendInvalid(err) + return result, err } // UPNode represent the user plane node -type UPNode struct { - Type string `json:"type" yaml:"type" valid:"upNodeType,required"` - NodeID string `json:"nodeID" yaml:"nodeID" valid:"host"` - Dnn string `json:"dnn" yaml:"dnn" valid:"type(string),minstringlength(1),optional"` - SNssaiInfos []*SnssaiUpfInfoItem `json:"sNssaiUpfInfos" yaml:"sNssaiUpfInfos,omitempty" valid:"optional"` - InterfaceUpfInfoList []*InterfaceUpfInfoItem `json:"interfaces" yaml:"interfaces,omitempty" valid:"optional"` +type UPNodeConfig struct { + Type string `json:"type" yaml:"type" valid:"upNodeType,required"` } -func (u *UPNode) validate() (bool, error) { - govalidator.TagMap["upNodeType"] = govalidator.Validator(func(str string) bool { - return str == "AN" || str == "UPF" - }) +type UPNodeConfigInterface interface { + Validate() (result bool, err error) + GetType() string +} - for _, snssaiInfo := range u.SNssaiInfos { - if result, err := snssaiInfo.Validate(); err != nil { - return result, err - } - } +type GNBConfig struct { + *UPNodeConfig +} + +type UPFConfig struct { + *UPNodeConfig + NodeID string `json:"nodeID" yaml:"nodeID" valid:"host,required"` + SNssaiInfos []*SnssaiUpfInfoItem `json:"sNssaiUpfInfos" yaml:"sNssaiUpfInfos,omitempty" valid:"required"` + Interfaces []*Interface `json:"interfaces" yaml:"interfaces" valid:"required"` +} + +func (gNB *GNBConfig) Validate() (result bool, err error) { + return true, nil +} + +func (gNB *GNBConfig) GetType() string { + return gNB.Type +} + +func (upf *UPFConfig) Validate() (result bool, err error) { + result = true n3IfsNum := 0 n9IfsNum := 0 - for _, interfaceUpfInfo := range u.InterfaceUpfInfoList { - if result, err := interfaceUpfInfo.validate(); err != nil { - return result, err + + for _, iface := range upf.Interfaces { + if ok, errIface := iface.Validate(); !ok { + result = false + err = appendInvalid(errIface) } - if interfaceUpfInfo.InterfaceType == "N3" { + + if iface.InterfaceType == "N3" { n3IfsNum++ } - if interfaceUpfInfo.InterfaceType == "N9" { + if iface.InterfaceType == "N9" { n9IfsNum++ } + } + + if n3IfsNum == 0 && n9IfsNum == 0 { + result = false + err = appendInvalid(fmt.Errorf("UPF %s must have a user plane interface (N3 or N9)", upf.NodeID)) + } + + if n3IfsNum > 1 || n9IfsNum > 1 { + result = false + err = appendInvalid(fmt.Errorf( + "UPF %s: There is currently no support for multiple N3/ N9 interfaces: N3 number(%d), N9 number(%d)", + upf.NodeID, n3IfsNum, n9IfsNum)) + } - if n3IfsNum > 1 || n9IfsNum > 1 { - return false, fmt.Errorf( - "Not support multiple InterfaceUpfInfo for the same type: N3 number(%d), N9 number(%d)", - n3IfsNum, n9IfsNum) + for _, snssaiInfo := range upf.SNssaiInfos { + if ok, errSNSSAI := snssaiInfo.Validate(); !ok { + result = false + err = appendInvalid(errSNSSAI) } } - result, err := govalidator.ValidateStruct(u) - return result, appendInvalid(err) + + return result, err +} + +func (upf *UPFConfig) GetNodeID() string { + return upf.NodeID +} + +func (upf *UPFConfig) GetType() string { + return upf.Type } -type InterfaceUpfInfoItem struct { +type Interface struct { InterfaceType models.UpInterfaceType `json:"interfaceType" yaml:"interfaceType" valid:"required"` Endpoints []string `json:"endpoints" yaml:"endpoints" valid:"required"` - NetworkInstances []string `json:"networkInstances" yaml:"networkInstances" valid:"required"` + NetworkInstances []string `json:"networkInstances" yaml:"networkInstances" valid:"optional"` } -func (i *InterfaceUpfInfoItem) validate() (bool, error) { - interfaceType := i.InterfaceType - if result := (interfaceType == "N3" || interfaceType == "N9"); !result { - err := errors.New("Invalid interfaceType: " + string(interfaceType) + ", should be N3 or N9.") - return false, err - } - +func (i *Interface) Validate() (result bool, err error) { + result = true for _, endpoint := range i.Endpoints { - if result := govalidator.IsHost(endpoint); !result { - err := errors.New("Invalid endpoint:" + endpoint + ", should be IPv4.") - return false, err + if ok := govalidator.IsHost(endpoint); !ok { + result = false + err = appendInvalid(fmt.Errorf("Invalid endpoint: %s should be one of IPv4, IPv6, FQDN.", endpoint)) } } - - result, err := govalidator.ValidateStruct(i) - return result, appendInvalid(err) + return } type SnssaiUpfInfoItem struct { @@ -547,54 +601,78 @@ type SnssaiUpfInfoItem struct { DnnUpfInfoList []*DnnUpfInfoItem `json:"dnnUpfInfoList" yaml:"dnnUpfInfoList" valid:"required"` } -func (s *SnssaiUpfInfoItem) Validate() (bool, error) { +func (s *SnssaiUpfInfoItem) Validate() (result bool, err error) { + result = true + if s.SNssai != nil { - if result := (s.SNssai.Sst >= 0 && s.SNssai.Sst <= 255); !result { - err := errors.New("Invalid sNssai.Sst: " + strconv.Itoa(int(s.SNssai.Sst)) + ", should be in range 0~255.") - return false, err + if ok := (s.SNssai.Sst >= 0 && s.SNssai.Sst <= 255); !ok { + result = false + err = appendInvalid(fmt.Errorf("Invalid sNssai.Sst: %s should be in range 0-255.", + strconv.Itoa(int(s.SNssai.Sst)))) } if s.SNssai.Sd != "" { - if result := govalidator.StringMatches(s.SNssai.Sd, "^[0-9A-Fa-f]{6}$"); !result { - err := errors.New("Invalid sNssai.Sd: " + s.SNssai.Sd + - ", should be 3 bytes hex string and in range 000000~FFFFFF.") - return false, err + if ok := govalidator.StringMatches(s.SNssai.Sd, "^[0-9A-Fa-f]{6}$"); !ok { + result = false + err = appendInvalid(fmt.Errorf( + "Invalid sNssai.Sd: %s should be 3 bytes hex string and in range 000000-FFFFFF.", s.SNssai.Sd)) } } } for _, dnnInfo := range s.DnnUpfInfoList { - if result, err := dnnInfo.validate(); err != nil { - return result, err + if ok, errDNNInfo := dnnInfo.Validate(); !ok { + result = false + err = appendInvalid(errDNNInfo) } } - result, err := govalidator.ValidateStruct(s) - return result, appendInvalid(err) + return } type DnnUpfInfoItem struct { Dnn string `json:"dnn" yaml:"dnn" valid:"required"` DnaiList []string `json:"dnaiList" yaml:"dnaiList" valid:"optional"` PduSessionTypes []models.PduSessionType `json:"pduSessionTypes" yaml:"pduSessionTypes" valid:"optional"` - Pools []*UEIPPool `json:"pools" yaml:"pools" valid:"optional"` + Pools []*UEIPPool `json:"pools" yaml:"pools" valid:"required"` StaticPools []*UEIPPool `json:"staticPools" yaml:"staticPools" valid:"optional"` } -func (d *DnnUpfInfoItem) validate() (bool, error) { - if result := len(d.Dnn); result == 0 { - err := errors.New("Invalid DnnUpfInfoItem.dnn: " + d.Dnn + ", should not be empty.") - return false, err +func (d *DnnUpfInfoItem) Validate() (result bool, err error) { + result = true + + if len(d.Dnn) == 0 { + result = false + err = appendInvalid(fmt.Errorf("Invalid DnnUpfInfoItem: dnn must not be empty.")) + } + + if len(d.Pools) == 0 { + result = false + err = appendInvalid(fmt.Errorf("Invalid DnnUpfInfoItem: requires at least one dynamic IP pool.")) } + var prefixes []netaddr.IPPrefix for _, pool := range d.Pools { - if result, err := pool.validate(); err != nil { - return result, err + prefix, errCidrCheck := netaddr.ParseIPPrefix(pool.Cidr) + if errCidrCheck != nil { + result = false + err = appendInvalid(fmt.Errorf("Invalid CIDR: %s.", pool.Cidr)) + } else { + prefixes = append(prefixes, prefix) } } - result, err := govalidator.ValidateStruct(d) - return result, appendInvalid(err) + // check overlap with dynamic and static pools + for i := 0; i < len(prefixes); i++ { + for j := i + 1; j < len(prefixes); j++ { + if prefixes[i].Overlaps(prefixes[j]) { + result = false + err = appendInvalid(fmt.Errorf("overlap detected between pools %s and %s", prefixes[i], prefixes[j])) + } + } + } + + return result, err } type UPLink struct { @@ -602,47 +680,17 @@ type UPLink struct { B string `json:"B" yaml:"B" valid:"required"` } -func (u *UPLink) validate() (bool, error) { - result, err := govalidator.ValidateStruct(u) - return result, appendInvalid(err) -} - -func appendInvalid(err error) error { - var errs govalidator.Errors - - if err == nil { - return nil - } - - es := err.(govalidator.Errors).Errors() - for _, e := range es { - errs = append(errs, fmt.Errorf("invalid %w", e)) - } - - return error(errs) -} - type UEIPPool struct { Cidr string `yaml:"cidr" valid:"cidr,required"` } -func (u *UEIPPool) validate() (bool, error) { - govalidator.TagMap["cidr"] = govalidator.Validator(func(str string) bool { - isCIDR := govalidator.IsCIDR(str) - return isCIDR - }) - - result, err := govalidator.ValidateStruct(u) - return result, appendInvalid(err) -} - type SpecificPath struct { DestinationIP string `yaml:"dest,omitempty" valid:"cidr,required"` DestinationPort string `yaml:"DestinationPort,omitempty" valid:"port,optional"` Path []string `yaml:"path" valid:"required"` } -func (p *SpecificPath) validate() (bool, error) { +func (p *SpecificPath) Validate() (bool, error) { govalidator.TagMap["cidr"] = govalidator.Validator(func(str string) bool { isCIDR := govalidator.IsCIDR(str) return isCIDR @@ -664,7 +712,7 @@ type PlmnID struct { Mnc string `yaml:"mnc"` } -func (p *PlmnID) validate() (bool, error) { +func (p *PlmnID) Validate() (bool, error) { mcc := p.Mcc if result := govalidator.StringMatches(mcc, "^[0-9]{3}$"); !result { err := fmt.Errorf("Invalid mcc: %s, should be a 3-digit number", mcc) @@ -685,7 +733,7 @@ type TimerValue struct { MaxRetryTimes int `yaml:"maxRetryTimes,omitempty" valid:"type(int)"` } -func (t *TimerValue) validate() (bool, error) { +func (t *TimerValue) Validate() (bool, error) { result, err := govalidator.ValidateStruct(t) return result, err } @@ -804,3 +852,18 @@ func (c *Config) GetCertKeyPath() string { defer c.RUnlock() return c.Configuration.Sbi.Tls.Key } + +func appendInvalid(err error) error { + var errs govalidator.Errors + + if err == nil { + return nil + } + + es := err.(govalidator.Errors).Errors() + for _, e := range es { + errs = append(errs, fmt.Errorf("invalid %w", e)) + } + + return error(errs) +} diff --git a/pkg/factory/config_test.go b/pkg/factory/config_test.go index 67b56b95..a8c7215a 100644 --- a/pkg/factory/config_test.go +++ b/pkg/factory/config_test.go @@ -9,6 +9,235 @@ import ( "github.com/free5gc/smf/pkg/factory" ) +func baseGNB() *factory.GNBConfig { + return &factory.GNBConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "AN", + }, + } +} + +func baseUPF() *factory.UPFConfig { + return &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, + NodeID: "127.0.0.8", + SNssaiInfos: []*factory.SnssaiUpfInfoItem{ + { + SNssai: &models.Snssai{ + Sst: int32(1), + Sd: "010203", + }, + DnnUpfInfoList: []*factory.DnnUpfInfoItem{ + { + Dnn: "internet", + Pools: []*factory.UEIPPool{ + { + Cidr: "10.60.0.0/16", + }, + }, + }, + }, + }, + }, + Interfaces: []*factory.Interface{ + { + InterfaceType: "N3", + Endpoints: []string{ + "127.0.0.8", + }, + NetworkInstances: []string{ + "internet", + }, + }, + }, + } +} + +func TestUPNodeConfigInterfaceValidation(t *testing.T) { + testcase := []struct { + Name string + UPNodeConfig factory.UPNodeConfigInterface + Valid bool + }{ + { + Name: "Valid gNB", + UPNodeConfig: baseGNB(), + Valid: true, + }, + { + Name: "Valid UPF", + UPNodeConfig: baseUPF(), + Valid: true, + }, + { + Name: "UPF with wrong type", + UPNodeConfig: func() *factory.UPFConfig { + config := baseUPF() + config.UPNodeConfig.Type = "xxx" + return config + }(), + Valid: false, + }, + { + Name: "UPF with wrong NodeID", + UPNodeConfig: func() *factory.UPFConfig { + config := baseUPF() + config.NodeID = "foobar" + return config + }(), + Valid: false, + }, + { + Name: "UPF with nil sNssaiUpfInfos", + UPNodeConfig: func() *factory.UPFConfig { + config := baseUPF() + config.SNssaiInfos = nil + return config + }(), + Valid: false, + }, + { + Name: "UPF with empty sNssaiUpfInfos", + UPNodeConfig: func() *factory.UPFConfig { + config := baseUPF() + config.SNssaiInfos = []*factory.SnssaiUpfInfoItem{} + return config + }(), + Valid: false, + }, + { + Name: "UPF with nil interfaces", + UPNodeConfig: func() *factory.UPFConfig { + config := baseUPF() + config.Interfaces = nil + return config + }(), + Valid: false, + }, + { + Name: "UPF with empty interfaces", + UPNodeConfig: func() *factory.UPFConfig { + config := baseUPF() + config.Interfaces = []*factory.Interface{} + return config + }(), + Valid: false, + }, + { + Name: "UPF with overlapping pools in DnnUpfInfoItem.Pools", + UPNodeConfig: func() *factory.UPFConfig { + config := baseUPF() + config.SNssaiInfos[0].DnnUpfInfoList = []*factory.DnnUpfInfoItem{ + { + Dnn: "internet", + Pools: []*factory.UEIPPool{ + { + Cidr: "10.60.0.0/16", + }, + { + Cidr: "10.60.10.0/16", + }, + }, + }, + } + return config + }(), + Valid: false, + }, + { + Name: "UPF with overlapping pools in DnnUpfInfoItem.Pools and DnnUpfInfoItem.StaticPools", + UPNodeConfig: func() *factory.UPFConfig { + config := baseUPF() + config.SNssaiInfos[0].DnnUpfInfoList = []*factory.DnnUpfInfoItem{ + { + Dnn: "internet", + Pools: []*factory.UEIPPool{ + { + Cidr: "10.60.0.0/16", + }, + }, + StaticPools: []*factory.UEIPPool{ + { + Cidr: "10.60.10.0/16", + }, + }, + }, + } + return config + }(), + Valid: false, + }, + { + Name: "UPF without N3 interface", + UPNodeConfig: func() *factory.UPFConfig { + config := baseUPF() + config.Interfaces = []*factory.Interface{} + return config + }(), + Valid: false, + }, + } + + for _, tc := range testcase { + t.Run(tc.Name, func(t *testing.T) { + ok, err := tc.UPNodeConfig.Validate() + require.Equal(t, tc.Valid, ok) + require.Nil(t, err) + }) + } +} + +func TestUserplaneInformationValidation(t *testing.T) { + testcase := []struct { + Name string + Upi *factory.UserPlaneInformation + Valid bool + }{ + { + Name: "Valid userPlaneInformation", + Upi: &factory.UserPlaneInformation{ + UPNodes: map[string]factory.UPNodeConfigInterface{ + "gNB": baseGNB(), + "UPF1": baseUPF(), + }, + Links: []*factory.UPLink{ + { + A: "gNB", + B: "UPF1", + }, + }, + }, + Valid: true, + }, + { + Name: "Link with non-existing node", + Upi: &factory.UserPlaneInformation{ + UPNodes: map[string]factory.UPNodeConfigInterface{ + "gNB": baseGNB(), + "UPF1": baseUPF(), + }, + Links: []*factory.UPLink{ + { + A: "gNB", + B: "UPF", + }, + }, + }, + Valid: false, + }, + } + + for _, tc := range testcase { + t.Run(tc.Name, func(t *testing.T) { + ok, err := tc.Upi.Validate() + require.Equal(t, tc.Valid, ok) + require.Nil(t, err) + }) + } +} + func TestSnssaiInfoItem(t *testing.T) { testcase := []struct { Name string From d8f841712784217c89a3c8b1b08a40a9eff9a370 Mon Sep 17 00:00:00 2001 From: Laura Henning Date: Fri, 4 Oct 2024 10:01:37 +0000 Subject: [PATCH 11/16] refactor: clean up config validation, use inbuilt govalidator functionality --- go.mod | 3 + go.sum | 14 + pkg/factory/config.go | 606 ++++++++++++------------------------- pkg/factory/config_test.go | 85 ++---- 4 files changed, 223 insertions(+), 485 deletions(-) diff --git a/go.mod b/go.mod index 659c5c7f..ede5b823 100644 --- a/go.mod +++ b/go.mod @@ -60,6 +60,8 @@ require ( github.com/tim-ywliu/nested-logrus-formatter v1.3.2 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect + go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect + go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/net v0.23.0 // indirect @@ -70,4 +72,5 @@ require ( google.golang.org/protobuf v1.33.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a // indirect ) diff --git a/go.sum b/go.sum index b05b6d44..7c65e43d 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -235,6 +236,7 @@ github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -242,6 +244,11 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE= +go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 h1:WJhcL4p+YeDxmZWg141nRm7XC8IDmhz7lk5GpadO1Sg= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -308,6 +315,7 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -325,6 +333,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -351,6 +360,8 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= @@ -406,6 +417,7 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -507,6 +519,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a h1:1XCVEdxrvL6c0TGOhecLuB7U9zYNdxZEjvOqJreKZiM= +inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a/go.mod h1:e83i32mAQOW1LAqEIweALsuK2Uw4mhQadA5r7b0Wobo= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/pkg/factory/config.go b/pkg/factory/config.go index b0ee3267..ac40787f 100644 --- a/pkg/factory/config.go +++ b/pkg/factory/config.go @@ -7,12 +7,14 @@ package factory import ( "errors" "fmt" + "slices" "strconv" "sync" "time" "github.com/asaskevich/govalidator" "github.com/davecgh/go-spew/spew" + "inet.af/netaddr" "github.com/free5gc/openapi/models" "github.com/free5gc/smf/internal/logger" @@ -39,18 +41,54 @@ const ( ) type Config struct { - Info *Info `yaml:"info" valid:"required"` + Info *Info `yaml:"info" valid:"required,configValidator"` Configuration *Configuration `yaml:"configuration" valid:"required"` Logger *Logger `yaml:"logger" valid:"required"` sync.RWMutex } func (c *Config) Validate() (bool, error) { - if configuration := c.Configuration; configuration != nil { - if result, err := configuration.validate(); err != nil { - return result, err + // register custom tag validators + govalidator.TagMap["scheme"] = govalidator.Validator(func(str string) bool { + return str == "https" || str == "http" + }) + + // register custom semantic validators + govalidator.CustomTypeTagMap.Set("snssaiValidator", func(i interface{}, context interface{}) bool { + // context == struct this field is in + // i == the validated field + sNssai := i.(models.Snssai) + ok, _ := ValidateSNssai(&sNssai) + return ok + }) + + govalidator.CustomTypeTagMap.Set("upNodeValidator", func(i interface{}, context interface{}) bool { + upNode := i.(UPNodeConfigInterface) + ok, _ := upNode.Validate() + return ok + }) + + govalidator.CustomTypeTagMap.Set("poolValidator", func(i interface{}, context interface{}) bool { + dnnUpfInfoItem := context.(*DnnUpfInfoItem) + ok, _ := ValidateUEIPPools(dnnUpfInfoItem.Pools, dnnUpfInfoItem.StaticPools) + return ok + }) + + govalidator.CustomTypeTagMap.Set("linkValidator", func(i interface{}, context interface{}) bool { + link := i.(UPLink) + var upNodeNames []string + for name, _ := range context.(UserPlaneInformation).UPNodes { + upNodeNames = append(upNodeNames, name) } - } + ok, _ := ValidateLink(&link, upNodeNames) + return ok + }) + + govalidator.CustomTypeTagMap.Set("pathValidator", func(i interface{}, context interface{}) bool { + path := context.([]string) + ok, _ := ValidatePath(path) + return ok + }) result, err := govalidator.ValidateStruct(c) return result, appendInvalid(err) @@ -69,29 +107,29 @@ type Info struct { Description string `yaml:"description,omitempty" valid:"type(string)"` } -func (i *Info) validate() (bool, error) { - result, err := govalidator.ValidateStruct(i) - return result, appendInvalid(err) -} - type Configuration struct { - SmfName string `yaml:"smfName" valid:"type(string),required"` - Sbi *Sbi `yaml:"sbi" valid:"required"` - PFCP *PFCP `yaml:"pfcp" valid:"required"` + SmfName string `yaml:"smfName" valid:"type(string),required"` + // done + Sbi *Sbi `yaml:"sbi" valid:"required"` + // done + PFCP *PFCP `yaml:"pfcp" valid:"required"` + // done NrfUri string `yaml:"nrfUri" valid:"url,required"` NrfCertPem string `yaml:"nrfCertPem,omitempty" valid:"optional"` UserPlaneInformation UserPlaneInformation `yaml:"userplaneInformation" valid:"required"` - ServiceNameList []string `yaml:"serviceNameList" valid:"required"` - SNssaiInfo []*SnssaiInfoItem `yaml:"snssaiInfos" valid:"required"` - ULCL bool `yaml:"ulcl" valid:"type(bool),optional"` - PLMNList []PlmnID `yaml:"plmnList" valid:"optional"` - Locality string `yaml:"locality" valid:"type(string),optional"` - UrrPeriod uint16 `yaml:"urrPeriod,omitempty" valid:"optional"` - UrrThreshold uint64 `yaml:"urrThreshold,omitempty" valid:"optional"` - T3591 *TimerValue `yaml:"t3591" valid:"required"` - T3592 *TimerValue `yaml:"t3592" valid:"required"` - NwInstFqdnEncoding bool `yaml:"nwInstFqdnEncoding" valid:"type(bool),optional"` - RequestedUnit int32 `yaml:"requestedUnit,omitempty" valid:"optional"` + // done + ServiceNameList []string `yaml:"serviceNameList" valid:"required,in(nsmf-pdusession,nsmf-event-exposure,nsmf-oam)"` + // done + SNssaiInfo []*SnssaiInfoItem `yaml:"snssaiInfos" valid:"required"` + ULCL bool `yaml:"ulcl" valid:"type(bool),optional"` + PLMNList []PlmnID `yaml:"plmnList" valid:"optional"` + Locality string `yaml:"locality" valid:"type(string),optional"` + UrrPeriod uint16 `yaml:"urrPeriod,omitempty" valid:"optional"` + UrrThreshold uint64 `yaml:"urrThreshold,omitempty" valid:"optional"` + T3591 *TimerValue `yaml:"t3591" valid:"required"` + T3592 *TimerValue `yaml:"t3592" valid:"required"` + NwInstFqdnEncoding bool `yaml:"nwInstFqdnEncoding" valid:"type(bool),optional"` + RequestedUnit int32 `yaml:"requestedUnit,omitempty" valid:"optional"` } type Logger struct { @@ -100,95 +138,25 @@ type Logger struct { ReportCaller bool `yaml:"reportCaller" valid:"type(bool)"` } -func (c *Configuration) validate() (bool, error) { - if sbi := c.Sbi; sbi != nil { - if result, err := sbi.validate(); err != nil { - return result, err - } - } - - if pfcp := c.PFCP; pfcp != nil { - if result, err := pfcp.validate(); err != nil { - return result, err - } - } - - if userPlaneInformation := &c.UserPlaneInformation; userPlaneInformation != nil { - if result, err := userPlaneInformation.validate(); err != nil { - return result, err - } - } - - for index, serviceName := range c.ServiceNameList { - switch { - case serviceName == "nsmf-pdusession": - case serviceName == "nsmf-event-exposure": - case serviceName == "nsmf-oam": - default: - err := errors.New("Invalid serviceNameList[" + strconv.Itoa(index) + "]: " + - serviceName + ", should be nsmf-pdusession, nsmf-event-exposure or nsmf-oam.") - return false, err - } - } - - for _, snssaiInfo := range c.SNssaiInfo { - if result, err := snssaiInfo.Validate(); err != nil { - return result, err - } - } - - if c.PLMNList != nil { - for _, plmnId := range c.PLMNList { - if result, err := plmnId.validate(); err != nil { - return result, err - } - } - } - - if t3591 := c.T3591; t3591 != nil { - if result, err := t3591.validate(); err != nil { - return result, err - } - } - - if t3592 := c.T3592; t3592 != nil { - if result, err := t3592.validate(); err != nil { - return result, err - } - } - - result, err := govalidator.ValidateStruct(c) - return result, appendInvalid(err) -} - type SnssaiInfoItem struct { - SNssai *models.Snssai `yaml:"sNssai" valid:"required"` + SNssai *models.Snssai `yaml:"sNssai" valid:"required,snssaiValidator"` DnnInfos []*SnssaiDnnInfoItem `yaml:"dnnInfos" valid:"required"` } -func (s *SnssaiInfoItem) Validate() (bool, error) { - if snssai := s.SNssai; snssai != nil { - if result := (snssai.Sst >= 0 && snssai.Sst <= 255); !result { - err := errors.New("Invalid sNssai.Sst: " + strconv.Itoa(int(snssai.Sst)) + ", should be in range 0~255.") - return false, err - } - - if snssai.Sd != "" { - if result := govalidator.StringMatches(snssai.Sd, "^[0-9A-Fa-f]{6}$"); !result { - err := errors.New("Invalid sNssai.Sd: " + snssai.Sd + - ", should be 3 bytes hex string and in range 000000~FFFFFF.") - return false, err - } - } +func ValidateSNssai(sNssai *models.Snssai) (bool, error) { + if result := (sNssai.Sst >= 0 && sNssai.Sst <= 255); !result { + err := errors.New("Invalid sNssai.Sst: " + strconv.Itoa(int(sNssai.Sst)) + ", should be in range 0~255.") + return false, err } - for _, dnnInfo := range s.DnnInfos { - if result, err := dnnInfo.validate(); err != nil { - return result, err + if sNssai.Sd != "" { + if result := govalidator.StringMatches(sNssai.Sd, "^[0-9A-Fa-f]{6}$"); !result { + err := errors.New("Invalid sNssai.Sd: " + sNssai.Sd + + ", should be 3 bytes hex string and in range 000000~FFFFFF.") + return false, err } } - result, err := govalidator.ValidateStruct(s) - return result, appendInvalid(err) + return true, nil } type SnssaiDnnInfoItem struct { @@ -197,25 +165,9 @@ type SnssaiDnnInfoItem struct { PCSCF *PCSCF `yaml:"pcscf,omitempty" valid:"optional"` } -func (s *SnssaiDnnInfoItem) validate() (bool, error) { - if dns := s.DNS; dns != nil { - if result, err := dns.validate(); err != nil { - return result, err - } - } - - if pcscf := s.PCSCF; pcscf != nil { - if result, err := pcscf.validate(); err != nil { - return result, err - } - } - - result, err := govalidator.ValidateStruct(s) - return result, appendInvalid(err) -} - type Sbi struct { - Scheme string `yaml:"scheme" valid:"scheme,required"` + Scheme string `yaml:"scheme" valid:"scheme,required"` + //done Tls *Tls `yaml:"tls" valid:"optional"` RegisterIPv4 string `yaml:"registerIPv4,omitempty" valid:"host,optional"` // IP that is registered at NRF. // IPv6Addr string `yaml:"ipv6Addr,omitempty"` @@ -223,31 +175,11 @@ type Sbi struct { Port int `yaml:"port,omitempty" valid:"port,optional"` } -func (s *Sbi) validate() (bool, error) { - govalidator.TagMap["scheme"] = govalidator.Validator(func(str string) bool { - return str == "https" || str == "http" - }) - - if tls := s.Tls; tls != nil { - if result, err := tls.validate(); err != nil { - return result, err - } - } - - result, err := govalidator.ValidateStruct(s) - return result, appendInvalid(err) -} - type Tls struct { Pem string `yaml:"pem,omitempty" valid:"type(string),minstringlength(1),required"` Key string `yaml:"key,omitempty" valid:"type(string),minstringlength(1),required"` } -func (t *Tls) validate() (bool, error) { - result, err := govalidator.ValidateStruct(t) - return result, appendInvalid(err) -} - type PFCP struct { ListenAddr string `yaml:"listenAddr,omitempty" valid:"host,required"` ExternalAddr string `yaml:"externalAddr,omitempty" valid:"host,required"` @@ -258,86 +190,29 @@ type PFCP struct { HeartbeatInterval time.Duration `yaml:"heartbeatInterval,omitempty" valid:"type(time.Duration),optional"` } -func (p *PFCP) validate() (bool, error) { - result, err := govalidator.ValidateStruct(p) - return result, appendInvalid(err) -} - type DNS struct { IPv4Addr string `yaml:"ipv4,omitempty" valid:"ipv4,required"` IPv6Addr string `yaml:"ipv6,omitempty" valid:"ipv6,optional"` } -func (d *DNS) validate() (bool, error) { - result, err := govalidator.ValidateStruct(d) - return result, appendInvalid(err) -} - type PCSCF struct { IPv4Addr string `yaml:"ipv4,omitempty" valid:"ipv4,required"` } -func (p *PCSCF) validate() (bool, error) { - result, err := govalidator.ValidateStruct(p) - return result, appendInvalid(err) -} - type Path struct { DestinationIP string `yaml:"DestinationIP,omitempty" valid:"ipv4,required"` DestinationPort string `yaml:"DestinationPort,omitempty" valid:"port,optional"` UPF []string `yaml:"UPF,omitempty" valid:"required"` } -func (p *Path) validate() (bool, error) { - for _, upf := range p.UPF { - if result := len(upf); result == 0 { - err := errors.New("Invalid UPF: " + upf + ", should not be empty") - return false, err - } - } - - result, err := govalidator.ValidateStruct(p) - return result, appendInvalid(err) -} - type UERoutingInfo struct { - Members []string `yaml:"members" valid:"required"` + Members []string `yaml:"members" valid:"required,matches(imsi-[0-9]{5,15}$)-Invalid member (SUPI)"` AN string `yaml:"AN,omitempty" valid:"ipv4,optional"` PathList []Path `yaml:"PathList,omitempty" valid:"optional"` - Topology []UPLink `yaml:"topology" valid:"required"` + Topology []UPLink `yaml:"topology" valid:"required"` // TODO: validation with topology names SpecificPaths []SpecificPath `yaml:"specificPath,omitempty" valid:"optional"` } -func (u *UERoutingInfo) validate() (bool, error) { - for _, member := range u.Members { - if result := govalidator.StringMatches(member, "imsi-[0-9]{5,15}$"); !result { - err := errors.New("Invalid member (SUPI): " + member) - return false, err - } - } - - for _, path := range u.PathList { - if result, err := path.validate(); err != nil { - return result, err - } - } - - for _, link := range u.Topology { - if result, err := link.validate(); err != nil { - return result, err - } - } - - for _, path := range u.SpecificPaths { - if result, err := path.validate(); err != nil { - return result, err - } - } - - result, err := govalidator.ValidateStruct(u) - return result, appendInvalid(err) -} - // RouteProfID is string providing a Route Profile identifier. type RouteProfID string @@ -347,50 +222,19 @@ type RouteProfile struct { ForwardingPolicyID string `yaml:"forwardingPolicyID,omitempty" valid:"type(string),stringlength(1|255),required"` } -func (r *RouteProfile) validate() (bool, error) { - result, err := govalidator.ValidateStruct(r) - return result, appendInvalid(err) -} - // PfdContent represents the flow of the application type PfdContent struct { // Identifies a PFD of an application identifier. PfdID string `yaml:"pfdID,omitempty" valid:"type(string),minstringlength(1),required"` // Represents a 3-tuple with protocol, server ip and server port for // UL/DL application traffic. - FlowDescriptions []string `yaml:"flowDescriptions,omitempty" valid:"optional"` + FlowDescriptions []string `yaml:"flowDescriptions,omitempty" valid:"minstringlen(1),optional"` // Indicates a URL or a regular expression which is used to match the // significant parts of the URL. - Urls []string `yaml:"urls,omitempty" valid:"optional"` + Urls []string `yaml:"urls,omitempty" valid:"url,optional"` // Indicates an FQDN or a regular expression as a domain name matching // criteria. - DomainNames []string `yaml:"domainNames,omitempty" valid:"optional"` -} - -func (p *PfdContent) validate() (bool, error) { - for _, flowDescription := range p.FlowDescriptions { - if result := len(flowDescription) > 0; !result { - err := errors.New("Invalid FlowDescription: " + flowDescription + ", should not be empty.") - return false, err - } - } - - for _, url := range p.Urls { - if result := govalidator.IsURL(url); !result { - err := errors.New("Invalid Url: " + url + ", should be url.") - return false, err - } - } - - for _, domainName := range p.DomainNames { - if result := govalidator.IsDNSName(domainName); !result { - err := errors.New("Invalid DomainName: " + domainName + ", should be domainName.") - return false, err - } - } - - result, err := govalidator.ValidateStruct(p) - return result, appendInvalid(err) + DomainNames []string `yaml:"domainNames,omitempty" valid:"dns,optional"` } // PfdDataForApp represents the PFDs for an application identifier @@ -403,17 +247,6 @@ type PfdDataForApp struct { CachingTime *time.Time `yaml:"cachingTime,omitempty" valid:"optional"` } -func (p *PfdDataForApp) validate() (bool, error) { - for _, pfd := range p.Pfds { - if result, err := pfd.validate(); err != nil { - return result, err - } - } - - result, err := govalidator.ValidateStruct(p) - return result, appendInvalid(err) -} - type RoutingConfig struct { Info *Info `yaml:"info" valid:"required"` UERoutingInfo map[string]UERoutingInfo `yaml:"ueRoutingInfo" valid:"optional"` @@ -423,180 +256,147 @@ type RoutingConfig struct { } func (r *RoutingConfig) Validate() (bool, error) { - if info := r.Info; info != nil { - if result, err := info.validate(); err != nil { - return result, err - } - } - - for _, ueRoutingInfo := range r.UERoutingInfo { - if result, err := ueRoutingInfo.validate(); err != nil { - return result, err - } - } - - for _, routeProf := range r.RouteProf { - if result, err := routeProf.validate(); err != nil { - return result, err - } - } - - for _, pfdData := range r.PfdDatas { - if result, err := pfdData.validate(); err != nil { - return result, err - } - } - result, err := govalidator.ValidateStruct(r) return result, appendInvalid(err) } // UserPlaneInformation describe core network userplane information type UserPlaneInformation struct { - UPNodes map[string]*UPNode `json:"upNodes" yaml:"upNodes" valid:"required"` - Links []*UPLink `json:"links" yaml:"links" valid:"required"` + UPNodes map[string]*UPNodeConfigInterface `json:"upNodes" yaml:"upNodes" valid:"required,upNodeValidator"` + Links []*UPLink `json:"links" yaml:"links" valid:"required,linkValidator"` } -func (u *UserPlaneInformation) validate() (bool, error) { - for _, upNode := range u.UPNodes { - if result, err := upNode.validate(); err != nil { - return result, err - } - } +// UPNode represent the user plane node +type UPNodeConfig struct { + Type string `json:"type" yaml:"type" valid:"required,in(AN,UPF)"` +} - for _, link := range u.Links { - if result, err := link.validate(); err != nil { - return result, err - } - } +type UPNodeConfigInterface interface { + Validate() (result bool, err error) + GetType() string +} - result, err := govalidator.ValidateStruct(u) - return result, appendInvalid(err) +type GNBConfig struct { + *UPNodeConfig } -// UPNode represent the user plane node -type UPNode struct { - Type string `json:"type" yaml:"type" valid:"upNodeType,required"` - NodeID string `json:"nodeID" yaml:"nodeID" valid:"host,optional"` - Addr string `json:"addr" yaml:"addr" valid:"host,optional"` - ANIP string `json:"anIP" yaml:"anIP" valid:"host,optional"` - Dnn string `json:"dnn" yaml:"dnn" valid:"type(string),minstringlength(1),optional"` - SNssaiInfos []*SnssaiUpfInfoItem `json:"sNssaiUpfInfos" yaml:"sNssaiUpfInfos,omitempty" valid:"optional"` - InterfaceUpfInfoList []*InterfaceUpfInfoItem `json:"interfaces" yaml:"interfaces,omitempty" valid:"optional"` -} - -func (u *UPNode) validate() (bool, error) { - govalidator.TagMap["upNodeType"] = govalidator.Validator(func(str string) bool { - return str == "AN" || str == "UPF" - }) +type UPFConfig struct { + *UPNodeConfig + NodeID string `json:"nodeID" yaml:"nodeID" valid:"host,required"` + SNssaiInfos []*SnssaiUpfInfoItem `json:"sNssaiUpfInfos" yaml:"sNssaiUpfInfos" valid:"required"` + Interfaces []*Interface `json:"interfaces" yaml:"interfaces" valid:"required"` +} - for _, snssaiInfo := range u.SNssaiInfos { - if result, err := snssaiInfo.Validate(); err != nil { - return result, err - } - } +func (gNB *GNBConfig) Validate() (result bool, err error) { + // no semantic validation (yet) for GNBs + return true, nil +} +func (gNB *GNBConfig) GetType() string { + return gNB.Type +} + +func (upf *UPFConfig) Validate() (result bool, err error) { n3IfsNum := 0 n9IfsNum := 0 - for _, interfaceUpfInfo := range u.InterfaceUpfInfoList { - if result, err := interfaceUpfInfo.validate(); err != nil { - return result, err - } - if interfaceUpfInfo.InterfaceType == "N3" { + for _, iface := range upf.Interfaces { + if iface.InterfaceType == "N3" { n3IfsNum++ } - if interfaceUpfInfo.InterfaceType == "N9" { + if iface.InterfaceType == "N9" { n9IfsNum++ } + } - if n3IfsNum > 1 || n9IfsNum > 1 { - return false, fmt.Errorf( - "Not support multiple InterfaceUpfInfo for the same type: N3 number(%d), N9 number(%d)", - n3IfsNum, n9IfsNum) - } + if n3IfsNum == 0 && n9IfsNum == 0 { + return false, fmt.Errorf("UPF %s must have a user plane interface (N3 or N9)", upf.NodeID) } - result, err := govalidator.ValidateStruct(u) - return result, appendInvalid(err) -} -type InterfaceUpfInfoItem struct { - InterfaceType models.UpInterfaceType `json:"interfaceType" yaml:"interfaceType" valid:"required"` - Endpoints []string `json:"endpoints" yaml:"endpoints" valid:"required"` - NetworkInstances []string `json:"networkInstances" yaml:"networkInstances" valid:"required"` + if n3IfsNum > 1 || n9IfsNum > 1 { + return false, fmt.Errorf( + "UPF %s: There is currently no support for multiple N3/ N9 interfaces: N3 number(%d), N9 number(%d)", + upf.NodeID, n3IfsNum, n9IfsNum) + } + + return true, nil } -func (i *InterfaceUpfInfoItem) validate() (bool, error) { - interfaceType := i.InterfaceType - if result := (interfaceType == "N3" || interfaceType == "N9"); !result { - err := errors.New("Invalid interfaceType: " + string(interfaceType) + ", should be N3 or N9.") - return false, err - } +func (upf *UPFConfig) GetNodeID() string { + return upf.NodeID +} - for _, endpoint := range i.Endpoints { - if result := govalidator.IsHost(endpoint); !result { - err := errors.New("Invalid endpoint:" + endpoint + ", should be IPv4.") - return false, err - } - } +func (upf *UPFConfig) GetType() string { + return upf.Type +} - result, err := govalidator.ValidateStruct(i) - return result, appendInvalid(err) +type Interface struct { + InterfaceType models.UpInterfaceType `json:"interfaceType" yaml:"interfaceType" valid:"required,in(N3,N9)"` + Endpoints []string `json:"endpoints" yaml:"endpoints" valid:"host,required"` + NetworkInstances []string `json:"networkInstances" yaml:"networkInstances" valid:"optional"` } type SnssaiUpfInfoItem struct { - SNssai *models.Snssai `json:"sNssai" yaml:"sNssai" valid:"required"` + SNssai *models.Snssai `json:"sNssai" yaml:"sNssai" valid:"snssaiValidator,required"` DnnUpfInfoList []*DnnUpfInfoItem `json:"dnnUpfInfoList" yaml:"dnnUpfInfoList" valid:"required"` } -func (s *SnssaiUpfInfoItem) Validate() (bool, error) { - if s.SNssai != nil { - if result := (s.SNssai.Sst >= 0 && s.SNssai.Sst <= 255); !result { - err := errors.New("Invalid sNssai.Sst: " + strconv.Itoa(int(s.SNssai.Sst)) + ", should be in range 0~255.") - return false, err - } +type DnnUpfInfoItem struct { + Dnn string `json:"dnn" yaml:"dnn" valid:"minstringlength(1),required"` + DnaiList []string `json:"dnaiList" yaml:"dnaiList" valid:"optional"` + PduSessionTypes []models.PduSessionType `json:"pduSessionTypes" yaml:"pduSessionTypes" valid:"optional,in(IPv4,IPv6,IPV4V6,UNSTRUCTURED,ETHERNET)"` + Pools []*UEIPPool `json:"pools" yaml:"pools" valid:"optional,poolValidator"` + StaticPools []*UEIPPool `json:"staticPools" yaml:"staticPools" valid:"optional,poolValidator"` +} - if s.SNssai.Sd != "" { - if result := govalidator.StringMatches(s.SNssai.Sd, "^[0-9A-Fa-f]{6}$"); !result { - err := errors.New("Invalid sNssai.Sd: " + s.SNssai.Sd + - ", should be 3 bytes hex string and in range 000000~FFFFFF.") - return false, err - } +func ValidateUEIPPools(dynamic []*UEIPPool, static []*UEIPPool) (bool, error) { + var prefixes []netaddr.IPPrefix + for _, pool := range dynamic { + // CIDR check + prefix, ok := netaddr.ParseIPPrefix(pool.Cidr) + if ok != nil { + return false, fmt.Errorf("Invalid pool CIDR: %s.", pool.Cidr) + } else { + prefixes = append(prefixes, prefix) } } - for _, dnnInfo := range s.DnnUpfInfoList { - if result, err := dnnInfo.validate(); err != nil { - return result, err + // check overlap within dynamic pools + for i := 0; i < len(prefixes); i++ { + for j := i + 1; j < len(prefixes); j++ { + if prefixes[i].Overlaps(prefixes[j]) { + return false, fmt.Errorf("overlap detected between dynamic pools %s and %s", prefixes[i], prefixes[j]) + } } } - result, err := govalidator.ValidateStruct(s) - return result, appendInvalid(err) -} - -type DnnUpfInfoItem struct { - Dnn string `json:"dnn" yaml:"dnn" valid:"required"` - DnaiList []string `json:"dnaiList" yaml:"dnaiList" valid:"optional"` - PduSessionTypes []models.PduSessionType `json:"pduSessionTypes" yaml:"pduSessionTypes" valid:"optional"` - Pools []*UEIPPool `json:"pools" yaml:"pools" valid:"optional"` - StaticPools []*UEIPPool `json:"staticPools" yaml:"staticPools" valid:"optional"` -} - -func (d *DnnUpfInfoItem) validate() (bool, error) { - if result := len(d.Dnn); result == 0 { - err := errors.New("Invalid DnnUpfInfoItem.dnn: " + d.Dnn + ", should not be empty.") - return false, err + // check static pools CIDR and overlap with dynamic pools + var staticPrefixes []netaddr.IPPrefix + for _, staticPool := range static { + // CIDR check + staticPrefix, ok := netaddr.ParseIPPrefix(staticPool.Cidr) + if ok != nil { + return false, fmt.Errorf("Invalid CIDR: %s.", staticPool.Cidr) + } else { + staticPrefixes = append(staticPrefixes, staticPrefix) + for _, prefix := range prefixes { + if staticPrefix.Overlaps(prefix) { + return false, fmt.Errorf("overlap detected between static pool %s and dynamic pool %s", staticPrefix, prefix) + } + } + } } - for _, pool := range d.Pools { - if result, err := pool.validate(); err != nil { - return result, err + // check overlap within static pools + for i := 0; i < len(staticPrefixes); i++ { + for j := i + 1; j < len(staticPrefixes); j++ { + if staticPrefixes[i].Overlaps(staticPrefixes[j]) { + return false, fmt.Errorf("overlap detected between static pools %s and %s", staticPrefixes[i], staticPrefixes[j]) + } } } - result, err := govalidator.ValidateStruct(d) - return result, appendInvalid(err) + return true, nil } type UPLink struct { @@ -604,9 +404,11 @@ type UPLink struct { B string `json:"B" yaml:"B" valid:"required"` } -func (u *UPLink) validate() (bool, error) { - result, err := govalidator.ValidateStruct(u) - return result, appendInvalid(err) +func ValidateLink(link *UPLink, upNodeNames []string) (bool, error) { + if ok := !slices.Contains(upNodeNames, link.A) || !slices.Contains(upNodeNames, link.B); !ok { + return false, fmt.Errorf("Link %s--%s contains unknown node name", link.A, link.B) + } + return true, nil } func appendInvalid(err error) error { @@ -628,57 +430,26 @@ type UEIPPool struct { Cidr string `yaml:"cidr" valid:"cidr,required"` } -func (u *UEIPPool) validate() (bool, error) { - govalidator.TagMap["cidr"] = govalidator.Validator(func(str string) bool { - isCIDR := govalidator.IsCIDR(str) - return isCIDR - }) - - result, err := govalidator.ValidateStruct(u) - return result, appendInvalid(err) -} - type SpecificPath struct { DestinationIP string `yaml:"dest,omitempty" valid:"cidr,required"` DestinationPort string `yaml:"DestinationPort,omitempty" valid:"port,optional"` - Path []string `yaml:"path" valid:"required"` + Path []string `yaml:"path" valid:"pathValidator,required"` } -func (p *SpecificPath) validate() (bool, error) { - govalidator.TagMap["cidr"] = govalidator.Validator(func(str string) bool { - isCIDR := govalidator.IsCIDR(str) - return isCIDR - }) - - for _, upf := range p.Path { +func ValidatePath(path []string) (bool, error) { + for _, upf := range path { if result := len(upf); result == 0 { err := errors.New("Invalid UPF: " + upf + ", should not be empty") return false, err } } - result, err := govalidator.ValidateStruct(p) - return result, appendInvalid(err) + return true, nil } type PlmnID struct { - Mcc string `yaml:"mcc"` - Mnc string `yaml:"mnc"` -} - -func (p *PlmnID) validate() (bool, error) { - mcc := p.Mcc - if result := govalidator.StringMatches(mcc, "^[0-9]{3}$"); !result { - err := fmt.Errorf("Invalid mcc: %s, should be a 3-digit number", mcc) - return false, err - } - - mnc := p.Mnc - if result := govalidator.StringMatches(mnc, "^[0-9]{2,3}$"); !result { - err := fmt.Errorf("Invalid mnc: %s, should be a 2 or 3-digit number", mnc) - return false, err - } - return true, nil + Mcc string `yaml:"mcc" valid:"matches(^[0-9]{3}$),required"` + Mnc string `yaml:"mnc" valid:"matches(^[0-9]{2,3}$),required"` } type TimerValue struct { @@ -687,11 +458,6 @@ type TimerValue struct { MaxRetryTimes int `yaml:"maxRetryTimes,omitempty" valid:"type(int)"` } -func (t *TimerValue) validate() (bool, error) { - result, err := govalidator.ValidateStruct(t) - return result, err -} - func (c *Config) GetVersion() string { c.RLock() defer c.RUnlock() diff --git a/pkg/factory/config_test.go b/pkg/factory/config_test.go index 67b56b95..1971e2d3 100644 --- a/pkg/factory/config_test.go +++ b/pkg/factory/config_test.go @@ -9,98 +9,53 @@ import ( "github.com/free5gc/smf/pkg/factory" ) -func TestSnssaiInfoItem(t *testing.T) { +func TestSnssaiValidator(t *testing.T) { testcase := []struct { - Name string - Snssai *models.Snssai - DnnInfos []*factory.SnssaiDnnInfoItem + Name string + Snssai *models.Snssai + Valid bool }{ { - Name: "Default", + Name: "Valid SNssai with SST and SD", Snssai: &models.Snssai{ Sst: int32(1), Sd: "010203", }, - DnnInfos: []*factory.SnssaiDnnInfoItem{ - { - Dnn: "internet", - DNS: &factory.DNS{ - IPv4Addr: "8.8.8.8", - }, - }, - }, + Valid: true, }, { - Name: "Empty SD", + Name: "Valid SNssai without SD", Snssai: &models.Snssai{ Sst: int32(1), }, - DnnInfos: []*factory.SnssaiDnnInfoItem{ - { - Dnn: "internet2", - DNS: &factory.DNS{ - IPv4Addr: "1.1.1.1", - }, - }, - }, + Valid: true, }, - } - - for _, tc := range testcase { - t.Run(tc.Name, func(t *testing.T) { - snssaiInfoItem := factory.SnssaiInfoItem{ - SNssai: tc.Snssai, - DnnInfos: tc.DnnInfos, - } - - ok, err := snssaiInfoItem.Validate() - require.True(t, ok) - require.Nil(t, err) - }) - } -} - -func TestSnssaiUpfInfoItem(t *testing.T) { - testcase := []struct { - Name string - Snssai *models.Snssai - DnnInfos []*factory.DnnUpfInfoItem - }{ { - Name: "Default", + Name: "Invalid SNssai: invalid SST", Snssai: &models.Snssai{ - Sst: int32(1), - Sd: "010203", - }, - DnnInfos: []*factory.DnnUpfInfoItem{ - { - Dnn: "internet", - }, + Sst: int32(256), }, + Valid: false, }, { - Name: "Empty SD", + Name: "Invalid SNssai: invalid SD", Snssai: &models.Snssai{ Sst: int32(1), + Sd: "32", }, - DnnInfos: []*factory.DnnUpfInfoItem{ - { - Dnn: "internet2", - }, - }, + Valid: false, }, } for _, tc := range testcase { t.Run(tc.Name, func(t *testing.T) { - snssaiInfoItem := factory.SnssaiUpfInfoItem{ - SNssai: tc.Snssai, - DnnUpfInfoList: tc.DnnInfos, + ok, err := factory.ValidateSNssai(tc.Snssai) + require.Equal(t, tc.Valid, ok) + if !ok { + require.Error(t, err) + } else { + require.Nil(t, err) } - - ok, err := snssaiInfoItem.Validate() - require.True(t, ok) - require.Nil(t, err) }) } } From 55cc1a3d068e429db06bccaaba2bcabc59d5b138 Mon Sep 17 00:00:00 2001 From: Laura Henning Date: Fri, 4 Oct 2024 09:02:38 +0000 Subject: [PATCH 12/16] refactor: use NodeID as PFCP address for UPF UPNodes; add unit tests --- go.mod | 2 +- go.sum | 2 + internal/context/upf.go | 31 ++- internal/sbi/consumer/pcf_service_test.go | 1 + pkg/factory/config.go | 127 ++++++++--- pkg/factory/config_test.go | 257 +++++++++++++++------- 6 files changed, 295 insertions(+), 125 deletions(-) diff --git a/go.mod b/go.mod index ede5b823..d503022e 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.21 require ( github.com/antihax/optional v1.0.0 - github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/davecgh/go-spew v1.1.1 github.com/free5gc/aper v1.0.5 github.com/free5gc/nas v1.1.3 diff --git a/go.sum b/go.sum index 7c65e43d..fb7a8eb6 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,8 @@ github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UME github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= diff --git a/internal/context/upf.go b/internal/context/upf.go index 79cc3c02..32e1f7f8 100644 --- a/internal/context/upf.go +++ b/internal/context/upf.go @@ -604,21 +604,12 @@ func (upf *UPF) AddURR(urrID uint32, opts ...UrrOpt) (urr *URR, err error) { opt(urr) } - if urrId == 0 { - if URRID, err := upf.urrID(); err != nil { - } else { - urr.URRID = URRID - upf.urrPool.Store(urr.URRID, urr) - } - } else { - urr.URRID = urrId - upf.urrPool.Store(urr.URRID, urr) - } + upf.urrPool.Store(urr.URRID, urr) return urr, nil } func (upf *UPF) GetUUID() uuid.UUID { - return upf.uuid + return upf.ID } func (upf *UPF) GetQERById(qerId uint32) *QER { @@ -701,3 +692,21 @@ func (upf *UPF) IsAssociated() error { return nil } } + +func (upf *UPF) MatchedSelection(selection *UPFSelectionParams) bool { + for _, snssaiInfo := range upf.SNssaiInfos { + currentSnssai := snssaiInfo.SNssai + if currentSnssai.Equal(selection.SNssai) { + for _, dnnInfo := range snssaiInfo.DnnList { + if dnnInfo.Dnn == selection.Dnn { + if selection.Dnai == "" { + return true + } else if dnnInfo.ContainsDNAI(selection.Dnai) { + return true + } + } + } + } + } + return false +} diff --git a/internal/sbi/consumer/pcf_service_test.go b/internal/sbi/consumer/pcf_service_test.go index df119e9a..d137dc2b 100644 --- a/internal/sbi/consumer/pcf_service_test.go +++ b/internal/sbi/consumer/pcf_service_test.go @@ -27,6 +27,7 @@ var testConfig = factory.Config{ BindingIPv4: "127.0.0.1", Port: 8000, }, + UserPlaneInformation: &factory.UserPlaneInformation{}, }, } diff --git a/pkg/factory/config.go b/pkg/factory/config.go index 73fa7b94..bf183c91 100644 --- a/pkg/factory/config.go +++ b/pkg/factory/config.go @@ -464,17 +464,14 @@ func (u *UserPlaneInformation) Validate() (result bool, err error) { return str == "N3" || str == "N9" }) - // register CIDR to govalidator - govalidator.TagMap["cidr"] = govalidator.Validator(func(str string) bool { - return govalidator.IsCIDR(str) - }) - + // collect all validation errors for UserPlaneInformation + var validationErrors govalidator.Errors result = true - // validate struct field correctness recursively + // validate struct field correctness if ok, errStruct := govalidator.ValidateStruct(u); !ok { result = false - err = appendInvalid(errStruct) + validationErrors = append(validationErrors, appendInvalid(errStruct).(govalidator.Errors)...) } var upNodeNames []string @@ -484,19 +481,19 @@ func (u *UserPlaneInformation) Validate() (result bool, err error) { // call custom validation function (semantic validation) if ok, errSemantic := upNode.Validate(); !ok { result = false - err = appendInvalid(errSemantic) + validationErrors = append(validationErrors, errSemantic.(govalidator.Errors)...) } } for _, link := range u.Links { if !slices.Contains(upNodeNames, link.A) || !slices.Contains(upNodeNames, link.B) { result = false - err = appendInvalid(fmt.Errorf("Link %s--%s contains unknown node name, check 'userplaneInformation'", - link.A, link.B)) + validationErrors = append(validationErrors, + fmt.Errorf("Link %s--%s contains unknown node name", link.A, link.B)) } } - return result, err + return result, error(validationErrors) } // UPNode represent the user plane node @@ -521,6 +518,7 @@ type UPFConfig struct { } func (gNB *GNBConfig) Validate() (result bool, err error) { + // no semantic validation (yet) for GNBs return true, nil } @@ -529,15 +527,15 @@ func (gNB *GNBConfig) GetType() string { } func (upf *UPFConfig) Validate() (result bool, err error) { + var validationErrors govalidator.Errors result = true n3IfsNum := 0 n9IfsNum := 0 - for _, iface := range upf.Interfaces { if ok, errIface := iface.Validate(); !ok { result = false - err = appendInvalid(errIface) + validationErrors = append(validationErrors, errIface.(govalidator.Errors)...) } if iface.InterfaceType == "N3" { @@ -551,24 +549,26 @@ func (upf *UPFConfig) Validate() (result bool, err error) { if n3IfsNum == 0 && n9IfsNum == 0 { result = false - err = appendInvalid(fmt.Errorf("UPF %s must have a user plane interface (N3 or N9)", upf.NodeID)) + validationErrors = append(validationErrors, + fmt.Errorf("UPF %s must have a user plane interface (N3 or N9)", upf.NodeID)) } if n3IfsNum > 1 || n9IfsNum > 1 { result = false - err = appendInvalid(fmt.Errorf( - "UPF %s: There is currently no support for multiple N3/ N9 interfaces: N3 number(%d), N9 number(%d)", - upf.NodeID, n3IfsNum, n9IfsNum)) + validationErrors = append(validationErrors, + fmt.Errorf( + "UPF %s: There is currently no support for multiple N3/ N9 interfaces: N3 number(%d), N9 number(%d)", + upf.NodeID, n3IfsNum, n9IfsNum)) } for _, snssaiInfo := range upf.SNssaiInfos { if ok, errSNSSAI := snssaiInfo.Validate(); !ok { result = false - err = appendInvalid(errSNSSAI) + validationErrors = append(validationErrors, errSNSSAI.(govalidator.Errors)...) } } - return result, err + return result, error(validationErrors) } func (upf *UPFConfig) GetNodeID() string { @@ -586,14 +586,27 @@ type Interface struct { } func (i *Interface) Validate() (result bool, err error) { + var validationErrors govalidator.Errors result = true + + switch i.InterfaceType { + case "N3": + case "N9": + case "N6": + default: + result = false + validationErrors = append(validationErrors, + fmt.Errorf("Invalid interface type %s: must be N3 or N9.", i.InterfaceType)) + } for _, endpoint := range i.Endpoints { if ok := govalidator.IsHost(endpoint); !ok { result = false - err = appendInvalid(fmt.Errorf("Invalid endpoint: %s should be one of IPv4, IPv6, FQDN.", endpoint)) + validationErrors = append(validationErrors, + fmt.Errorf("Invalid endpoint: %s should be one of IPv4, IPv6, FQDN.", endpoint)) } } - return + + return result, error(validationErrors) } type SnssaiUpfInfoItem struct { @@ -602,20 +615,23 @@ type SnssaiUpfInfoItem struct { } func (s *SnssaiUpfInfoItem) Validate() (result bool, err error) { + var validationErrors govalidator.Errors result = true if s.SNssai != nil { if ok := (s.SNssai.Sst >= 0 && s.SNssai.Sst <= 255); !ok { result = false - err = appendInvalid(fmt.Errorf("Invalid sNssai.Sst: %s should be in range 0-255.", - strconv.Itoa(int(s.SNssai.Sst)))) + validationErrors = append(validationErrors, + fmt.Errorf("Invalid sNssai.Sst: %s should be in range 0-255.", + strconv.Itoa(int(s.SNssai.Sst)))) } if s.SNssai.Sd != "" { if ok := govalidator.StringMatches(s.SNssai.Sd, "^[0-9A-Fa-f]{6}$"); !ok { result = false - err = appendInvalid(fmt.Errorf( - "Invalid sNssai.Sd: %s should be 3 bytes hex string and in range 000000-FFFFFF.", s.SNssai.Sd)) + validationErrors = append(validationErrors, + fmt.Errorf("Invalid sNssai.Sd: %s should be 3 bytes hex string and in range 000000-FFFFFF.", + s.SNssai.Sd)) } } } @@ -623,11 +639,15 @@ func (s *SnssaiUpfInfoItem) Validate() (result bool, err error) { for _, dnnInfo := range s.DnnUpfInfoList { if ok, errDNNInfo := dnnInfo.Validate(); !ok { result = false - err = appendInvalid(errDNNInfo) + validationErrors = append(validationErrors, errDNNInfo.(govalidator.Errors)...) } } - return + if len(validationErrors) > 0 { + return result, error(validationErrors) + } + + return result, nil } type DnnUpfInfoItem struct { @@ -639,40 +659,77 @@ type DnnUpfInfoItem struct { } func (d *DnnUpfInfoItem) Validate() (result bool, err error) { + // collect all errors + var validationErrors govalidator.Errors result = true if len(d.Dnn) == 0 { result = false - err = appendInvalid(fmt.Errorf("Invalid DnnUpfInfoItem: dnn must not be empty.")) + validationErrors = append(validationErrors, + fmt.Errorf("Invalid DnnUpfInfoItem: dnn must not be empty.")) } if len(d.Pools) == 0 { result = false - err = appendInvalid(fmt.Errorf("Invalid DnnUpfInfoItem: requires at least one dynamic IP pool.")) + validationErrors = append(validationErrors, + fmt.Errorf("Invalid DnnUpfInfoItem: requires at least one dynamic IP pool.")) } var prefixes []netaddr.IPPrefix for _, pool := range d.Pools { - prefix, errCidrCheck := netaddr.ParseIPPrefix(pool.Cidr) - if errCidrCheck != nil { + // CIDR check + prefix, ok := netaddr.ParseIPPrefix(pool.Cidr) + if ok != nil { result = false - err = appendInvalid(fmt.Errorf("Invalid CIDR: %s.", pool.Cidr)) + validationErrors = append(validationErrors, fmt.Errorf("Invalid CIDR: %s.", pool.Cidr)) } else { prefixes = append(prefixes, prefix) } } - // check overlap with dynamic and static pools + // check overlap within dynamic pools for i := 0; i < len(prefixes); i++ { for j := i + 1; j < len(prefixes); j++ { if prefixes[i].Overlaps(prefixes[j]) { result = false - err = appendInvalid(fmt.Errorf("overlap detected between pools %s and %s", prefixes[i], prefixes[j])) + validationErrors = append(validationErrors, + fmt.Errorf("overlap detected between dynamic pools %s and %s", prefixes[i], prefixes[j])) } } } - return result, err + // check static pools CIDR and overlap with dynamic pools + var staticPrefixes []netaddr.IPPrefix + for _, staticPool := range d.StaticPools { + // CIDR check + staticPrefix, ok := netaddr.ParseIPPrefix(staticPool.Cidr) + if ok != nil { + result = false + validationErrors = append(validationErrors, fmt.Errorf("Invalid CIDR: %s.", staticPool.Cidr)) + } else { + staticPrefixes = append(staticPrefixes, staticPrefix) + for _, prefix := range prefixes { + if staticPrefix.Overlaps(prefix) { + result = false + validationErrors = append(validationErrors, + fmt.Errorf("overlap detected between static pool %s and dynamic pool %s", staticPrefix, prefix)) + } + } + } + } + + // check overlap within static pools + for i := 0; i < len(staticPrefixes); i++ { + for j := i + 1; j < len(staticPrefixes); j++ { + if staticPrefixes[i].Overlaps(staticPrefixes[j]) { + result = false + validationErrors = append(validationErrors, + fmt.Errorf("overlap detected between static pools %s and %s", staticPrefixes[i], staticPrefixes[j])) + } + } + } + + return result, error(validationErrors) } type UPLink struct { diff --git a/pkg/factory/config_test.go b/pkg/factory/config_test.go index a8c7215a..be2726a9 100644 --- a/pkg/factory/config_test.go +++ b/pkg/factory/config_test.go @@ -1,6 +1,7 @@ package factory_test import ( + "fmt" "testing" "github.com/stretchr/testify/require" @@ -55,84 +56,124 @@ func baseUPF() *factory.UPFConfig { } } -func TestUPNodeConfigInterfaceValidation(t *testing.T) { +func baseUPI() *factory.UserPlaneInformation { + return &factory.UserPlaneInformation{ + UPNodes: map[string]factory.UPNodeConfigInterface{ + "gNB": baseGNB(), + "UPF1": baseUPF(), + }, + Links: []*factory.UPLink{ + { + A: "gNB", + B: "UPF1", + }, + }, + } +} + +func TestUserplaneInformationValidation(t *testing.T) { testcase := []struct { - Name string - UPNodeConfig factory.UPNodeConfigInterface - Valid bool + Name string + Upi *factory.UserPlaneInformation + Valid bool }{ { - Name: "Valid gNB", - UPNodeConfig: baseGNB(), - Valid: true, + Name: "Valid userPlaneInformation", + Upi: baseUPI(), + Valid: true, }, { - Name: "Valid UPF", - UPNodeConfig: baseUPF(), - Valid: true, + Name: "gNB with wrong type", + Upi: func() *factory.UserPlaneInformation { + config := baseUPI() + config.UPNodes["gNB"].(*factory.GNBConfig).Type = "xxx" + return config + }(), + Valid: false, }, { Name: "UPF with wrong type", - UPNodeConfig: func() *factory.UPFConfig { - config := baseUPF() - config.UPNodeConfig.Type = "xxx" + Upi: func() *factory.UserPlaneInformation { + config := baseUPI() + config.UPNodes["UPF1"].(*factory.UPFConfig).Type = "xxx" return config }(), Valid: false, }, { Name: "UPF with wrong NodeID", - UPNodeConfig: func() *factory.UPFConfig { - config := baseUPF() - config.NodeID = "foobar" + Upi: func() *factory.UserPlaneInformation { + config := baseUPI() + config.UPNodes["UPF1"].(*factory.UPFConfig).NodeID = "127.0.0.1/24" return config }(), Valid: false, }, { Name: "UPF with nil sNssaiUpfInfos", - UPNodeConfig: func() *factory.UPFConfig { - config := baseUPF() - config.SNssaiInfos = nil + Upi: func() *factory.UserPlaneInformation { + config := baseUPI() + config.UPNodes["UPF1"].(*factory.UPFConfig).SNssaiInfos = nil return config }(), Valid: false, }, { Name: "UPF with empty sNssaiUpfInfos", - UPNodeConfig: func() *factory.UPFConfig { - config := baseUPF() - config.SNssaiInfos = []*factory.SnssaiUpfInfoItem{} + Upi: func() *factory.UserPlaneInformation { + config := baseUPI() + config.UPNodes["UPF1"].(*factory.UPFConfig).SNssaiInfos = []*factory.SnssaiUpfInfoItem{} return config }(), Valid: false, }, { - Name: "UPF with nil interfaces", - UPNodeConfig: func() *factory.UPFConfig { - config := baseUPF() - config.Interfaces = nil + Name: "UPF with invalid pool cidr", + Upi: func() *factory.UserPlaneInformation { + config := baseUPI() + config.UPNodes["UPF1"].(*factory.UPFConfig).SNssaiInfos[0].DnnUpfInfoList[0].Pools = []*factory.UEIPPool{ + { + Cidr: "10.60.0.0", + }, + } return config }(), Valid: false, }, { - Name: "UPF with empty interfaces", - UPNodeConfig: func() *factory.UPFConfig { - config := baseUPF() - config.Interfaces = []*factory.Interface{} + Name: "UPF with overlapping dynamic pools in DnnUpfInfoItem.Pools", + Upi: func() *factory.UserPlaneInformation { + config := baseUPI() + config.UPNodes["UPF1"].(*factory.UPFConfig).SNssaiInfos[0].DnnUpfInfoList = []*factory.DnnUpfInfoItem{ + { + Dnn: "internet", + Pools: []*factory.UEIPPool{ + { + Cidr: "10.60.0.0/16", + }, + { + Cidr: "10.60.10.0/16", + }, + }, + }, + } return config }(), Valid: false, }, { - Name: "UPF with overlapping pools in DnnUpfInfoItem.Pools", - UPNodeConfig: func() *factory.UPFConfig { - config := baseUPF() - config.SNssaiInfos[0].DnnUpfInfoList = []*factory.DnnUpfInfoItem{ + Name: "UPF with overlapping static pools in DnnUpfInfoItem.Pools", + Upi: func() *factory.UserPlaneInformation { + config := baseUPI() + config.UPNodes["UPF1"].(*factory.UPFConfig).SNssaiInfos[0].DnnUpfInfoList = []*factory.DnnUpfInfoItem{ { Dnn: "internet", Pools: []*factory.UEIPPool{ + { + Cidr: "10.80.0.0/16", + }, + }, + StaticPools: []*factory.UEIPPool{ { Cidr: "10.60.0.0/16", }, @@ -148,9 +189,9 @@ func TestUPNodeConfigInterfaceValidation(t *testing.T) { }, { Name: "UPF with overlapping pools in DnnUpfInfoItem.Pools and DnnUpfInfoItem.StaticPools", - UPNodeConfig: func() *factory.UPFConfig { - config := baseUPF() - config.SNssaiInfos[0].DnnUpfInfoList = []*factory.DnnUpfInfoItem{ + Upi: func() *factory.UserPlaneInformation { + config := baseUPI() + config.UPNodes["UPF1"].(*factory.UPFConfig).SNssaiInfos[0].DnnUpfInfoList = []*factory.DnnUpfInfoItem{ { Dnn: "internet", Pools: []*factory.UEIPPool{ @@ -170,61 +211,105 @@ func TestUPNodeConfigInterfaceValidation(t *testing.T) { Valid: false, }, { - Name: "UPF without N3 interface", - UPNodeConfig: func() *factory.UPFConfig { - config := baseUPF() - config.Interfaces = []*factory.Interface{} + Name: "UPF with nil interfaces", + Upi: func() *factory.UserPlaneInformation { + config := baseUPI() + config.UPNodes["UPF1"].(*factory.UPFConfig).Interfaces = nil return config }(), Valid: false, }, - } - - for _, tc := range testcase { - t.Run(tc.Name, func(t *testing.T) { - ok, err := tc.UPNodeConfig.Validate() - require.Equal(t, tc.Valid, ok) - require.Nil(t, err) - }) - } -} - -func TestUserplaneInformationValidation(t *testing.T) { - testcase := []struct { - Name string - Upi *factory.UserPlaneInformation - Valid bool - }{ { - Name: "Valid userPlaneInformation", - Upi: &factory.UserPlaneInformation{ - UPNodes: map[string]factory.UPNodeConfigInterface{ - "gNB": baseGNB(), - "UPF1": baseUPF(), - }, - Links: []*factory.UPLink{ + Name: "UPF with empty interfaces", + Upi: func() *factory.UserPlaneInformation { + config := baseUPI() + config.UPNodes["UPF1"].(*factory.UPFConfig).Interfaces = []*factory.Interface{} + return config + }(), + Valid: false, + }, + { + Name: "UPF with invalid interface", + Upi: func() *factory.UserPlaneInformation { + config := baseUPI() + config.UPNodes["UPF1"].(*factory.UPFConfig).Interfaces = []*factory.Interface{ { - A: "gNB", - B: "UPF1", + InterfaceType: "N3", + Endpoints: []string{ + "127.0.0.8", + }, }, - }, - }, + { + InterfaceType: "N4", + Endpoints: []string{ + "127.0.0.89", + }, + }, + } + return config + }(), + Valid: false, + }, + { + Name: "UPF with only N9 interface", + Upi: func() *factory.UserPlaneInformation { + config := baseUPI() + config.UPNodes["UPF1"].(*factory.UPFConfig).Interfaces = []*factory.Interface{ + { + InterfaceType: "N9", + Endpoints: []string{ + "127.0.0.8", + }, + NetworkInstances: []string{ + "internet", + }, + }, + } + return config + }(), Valid: true, }, + { + Name: "UPF with two N3 interfaces", + Upi: func() *factory.UserPlaneInformation { + config := baseUPI() + config.UPNodes["UPF1"].(*factory.UPFConfig).Interfaces = []*factory.Interface{ + { + InterfaceType: "N3", + Endpoints: []string{ + "127.0.0.8", + }, + NetworkInstances: []string{ + "internet", + }, + }, + { + InterfaceType: "N3", + Endpoints: []string{ + "127.0.0.88", + }, + NetworkInstances: []string{ + "internet", + }, + }, + } + return config + }(), + Valid: false, + }, { Name: "Link with non-existing node", - Upi: &factory.UserPlaneInformation{ - UPNodes: map[string]factory.UPNodeConfigInterface{ - "gNB": baseGNB(), - "UPF1": baseUPF(), - }, - Links: []*factory.UPLink{ + Upi: func() *factory.UserPlaneInformation { + config := baseUPI() + config.Links = []*factory.UPLink{ { A: "gNB", B: "UPF", }, - }, - }, + } + config.UPNodes["UPF1"].(*factory.UPFConfig).Interfaces = []*factory.Interface{} + return config + }(), Valid: false, }, } @@ -233,7 +318,12 @@ func TestUserplaneInformationValidation(t *testing.T) { t.Run(tc.Name, func(t *testing.T) { ok, err := tc.Upi.Validate() require.Equal(t, tc.Valid, ok) - require.Nil(t, err) + if !ok { + require.Error(t, err) + fmt.Println(err) + } else { + require.Nil(t, err) + } }) } } @@ -303,6 +393,11 @@ func TestSnssaiUpfInfoItem(t *testing.T) { }, DnnInfos: []*factory.DnnUpfInfoItem{ { + Pools: []*factory.UEIPPool{ + { + Cidr: "10.60.0.0/16", + }, + }, Dnn: "internet", }, }, @@ -314,6 +409,11 @@ func TestSnssaiUpfInfoItem(t *testing.T) { }, DnnInfos: []*factory.DnnUpfInfoItem{ { + Pools: []*factory.UEIPPool{ + { + Cidr: "10.60.0.0/16", + }, + }, Dnn: "internet2", }, }, @@ -328,6 +428,7 @@ func TestSnssaiUpfInfoItem(t *testing.T) { } ok, err := snssaiInfoItem.Validate() + fmt.Println("Error: ", err) require.True(t, ok) require.Nil(t, err) }) From b72f36df287fc9c60fb53d9f005dd0ab5fa7ddc2 Mon Sep 17 00:00:00 2001 From: Laura Henning Date: Mon, 7 Oct 2024 07:51:56 +0000 Subject: [PATCH 13/16] fix: linter --- pkg/factory/config.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/factory/config.go b/pkg/factory/config.go index bf183c91..7b3a6401 100644 --- a/pkg/factory/config.go +++ b/pkg/factory/config.go @@ -493,7 +493,11 @@ func (u *UserPlaneInformation) Validate() (result bool, err error) { } } - return result, error(validationErrors) + if len(validationErrors) > 0 { + return result, error(validationErrors) + } else { + return false, nil + } } // UPNode represent the user plane node From a9174ab60fdf1d02f038c9351dd8f4b3f1450406 Mon Sep 17 00:00:00 2001 From: Laura Henning Date: Mon, 7 Oct 2024 08:02:28 +0000 Subject: [PATCH 14/16] fix: typo in function return --- pkg/factory/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/factory/config.go b/pkg/factory/config.go index 7b3a6401..63c8ae0b 100644 --- a/pkg/factory/config.go +++ b/pkg/factory/config.go @@ -496,7 +496,7 @@ func (u *UserPlaneInformation) Validate() (result bool, err error) { if len(validationErrors) > 0 { return result, error(validationErrors) } else { - return false, nil + return true, nil } } From ad7f8cee64f9a313a1c4c5a422108fb6ca4ce39d Mon Sep 17 00:00:00 2001 From: Laura Henning Date: Mon, 7 Oct 2024 09:15:20 +0000 Subject: [PATCH 15/16] fix: correct in(string1|string2) validators --- pkg/factory/config.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/factory/config.go b/pkg/factory/config.go index 1ad61eb3..2856bfce 100644 --- a/pkg/factory/config.go +++ b/pkg/factory/config.go @@ -118,7 +118,7 @@ type Configuration struct { NrfCertPem string `yaml:"nrfCertPem,omitempty" valid:"optional"` UserPlaneInformation *UserPlaneInformation `yaml:"userplaneInformation" valid:"required"` // done - ServiceNameList []string `yaml:"serviceNameList" valid:"required,in(nsmf-pdusession,nsmf-event-exposure,nsmf-oam)"` + ServiceNameList []string `yaml:"serviceNameList" valid:"required,in(nsmf-pdusession|nsmf-event-exposure|nsmf-oam)"` // done SNssaiInfo []*SnssaiInfoItem `yaml:"snssaiInfos" valid:"required"` ULCL bool `yaml:"ulcl" valid:"type(bool),optional"` @@ -267,7 +267,7 @@ type UserPlaneInformation struct { // UPNode represent the user plane node type UPNodeConfig struct { - Type string `json:"type" yaml:"type" valid:"required,in(AN,UPF)"` + Type string `json:"type" yaml:"type" valid:"required,in(AN|UPF)"` } type UPNodeConfigInterface interface { @@ -330,7 +330,7 @@ func (upf *UPFConfig) GetType() string { } type Interface struct { - InterfaceType models.UpInterfaceType `json:"interfaceType" yaml:"interfaceType" valid:"required,in(N3,N9)"` + InterfaceType models.UpInterfaceType `json:"interfaceType" yaml:"interfaceType" valid:"required,in(N3|N9)"` Endpoints []string `json:"endpoints" yaml:"endpoints" valid:"host,required"` NetworkInstances []string `json:"networkInstances" yaml:"networkInstances" valid:"optional"` } @@ -343,7 +343,7 @@ type SnssaiUpfInfoItem struct { type DnnUpfInfoItem struct { Dnn string `json:"dnn" yaml:"dnn" valid:"minstringlength(1),required"` DnaiList []string `json:"dnaiList" yaml:"dnaiList" valid:"optional"` - PduSessionTypes []models.PduSessionType `json:"pduSessionTypes" yaml:"pduSessionTypes" valid:"optional,in(IPv4,IPv6,IPV4V6,UNSTRUCTURED,ETHERNET)"` + PduSessionTypes []models.PduSessionType `json:"pduSessionTypes" yaml:"pduSessionTypes" valid:"optional,in(IPv4|IPv6|IPV4V6|UNSTRUCTURED|ETHERNET)"` Pools []*UEIPPool `json:"pools" yaml:"pools" valid:"optional,poolValidator"` StaticPools []*UEIPPool `json:"staticPools" yaml:"staticPools" valid:"optional,poolValidator"` } From 91d0907a952eeec2187b4f6246595afff47b43f2 Mon Sep 17 00:00:00 2001 From: Laura Henning Date: Tue, 8 Oct 2024 12:19:23 +0000 Subject: [PATCH 16/16] refactor: ensure all fields and nested UserPlaneInformation structs with pointers are correctly validated --- pkg/factory/config.go | 249 ++++++++++++++++++++++++------------- pkg/factory/config_test.go | 113 ++++++++++++++++- 2 files changed, 271 insertions(+), 91 deletions(-) diff --git a/pkg/factory/config.go b/pkg/factory/config.go index 2856bfce..d3fbe346 100644 --- a/pkg/factory/config.go +++ b/pkg/factory/config.go @@ -41,7 +41,7 @@ const ( ) type Config struct { - Info *Info `yaml:"info" valid:"required,configValidator"` + Info *Info `yaml:"info" valid:"required"` Configuration *Configuration `yaml:"configuration" valid:"required"` Logger *Logger `yaml:"logger" valid:"required"` sync.RWMutex @@ -54,41 +54,11 @@ func (c *Config) Validate() (bool, error) { }) // register custom semantic validators - govalidator.CustomTypeTagMap.Set("snssaiValidator", func(i interface{}, context interface{}) bool { - // context == struct this field is in - // i == the validated field - sNssai := i.(models.Snssai) - ok, _ := ValidateSNssai(&sNssai) - return ok - }) - - govalidator.CustomTypeTagMap.Set("upNodeValidator", func(i interface{}, context interface{}) bool { - upNode := i.(UPNodeConfigInterface) - ok, _ := upNode.Validate() - return ok - }) - - govalidator.CustomTypeTagMap.Set("poolValidator", func(i interface{}, context interface{}) bool { - dnnUpfInfoItem := context.(*DnnUpfInfoItem) - ok, _ := ValidateUEIPPools(dnnUpfInfoItem.Pools, dnnUpfInfoItem.StaticPools) - return ok - }) - - govalidator.CustomTypeTagMap.Set("linkValidator", func(i interface{}, context interface{}) bool { - link := i.(UPLink) - var upNodeNames []string - for name, _ := range context.(UserPlaneInformation).UPNodes { - upNodeNames = append(upNodeNames, name) - } - ok, _ := ValidateLink(&link, upNodeNames) - return ok - }) - - govalidator.CustomTypeTagMap.Set("pathValidator", func(i interface{}, context interface{}) bool { - path := context.([]string) - ok, _ := ValidatePath(path) - return ok - }) + RegisterSNssaiValidator() + RegisterUPNodeValidator() + RegisterLinkValidator() + RegisterPoolValidator() + RegisterDnnUpfInfoItemValidator() result, err := govalidator.ValidateStruct(c) return result, appendInvalid(err) @@ -296,14 +266,26 @@ func (gNB *GNBConfig) GetType() string { } func (upf *UPFConfig) Validate() (result bool, err error) { + // direct call for nested struct validation necessary for slices containing pointers + // (too much nesting for govalidator) + for _, iface := range upf.Interfaces { + if result, err = govalidator.ValidateStruct(iface); !result { + return result, err + } + } + for _, sNssaiInfo := range upf.SNssaiInfos { + if result, err = govalidator.ValidateStruct(sNssaiInfo); !result { + return result, err + } + } + n3IfsNum := 0 n9IfsNum := 0 for _, iface := range upf.Interfaces { - if iface.InterfaceType == "N3" { + switch iface.InterfaceType { + case "N3": n3IfsNum++ - } - - if iface.InterfaceType == "N9" { + case "N9": n9IfsNum++ } } @@ -337,7 +319,7 @@ type Interface struct { type SnssaiUpfInfoItem struct { SNssai *models.Snssai `json:"sNssai" yaml:"sNssai" valid:"snssaiValidator,required"` - DnnUpfInfoList []*DnnUpfInfoItem `json:"dnnUpfInfoList" yaml:"dnnUpfInfoList" valid:"required"` + DnnUpfInfoList []*DnnUpfInfoItem `json:"dnnUpfInfoList" yaml:"dnnUpfInfoList" valid:"required,dnnUpfInfoItemValidator"` } type DnnUpfInfoItem struct { @@ -348,50 +330,36 @@ type DnnUpfInfoItem struct { StaticPools []*UEIPPool `json:"staticPools" yaml:"staticPools" valid:"optional,poolValidator"` } -func ValidateUEIPPools(dynamic []*UEIPPool, static []*UEIPPool) (bool, error) { +func ValidateUEIPPool(poolToCheck *UEIPPool, dynamic []*UEIPPool, static []*UEIPPool) (bool, error) { + prefixToCheck, err := netaddr.ParseIPPrefix(poolToCheck.Cidr) + if err != nil { + return false, fmt.Errorf("Invalid pool CIDR: %s.", poolToCheck.Cidr) + } + var prefixes []netaddr.IPPrefix for _, pool := range dynamic { - // CIDR check + if poolToCheck == pool { + continue + } prefix, ok := netaddr.ParseIPPrefix(pool.Cidr) if ok != nil { - return false, fmt.Errorf("Invalid pool CIDR: %s.", pool.Cidr) - } else { - prefixes = append(prefixes, prefix) + return false, fmt.Errorf("Invalid pool CIDR: %s.", poolToCheck.Cidr) } + prefixes = append(prefixes, prefix) } - // check overlap within dynamic pools - for i := 0; i < len(prefixes); i++ { - for j := i + 1; j < len(prefixes); j++ { - if prefixes[i].Overlaps(prefixes[j]) { - return false, fmt.Errorf("overlap detected between dynamic pools %s and %s", prefixes[i], prefixes[j]) - } - } - } - - // check static pools CIDR and overlap with dynamic pools - var staticPrefixes []netaddr.IPPrefix for _, staticPool := range static { - // CIDR check staticPrefix, ok := netaddr.ParseIPPrefix(staticPool.Cidr) if ok != nil { - return false, fmt.Errorf("Invalid CIDR: %s.", staticPool.Cidr) - } else { - staticPrefixes = append(staticPrefixes, staticPrefix) - for _, prefix := range prefixes { - if staticPrefix.Overlaps(prefix) { - return false, fmt.Errorf("overlap detected between static pool %s and dynamic pool %s", staticPrefix, prefix) - } - } + return false, fmt.Errorf("Invalid pool CIDR: %s.", poolToCheck.Cidr) } + prefixes = append(prefixes, staticPrefix) } - // check overlap within static pools - for i := 0; i < len(staticPrefixes); i++ { - for j := i + 1; j < len(staticPrefixes); j++ { - if staticPrefixes[i].Overlaps(staticPrefixes[j]) { - return false, fmt.Errorf("overlap detected between static pools %s and %s", staticPrefixes[i], staticPrefixes[j]) - } + // check overlap within other pools + for i := 0; i < len(prefixes); i++ { + if prefixToCheck.Overlaps(prefixes[i]) { + return false, fmt.Errorf("overlap detected between pools %s and %s", prefixToCheck, prefixes[i]) } } @@ -404,31 +372,20 @@ type UPLink struct { } func ValidateLink(link *UPLink, upNodeNames []string) (bool, error) { - if ok := !slices.Contains(upNodeNames, link.A) || !slices.Contains(upNodeNames, link.B); !ok { - return false, fmt.Errorf("Link %s--%s contains unknown node name", link.A, link.B) + if ok := slices.Contains(upNodeNames, link.A) && slices.Contains(upNodeNames, link.B); !ok { + return false, fmt.Errorf("Link %s -- %s contains unknown node name", link.A, link.B) } return true, nil } type UEIPPool struct { - Cidr string `yaml:"cidr" valid:"cidr,required"` + Cidr string `yaml:"cidr" valid:"required"` } type SpecificPath struct { DestinationIP string `yaml:"dest,omitempty" valid:"cidr,required"` DestinationPort string `yaml:"DestinationPort,omitempty" valid:"port,optional"` - Path []string `yaml:"path" valid:"pathValidator,required"` -} - -func ValidatePath(path []string) (bool, error) { - for _, upf := range path { - if result := len(upf); result == 0 { - err := errors.New("Invalid UPF: " + upf + ", should not be empty") - return false, err - } - } - - return true, nil + Path []string `yaml:"path" valid:"required,"` } type PlmnID struct { @@ -571,3 +528,121 @@ func appendInvalid(err error) error { return error(errs) } + +// functions to make validators testable +func RegisterSchemeValidator() { + govalidator.TagMap["scheme"] = govalidator.Validator(func(str string) bool { + return str == "https" || str == "http" + }) +} + +func RegisterSNssaiValidator() { + govalidator.CustomTypeTagMap.Set("snssaiValidator", func(i interface{}, context interface{}) bool { + valid := true + switch sNssai := i.(type) { + case *models.Snssai: + if ok, err := ValidateSNssai(sNssai); !ok { + fmt.Println(err) + valid = false + } + default: + fmt.Printf("Cannot use snssaiValidator on field of type %T", sNssai) + valid = false + } + return valid + }) +} + +func RegisterUPNodeValidator() { + govalidator.CustomTypeTagMap.Set("upNodeValidator", func(i interface{}, context interface{}) bool { + valid := true + switch upNodes := i.(type) { + case map[string]UPNodeConfigInterface: + for _, upNode := range upNodes { + if ok, err := upNode.Validate(); !ok { + fmt.Println(err) + valid = false + } + } + default: + fmt.Printf("Cannot use upNodeValidator on field of type %T", upNodes) + valid = false + } + return valid + }) +} + +func RegisterLinkValidator() { + govalidator.CustomTypeTagMap.Set("linkValidator", func(i interface{}, context interface{}) bool { + var upNodeNames []string + switch contextType := context.(type) { + case UserPlaneInformation: + for name := range contextType.UPNodes { + upNodeNames = append(upNodeNames, name) + } + default: + fmt.Printf("linkValidator can only be used on UserPlaneInformation links, got %T", context) + return false + } + + valid := true + switch links := i.(type) { + case []*UPLink: + for _, link := range links { + if ok, err := ValidateLink(link, upNodeNames); !ok { + fmt.Println(err) + valid = false + } + } + default: + fmt.Printf("Cannot use linkValidator on field of type %T", links) + valid = false + } + + return valid + }) +} + +func RegisterPoolValidator() { + govalidator.CustomTypeTagMap.Set("poolValidator", func(i interface{}, context interface{}) bool { + dnnUpfInfoItem, isType := context.(DnnUpfInfoItem) + if !isType { + fmt.Printf("poolValidator can only be used on DnnUpfInfoItem UE IP pools, got %T", context) + return false + } + + valid := true + switch pools := i.(type) { + case []*UEIPPool: + for _, pool := range pools { + if ok, err := ValidateUEIPPool(pool, dnnUpfInfoItem.Pools, dnnUpfInfoItem.StaticPools); !ok { + fmt.Println(err) + valid = false + } + } + default: + fmt.Printf("Cannot use poolValidator on field of type %T", pools) + valid = false + } + return valid + }) +} + +func RegisterDnnUpfInfoItemValidator() { + govalidator.CustomTypeTagMap.Set("dnnUpfInfoItemValidator", func(i interface{}, context interface{}) bool { + valid := true + switch dnnUpfInfoItems := i.(type) { + case []*DnnUpfInfoItem: + for _, dnnUpfInfoItem := range dnnUpfInfoItems { + if ok, err := govalidator.ValidateStruct(dnnUpfInfoItem); !ok { + fmt.Println(err) + valid = false + } + } + default: + fmt.Printf("Cannot use dnnUpfInfoItemValidator on field of type %T", dnnUpfInfoItems) + valid = false + } + return valid + }) +} diff --git a/pkg/factory/config_test.go b/pkg/factory/config_test.go index 3ca146fc..0243f5f0 100644 --- a/pkg/factory/config_test.go +++ b/pkg/factory/config_test.go @@ -308,7 +308,17 @@ func TestUserplaneInformationValidation(t *testing.T) { B: "UPF", }, } - config.UPNodes["UPF1"].(*factory.UPFConfig).Interfaces = []*factory.Interface{} + return config + }(), + Valid: false, + }, + { + Name: "Invalid SnssaiUpfInfoItem", + Upi: func() *factory.UserPlaneInformation { + config := baseUPI() + config.UPNodes["UPF1"].(*factory.UPFConfig).SNssaiInfos[0].SNssai = &models.Snssai{ + Sst: int32(256), + } return config }(), Valid: false, @@ -317,11 +327,90 @@ func TestUserplaneInformationValidation(t *testing.T) { for _, tc := range testcase { t.Run(tc.Name, func(t *testing.T) { + // register custom semantic validators + factory.RegisterSNssaiValidator() + factory.RegisterUPNodeValidator() + factory.RegisterLinkValidator() + factory.RegisterPoolValidator() + factory.RegisterDnnUpfInfoItemValidator() + ok, err := govalidator.ValidateStruct(tc.Upi) + if err != nil { + errs := err.(govalidator.Errors).Errors() + for _, e := range errs { + fmt.Println(e.Error()) + } + } + require.Equal(t, tc.Valid, ok) + if !ok { + require.Error(t, err) + } else { + require.Nil(t, err) + } + }) + } +} + +func TestSnssaiValidator_SnssaiInfoItem(t *testing.T) { + testcase := []struct { + Name string + Snssai *models.Snssai + Valid bool + }{ + { + Name: "Valid SNssai with SST and SD", + Snssai: &models.Snssai{ + Sst: int32(1), + Sd: "010203", + }, + Valid: true, + }, + { + Name: "Valid SNssai without SD", + Snssai: &models.Snssai{ + Sst: int32(1), + }, + Valid: true, + }, + { + Name: "Invalid SNssai: invalid SST", + Snssai: &models.Snssai{ + Sst: int32(256), + }, + Valid: false, + }, + { + Name: "Invalid SNssai: invalid SD", + Snssai: &models.Snssai{ + Sst: int32(1), + Sd: "32", + }, + Valid: false, + }, + } + + for _, tc := range testcase { + t.Run(tc.Name, func(t *testing.T) { + factory.RegisterSNssaiValidator() + snssaiInfoItem := factory.SnssaiInfoItem{ + SNssai: tc.Snssai, + DnnInfos: []*factory.SnssaiDnnInfoItem{ + { + Dnn: "internet", + DNS: &factory.DNS{}, + }, + }, + } + ok, err := govalidator.ValidateStruct(snssaiInfoItem) + // if err != nil { + // errs := err.(govalidator.Errors).Errors() + // for _, e := range errs { + // fmt.Println(e.Error()) + // } + // } require.Equal(t, tc.Valid, ok) if !ok { require.Error(t, err) - fmt.Println(err) } else { require.Nil(t, err) } @@ -329,7 +418,7 @@ func TestUserplaneInformationValidation(t *testing.T) { } } -func TestSnssaiValidator(t *testing.T) { +func TestSnssaiValidator_SnssaiUpfInfoItem(t *testing.T) { testcase := []struct { Name string Snssai *models.Snssai @@ -369,7 +458,23 @@ func TestSnssaiValidator(t *testing.T) { for _, tc := range testcase { t.Run(tc.Name, func(t *testing.T) { - ok, err := factory.ValidateSNssai(tc.Snssai) + factory.RegisterDnnUpfInfoItemValidator() + factory.RegisterSNssaiValidator() + snssaiUpfInfoItem := factory.SnssaiUpfInfoItem{ + SNssai: tc.Snssai, + DnnUpfInfoList: []*factory.DnnUpfInfoItem{ + { + Dnn: "internet", + }, + }, + } + ok, err := govalidator.ValidateStruct(snssaiUpfInfoItem) + if err != nil { + errs := err.(govalidator.Errors).Errors() + for _, e := range errs { + fmt.Println(e.Error()) + } + } require.Equal(t, tc.Valid, ok) if !ok { require.Error(t, err)