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/context.go b/internal/context/context.go index 503ee834..1c97d6eb 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 { @@ -239,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/datapath.go b/internal/context/datapath.go index 66780904..820f8be9 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.DownLinkTunnel.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/gnb.go b/internal/context/gnb.go new file mode 100644 index 00000000..cb278223 --- /dev/null +++ b/internal/context/gnb.go @@ -0,0 +1,71 @@ +package context + +import ( + "fmt" + + "github.com/google/uuid" + + "github.com/free5gc/smf/internal/logger" +) + +// embeds the UPNode struct ("inheritance") +// implements UPNodeInterface +type GNB struct { + *UPNode +} + +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) String() string { + str := "gNB {\n" + prefix := " " + str += prefix + fmt.Sprintf("Name: %s\n", gNB.Name) + str += prefix + fmt.Sprintf("ID: %s\n", gNB.ID) + str += prefix + fmt.Sprintln("Links:") + for _, link := range gNB.Links { + str += prefix + fmt.Sprintf("-- %s: %s\n", link.GetName(), link.GetName()) + } + str += "}" + return str +} + +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.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 + } + } + 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/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..17ae944c 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 @@ -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.NodeID.ResolveNodeIdToIp().String() +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.NodeID, + NodeID: upf.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 58225916..1db64cb8 100644 --- a/internal/context/sm_context_policy_test.go +++ b/internal/context/sm_context_policy_test.go @@ -11,15 +11,18 @@ import ( "github.com/free5gc/smf/pkg/factory" ) -var userPlaneConfig = factory.UserPlaneInformation{ - UPNodes: map[string]*factory.UPNode{ - "GNodeB": { - Type: "AN", +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", - Addr: "10.4.0.11", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { SNssai: &models.Snssai{ @@ -34,7 +37,7 @@ var userPlaneConfig = factory.UserPlaneInformation{ }, }, }, - InterfaceUpfInfoList: []*factory.InterfaceUpfInfoItem{ + Interfaces: []*factory.Interface{ { InterfaceType: "N3", Endpoints: []string{ @@ -51,10 +54,11 @@ var userPlaneConfig = factory.UserPlaneInformation{ }, }, }, - "UPF2": { - Type: "UPF", + "UPF2": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "10.4.0.12", - Addr: "10.4.0.12", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { SNssai: &models.Snssai{ @@ -71,7 +75,7 @@ var userPlaneConfig = factory.UserPlaneInformation{ }, }, }, - InterfaceUpfInfoList: []*factory.InterfaceUpfInfoItem{ + Interfaces: []*factory.Interface{ { InterfaceType: "N9", Endpoints: []string{ @@ -97,7 +101,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{ @@ -621,9 +625,9 @@ func TestApplyPccRules(t *testing.T) { } smfContext := smf_context.GetSelf() - smfContext.UserPlaneInformation = smf_context.NewUserPlaneInformation(&userPlaneConfig) - for _, n := range smfContext.UserPlaneInformation.UPFs { - n.UPF.AssociationContext = context.Background() + smfContext.UserPlaneInformation = smf_context.NewUserPlaneInformation(userPlaneConfig) + for _, upf := range smfContext.UserPlaneInformation.UPFs { + upf.AssociationContext = context.Background() } smctx := smf_context.NewSMContext("imsi-208930000000002", 10) diff --git a/internal/context/ue_datapath.go b/internal/context/ue_datapath.go index c8f918f5..c21edf6b 100644 --- a/internal/context/ue_datapath.go +++ b/internal/context/ue_datapath.go @@ -15,17 +15,20 @@ type UEPreConfigPaths struct { } func NewUEDataPathNode(name string) (node *DataPathNode, err error) { - upNodes := smfContext.UserPlaneInformation.UPNodes - - if _, exist := 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 - } + } 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: upNodes[name].UPF, - UpLinkTunnel: >PTunnel{}, - DownLinkTunnel: >PTunnel{}, + node = &DataPathNode{ + UPF: upNode.(*UPF), + UpLinkTunnel: >PTunnel{}, + DownLinkTunnel: >PTunnel{}, + } } return } diff --git a/internal/context/upf.go b/internal/context/upf.go index 0de70d74..32e1f7f8 100644 --- a/internal/context/upf.go +++ b/internal/context/upf.go @@ -65,9 +65,10 @@ const ( ) type UPF struct { - uuid uuid.UUID - NodeID pfcpType.NodeID - Addr string + *UPNode + + NodeID pfcpType.NodeID + UPIPInfo pfcpType.UserPlaneIPResourceInformation RecoveryTimeStamp time.Time @@ -91,6 +92,91 @@ 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.Sprintln("Links:") + for _, link := range upf.Links { + str += prefix + fmt.Sprintf("-- %s: %s\n", link.GetName(), link.GetName()) + } + 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 fmt.Sprintf("%s[%s]", upf.Name, upf.GetNodeIDString()) +} + +func (upf *UPF) GetID() uuid.UUID { + return upf.ID +} + +func (upf *UPF) GetType() UPNodeType { + return upf.Type +} + +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.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 + } + } + 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 @@ -107,6 +193,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 { @@ -116,7 +210,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) @@ -177,37 +271,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), @@ -236,11 +316,15 @@ 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, + nodeID *pfcpType.NodeID, + ifaces []*factory.Interface, +) (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.AssociationContext, upf.CancelAssociation = context.WithCancel(context.Background()) @@ -366,17 +450,6 @@ 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() (pdrID uint16, err error) { if err = upf.IsAssociated(); err != nil { return @@ -532,11 +605,11 @@ func (upf *UPF) AddURR(urrID uint32, opts ...UrrOpt) (urr *URR, err error) { } upf.urrPool.Store(urr.URRID, urr) - return + return urr, nil } func (upf *UPF) GetUUID() uuid.UUID { - return upf.uuid + return upf.ID } func (upf *UPF) GetQERById(qerId uint32) *QER { @@ -603,7 +676,7 @@ 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 @@ -619,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/context/upf_test.go b/internal/context/upf_test.go index 75c08d73..56429216 100644 --- a/internal/context/upf_test.go +++ b/internal/context/upf_test.go @@ -6,6 +6,7 @@ import ( "net" "testing" + "github.com/google/uuid" . "github.com/smartystreets/goconvey/convey" "github.com/free5gc/nas/nasMessage" @@ -14,12 +15,18 @@ import ( "github.com/free5gc/smf/pkg/factory" ) -var mockIPv4NodeID = &pfcpType.NodeID{ +var mockUPNode = &smf_context.UPNode{ + Name: "UPF1", + Type: smf_context.UPNODE_UPF, + ID: uuid.New(), +} + +var mockNodeID = &pfcpType.NodeID{ NodeIdType: pfcpType.NodeIdTypeIpv4Address, IP: net.ParseIP("127.0.0.1"), } -var mockIfaces = []*factory.InterfaceUpfInfoItem{ +var mockIfaces = []*factory.Interface{ { InterfaceType: "N3", Endpoints: []string{"127.0.0.1"}, @@ -144,12 +151,12 @@ func TestAddPDR(t *testing.T) { expectedError error }{ { - upf: smf_context.NewUPF(mockIPv4NodeID, mockIfaces), + upf: smf_context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddPDR should success", expectedError: nil, }, { - upf: smf_context.NewUPF(mockIPv4NodeID, mockIfaces), + upf: smf_context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddPDR should fail", expectedError: fmt.Errorf("UPF[127.0.0.1] not associated with SMF"), }, @@ -187,12 +194,12 @@ func TestAddFAR(t *testing.T) { expectedError error }{ { - upf: smf_context.NewUPF(mockIPv4NodeID, mockIfaces), + upf: smf_context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddFAR should success", expectedError: nil, }, { - upf: smf_context.NewUPF(mockIPv4NodeID, mockIfaces), + upf: smf_context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddFAR should fail", expectedError: fmt.Errorf("UPF[127.0.0.1] not associated with SMF"), }, @@ -230,12 +237,12 @@ func TestAddQER(t *testing.T) { expectedError error }{ { - upf: smf_context.NewUPF(mockIPv4NodeID, mockIfaces), + upf: smf_context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddQER should success", expectedError: nil, }, { - upf: smf_context.NewUPF(mockIPv4NodeID, mockIfaces), + upf: smf_context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddQER should fail", expectedError: fmt.Errorf("UPF[127.0.0.1] not associated with SMF"), }, @@ -273,12 +280,12 @@ func TestAddBAR(t *testing.T) { expectedError error }{ { - upf: smf_context.NewUPF(mockIPv4NodeID, mockIfaces), + upf: smf_context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddBAR should success", expectedError: nil, }, { - upf: smf_context.NewUPF(mockIPv4NodeID, mockIfaces), + upf: smf_context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddBAR should fail", expectedError: fmt.Errorf("UPF[127.0.0.1] not associated with SMF"), }, diff --git a/internal/context/user_plane_information.go b/internal/context/user_plane_information.go index 1978ba10..260d3e7b 100644 --- a/internal/context/user_plane_information.go +++ b/internal/context/user_plane_information.go @@ -2,12 +2,15 @@ package context import ( "errors" + "fmt" "math/rand" "net" "reflect" "sort" "sync" + "github.com/google/uuid" + "github.com/free5gc/openapi/models" "github.com/free5gc/pfcp/pfcpType" "github.com/free5gc/smf/internal/logger" @@ -17,14 +20,14 @@ import ( // 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,100 +37,152 @@ 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 - NodeID pfcpType.NodeID - ANIP net.IP - Dnn string - Links []*UPNode - UPF *UPF + Name string + Type UPNodeType + ID uuid.UUID + 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 + GetLinks() UPPath + AddLink(link UPNodeInterface) bool + RemoveLink(link UPNodeInterface) bool + RemoveLinkByIndex(index int) bool +} + +// 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 false + return -1 } -// UPPath represent User Plane Sequence of this path -type UPPath []*UPNode +// 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 "" + } +} 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, 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) + } + + 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 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.GetType()), + ID: uuid.New(), + } 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() + gNB := &GNB{ + UPNode: upNode, } - - 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, - } + anPool[name] = gNB + nodePool[name] = gNB + case UPNODE_UPF: + 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) - upNode.UPF = NewUPF(&upNode.NodeID, node.InterfaceUpfInfoList) - upNode.UPF.Addr = node.Addr snssaiInfos := make([]*SnssaiUPFInfo, 0) - for _, snssaiInfoConfig := range node.SNssaiInfos { + for _, snssaiInfoConfig := range upfConfig.SNssaiInfos { snssaiInfo := SnssaiUPFInfo{ SNssai: &SNssai{ Sst: snssaiInfoConfig.SNssai.Sst, @@ -156,7 +211,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) } @@ -166,11 +221,13 @@ 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) + 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 { - 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.Debugf("%d-%s %s %s", @@ -187,72 +244,59 @@ 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) +func (upi *UserPlaneInformation) UpNodesToConfiguration() map[string]factory.UPNodeConfigInterface { + nodes := make(map[string]factory.UPNodeConfigInterface) for name, upNode := range upi.UPNodes { - u := new(factory.UPNode) - switch upNode.Type { - case UPNODE_UPF: - u.Type = "UPF" + switch upNode.GetType() { 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() - } - if upNode.UPF != nil { - if upNode.UPF.SNssaiInfos != nil { + gNBConfig := &factory.GNBConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "AN", + }, + } + nodes[name] = gNBConfig + case UPNODE_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 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 +326,10 @@ func (upi *UserPlaneInformation) UpNodesToConfiguration() map[string]*factory.UP } FsNssaiInfoList = append(FsNssaiInfoList, Fsnssai) } // for sNssaiInfo - u.SNssaiInfos = FsNssaiInfoList + upfConfig.SNssaiInfos = FsNssaiInfoList } // if UPF.SNssaiInfos - FNxList := make([]*factory.InterfaceUpfInfoItem, 0) - for _, iface := range upNode.UPF.N3Interfaces { + FNxList := make([]*factory.Interface, 0) + for _, iface := range upf.N3Interfaces { endpoints := make([]string, 0) // upf.go L90 if iface.EndpointFQDN != "" { @@ -294,14 +338,14 @@ 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, }) } // for N3Interfaces - for _, iface := range upNode.UPF.N9Interfaces { + for _, iface := range upf.N9Interfaces { endpoints := make([]string, 0) // upf.go L90 if iface.EndpointFQDN != "" { @@ -310,15 +354,16 @@ 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 - u.InterfaceUpfInfoList = FNxList + upfConfig.Interfaces = FNxList + default: + logger.InitLog.Fatalf("invalid UPNodeType: %s\n", upNode.GetType()) } - nodes[name] = u } return nodes @@ -330,18 +375,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.(*UPF).GetNodeIDString() + ipStr := link.(*UPF).GetNodeIDString() linkA := upi.UPFIPToName[nodeIpStr] linkB := upi.UPFIPToName[ipStr] links = append(links, &factory.UPLink{ @@ -359,46 +404,38 @@ 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.GetType()), + ID: uuid.New(), + } 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, } - 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 + + case UPNODE_UPF: + upfConfig := node.(*factory.UPFConfig) + nodeID, err := ConfigToNodeID(upfConfig.GetNodeID()) + if err != nil { + logger.InitLog.Fatalf("[UpNodesFromConfiguration] cannot parse NodeID from config: %+v", err) } - upNode.UPF = NewUPF(&upNode.NodeID, node.InterfaceUpfInfoList) + 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, @@ -426,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) } @@ -444,37 +481,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 +515,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 +531,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.(*UPF).GetNodeID()) if _, ok = upi.UPFs[upNodeName]; ok { logger.InitLog.Tracef("Delete UPF [%s] from upi.UPFs.\n", upNodeName) delete(upi.UPFs, upNodeName) @@ -517,7 +547,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 +556,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 +573,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 +590,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 +615,15 @@ func (upi *UserPlaneInformation) GetDefaultUserPlanePathByDNN(selection *UPFSele return nil } -func (upi *UserPlaneInformation) GetDefaultUserPlanePathByDNNAndUPF(selection *UPFSelectionParams, - upf *UPNode, -) (path UPPath) { - nodeID := upf.NodeID.ResolveNodeIdToIp().String() +func (upi *UserPlaneInformation) GetDefaultUserPlanePathByDNNAndUPF( + selection *UPFSelectionParams, + upf *UPF, +) UPPath { + 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 +652,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 +676,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 +691,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 +726,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 +742,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,56 +751,60 @@ 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 if currentSnssai.Equal(targetSnssai) { for _, dnnInfo := range snssaiInfo.DnnList { if dnnInfo.Dnn == selection.Dnn && dnnInfo.ContainsDNAI(selection.Dnai) { - 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.GetName(), dest.GetName(), dest.GetName()) 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 } @@ -776,7 +812,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...) @@ -789,11 +825,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, @@ -805,9 +841,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 @@ -816,8 +852,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)) } } @@ -825,19 +861,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) } } @@ -845,7 +881,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 { @@ -856,24 +892,24 @@ 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 err = upf.UPF.IsAssociated(); err != nil { + if err = upf.IsAssociated(); err != nil { logger.CtxLog.Infoln(err) continue } @@ -888,7 +924,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 @@ -903,7 +939,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]...) } @@ -914,8 +950,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 @@ -955,19 +991,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 2a07c983..08940c10 100644 --- a/internal/context/user_plane_information_test.go +++ b/internal/context/user_plane_information_test.go @@ -6,21 +6,26 @@ import ( "net" "testing" + . "github.com/smartystreets/goconvey/convey" "github.com/stretchr/testify/require" "github.com/free5gc/openapi/models" + "github.com/free5gc/pfcp/pfcpType" smf_context "github.com/free5gc/smf/internal/context" "github.com/free5gc/smf/pkg/factory" ) 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{ { @@ -62,8 +67,10 @@ var configuration = &factory.UserPlaneInformation{ }, }, }, - "UPF2": { - Type: "UPF", + "UPF2": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.2", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -84,8 +91,10 @@ var configuration = &factory.UserPlaneInformation{ }, }, }, - "UPF3": { - Type: "UPF", + "UPF3": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.3", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -106,8 +115,10 @@ var configuration = &factory.UserPlaneInformation{ }, }, }, - "UPF4": { - Type: "UPF", + "UPF4": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.4", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -270,7 +281,7 @@ func TestSelectUPFAndAllocUEIP(t *testing.T) { userplaneInformation := smf_context.NewUserPlaneInformation(configuration) for _, upf := range userplaneInformation.UPFs { - upf.UPF.AssociationContext = context.Background() + upf.AssociationContext = context.Background() } for i := 0; i <= 100; i++ { @@ -288,13 +299,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{ { @@ -320,8 +334,10 @@ var configForIPPoolAllocate = &factory.UserPlaneInformation{ }, }, }, - "UPF2": { - Type: "UPF", + "UPF2": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.2", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -347,8 +363,10 @@ var configForIPPoolAllocate = &factory.UserPlaneInformation{ }, }, }, - "UPF3": { - Type: "UPF", + "UPF3": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.3", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -487,7 +505,7 @@ var testCasesOfGetUEIPPool = []struct { func TestGetUEIPPool(t *testing.T) { userplaneInformation := smf_context.NewUserPlaneInformation(configForIPPoolAllocate) for _, upf := range userplaneInformation.UPFs { - upf.UPF.AssociationContext = context.Background() + upf.AssociationContext = context.Background() } for ci, tc := range testCasesOfGetUEIPPool { @@ -499,7 +517,7 @@ func TestGetUEIPPool(t *testing.T) { } } - var upf *smf_context.UPNode + var upf *smf_context.UPF var allocatedIP net.IP var useStatic bool for times := 1; times <= tc.allocateTimes; times++ { @@ -518,3 +536,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()) + } + } + }) + } + }) +} diff --git a/internal/pfcp/message/send.go b/internal/pfcp/message/send.go index 7905e3d4..f65f1546 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 err = upf.IsAssociated(); err != nil { return nil, err } - 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 err = upf.IsAssociated(); err != nil { return nil, err } - 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) @@ -300,7 +300,7 @@ func SendPfcpSessionDeletionRequest( upf *context.UPF, ctx *context.SMContext, ) (resMsg *pfcpUdp.Message, err error) { - nodeIDtoIP := upf.NodeID.ResolveNodeIdToIp() + nodeIDtoIP := upf.GetNodeIDString() if err = upf.IsAssociated(); err != nil { return nil, err } @@ -311,7 +311,7 @@ func SendPfcpSessionDeletionRequest( 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, @@ -326,7 +326,7 @@ func SendPfcpSessionDeletionRequest( } upaddr := &net.UDPAddr{ - IP: nodeIDtoIP, + IP: net.ParseIP(nodeIDtoIP), Port: pfcpUdp.PFCP_PORT, } @@ -339,7 +339,7 @@ func SendPfcpSessionDeletionRequest( 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 254cc78d..d3049eba 100644 --- a/internal/sbi/api_upi.go +++ b/internal/sbi/api_upi.go @@ -68,8 +68,8 @@ func (s *Server) PostUpNodesLinks(c *gin.Context) { for _, upf := range upi.UPFs { // only associate new ones - if err := upf.UPF.IsAssociated(); err != nil { - go s.Processor().ToBeAssociatedWithUPF(smf_context.GetSelf().PfcpContext, upf.UPF) + if err := upf.IsAssociated(); err != nil { + go s.Processor().ToBeAssociatedWithUPF(smf_context.GetSelf().PfcpContext, upf) } } c.JSON(http.StatusOK, gin.H{"status": "OK"}) @@ -85,11 +85,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 { + upNode.(*smf_context.UPF).CancelAssociation() + go s.Processor().ReleaseAllResourcesOfUPF(upNode.(*smf_context.UPF)) } upi.UpNodeDelete(upNodeRef) - upNode.UPF.CancelAssociation() c.JSON(http.StatusOK, gin.H{"status": "OK"}) } else { c.JSON(http.StatusNotFound, gin.H{}) diff --git a/internal/sbi/consumer/pcf_service_test.go b/internal/sbi/consumer/pcf_service_test.go index 252d70aa..d137dc2b 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{ @@ -27,6 +27,7 @@ var testConfig = factory.Config{ BindingIPv4: "127.0.0.1", Port: 8000, }, + UserPlaneInformation: &factory.UserPlaneInformation{}, }, } diff --git a/internal/sbi/processor/association.go b/internal/sbi/processor/association.go index 7a0306f1..67313127 100644 --- a/internal/sbi/processor/association.go +++ b/internal/sbi/processor/association.go @@ -18,9 +18,9 @@ import ( func (p *Processor) ToBeAssociatedWithUPF(smfPfcpContext 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 { @@ -46,9 +46,9 @@ func (p *Processor) ToBeAssociatedWithUPF(smfPfcpContext context.Context, upf *s 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) } @@ -109,7 +109,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..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" @@ -383,7 +385,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 +393,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 4e811910..bfb0e838 100644 --- a/internal/sbi/processor/pdu_session_test.go +++ b/internal/sbi/processor/pdu_session_test.go @@ -30,13 +30,17 @@ import ( "github.com/free5gc/util/httpwrapper" ) -var userPlaneConfig = factory.UserPlaneInformation{ - UPNodes: map[string]*factory.UPNode{ - "GNodeB": { - Type: "AN", +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{ { @@ -54,7 +58,7 @@ var userPlaneConfig = factory.UserPlaneInformation{ }, }, }, - InterfaceUpfInfoList: []*factory.InterfaceUpfInfoItem{ + Interfaces: []*factory.Interface{ { InterfaceType: "N3", Endpoints: []string{ @@ -76,7 +80,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", @@ -446,9 +450,8 @@ func TestHandlePDUSessionSMContextCreate(t *testing.T) { initStubPFCP() // modify associate setup status - allUPFs := smf_context.GetSelf().UserPlaneInformation.UPFs - for _, upfNode := range allUPFs { - upfNode.UPF.AssociationContext = context.Background() + for _, upf := range smf_context.GetSelf().UserPlaneInformation.UPFs { + upf.AssociationContext = context.Background() } testCases := []struct { diff --git a/internal/sbi/processor/ulcl_procedure.go b/internal/sbi/processor/ulcl_procedure.go index f19d9455..b6f7e1dd 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") @@ -339,7 +339,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 ac40787f..1ad61eb3 100644 --- a/pkg/factory/config.go +++ b/pkg/factory/config.go @@ -114,9 +114,9 @@ type Configuration struct { // 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"` + NrfUri string `yaml:"nrfUri" valid:"url,required"` + 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)"` // done @@ -166,8 +166,7 @@ type SnssaiDnnInfoItem struct { } type Sbi struct { - Scheme string `yaml:"scheme" valid:"scheme,required"` - //done + Scheme string `yaml:"scheme" valid:"scheme,required"` 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"` @@ -262,8 +261,8 @@ func (r *RoutingConfig) Validate() (bool, error) { // UserPlaneInformation describe core network userplane information type UserPlaneInformation struct { - UPNodes map[string]*UPNodeConfigInterface `json:"upNodes" yaml:"upNodes" valid:"required,upNodeValidator"` - Links []*UPLink `json:"links" yaml:"links" valid:"required,linkValidator"` + UPNodes map[string]UPNodeConfigInterface `json:"upNodes" yaml:"upNodes" valid:"required,upNodeValidator"` + Links []*UPLink `json:"links" yaml:"links" valid:"required,linkValidator"` } // UPNode represent the user plane node @@ -411,21 +410,6 @@ func ValidateLink(link *UPLink, upNodeNames []string) (bool, error) { return true, nil } -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"` } @@ -572,3 +556,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 1971e2d3..3ca146fc 100644 --- a/pkg/factory/config_test.go +++ b/pkg/factory/config_test.go @@ -1,14 +1,334 @@ package factory_test import ( + "fmt" "testing" + "github.com/asaskevich/govalidator" "github.com/stretchr/testify/require" "github.com/free5gc/openapi/models" "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 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 + Upi *factory.UserPlaneInformation + Valid bool + }{ + { + Name: "Valid userPlaneInformation", + Upi: baseUPI(), + 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", + Upi: func() *factory.UserPlaneInformation { + config := baseUPI() + config.UPNodes["UPF1"].(*factory.UPFConfig).Type = "xxx" + return config + }(), + Valid: false, + }, + { + Name: "UPF with wrong NodeID", + 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", + Upi: func() *factory.UserPlaneInformation { + config := baseUPI() + config.UPNodes["UPF1"].(*factory.UPFConfig).SNssaiInfos = nil + return config + }(), + Valid: false, + }, + { + Name: "UPF with empty sNssaiUpfInfos", + Upi: func() *factory.UserPlaneInformation { + config := baseUPI() + config.UPNodes["UPF1"].(*factory.UPFConfig).SNssaiInfos = []*factory.SnssaiUpfInfoItem{} + return config + }(), + Valid: false, + }, + { + 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 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 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", + }, + { + Cidr: "10.60.10.0/16", + }, + }, + }, + } + return config + }(), + Valid: false, + }, + { + Name: "UPF with overlapping pools in DnnUpfInfoItem.Pools and DnnUpfInfoItem.StaticPools", + 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", + }, + }, + StaticPools: []*factory.UEIPPool{ + { + Cidr: "10.60.10.0/16", + }, + }, + }, + } + return config + }(), + Valid: false, + }, + { + Name: "UPF with nil interfaces", + Upi: func() *factory.UserPlaneInformation { + config := baseUPI() + config.UPNodes["UPF1"].(*factory.UPFConfig).Interfaces = nil + return config + }(), + Valid: false, + }, + { + 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{ + { + 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: 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, + }, + } + + for _, tc := range testcase { + t.Run(tc.Name, func(t *testing.T) { + ok, err := govalidator.ValidateStruct(tc.Upi) + require.Equal(t, tc.Valid, ok) + if !ok { + require.Error(t, err) + fmt.Println(err) + } else { + require.Nil(t, err) + } + }) + } +} + func TestSnssaiValidator(t *testing.T) { testcase := []struct { Name string diff --git a/pkg/utils/pfcp_util.go b/pkg/utils/pfcp_util.go index 761ade7e..224558f6 100644 --- a/pkg/utils/pfcp_util.go +++ b/pkg/utils/pfcp_util.go @@ -23,8 +23,8 @@ func InitPFCPFunc(pCtx context.Context) (func(a *service.SmfApp), func()) { // Wait for PFCP start time.Sleep(1000 * time.Millisecond) - for _, upNode := range smf_context.GetSelf().UserPlaneInformation.UPFs { - go a.Processor().ToBeAssociatedWithUPF(smfContext.PfcpContext, upNode.UPF) + for _, upf := range smf_context.GetSelf().UserPlaneInformation.UPFs { + go a.Processor().ToBeAssociatedWithUPF(smfContext.PfcpContext, upf) } }