diff --git a/go.mod b/go.mod index 659c5c7f..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 @@ -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..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= @@ -57,6 +59,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 +238,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 +246,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 +317,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 +335,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 +362,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 +419,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 +521,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 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 b0ee3267..63c8ae0b 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,95 +449,168 @@ 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" + }) + + // collect all validation errors for UserPlaneInformation + var validationErrors govalidator.Errors + result = true + + // validate struct field correctness + if ok, errStruct := govalidator.ValidateStruct(u); !ok { + result = false + validationErrors = append(validationErrors, appendInvalid(errStruct).(govalidator.Errors)...) + } + + 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 + validationErrors = append(validationErrors, errSemantic.(govalidator.Errors)...) } } 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 + validationErrors = append(validationErrors, + fmt.Errorf("Link %s--%s contains unknown node name", link.A, link.B)) } } - result, err := govalidator.ValidateStruct(u) - return result, appendInvalid(err) + if len(validationErrors) > 0 { + return result, error(validationErrors) + } else { + return true, nil + } } // 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"` +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) { + // 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) { + var validationErrors govalidator.Errors + 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 + validationErrors = append(validationErrors, errIface.(govalidator.Errors)...) } - if interfaceUpfInfo.InterfaceType == "N3" { + + 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 { + result = false + 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 + 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 + validationErrors = append(validationErrors, errSNSSAI.(govalidator.Errors)...) } } - result, err := govalidator.ValidateStruct(u) - return result, appendInvalid(err) + + return result, error(validationErrors) } -type InterfaceUpfInfoItem struct { +func (upf *UPFConfig) GetNodeID() string { + return upf.NodeID +} + +func (upf *UPFConfig) GetType() string { + return upf.Type +} + +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) { + 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 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 + validationErrors = append(validationErrors, + fmt.Errorf("Invalid endpoint: %s should be one of IPv4, IPv6, FQDN.", endpoint)) } } - result, err := govalidator.ValidateStruct(i) - return result, appendInvalid(err) + return result, error(validationErrors) } type SnssaiUpfInfoItem struct { @@ -549,54 +618,122 @@ 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) { + var validationErrors govalidator.Errors + 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 + 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 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 + validationErrors = append(validationErrors, + 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 + validationErrors = append(validationErrors, errDNNInfo.(govalidator.Errors)...) } } - result, err := govalidator.ValidateStruct(s) - return result, appendInvalid(err) + if len(validationErrors) > 0 { + return result, error(validationErrors) + } + + return result, nil } 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) { + // collect all errors + var validationErrors govalidator.Errors + result = true + + if len(d.Dnn) == 0 { + result = false + validationErrors = append(validationErrors, + fmt.Errorf("Invalid DnnUpfInfoItem: dnn must not be empty.")) + } + + if len(d.Pools) == 0 { + result = false + validationErrors = append(validationErrors, + 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 + // CIDR check + prefix, ok := netaddr.ParseIPPrefix(pool.Cidr) + if ok != nil { + result = false + validationErrors = append(validationErrors, fmt.Errorf("Invalid CIDR: %s.", pool.Cidr)) + } else { + 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]) { + result = false + validationErrors = append(validationErrors, + fmt.Errorf("overlap detected between dynamic pools %s and %s", prefixes[i], prefixes[j])) + } } } - result, err := govalidator.ValidateStruct(d) - return result, appendInvalid(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 { @@ -604,47 +741,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 @@ -666,7 +773,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) @@ -687,7 +794,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 } @@ -806,3 +913,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..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" @@ -9,6 +10,324 @@ 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 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 := tc.Upi.Validate() + require.Equal(t, tc.Valid, ok) + if !ok { + require.Error(t, err) + fmt.Println(err) + } else { + require.Nil(t, err) + } + }) + } +} + func TestSnssaiInfoItem(t *testing.T) { testcase := []struct { Name string @@ -74,6 +393,11 @@ func TestSnssaiUpfInfoItem(t *testing.T) { }, DnnInfos: []*factory.DnnUpfInfoItem{ { + Pools: []*factory.UEIPPool{ + { + Cidr: "10.60.0.0/16", + }, + }, Dnn: "internet", }, }, @@ -85,6 +409,11 @@ func TestSnssaiUpfInfoItem(t *testing.T) { }, DnnInfos: []*factory.DnnUpfInfoItem{ { + Pools: []*factory.UEIPPool{ + { + Cidr: "10.60.0.0/16", + }, + }, Dnn: "internet2", }, }, @@ -99,6 +428,7 @@ func TestSnssaiUpfInfoItem(t *testing.T) { } ok, err := snssaiInfoItem.Validate() + fmt.Println("Error: ", err) require.True(t, ok) require.Nil(t, err) }) 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) } }