diff --git a/go.mod b/go.mod index 659c5c7f..ede5b823 100644 --- a/go.mod +++ b/go.mod @@ -60,6 +60,8 @@ require ( github.com/tim-ywliu/nested-logrus-formatter v1.3.2 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect + go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect + go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/net v0.23.0 // indirect @@ -70,4 +72,5 @@ require ( google.golang.org/protobuf v1.33.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a // indirect ) diff --git a/go.sum b/go.sum index b05b6d44..7c65e43d 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -235,6 +236,7 @@ github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -242,6 +244,11 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE= +go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 h1:WJhcL4p+YeDxmZWg141nRm7XC8IDmhz7lk5GpadO1Sg= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -308,6 +315,7 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -325,6 +333,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -351,6 +360,8 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= @@ -406,6 +417,7 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -507,6 +519,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a h1:1XCVEdxrvL6c0TGOhecLuB7U9zYNdxZEjvOqJreKZiM= +inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a/go.mod h1:e83i32mAQOW1LAqEIweALsuK2Uw4mhQadA5r7b0Wobo= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/internal/context/context.go b/internal/context/context.go index 23606636..8f29ed31 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -226,7 +226,7 @@ func InitSmfContext(config *factory.Config) { smfContext.SupportedPDUSessionType = "IPv4" - smfContext.UserPlaneInformation = NewUserPlaneInformation(&configuration.UserPlaneInformation) + smfContext.UserPlaneInformation = NewUserPlaneInformation(configuration.UserPlaneInformation) smfContext.ChargingIDGenerator = idgenerator.NewGenerator(1, math.MaxUint32) diff --git a/internal/context/gnb.go b/internal/context/gnb.go index 0a2eca6f..cb278223 100644 --- a/internal/context/gnb.go +++ b/internal/context/gnb.go @@ -2,19 +2,16 @@ package context import ( "fmt" - "net" "github.com/google/uuid" - "github.com/free5gc/pfcp/pfcpType" "github.com/free5gc/smf/internal/logger" ) // embeds the UPNode struct ("inheritance") // implements UPNodeInterface type GNB struct { - UPNode - ANIP net.IP + *UPNode } func (gNB *GNB) GetName() string { @@ -29,42 +26,19 @@ func (gNB *GNB) GetType() UPNodeType { return gNB.Type } -func (gNB *GNB) GetDnn() string { - return gNB.Dnn -} - func (gNB *GNB) String() string { str := "gNB {\n" prefix := " " str += prefix + fmt.Sprintf("Name: %s\n", gNB.Name) - str += prefix + fmt.Sprintf("ANIP: %s\n", gNB.ANIP) str += prefix + fmt.Sprintf("ID: %s\n", gNB.ID) - str += prefix + fmt.Sprintf("NodeID: %s\n", gNB.GetNodeIDString()) - str += prefix + fmt.Sprintf("Dnn: %s\n", gNB.Dnn) str += prefix + fmt.Sprintln("Links:") for _, link := range gNB.Links { - str += prefix + fmt.Sprintf("-- %s: %s\n", link.GetName(), link.GetNodeIDString()) + str += prefix + fmt.Sprintf("-- %s: %s\n", link.GetName(), link.GetName()) } str += "}" return str } -func (gNB *GNB) GetNodeIDString() string { - switch gNB.NodeID.NodeIdType { - case pfcpType.NodeIdTypeIpv4Address, pfcpType.NodeIdTypeIpv6Address: - return gNB.NodeID.IP.String() - case pfcpType.NodeIdTypeFqdn: - return gNB.NodeID.FQDN - default: - logger.CtxLog.Errorf("nodeID has unknown type %d", gNB.NodeID.NodeIdType) - return "" - } -} - -func (gNB *GNB) GetNodeID() pfcpType.NodeID { - return gNB.NodeID -} - func (gNB *GNB) GetLinks() UPPath { return gNB.Links } @@ -82,7 +56,7 @@ func (gNB *GNB) AddLink(link UPNodeInterface) bool { func (gNB *GNB) RemoveLink(link UPNodeInterface) bool { for i, existingLink := range gNB.Links { - if link.GetName() == existingLink.GetName() && existingLink.GetNodeIDString() == link.GetNodeIDString() { + if link.GetName() == existingLink.GetName() && existingLink.GetName() == link.GetName() { logger.CfgLog.Warningf("Remove UPLink [%s] <=> [%s]\n", existingLink.GetName(), link.GetName()) gNB.Links = append(gNB.Links[:i], gNB.Links[i+1:]...) return true diff --git a/internal/context/sm_context.go b/internal/context/sm_context.go index 987af5c6..17ae944c 100644 --- a/internal/context/sm_context.go +++ b/internal/context/sm_context.go @@ -474,15 +474,15 @@ func (smContext *SMContext) GetNodeIDByLocalSEID(seid uint64) pfcpType.NodeID { return pfcpType.NodeID{} } -func (smContext *SMContext) AllocateLocalSEIDForUPPath(path UPPath) { - for _, upNode := range path { - NodeIDtoIP := upNode.GetNodeIDString() +func (smContext *SMContext) AllocateLocalSEIDForUPPath(path []*UPF) { + for _, upf := range path { + NodeIDtoIP := upf.GetName() if _, exist := smContext.PFCPContext[NodeIDtoIP]; !exist { allocatedSEID := AllocateLocalSEID() smContext.PFCPContext[NodeIDtoIP] = &PFCPSessionContext{ PDRs: make(map[uint16]*PDR), - NodeID: upNode.GetNodeID(), + NodeID: upf.GetNodeID(), LocalSEID: allocatedSEID, } diff --git a/internal/context/sm_context_policy_test.go b/internal/context/sm_context_policy_test.go index 5c17fa2a..f8e1f4a7 100644 --- a/internal/context/sm_context_policy_test.go +++ b/internal/context/sm_context_policy_test.go @@ -10,14 +10,17 @@ import ( "github.com/free5gc/smf/pkg/factory" ) -var userPlaneConfig = factory.UserPlaneInformation{ - UPNodes: map[string]*factory.UPNode{ - "GNodeB": { - Type: "AN", - NodeID: "192.168.1.1", +var userPlaneConfig = &factory.UserPlaneInformation{ + UPNodes: map[string]factory.UPNodeConfigInterface{ + "GNodeB": &factory.GNBConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "AN", + }, }, - "UPF1": { - Type: "UPF", + "UPF1": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "10.4.0.11", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -33,7 +36,7 @@ var userPlaneConfig = factory.UserPlaneInformation{ }, }, }, - InterfaceUpfInfoList: []*factory.InterfaceUpfInfoItem{ + Interfaces: []*factory.Interface{ { InterfaceType: "N3", Endpoints: []string{ @@ -50,8 +53,10 @@ var userPlaneConfig = factory.UserPlaneInformation{ }, }, }, - "UPF2": { - Type: "UPF", + "UPF2": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "10.4.0.12", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -69,7 +74,7 @@ var userPlaneConfig = factory.UserPlaneInformation{ }, }, }, - InterfaceUpfInfoList: []*factory.InterfaceUpfInfoItem{ + Interfaces: []*factory.Interface{ { InterfaceType: "N9", Endpoints: []string{ @@ -619,7 +624,7 @@ func TestApplyPccRules(t *testing.T) { } smfContext := context.GetSelf() - smfContext.UserPlaneInformation = context.NewUserPlaneInformation(&userPlaneConfig) + smfContext.UserPlaneInformation = context.NewUserPlaneInformation(userPlaneConfig) for _, n := range smfContext.UserPlaneInformation.UPFs { n.UPFStatus = context.AssociatedSetUpSuccess } diff --git a/internal/context/upf.go b/internal/context/upf.go index f1a7d4a0..1ad0a73f 100644 --- a/internal/context/upf.go +++ b/internal/context/upf.go @@ -67,6 +67,8 @@ const ( type UPF struct { *UPNode + NodeID pfcpType.NodeID + UPIPInfo pfcpType.UserPlaneIPResourceInformation UPFStatus UPFStatus RecoveryTimeStamp time.Time @@ -97,10 +99,9 @@ func (upf *UPF) String() string { str += prefix + fmt.Sprintf("Name: %s\n", upf.Name) str += prefix + fmt.Sprintf("ID: %s\n", upf.ID) str += prefix + fmt.Sprintf("NodeID: %s\n", upf.GetNodeIDString()) - str += prefix + fmt.Sprintf("Dnn: %s\n", upf.Dnn) str += prefix + fmt.Sprintln("Links:") for _, link := range upf.Links { - str += prefix + fmt.Sprintf("-- %s: %s\n", link.GetName(), link.GetNodeIDString()) + str += prefix + fmt.Sprintf("-- %s: %s\n", link.GetName(), link.GetName()) } str += prefix + fmt.Sprintln("N3 interfaces:") for _, iface := range upf.N3Interfaces { @@ -135,7 +136,7 @@ func (upf *UPF) GetNodeID() pfcpType.NodeID { } func (upf *UPF) GetName() string { - return upf.Name + return fmt.Sprintf("%s[%s]", upf.Name, upf.GetNodeIDString()) } func (upf *UPF) GetID() uuid.UUID { @@ -146,10 +147,6 @@ func (upf *UPF) GetType() UPNodeType { return upf.Type } -func (upf *UPF) GetDnn() string { - return upf.Dnn -} - func (upf *UPF) GetLinks() UPPath { return upf.Links } @@ -167,7 +164,7 @@ func (upf *UPF) AddLink(link UPNodeInterface) bool { func (upf *UPF) RemoveLink(link UPNodeInterface) bool { for i, existingLink := range upf.Links { - if link.GetName() == existingLink.GetName() && existingLink.GetNodeIDString() == link.GetNodeIDString() { + if link.GetName() == existingLink.GetName() && existingLink.GetName() == link.GetName() { logger.CfgLog.Warningf("Remove UPLink [%s] <=> [%s]\n", existingLink.GetName(), link.GetName()) upf.Links = append(upf.Links[:i], upf.Links[i+1:]...) return true @@ -214,7 +211,7 @@ func GetUpfById(uuid string) *UPF { } // NewUPFInterfaceInfo parse the InterfaceUpfInfoItem to generate UPFInterfaceInfo -func NewUPFInterfaceInfo(i *factory.InterfaceUpfInfoItem) *UPFInterfaceInfo { +func NewUPFInterfaceInfo(i *factory.Interface) *UPFInterfaceInfo { interfaceInfo := new(UPFInterfaceInfo) interfaceInfo.IPv4EndPointAddresses = make([]net.IP, 0) @@ -322,7 +319,8 @@ func (t *UPTunnel) RemoveDataPath(pathID int64) { // NewUPF returns a new UPF context in SMF func NewUPF( upNode *UPNode, - ifaces []*factory.InterfaceUpfInfoItem, + nodeID *pfcpType.NodeID, + ifaces []*factory.Interface, ) (upf *UPF) { upf = new(UPF) upf.UPNode = upNode diff --git a/internal/context/upf_test.go b/internal/context/upf_test.go index 3b0a662b..c4f49d46 100644 --- a/internal/context/upf_test.go +++ b/internal/context/upf_test.go @@ -19,14 +19,14 @@ var mockUPNode = &smf_context.UPNode{ Name: "UPF1", Type: smf_context.UPNODE_UPF, ID: uuid.New(), - NodeID: pfcpType.NodeID{ - NodeIdType: pfcpType.NodeIdTypeIpv4Address, - IP: net.ParseIP("127.0.0.1"), - }, - Dnn: "internet", } -var mockIfaces = []*factory.InterfaceUpfInfoItem{ +var mockNodeID = &pfcpType.NodeID{ + NodeIdType: pfcpType.NodeIdTypeIpv4Address, + IP: net.ParseIP("127.0.0.1"), +} + +var mockIfaces = []*factory.Interface{ { InterfaceType: "N3", Endpoints: []string{"127.0.0.1"}, @@ -151,12 +151,12 @@ func TestAddPDR(t *testing.T) { expectedError error }{ { - upf: smf_context.NewUPF(mockUPNode, mockIfaces), + upf: smf_context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddPDR should success", expectedError: nil, }, { - upf: smf_context.NewUPF(mockUPNode, mockIfaces), + upf: smf_context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddPDR should fail", expectedError: fmt.Errorf("UPF[127.0.0.1] not Associate with SMF"), }, @@ -194,12 +194,12 @@ func TestAddFAR(t *testing.T) { expectedError error }{ { - upf: context.NewUPF(mockUPNode, mockIfaces), + upf: context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddFAR should success", expectedError: nil, }, { - upf: context.NewUPF(mockUPNode, mockIfaces), + upf: context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddFAR should fail", expectedError: fmt.Errorf("UPF[127.0.0.1] not Associate with SMF"), }, @@ -237,12 +237,12 @@ func TestAddQER(t *testing.T) { expectedError error }{ { - upf: context.NewUPF(mockUPNode, mockIfaces), + upf: context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddQER should success", expectedError: nil, }, { - upf: context.NewUPF(mockUPNode, mockIfaces), + upf: context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddQER should fail", expectedError: fmt.Errorf("UPF[127.0.0.1] not Associate with SMF"), }, @@ -280,12 +280,12 @@ func TestAddBAR(t *testing.T) { expectedError error }{ { - upf: context.NewUPF(mockUPNode, mockIfaces), + upf: context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddBAR should success", expectedError: nil, }, { - upf: context.NewUPF(mockUPNode, mockIfaces), + upf: context.NewUPF(mockUPNode, mockNodeID, mockIfaces), resultStr: "AddBAR should fail", expectedError: fmt.Errorf("UPF[127.0.0.1] not Associate with SMF"), }, diff --git a/internal/context/user_plane_information.go b/internal/context/user_plane_information.go index f965b395..55b31d70 100644 --- a/internal/context/user_plane_information.go +++ b/internal/context/user_plane_information.go @@ -40,12 +40,10 @@ const ( // UPNode represents a gNB or UPF in the user plane node topology // UPF and gNB structs embed this ("interitance") type UPNode struct { - Name string - Type UPNodeType - ID uuid.UUID - NodeID pfcpType.NodeID - Dnn string - Links UPPath + Name string + Type UPNodeType + ID uuid.UUID + Links UPPath } // UPF and gNB structs implement this interface to provide common methods @@ -55,13 +53,10 @@ type UPNodeInterface interface { GetName() string GetID() uuid.UUID GetType() UPNodeType - GetDnn() string GetLinks() UPPath AddLink(link UPNodeInterface) bool RemoveLink(link UPNodeInterface) bool RemoveLinkByIndex(index int) bool - GetNodeID() pfcpType.NodeID - GetNodeIDString() string } // UPPath represents the User Plane Node Sequence of this path @@ -166,30 +161,28 @@ func NewUserPlaneInformation(upTopology *factory.UserPlaneInformation) *UserPlan allUEIPPools := []*UeIPPool{} for name, node := range upTopology.UPNodes { - nodeID, err := ConfigToNodeID(node.NodeID) - if err != nil { - logger.InitLog.Fatalf("[NewUserPlaneInformation] cannot parse %s NodeID from config: %+v", name, err) - } upNode := &UPNode{ - Name: name, - Type: UPNodeType(node.Type), - ID: uuid.New(), - NodeID: nodeID, - Dnn: node.Dnn, + Name: name, + Type: UPNodeType(node.GetType()), + ID: uuid.New(), } switch upNode.Type { case UPNODE_AN: gNB := &GNB{ - UPNode: *upNode, - ANIP: upNode.NodeID.ResolveNodeIdToIp(), + UPNode: upNode, } anPool[name] = gNB nodePool[name] = gNB case UPNODE_UPF: - upf := NewUPF(upNode, node.InterfaceUpfInfoList) + upfConfig := node.(*factory.UPFConfig) + nodeID, err := ConfigToNodeID(upfConfig.GetNodeID()) + if err != nil { + logger.InitLog.Fatalf("[NewUserPlaneInformation] cannot parse %s NodeID from config: %+v", name, err) + } + upf := NewUPF(upNode, &nodeID, upfConfig.Interfaces) snssaiInfos := make([]*SnssaiUPFInfo, 0) - for _, snssaiInfoConfig := range node.SNssaiInfos { + for _, snssaiInfoConfig := range upfConfig.SNssaiInfos { snssaiInfo := SnssaiUPFInfo{ SNssai: &SNssai{ Sst: snssaiInfoConfig.SNssai.Sst, @@ -229,10 +222,12 @@ func NewUserPlaneInformation(upTopology *factory.UserPlaneInformation) *UserPlan for _, pool := range ueIPPools { if pool.pool.Min() != pool.pool.Max() { if err = pool.pool.Reserve(pool.pool.Min(), pool.pool.Min()); err != nil { - logger.InitLog.Errorf("Remove network address failed for %s: %s", pool.ueSubNet.String(), err) + logger.InitLog.Errorf("Remove network address failed for %s: %s", + pool.ueSubNet.String(), err) } if err = pool.pool.Reserve(pool.pool.Max(), pool.pool.Max()); err != nil { - logger.InitLog.Errorf("Remove network address failed for %s: %s", pool.ueSubNet.String(), err) + logger.InitLog.Errorf("Remove network address failed for %s: %s", + pool.ueSubNet.String(), err) } } logger.InitLog.Debugf("%d-%s %s %s", @@ -278,21 +273,27 @@ func NewUserPlaneInformation(upTopology *factory.UserPlaneInformation) *UserPlan return userplaneInformation } -func (upi *UserPlaneInformation) UpNodesToConfiguration() map[string]*factory.UPNode { - nodes := make(map[string]*factory.UPNode) +func (upi *UserPlaneInformation) UpNodesToConfiguration() map[string]factory.UPNodeConfigInterface { + nodes := make(map[string]factory.UPNodeConfigInterface) for name, upNode := range upi.UPNodes { - node := &factory.UPNode{ - NodeID: upNode.GetNodeIDString(), - Dnn: upNode.GetDnn(), - } - nodes[name] = node - switch upNode.GetType() { case UPNODE_AN: - node.Type = "AN" + gNBConfig := &factory.GNBConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "AN", + }, + } + nodes[name] = gNBConfig case UPNODE_UPF: - node.Type = "UPF" upf := upNode.(*UPF) + upfConfig := &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, + NodeID: upf.GetNodeIDString(), + } + nodes[name] = upfConfig + if upf.SNssaiInfos != nil { FsNssaiInfoList := make([]*factory.SnssaiUpfInfoItem, 0) for _, sNssaiInfo := range upf.SNssaiInfos { @@ -325,9 +326,9 @@ func (upi *UserPlaneInformation) UpNodesToConfiguration() map[string]*factory.UP } FsNssaiInfoList = append(FsNssaiInfoList, Fsnssai) } // for sNssaiInfo - node.SNssaiInfos = FsNssaiInfoList + upfConfig.SNssaiInfos = FsNssaiInfoList } // if UPF.SNssaiInfos - FNxList := make([]*factory.InterfaceUpfInfoItem, 0) + FNxList := make([]*factory.Interface, 0) for _, iface := range upf.N3Interfaces { endpoints := make([]string, 0) // upf.go L90 @@ -337,7 +338,7 @@ func (upi *UserPlaneInformation) UpNodesToConfiguration() map[string]*factory.UP for _, eIP := range iface.IPv4EndPointAddresses { endpoints = append(endpoints, eIP.String()) } - FNxList = append(FNxList, &factory.InterfaceUpfInfoItem{ + FNxList = append(FNxList, &factory.Interface{ InterfaceType: models.UpInterfaceType_N3, Endpoints: endpoints, NetworkInstances: iface.NetworkInstances, @@ -353,16 +354,15 @@ func (upi *UserPlaneInformation) UpNodesToConfiguration() map[string]*factory.UP for _, eIP := range iface.IPv4EndPointAddresses { endpoints = append(endpoints, eIP.String()) } - FNxList = append(FNxList, &factory.InterfaceUpfInfoItem{ + FNxList = append(FNxList, &factory.Interface{ InterfaceType: models.UpInterfaceType_N9, Endpoints: endpoints, NetworkInstances: iface.NetworkInstances, }) } // N9Interfaces - node.InterfaceUpfInfoList = FNxList + upfConfig.Interfaces = FNxList default: - logger.InitLog.Warningf("invalid UPNodeType: %s\n", upNode.GetType()) - node.Type = "Unknown" + logger.InitLog.Fatalf("invalid UPNodeType: %s\n", upNode.GetType()) } } @@ -385,8 +385,8 @@ func (upi *UserPlaneInformation) LinksToConfiguration() []*factory.UPLink { for _, link := range node.GetLinks() { if !visited[link] { queue = append(queue, link) - nodeIpStr := node.GetNodeIDString() - ipStr := link.GetNodeIDString() + nodeIpStr := node.(*UPF).GetNodeIDString() + ipStr := link.(*UPF).GetNodeIDString() linkA := upi.UPFIPToName[nodeIpStr] linkB := upi.UPFIPToName[ipStr] links = append(links, &factory.UPLink{ @@ -411,31 +411,31 @@ func (upi *UserPlaneInformation) UpNodesFromConfiguration(upTopology *factory.Us logger.InitLog.Warningf("Node [%s] already exists in SMF.\n", name) continue } - nodeID, err := ConfigToNodeID(node.NodeID) - if err != nil { - logger.InitLog.Fatalf("[UpNodesFromConfiguration] cannot parse NodeID from config: %+v", err) - } upNode := &UPNode{ - Name: name, - Type: UPNodeType(node.Type), - ID: uuid.New(), - NodeID: nodeID, - Dnn: node.Dnn, + Name: name, + Type: UPNodeType(node.GetType()), + ID: uuid.New(), } switch upNode.Type { case UPNODE_AN: gNB := &GNB{ - UPNode: *upNode, - ANIP: upNode.NodeID.ResolveNodeIdToIp(), + UPNode: upNode, } upi.AccessNetwork[name] = gNB upi.UPNodes[name] = gNB case UPNODE_UPF: - upf := NewUPF(upNode, node.InterfaceUpfInfoList) + upfConfig := node.(*factory.UPFConfig) + nodeID, err := ConfigToNodeID(upfConfig.GetNodeID()) + if err != nil { + logger.InitLog.Fatalf("[UpNodesFromConfiguration] cannot parse NodeID from config: %+v", err) + } + + upf := NewUPF(upNode, &nodeID, upfConfig.Interfaces) + snssaiInfos := make([]*SnssaiUPFInfo, 0) - for _, snssaiInfoConfig := range node.SNssaiInfos { + for _, snssaiInfoConfig := range upfConfig.SNssaiInfos { snssaiInfo := &SnssaiUPFInfo{ SNssai: &SNssai{ Sst: snssaiInfoConfig.SNssai.Sst, @@ -533,7 +533,7 @@ func (upi *UserPlaneInformation) UpNodeDelete(upNodeName string) { logger.InitLog.Infof("UPNode [%s] found. Deleting it.\n", upNodeName) if upNode.GetType() == UPNODE_UPF { logger.InitLog.Tracef("Delete UPF [%s] from its NodeID.\n", upNodeName) - RemoveUPFNodeByNodeID(upNode.GetNodeID()) + RemoveUPFNodeByNodeID(upNode.(*UPF).GetNodeID()) if _, ok = upi.UPFs[upNodeName]; ok { logger.InitLog.Tracef("Delete UPF [%s] from upi.UPFs.\n", upNodeName) delete(upi.UPFs, upNodeName) @@ -795,7 +795,7 @@ func getPathBetween( selection *UPFSelectionParams, ) (path UPPath, pathExist bool) { logger.CtxLog.Tracef("[getPathBetween] node %s[%s] and %s[%s]", - cur.GetName(), cur.GetNodeIDString(), dest.GetName(), dest.GetNodeIDString()) + cur.GetName(), cur.GetName(), dest.GetName(), dest.GetName()) visited[cur] = true if reflect.DeepEqual(cur, dest) { diff --git a/internal/context/user_plane_information_test.go b/internal/context/user_plane_information_test.go index 6906ae24..1564653b 100644 --- a/internal/context/user_plane_information_test.go +++ b/internal/context/user_plane_information_test.go @@ -15,13 +15,16 @@ import ( ) var configuration = &factory.UserPlaneInformation{ - UPNodes: map[string]*factory.UPNode{ - "GNodeB": { - Type: "AN", - NodeID: "192.168.179.100", + UPNodes: map[string]factory.UPNodeConfigInterface{ + "GNodeB": &factory.GNBConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "AN", + }, }, - "UPF1": { - Type: "UPF", + "UPF1": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.1", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -63,8 +66,10 @@ var configuration = &factory.UserPlaneInformation{ }, }, }, - "UPF2": { - Type: "UPF", + "UPF2": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.2", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -85,8 +90,10 @@ var configuration = &factory.UserPlaneInformation{ }, }, }, - "UPF3": { - Type: "UPF", + "UPF3": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.3", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -107,8 +114,10 @@ var configuration = &factory.UserPlaneInformation{ }, }, }, - "UPF4": { - Type: "UPF", + "UPF4": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.4", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -289,13 +298,16 @@ func TestSelectUPFAndAllocUEIP(t *testing.T) { } var configForIPPoolAllocate = &factory.UserPlaneInformation{ - UPNodes: map[string]*factory.UPNode{ - "GNodeB": { - Type: "AN", - NodeID: "192.168.179.100", + UPNodes: map[string]factory.UPNodeConfigInterface{ + "GNodeB": &factory.GNBConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "AN", + }, }, - "UPF1": { - Type: "UPF", + "UPF1": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.1", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -321,8 +333,10 @@ var configForIPPoolAllocate = &factory.UserPlaneInformation{ }, }, }, - "UPF2": { - Type: "UPF", + "UPF2": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.2", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -348,8 +362,10 @@ var configForIPPoolAllocate = &factory.UserPlaneInformation{ }, }, }, - "UPF3": { - Type: "UPF", + "UPF3": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.3", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { diff --git a/internal/sbi/processor/pdu_session_test.go b/internal/sbi/processor/pdu_session_test.go index 62712ee8..738fc8cd 100644 --- a/internal/sbi/processor/pdu_session_test.go +++ b/internal/sbi/processor/pdu_session_test.go @@ -30,14 +30,17 @@ import ( "github.com/free5gc/util/httpwrapper" ) -var userPlaneConfig = factory.UserPlaneInformation{ - UPNodes: map[string]*factory.UPNode{ - "GNodeB": { - Type: "AN", - NodeID: "192.168.1.1", +var userPlaneConfig = &factory.UserPlaneInformation{ + UPNodes: map[string]factory.UPNodeConfigInterface{ + "GNodeB": &factory.GNBConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "AN", + }, }, - "UPF1": { - Type: "UPF", + "UPF1": &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, NodeID: "192.168.179.1", SNssaiInfos: []*factory.SnssaiUpfInfoItem{ { @@ -55,7 +58,7 @@ var userPlaneConfig = factory.UserPlaneInformation{ }, }, }, - InterfaceUpfInfoList: []*factory.InterfaceUpfInfoItem{ + Interfaces: []*factory.Interface{ { InterfaceType: "N3", Endpoints: []string{ diff --git a/pkg/factory/config.go b/pkg/factory/config.go index 95c645c8..73fa7b94 100644 --- a/pkg/factory/config.go +++ b/pkg/factory/config.go @@ -7,12 +7,14 @@ package factory import ( "errors" "fmt" + "slices" "strconv" "sync" "time" "github.com/asaskevich/govalidator" "github.com/davecgh/go-spew/spew" + "inet.af/netaddr" "github.com/free5gc/openapi/models" "github.com/free5gc/smf/internal/logger" @@ -47,7 +49,7 @@ type Config struct { func (c *Config) Validate() (bool, error) { if configuration := c.Configuration; configuration != nil { - if result, err := configuration.validate(); err != nil { + if result, err := configuration.Validate(); err != nil { return result, err } } @@ -69,29 +71,29 @@ type Info struct { Description string `yaml:"description,omitempty" valid:"type(string)"` } -func (i *Info) validate() (bool, error) { +func (i *Info) Validate() (bool, error) { result, err := govalidator.ValidateStruct(i) return result, appendInvalid(err) } type Configuration struct { - SmfName string `yaml:"smfName" valid:"type(string),required"` - Sbi *Sbi `yaml:"sbi" valid:"required"` - PFCP *PFCP `yaml:"pfcp" valid:"required"` - NrfUri string `yaml:"nrfUri" valid:"url,required"` - NrfCertPem string `yaml:"nrfCertPem,omitempty" valid:"optional"` - UserPlaneInformation UserPlaneInformation `yaml:"userplaneInformation" valid:"required"` - ServiceNameList []string `yaml:"serviceNameList" valid:"required"` - SNssaiInfo []*SnssaiInfoItem `yaml:"snssaiInfos" valid:"required"` - ULCL bool `yaml:"ulcl" valid:"type(bool),optional"` - PLMNList []PlmnID `yaml:"plmnList" valid:"optional"` - Locality string `yaml:"locality" valid:"type(string),optional"` - UrrPeriod uint16 `yaml:"urrPeriod,omitempty" valid:"optional"` - UrrThreshold uint64 `yaml:"urrThreshold,omitempty" valid:"optional"` - T3591 *TimerValue `yaml:"t3591" valid:"required"` - T3592 *TimerValue `yaml:"t3592" valid:"required"` - NwInstFqdnEncoding bool `yaml:"nwInstFqdnEncoding" valid:"type(bool),optional"` - RequestedUnit int32 `yaml:"requestedUnit,omitempty" valid:"optional"` + SmfName string `yaml:"smfName" valid:"type(string),required"` + Sbi *Sbi `yaml:"sbi" valid:"required"` + PFCP *PFCP `yaml:"pfcp" valid:"required"` + NrfUri string `yaml:"nrfUri" valid:"url,required"` + NrfCertPem string `yaml:"nrfCertPem,omitempty" valid:"optional"` + UserPlaneInformation *UserPlaneInformation `yaml:"userplaneInformation" valid:"required"` + ServiceNameList []string `yaml:"serviceNameList" valid:"required"` + SNssaiInfo []*SnssaiInfoItem `yaml:"snssaiInfos" valid:"required"` + ULCL bool `yaml:"ulcl" valid:"type(bool),optional"` + PLMNList []PlmnID `yaml:"plmnList" valid:"optional"` + Locality string `yaml:"locality" valid:"type(string),optional"` + UrrPeriod uint16 `yaml:"urrPeriod,omitempty" valid:"optional"` + UrrThreshold uint64 `yaml:"urrThreshold,omitempty" valid:"optional"` + T3591 *TimerValue `yaml:"t3591" valid:"required"` + T3592 *TimerValue `yaml:"t3592" valid:"required"` + NwInstFqdnEncoding bool `yaml:"nwInstFqdnEncoding" valid:"type(bool),optional"` + RequestedUnit int32 `yaml:"requestedUnit,omitempty" valid:"optional"` } type Logger struct { @@ -100,21 +102,21 @@ type Logger struct { ReportCaller bool `yaml:"reportCaller" valid:"type(bool)"` } -func (c *Configuration) validate() (bool, error) { +func (c *Configuration) Validate() (bool, error) { if sbi := c.Sbi; sbi != nil { - if result, err := sbi.validate(); err != nil { + if result, err := sbi.Validate(); err != nil { return result, err } } if pfcp := c.PFCP; pfcp != nil { - if result, err := pfcp.validate(); err != nil { + if result, err := pfcp.Validate(); err != nil { return result, err } } - if userPlaneInformation := &c.UserPlaneInformation; userPlaneInformation != nil { - if result, err := userPlaneInformation.validate(); err != nil { + if userPlaneInformation := c.UserPlaneInformation; userPlaneInformation != nil { + if result, err := userPlaneInformation.Validate(); err != nil { return result, err } } @@ -139,20 +141,20 @@ func (c *Configuration) validate() (bool, error) { if c.PLMNList != nil { for _, plmnId := range c.PLMNList { - if result, err := plmnId.validate(); err != nil { + if result, err := plmnId.Validate(); err != nil { return result, err } } } if t3591 := c.T3591; t3591 != nil { - if result, err := t3591.validate(); err != nil { + if result, err := t3591.Validate(); err != nil { return result, err } } if t3592 := c.T3592; t3592 != nil { - if result, err := t3592.validate(); err != nil { + if result, err := t3592.Validate(); err != nil { return result, err } } @@ -183,7 +185,7 @@ func (s *SnssaiInfoItem) Validate() (bool, error) { } for _, dnnInfo := range s.DnnInfos { - if result, err := dnnInfo.validate(); err != nil { + if result, err := dnnInfo.Validate(); err != nil { return result, err } } @@ -197,15 +199,15 @@ type SnssaiDnnInfoItem struct { PCSCF *PCSCF `yaml:"pcscf,omitempty" valid:"optional"` } -func (s *SnssaiDnnInfoItem) validate() (bool, error) { +func (s *SnssaiDnnInfoItem) Validate() (bool, error) { if dns := s.DNS; dns != nil { - if result, err := dns.validate(); err != nil { + if result, err := dns.Validate(); err != nil { return result, err } } if pcscf := s.PCSCF; pcscf != nil { - if result, err := pcscf.validate(); err != nil { + if result, err := pcscf.Validate(); err != nil { return result, err } } @@ -223,13 +225,13 @@ type Sbi struct { Port int `yaml:"port,omitempty" valid:"port,optional"` } -func (s *Sbi) validate() (bool, error) { +func (s *Sbi) Validate() (bool, error) { govalidator.TagMap["scheme"] = govalidator.Validator(func(str string) bool { return str == "https" || str == "http" }) if tls := s.Tls; tls != nil { - if result, err := tls.validate(); err != nil { + if result, err := tls.Validate(); err != nil { return result, err } } @@ -243,7 +245,7 @@ type Tls struct { Key string `yaml:"key,omitempty" valid:"type(string),minstringlength(1),required"` } -func (t *Tls) validate() (bool, error) { +func (t *Tls) Validate() (bool, error) { result, err := govalidator.ValidateStruct(t) return result, appendInvalid(err) } @@ -258,7 +260,7 @@ type PFCP struct { HeartbeatInterval time.Duration `yaml:"heartbeatInterval,omitempty" valid:"type(time.Duration),optional"` } -func (p *PFCP) validate() (bool, error) { +func (p *PFCP) Validate() (bool, error) { result, err := govalidator.ValidateStruct(p) return result, appendInvalid(err) } @@ -268,7 +270,7 @@ type DNS struct { IPv6Addr string `yaml:"ipv6,omitempty" valid:"ipv6,optional"` } -func (d *DNS) validate() (bool, error) { +func (d *DNS) Validate() (bool, error) { result, err := govalidator.ValidateStruct(d) return result, appendInvalid(err) } @@ -277,7 +279,7 @@ type PCSCF struct { IPv4Addr string `yaml:"ipv4,omitempty" valid:"ipv4,required"` } -func (p *PCSCF) validate() (bool, error) { +func (p *PCSCF) Validate() (bool, error) { result, err := govalidator.ValidateStruct(p) return result, appendInvalid(err) } @@ -288,7 +290,7 @@ type Path struct { UPF []string `yaml:"UPF,omitempty" valid:"required"` } -func (p *Path) validate() (bool, error) { +func (p *Path) Validate() (bool, error) { for _, upf := range p.UPF { if result := len(upf); result == 0 { err := errors.New("Invalid UPF: " + upf + ", should not be empty") @@ -308,7 +310,7 @@ type UERoutingInfo struct { SpecificPaths []SpecificPath `yaml:"specificPath,omitempty" valid:"optional"` } -func (u *UERoutingInfo) validate() (bool, error) { +func (u *UERoutingInfo) Validate() (bool, error) { for _, member := range u.Members { if result := govalidator.StringMatches(member, "imsi-[0-9]{5,15}$"); !result { err := errors.New("Invalid member (SUPI): " + member) @@ -317,19 +319,13 @@ func (u *UERoutingInfo) validate() (bool, error) { } for _, path := range u.PathList { - if result, err := path.validate(); err != nil { - return result, err - } - } - - for _, link := range u.Topology { - if result, err := link.validate(); err != nil { + if result, err := path.Validate(); err != nil { return result, err } } for _, path := range u.SpecificPaths { - if result, err := path.validate(); err != nil { + if result, err := path.Validate(); err != nil { return result, err } } @@ -347,7 +343,7 @@ type RouteProfile struct { ForwardingPolicyID string `yaml:"forwardingPolicyID,omitempty" valid:"type(string),stringlength(1|255),required"` } -func (r *RouteProfile) validate() (bool, error) { +func (r *RouteProfile) Validate() (bool, error) { result, err := govalidator.ValidateStruct(r) return result, appendInvalid(err) } @@ -367,7 +363,7 @@ type PfdContent struct { DomainNames []string `yaml:"domainNames,omitempty" valid:"optional"` } -func (p *PfdContent) validate() (bool, error) { +func (p *PfdContent) Validate() (bool, error) { for _, flowDescription := range p.FlowDescriptions { if result := len(flowDescription) > 0; !result { err := errors.New("Invalid FlowDescription: " + flowDescription + ", should not be empty.") @@ -403,9 +399,9 @@ type PfdDataForApp struct { CachingTime *time.Time `yaml:"cachingTime,omitempty" valid:"optional"` } -func (p *PfdDataForApp) validate() (bool, error) { +func (p *PfdDataForApp) Validate() (bool, error) { for _, pfd := range p.Pfds { - if result, err := pfd.validate(); err != nil { + if result, err := pfd.Validate(); err != nil { return result, err } } @@ -424,25 +420,25 @@ type RoutingConfig struct { func (r *RoutingConfig) Validate() (bool, error) { if info := r.Info; info != nil { - if result, err := info.validate(); err != nil { + if result, err := info.Validate(); err != nil { return result, err } } for _, ueRoutingInfo := range r.UERoutingInfo { - if result, err := ueRoutingInfo.validate(); err != nil { + if result, err := ueRoutingInfo.Validate(); err != nil { return result, err } } for _, routeProf := range r.RouteProf { - if result, err := routeProf.validate(); err != nil { + if result, err := routeProf.Validate(); err != nil { return result, err } } for _, pfdData := range r.PfdDatas { - if result, err := pfdData.validate(); err != nil { + if result, err := pfdData.Validate(); err != nil { return result, err } } @@ -453,93 +449,151 @@ func (r *RoutingConfig) Validate() (bool, error) { // UserPlaneInformation describe core network userplane information type UserPlaneInformation struct { - UPNodes map[string]*UPNode `json:"upNodes" yaml:"upNodes" valid:"required"` - Links []*UPLink `json:"links" yaml:"links" valid:"required"` + UPNodes map[string]UPNodeConfigInterface `json:"upNodes" yaml:"upNodes" valid:"required"` + Links []*UPLink `json:"links" yaml:"links" valid:"required"` } -func (u *UserPlaneInformation) validate() (bool, error) { - for _, upNode := range u.UPNodes { - if result, err := upNode.validate(); err != nil { - return result, err +func (u *UserPlaneInformation) Validate() (result bool, err error) { + // register valid upNodeTypes to govalidator + govalidator.TagMap["upNodeType"] = govalidator.Validator(func(str string) bool { + return str == "AN" || str == "UPF" + }) + + // register valid interfaceTypes to govalidator + govalidator.TagMap["interfaceType"] = govalidator.Validator(func(str string) bool { + return str == "N3" || str == "N9" + }) + + // register CIDR to govalidator + govalidator.TagMap["cidr"] = govalidator.Validator(func(str string) bool { + return govalidator.IsCIDR(str) + }) + + result = true + + // validate struct field correctness recursively + if ok, errStruct := govalidator.ValidateStruct(u); !ok { + result = false + err = appendInvalid(errStruct) + } + + var upNodeNames []string + for name, upNode := range u.UPNodes { + upNodeNames = append(upNodeNames, name) + + // call custom validation function (semantic validation) + if ok, errSemantic := upNode.Validate(); !ok { + result = false + err = appendInvalid(errSemantic) } } for _, link := range u.Links { - if result, err := link.validate(); err != nil { - return result, err + if !slices.Contains(upNodeNames, link.A) || !slices.Contains(upNodeNames, link.B) { + result = false + err = appendInvalid(fmt.Errorf("Link %s--%s contains unknown node name, check 'userplaneInformation'", + link.A, link.B)) } } - result, err := govalidator.ValidateStruct(u) - return result, appendInvalid(err) + return result, err } // UPNode represent the user plane node -type UPNode struct { - Type string `json:"type" yaml:"type" valid:"upNodeType,required"` - NodeID string `json:"nodeID" yaml:"nodeID" valid:"host"` - Dnn string `json:"dnn" yaml:"dnn" valid:"type(string),minstringlength(1),optional"` - SNssaiInfos []*SnssaiUpfInfoItem `json:"sNssaiUpfInfos" yaml:"sNssaiUpfInfos,omitempty" valid:"optional"` - InterfaceUpfInfoList []*InterfaceUpfInfoItem `json:"interfaces" yaml:"interfaces,omitempty" valid:"optional"` +type UPNodeConfig struct { + Type string `json:"type" yaml:"type" valid:"upNodeType,required"` } -func (u *UPNode) validate() (bool, error) { - govalidator.TagMap["upNodeType"] = govalidator.Validator(func(str string) bool { - return str == "AN" || str == "UPF" - }) +type UPNodeConfigInterface interface { + Validate() (result bool, err error) + GetType() string +} - for _, snssaiInfo := range u.SNssaiInfos { - if result, err := snssaiInfo.Validate(); err != nil { - return result, err - } - } +type GNBConfig struct { + *UPNodeConfig +} + +type UPFConfig struct { + *UPNodeConfig + NodeID string `json:"nodeID" yaml:"nodeID" valid:"host,required"` + SNssaiInfos []*SnssaiUpfInfoItem `json:"sNssaiUpfInfos" yaml:"sNssaiUpfInfos,omitempty" valid:"required"` + Interfaces []*Interface `json:"interfaces" yaml:"interfaces" valid:"required"` +} + +func (gNB *GNBConfig) Validate() (result bool, err error) { + return true, nil +} + +func (gNB *GNBConfig) GetType() string { + return gNB.Type +} + +func (upf *UPFConfig) Validate() (result bool, err error) { + result = true n3IfsNum := 0 n9IfsNum := 0 - for _, interfaceUpfInfo := range u.InterfaceUpfInfoList { - if result, err := interfaceUpfInfo.validate(); err != nil { - return result, err + + for _, iface := range upf.Interfaces { + if ok, errIface := iface.Validate(); !ok { + result = false + err = appendInvalid(errIface) } - if interfaceUpfInfo.InterfaceType == "N3" { + + if iface.InterfaceType == "N3" { n3IfsNum++ } - if interfaceUpfInfo.InterfaceType == "N9" { + if iface.InterfaceType == "N9" { n9IfsNum++ } + } + + if n3IfsNum == 0 && n9IfsNum == 0 { + result = false + err = appendInvalid(fmt.Errorf("UPF %s must have a user plane interface (N3 or N9)", upf.NodeID)) + } + + if n3IfsNum > 1 || n9IfsNum > 1 { + result = false + err = appendInvalid(fmt.Errorf( + "UPF %s: There is currently no support for multiple N3/ N9 interfaces: N3 number(%d), N9 number(%d)", + upf.NodeID, n3IfsNum, n9IfsNum)) + } - if n3IfsNum > 1 || n9IfsNum > 1 { - return false, fmt.Errorf( - "Not support multiple InterfaceUpfInfo for the same type: N3 number(%d), N9 number(%d)", - n3IfsNum, n9IfsNum) + for _, snssaiInfo := range upf.SNssaiInfos { + if ok, errSNSSAI := snssaiInfo.Validate(); !ok { + result = false + err = appendInvalid(errSNSSAI) } } - result, err := govalidator.ValidateStruct(u) - return result, appendInvalid(err) + + return result, err +} + +func (upf *UPFConfig) GetNodeID() string { + return upf.NodeID +} + +func (upf *UPFConfig) GetType() string { + return upf.Type } -type InterfaceUpfInfoItem struct { +type Interface struct { InterfaceType models.UpInterfaceType `json:"interfaceType" yaml:"interfaceType" valid:"required"` Endpoints []string `json:"endpoints" yaml:"endpoints" valid:"required"` - NetworkInstances []string `json:"networkInstances" yaml:"networkInstances" valid:"required"` + NetworkInstances []string `json:"networkInstances" yaml:"networkInstances" valid:"optional"` } -func (i *InterfaceUpfInfoItem) validate() (bool, error) { - interfaceType := i.InterfaceType - if result := (interfaceType == "N3" || interfaceType == "N9"); !result { - err := errors.New("Invalid interfaceType: " + string(interfaceType) + ", should be N3 or N9.") - return false, err - } - +func (i *Interface) Validate() (result bool, err error) { + result = true for _, endpoint := range i.Endpoints { - if result := govalidator.IsHost(endpoint); !result { - err := errors.New("Invalid endpoint:" + endpoint + ", should be IPv4.") - return false, err + if ok := govalidator.IsHost(endpoint); !ok { + result = false + err = appendInvalid(fmt.Errorf("Invalid endpoint: %s should be one of IPv4, IPv6, FQDN.", endpoint)) } } - - result, err := govalidator.ValidateStruct(i) - return result, appendInvalid(err) + return } type SnssaiUpfInfoItem struct { @@ -547,54 +601,78 @@ type SnssaiUpfInfoItem struct { DnnUpfInfoList []*DnnUpfInfoItem `json:"dnnUpfInfoList" yaml:"dnnUpfInfoList" valid:"required"` } -func (s *SnssaiUpfInfoItem) Validate() (bool, error) { +func (s *SnssaiUpfInfoItem) Validate() (result bool, err error) { + result = true + if s.SNssai != nil { - if result := (s.SNssai.Sst >= 0 && s.SNssai.Sst <= 255); !result { - err := errors.New("Invalid sNssai.Sst: " + strconv.Itoa(int(s.SNssai.Sst)) + ", should be in range 0~255.") - return false, err + if ok := (s.SNssai.Sst >= 0 && s.SNssai.Sst <= 255); !ok { + result = false + err = appendInvalid(fmt.Errorf("Invalid sNssai.Sst: %s should be in range 0-255.", + strconv.Itoa(int(s.SNssai.Sst)))) } if s.SNssai.Sd != "" { - if result := govalidator.StringMatches(s.SNssai.Sd, "^[0-9A-Fa-f]{6}$"); !result { - err := errors.New("Invalid sNssai.Sd: " + s.SNssai.Sd + - ", should be 3 bytes hex string and in range 000000~FFFFFF.") - return false, err + if ok := govalidator.StringMatches(s.SNssai.Sd, "^[0-9A-Fa-f]{6}$"); !ok { + result = false + err = appendInvalid(fmt.Errorf( + "Invalid sNssai.Sd: %s should be 3 bytes hex string and in range 000000-FFFFFF.", s.SNssai.Sd)) } } } for _, dnnInfo := range s.DnnUpfInfoList { - if result, err := dnnInfo.validate(); err != nil { - return result, err + if ok, errDNNInfo := dnnInfo.Validate(); !ok { + result = false + err = appendInvalid(errDNNInfo) } } - result, err := govalidator.ValidateStruct(s) - return result, appendInvalid(err) + return } type DnnUpfInfoItem struct { Dnn string `json:"dnn" yaml:"dnn" valid:"required"` DnaiList []string `json:"dnaiList" yaml:"dnaiList" valid:"optional"` PduSessionTypes []models.PduSessionType `json:"pduSessionTypes" yaml:"pduSessionTypes" valid:"optional"` - Pools []*UEIPPool `json:"pools" yaml:"pools" valid:"optional"` + Pools []*UEIPPool `json:"pools" yaml:"pools" valid:"required"` StaticPools []*UEIPPool `json:"staticPools" yaml:"staticPools" valid:"optional"` } -func (d *DnnUpfInfoItem) validate() (bool, error) { - if result := len(d.Dnn); result == 0 { - err := errors.New("Invalid DnnUpfInfoItem.dnn: " + d.Dnn + ", should not be empty.") - return false, err +func (d *DnnUpfInfoItem) Validate() (result bool, err error) { + result = true + + if len(d.Dnn) == 0 { + result = false + err = appendInvalid(fmt.Errorf("Invalid DnnUpfInfoItem: dnn must not be empty.")) + } + + if len(d.Pools) == 0 { + result = false + err = appendInvalid(fmt.Errorf("Invalid DnnUpfInfoItem: requires at least one dynamic IP pool.")) } + var prefixes []netaddr.IPPrefix for _, pool := range d.Pools { - if result, err := pool.validate(); err != nil { - return result, err + prefix, errCidrCheck := netaddr.ParseIPPrefix(pool.Cidr) + if errCidrCheck != nil { + result = false + err = appendInvalid(fmt.Errorf("Invalid CIDR: %s.", pool.Cidr)) + } else { + prefixes = append(prefixes, prefix) } } - result, err := govalidator.ValidateStruct(d) - return result, appendInvalid(err) + // check overlap with dynamic and static pools + for i := 0; i < len(prefixes); i++ { + for j := i + 1; j < len(prefixes); j++ { + if prefixes[i].Overlaps(prefixes[j]) { + result = false + err = appendInvalid(fmt.Errorf("overlap detected between pools %s and %s", prefixes[i], prefixes[j])) + } + } + } + + return result, err } type UPLink struct { @@ -602,47 +680,17 @@ type UPLink struct { B string `json:"B" yaml:"B" valid:"required"` } -func (u *UPLink) validate() (bool, error) { - result, err := govalidator.ValidateStruct(u) - return result, appendInvalid(err) -} - -func appendInvalid(err error) error { - var errs govalidator.Errors - - if err == nil { - return nil - } - - es := err.(govalidator.Errors).Errors() - for _, e := range es { - errs = append(errs, fmt.Errorf("invalid %w", e)) - } - - return error(errs) -} - type UEIPPool struct { Cidr string `yaml:"cidr" valid:"cidr,required"` } -func (u *UEIPPool) validate() (bool, error) { - govalidator.TagMap["cidr"] = govalidator.Validator(func(str string) bool { - isCIDR := govalidator.IsCIDR(str) - return isCIDR - }) - - result, err := govalidator.ValidateStruct(u) - return result, appendInvalid(err) -} - type SpecificPath struct { DestinationIP string `yaml:"dest,omitempty" valid:"cidr,required"` DestinationPort string `yaml:"DestinationPort,omitempty" valid:"port,optional"` Path []string `yaml:"path" valid:"required"` } -func (p *SpecificPath) validate() (bool, error) { +func (p *SpecificPath) Validate() (bool, error) { govalidator.TagMap["cidr"] = govalidator.Validator(func(str string) bool { isCIDR := govalidator.IsCIDR(str) return isCIDR @@ -664,7 +712,7 @@ type PlmnID struct { Mnc string `yaml:"mnc"` } -func (p *PlmnID) validate() (bool, error) { +func (p *PlmnID) Validate() (bool, error) { mcc := p.Mcc if result := govalidator.StringMatches(mcc, "^[0-9]{3}$"); !result { err := fmt.Errorf("Invalid mcc: %s, should be a 3-digit number", mcc) @@ -685,7 +733,7 @@ type TimerValue struct { MaxRetryTimes int `yaml:"maxRetryTimes,omitempty" valid:"type(int)"` } -func (t *TimerValue) validate() (bool, error) { +func (t *TimerValue) Validate() (bool, error) { result, err := govalidator.ValidateStruct(t) return result, err } @@ -804,3 +852,18 @@ func (c *Config) GetCertKeyPath() string { defer c.RUnlock() return c.Configuration.Sbi.Tls.Key } + +func appendInvalid(err error) error { + var errs govalidator.Errors + + if err == nil { + return nil + } + + es := err.(govalidator.Errors).Errors() + for _, e := range es { + errs = append(errs, fmt.Errorf("invalid %w", e)) + } + + return error(errs) +} diff --git a/pkg/factory/config_test.go b/pkg/factory/config_test.go index 67b56b95..a8c7215a 100644 --- a/pkg/factory/config_test.go +++ b/pkg/factory/config_test.go @@ -9,6 +9,235 @@ import ( "github.com/free5gc/smf/pkg/factory" ) +func baseGNB() *factory.GNBConfig { + return &factory.GNBConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "AN", + }, + } +} + +func baseUPF() *factory.UPFConfig { + return &factory.UPFConfig{ + UPNodeConfig: &factory.UPNodeConfig{ + Type: "UPF", + }, + NodeID: "127.0.0.8", + SNssaiInfos: []*factory.SnssaiUpfInfoItem{ + { + SNssai: &models.Snssai{ + Sst: int32(1), + Sd: "010203", + }, + DnnUpfInfoList: []*factory.DnnUpfInfoItem{ + { + Dnn: "internet", + Pools: []*factory.UEIPPool{ + { + Cidr: "10.60.0.0/16", + }, + }, + }, + }, + }, + }, + Interfaces: []*factory.Interface{ + { + InterfaceType: "N3", + Endpoints: []string{ + "127.0.0.8", + }, + NetworkInstances: []string{ + "internet", + }, + }, + }, + } +} + +func TestUPNodeConfigInterfaceValidation(t *testing.T) { + testcase := []struct { + Name string + UPNodeConfig factory.UPNodeConfigInterface + Valid bool + }{ + { + Name: "Valid gNB", + UPNodeConfig: baseGNB(), + Valid: true, + }, + { + Name: "Valid UPF", + UPNodeConfig: baseUPF(), + Valid: true, + }, + { + Name: "UPF with wrong type", + UPNodeConfig: func() *factory.UPFConfig { + config := baseUPF() + config.UPNodeConfig.Type = "xxx" + return config + }(), + Valid: false, + }, + { + Name: "UPF with wrong NodeID", + UPNodeConfig: func() *factory.UPFConfig { + config := baseUPF() + config.NodeID = "foobar" + return config + }(), + Valid: false, + }, + { + Name: "UPF with nil sNssaiUpfInfos", + UPNodeConfig: func() *factory.UPFConfig { + config := baseUPF() + config.SNssaiInfos = nil + return config + }(), + Valid: false, + }, + { + Name: "UPF with empty sNssaiUpfInfos", + UPNodeConfig: func() *factory.UPFConfig { + config := baseUPF() + config.SNssaiInfos = []*factory.SnssaiUpfInfoItem{} + return config + }(), + Valid: false, + }, + { + Name: "UPF with nil interfaces", + UPNodeConfig: func() *factory.UPFConfig { + config := baseUPF() + config.Interfaces = nil + return config + }(), + Valid: false, + }, + { + Name: "UPF with empty interfaces", + UPNodeConfig: func() *factory.UPFConfig { + config := baseUPF() + config.Interfaces = []*factory.Interface{} + return config + }(), + Valid: false, + }, + { + Name: "UPF with overlapping pools in DnnUpfInfoItem.Pools", + UPNodeConfig: func() *factory.UPFConfig { + config := baseUPF() + config.SNssaiInfos[0].DnnUpfInfoList = []*factory.DnnUpfInfoItem{ + { + Dnn: "internet", + Pools: []*factory.UEIPPool{ + { + Cidr: "10.60.0.0/16", + }, + { + Cidr: "10.60.10.0/16", + }, + }, + }, + } + return config + }(), + Valid: false, + }, + { + Name: "UPF with overlapping pools in DnnUpfInfoItem.Pools and DnnUpfInfoItem.StaticPools", + UPNodeConfig: func() *factory.UPFConfig { + config := baseUPF() + config.SNssaiInfos[0].DnnUpfInfoList = []*factory.DnnUpfInfoItem{ + { + Dnn: "internet", + Pools: []*factory.UEIPPool{ + { + Cidr: "10.60.0.0/16", + }, + }, + StaticPools: []*factory.UEIPPool{ + { + Cidr: "10.60.10.0/16", + }, + }, + }, + } + return config + }(), + Valid: false, + }, + { + Name: "UPF without N3 interface", + UPNodeConfig: func() *factory.UPFConfig { + config := baseUPF() + config.Interfaces = []*factory.Interface{} + return config + }(), + Valid: false, + }, + } + + for _, tc := range testcase { + t.Run(tc.Name, func(t *testing.T) { + ok, err := tc.UPNodeConfig.Validate() + require.Equal(t, tc.Valid, ok) + require.Nil(t, err) + }) + } +} + +func TestUserplaneInformationValidation(t *testing.T) { + testcase := []struct { + Name string + Upi *factory.UserPlaneInformation + Valid bool + }{ + { + Name: "Valid userPlaneInformation", + Upi: &factory.UserPlaneInformation{ + UPNodes: map[string]factory.UPNodeConfigInterface{ + "gNB": baseGNB(), + "UPF1": baseUPF(), + }, + Links: []*factory.UPLink{ + { + A: "gNB", + B: "UPF1", + }, + }, + }, + Valid: true, + }, + { + Name: "Link with non-existing node", + Upi: &factory.UserPlaneInformation{ + UPNodes: map[string]factory.UPNodeConfigInterface{ + "gNB": baseGNB(), + "UPF1": baseUPF(), + }, + Links: []*factory.UPLink{ + { + A: "gNB", + B: "UPF", + }, + }, + }, + Valid: false, + }, + } + + for _, tc := range testcase { + t.Run(tc.Name, func(t *testing.T) { + ok, err := tc.Upi.Validate() + require.Equal(t, tc.Valid, ok) + require.Nil(t, err) + }) + } +} + func TestSnssaiInfoItem(t *testing.T) { testcase := []struct { Name string