diff --git a/README.md b/README.md index 5c6a658..915c90a 100644 --- a/README.md +++ b/README.md @@ -12,30 +12,9 @@ If you don't need to use handover procedures, consider using [UERANSIM](https:// - golang - make (optional) -### Runtime dependencies -- iproute2 -- iptables - - ### Build and install Simply run `make build` and `make install`. -### Docker -If you plan using NextMN-gNB Lite with Docker: -- The container required the `NET_ADMIN` capability; -- - The container required the forwarding to be enabled (not enabled by the gNB itself); -- The tun interface (`/dev/net/tun`) must be available in the container. - -This can be done in `docker-compose.yaml` by defining the following for the service: - -```yaml -cap_add: - - NET_ADMIN -devices: - - "/dev/net/tun" -sysctls: - - net.ipv4.ip_forward=1 -``` ## Author Louis Royer diff --git a/config/config.yaml b/config/config.yaml index 7d846b4..6020bd0 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -1,6 +1,11 @@ control: uri: "http://192.0.2.2:8080" bind-addr: "192.0.2.2:8080" +ran: + bind-addr: "198.51.100.2:1234" +cp: + uri: "http://192.0.2.3:8080" +gtp: "198.51.100.10" logger: - level: "debug" + level: "trace" diff --git a/go.mod b/go.mod index e73b824..c0833b9 100644 --- a/go.mod +++ b/go.mod @@ -5,41 +5,43 @@ go 1.22.7 require ( github.com/adrg/xdg v0.5.3 github.com/gin-gonic/gin v1.10.0 - github.com/nextmn/json-api v0.0.13 + github.com/nextmn/json-api v0.0.14 github.com/nextmn/logrus-formatter v0.0.1 github.com/sirupsen/logrus v1.9.3 github.com/urfave/cli/v2 v2.27.5 + github.com/wmnsk/go-gtp v0.8.11 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/bytedance/sonic v1.11.6 // indirect - github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/bytedance/sonic v1.12.5 // indirect + github.com/bytedance/sonic/loader v0.2.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gabriel-vasile/mimetype v1.4.7 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.20.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect - github.com/gofrs/uuid v4.4.0+incompatible // indirect + github.com/go-playground/validator/v10 v10.23.0 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect + github.com/vishvananda/netlink v1.3.0 // indirect + github.com/vishvananda/netns v0.0.5 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - golang.org/x/arch v0.8.0 // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/text v0.15.0 // indirect - google.golang.org/protobuf v1.34.1 // indirect + golang.org/x/arch v0.12.0 // indirect + golang.org/x/crypto v0.29.0 // indirect + golang.org/x/net v0.31.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.20.0 // indirect + google.golang.org/protobuf v1.35.2 // indirect ) diff --git a/go.sum b/go.sum index 1d2277c..b763da7 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,10 @@ github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= -github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= -github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= -github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic v1.12.5 h1:hoZxY8uW+mT+OpkcUWw4k0fDINtOcVavEsGfzwzFU/w= +github.com/bytedance/sonic v1.12.5/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E= +github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= @@ -13,8 +14,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t 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/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA= +github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= @@ -25,20 +26,18 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= -github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= -github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= +github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= @@ -49,12 +48,14 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/nextmn/json-api v0.0.13 h1:k8Z0Oo9et5PvdCa4wUmJE9TAHJp1zTkoAmvy1LQcoyQ= -github.com/nextmn/json-api v0.0.13/go.mod h1:CQXeNPj9MDGsEExtnqJFIGjLgZAKsmOoO2fy+mep7Ak= +github.com/nextmn/json-api v0.0.14 h1:m4uHOVcXsxkXoxbrhqemLTRG4T86eYkejjirew1nDUU= +github.com/nextmn/json-api v0.0.14/go.mod h1:CQXeNPj9MDGsEExtnqJFIGjLgZAKsmOoO2fy+mep7Ak= github.com/nextmn/logrus-formatter v0.0.1 h1:Bsf78jjiEESc+rV8xE6IyKj4frDPGMwXFNrLQzm6A1E= github.com/nextmn/logrus-formatter v0.0.1/go.mod h1:vdSZ+sIcSna8vjbXkSFxsnsKHqRwaUEed4JCPcXoGyM= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pascaldekloe/goe v0.1.1 h1:Ah6WQ56rZONR3RW3qWa2NCZ6JAVvSpUcoLBaOmYFt9Q= +github.com/pascaldekloe/goe v0.1.1/go.mod h1:KSyfaxQOh0HZPjDP1FL/kFtbqYqrALJTaMafFUIccqU= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -64,13 +65,11 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -79,30 +78,34 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk= +github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= +github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= +github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/wmnsk/go-gtp v0.8.11 h1:5TaFh4eIz4b6xMyUXGwEeei8QkOscVOZQ4hFqS7p0ko= +github.com/wmnsk/go-gtp v0.8.11/go.mod h1:eXT4O+VPLMM5Pjf4PejmPvXjB0RdJPt3B86PDLGDhjw= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= -golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg= +golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/app/control.go b/internal/app/control.go index b47f6be..50017d3 100644 --- a/internal/app/control.go +++ b/internal/app/control.go @@ -9,6 +9,7 @@ import ( "context" "net" "net/http" + "net/netip" "time" "github.com/nextmn/json-api/healthcheck" @@ -18,19 +19,31 @@ import ( ) type HttpServerEntity struct { - srv *http.Server + srv *http.Server + ps *PduSessions + radio *Radio } -func NewHttpServerEntity(bindAddr string) *HttpServerEntity { +func NewHttpServerEntity(bindAddr netip.AddrPort, radio *Radio, ps *PduSessions) *HttpServerEntity { // TODO: gin.SetMode(gin.DebugMode) / gin.SetMode(gin.ReleaseMode) depending on log level r := gin.Default() r.GET("/status", Status) + + // Radio + r.POST("/radio/peer", radio.Peer) + + // Pdu Sessions + r.POST("/ps/establishment-request", ps.EstablishmentRequest) + r.POST("/ps/n2-establishment-request", ps.N2EstablishmentRequest) + logrus.WithFields(logrus.Fields{"http-addr": bindAddr}).Info("HTTP Server created") e := HttpServerEntity{ srv: &http.Server{ - Addr: bindAddr, + Addr: bindAddr.String(), Handler: r, }, + ps: ps, + radio: radio, } return &e } diff --git a/internal/app/gtp.go b/internal/app/gtp.go new file mode 100644 index 0000000..88254e9 --- /dev/null +++ b/internal/app/gtp.go @@ -0,0 +1,50 @@ +// Copyright 2024 Louis Royer and the NextMN contributors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. +// SPDX-License-Identifier: MIT + +package app + +import ( + "context" + "net" + "net/netip" + + "github.com/sirupsen/logrus" + "github.com/wmnsk/go-gtp/gtpv1" + "github.com/wmnsk/go-gtp/gtpv1/message" +) + +const GTPU_PORT = 2152 + +func (s *Setup) StartGtpUProtocolEntity(ctx context.Context, ipAddress netip.Addr) error { + logrus.WithFields(logrus.Fields{"listen-addr": ipAddress}).Info("Creating new GTP-U Protocol Entity") + laddr := net.UDPAddrFromAddrPort(netip.AddrPortFrom(ipAddress, GTPU_PORT)) + uConn := gtpv1.NewUPlaneConn(laddr) + uConn.DisableErrorIndication() + uConn.AddHandler(message.MsgTypeTPDU, func(c gtpv1.Conn, senderAddr net.Addr, msg message.Message) error { + return tpduHandler(c, senderAddr, msg, s.psMan, s.rDaemon) + }) + go func(ctx context.Context) error { + defer uConn.Close() + if err := uConn.ListenAndServe(ctx); err != nil { + logrus.WithError(err).Trace("GTP uConn closed") + return err + } + logrus.Trace("GTP uConn closed") + return nil + }(ctx) + + return nil +} + +// handle GTP PDU (Downlink) +func tpduHandler(c gtpv1.Conn, senderAddr net.Addr, msg message.Message, psMan *PduSessionsManager, rDaemon *RadioDaemon) error { + teid := msg.TEID() + ue, err := psMan.GetUECtrl(teid) + if err != nil { + return err + } + packet := msg.(*message.TPDU).Decapsulate() + return rDaemon.WriteDownlink(packet, ue) +} diff --git a/internal/app/pdu_session.go b/internal/app/pdu_session.go new file mode 100644 index 0000000..2be9fe9 --- /dev/null +++ b/internal/app/pdu_session.go @@ -0,0 +1,145 @@ +// Copyright 2024 Louis Royer and the NextMN contributors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. +// SPDX-License-Identifier: MIT + +package app + +import ( + "bytes" + "encoding/json" + "net/http" + "net/netip" + "sync" + + "github.com/nextmn/json-api/jsonapi" + "github.com/nextmn/json-api/jsonapi/n1n2" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" +) + +type PduSessions struct { + PduSessionsMap sync.Map // key : UE 5G ip address; value: UE Control URI + UserAgent string + Client http.Client + Control jsonapi.ControlURI + Cp jsonapi.ControlURI + GnbGtp netip.Addr + manager *PduSessionsManager +} + +func NewPduSessions(control jsonapi.ControlURI, cp jsonapi.ControlURI, manager *PduSessionsManager, userAgent string, gnbGtp netip.Addr) *PduSessions { + return &PduSessions{ + Client: http.Client{}, + PduSessionsMap: sync.Map{}, + UserAgent: userAgent, + Control: control, + Cp: cp, + GnbGtp: gnbGtp, + manager: manager, + } + +} + +// request from UE +func (p *PduSessions) EstablishmentRequest(c *gin.Context) { + // get PseReq + var ps n1n2.PduSessionEstabReqMsg + if err := c.BindJSON(&ps); err != nil { + logrus.WithError(err).Error("could not deserialize") + c.JSON(http.StatusBadRequest, jsonapi.MessageWithError{Message: "could not deserialize", Error: err}) + return + } + + logrus.WithFields(logrus.Fields{ + "ue": ps.Ue.String(), + }).Info("New PDU Session establishment Request") + + // forward to cp + reqBody, err := json.Marshal(ps) + if err != nil { + c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "could not marshal json", Error: err}) + return + } + req, err := http.NewRequestWithContext(c, http.MethodPost, p.Cp.JoinPath("ps/establishment-request").String(), bytes.NewBuffer(reqBody)) + if err != nil { + c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "could not create request", Error: err}) + return + } + req.Header.Set("User-Agent", p.UserAgent) + req.Header.Set("Content-Type", "application/json; charset=UTF-8") + resp, err := p.Client.Do(req) + if err != nil { + c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "no http response", Error: err}) + return + } + defer resp.Body.Close() +} + +// request from CP +func (p *PduSessions) N2EstablishmentRequest(c *gin.Context) { + var ps n1n2.N2PduSessionReqMsg + if err := c.BindJSON(&ps); err != nil { + logrus.WithError(err).Error("could not deserialize") + c.JSON(http.StatusBadRequest, jsonapi.MessageWithError{Message: "could not deserialize", Error: err}) + return + } + logrus.WithFields(logrus.Fields{ + "ue": ps.UeInfo.Header.Ue.String(), + "upf": ps.Upf, + "uplink-teid": ps.UplinkTeid, + }).Info("New PDU Session establishment Request") + // allocate downlink teid + downlinkTeid, err := p.manager.NewPduSession(c, ps.UeInfo.Addr, ps.UeInfo.Header.Ue, ps.Upf, ps.UplinkTeid) + if err != nil { + c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "could create PDU Session", Error: err}) + return + } + + // send PseAccept to UE + reqBody, err := json.Marshal(ps.UeInfo) + if err != nil { + c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "could not marshal json", Error: err}) + return + } + req, err := http.NewRequestWithContext(c, http.MethodPost, ps.UeInfo.Header.Ue.JoinPath("ps/establishment-accept").String(), bytes.NewBuffer(reqBody)) + if err != nil { + c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "could not create request", Error: err}) + return + } + req.Header.Set("User-Agent", p.UserAgent) + req.Header.Set("Content-Type", "application/json; charset=UTF-8") + resp, err := p.Client.Do(req) + if err != nil { + c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "no http response", Error: err}) + return + } + defer resp.Body.Close() + + psresp := n1n2.N2PduSessionRespMsg{ + UeInfo: ps.UeInfo, + Gnb: p.GnbGtp, + DownlinkTeid: downlinkTeid, + } + // send N2PsResp to CP (with dl fteid) + n2reqBody, err := json.Marshal(psresp) + if err != nil { + c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "could not marshal json", Error: err}) + return + } + req2, err := http.NewRequestWithContext(c, http.MethodPost, ps.Cp.JoinPath("ps/n2-establishment-response").String(), bytes.NewBuffer(n2reqBody)) + if err != nil { + c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "could not create request", Error: err}) + return + } + req2.Header.Set("User-Agent", p.UserAgent) + req2.Header.Set("Content-Type", "application/json; charset=UTF-8") + resp2, err := p.Client.Do(req2) + if err != nil { + c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "no http response", Error: err}) + return + } + defer resp2.Body.Close() + +} diff --git a/internal/app/pdu_sessions_manager.go b/internal/app/pdu_sessions_manager.go new file mode 100644 index 0000000..b70900f --- /dev/null +++ b/internal/app/pdu_sessions_manager.go @@ -0,0 +1,140 @@ +// Copyright 2024 Louis Royer and the NextMN contributors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. +// SPDX-License-Identifier: MIT + +package app + +import ( + "context" + "fmt" + "math/rand" + "net" + "net/netip" + "sync" + "time" + + "github.com/nextmn/json-api/jsonapi" + + "github.com/sirupsen/logrus" + "github.com/wmnsk/go-gtp/gtpv1" + "github.com/wmnsk/go-gtp/gtpv1/message" +) + +type PduSessionsManager struct { + sync.Mutex + + Downlink map[uint32]jsonapi.ControlURI // teid: UE control uri + Uplink map[netip.Addr]*Fteid // ue 5G ip address: uplink fteid + GtpAddr netip.Addr + upfs map[netip.Addr]*gtpv1.UPlaneConn +} + +func NewPduSessionsManager(gtpAddr netip.Addr) *PduSessionsManager { + return &PduSessionsManager{ + Downlink: make(map[uint32]jsonapi.ControlURI), + Uplink: make(map[netip.Addr]*Fteid), + GtpAddr: gtpAddr, + upfs: make(map[netip.Addr]*gtpv1.UPlaneConn), + } +} + +func (p *PduSessionsManager) WriteUplink(ctx context.Context, pkt []byte) error { + if len(pkt) < 20 { + logrus.Trace("too small to be an ipv4 packet") + return fmt.Errorf("Too small to be an ipv4 packet") + } + if (pkt[0] >> 4) != 4 { + logrus.Trace("not an ipv4 packet") + return fmt.Errorf("Not an ipv4 packet") + } + src := netip.AddrFrom4([4]byte{pkt[12], pkt[13], pkt[14], pkt[15]}) + fteid, ok := p.Uplink[src] + if !ok { + logrus.WithFields(logrus.Fields{ + "ue": src, + }).Trace("unknown UE") + return fmt.Errorf("Unknown UE") + } + gpdu := message.NewHeaderWithExtensionHeaders(0x30, message.MsgTypeTPDU, fteid.Teid, 0, pkt, []*message.ExtensionHeader{}...) + b, err := gpdu.Marshal() + if err != nil { + return err + } + uConn, ok := p.upfs[fteid.IpAddr] + raddr := net.UDPAddrFromAddrPort(netip.AddrPortFrom(fteid.IpAddr, GTPU_PORT)) + if !ok { + laddr := net.UDPAddrFromAddrPort(netip.AddrPortFrom(p.GtpAddr, 0)) + uConn, err = gtpv1.DialUPlane(ctx, laddr, raddr) + if err != nil { + logrus.WithFields(logrus.Fields{ + "upf": raddr, + }).Error("Failure to dial UPF") + return err + } + p.upfs[fteid.IpAddr] = uConn + go func(ctx context.Context, uConn *gtpv1.UPlaneConn) error { + select { + case <-ctx.Done(): + uConn.Close() + return ctx.Err() + } + return nil + }(ctx, uConn) + } + logrus.WithFields(logrus.Fields{ + "fteid": fteid, + }).Trace("Forwarding packet to GTP") + _, err = uConn.WriteTo(b, raddr) + return err +} + +func (p *PduSessionsManager) GetUECtrl(teid uint32) (jsonapi.ControlURI, error) { + ueCtrl, ok := p.Downlink[teid] + if !ok { + return ueCtrl, fmt.Errorf("Unknown UE") + } + return ueCtrl, nil +} + +type Fteid struct { + IpAddr netip.Addr + Teid uint32 +} + +func (p *PduSessionsManager) NewPduSession(ctx context.Context, ueIpAddr netip.Addr, ueControlURI jsonapi.ControlURI, upf netip.Addr, uplinkTeid uint32) (uint32, error) { + p.Lock() + defer p.Unlock() + + ctxTimeout, cancel := context.WithTimeout(ctx, time.Duration(time.Millisecond*10)) // 10 ms should be more than enough… + defer cancel() + dlTeid, err := p.newTeidDl(ctxTimeout, ueControlURI) + if err != nil { + return dlTeid, err + } + p.Uplink[ueIpAddr] = &Fteid{ + IpAddr: upf, + Teid: uplinkTeid, + } + return dlTeid, err +} + +// Warning: not thread safe +func (p *PduSessionsManager) newTeidDl(ctx context.Context, ueControlURI jsonapi.ControlURI) (uint32, error) { + // teid are attributed randomly, and unique per pdu session + for { + select { + case <-ctx.Done(): + return 0, ctx.Err() + default: + teid := rand.Uint32() + if teid == 0 { + continue // bad luck :( + } + if _, exists := p.Downlink[teid]; !exists { + p.Downlink[teid] = ueControlURI + return teid, nil + } + } + } +} diff --git a/internal/app/radio.go b/internal/app/radio.go new file mode 100644 index 0000000..89f96d8 --- /dev/null +++ b/internal/app/radio.go @@ -0,0 +1,95 @@ +// Copyright 2024 Louis Royer and the NextMN contributors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. +// SPDX-License-Identifier: MIT + +package app + +import ( + "bytes" + "encoding/json" + "fmt" + "net" + "net/http" + "net/netip" + "sync" + + "github.com/nextmn/json-api/jsonapi" + "github.com/nextmn/json-api/jsonapi/n1n2" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" +) + +type Radio struct { + peerMap sync.Map // key: UE Control URI, value: UE ran ip address + Client http.Client + Control jsonapi.ControlURI + Data netip.AddrPort + UserAgent string +} + +func NewRadio(control jsonapi.ControlURI, data netip.AddrPort, userAgent string) *Radio { + return &Radio{ + peerMap: sync.Map{}, + Client: http.Client{}, + Control: control, + Data: data, + UserAgent: userAgent, + } +} + +func (r *Radio) Write(pkt []byte, srv *net.UDPConn, ue jsonapi.ControlURI) error { + ueRan, ok := r.peerMap.Load(ue) + if !ok { + logrus.Trace("Unknown UE") + return fmt.Errorf("Unknown UE") + } + + _, err := srv.WriteToUDPAddrPort(pkt, ueRan.(netip.AddrPort)) + + return err +} + +// allow to peer to ue +func (r *Radio) Peer(c *gin.Context) { + var peer n1n2.RadioPeerMsg + if err := c.BindJSON(&peer); err != nil { + logrus.WithError(err).Error("could not deserialize") + c.JSON(http.StatusBadRequest, jsonapi.MessageWithError{Message: "could not deserialize", Error: err}) + return + } + r.peerMap.Store(peer.Control, peer.Data) + logrus.WithFields(logrus.Fields{ + "peer-control": peer.Control.String(), + "peer-ran": peer.Data, + }).Info("New peer radio link") + c.Status(http.StatusNoContent) + msg := n1n2.RadioPeerMsg{ + Control: r.Control, + Data: r.Data, + } + + reqBody, err := json.Marshal(msg) + if err != nil { + c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "could not marshal json", Error: err}) + return + } + req, err := http.NewRequestWithContext(c, http.MethodPost, peer.Control.JoinPath("radio/peer").String(), bytes.NewBuffer(reqBody)) + if err != nil { + c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "could not create request", Error: err}) + return + } + req.Header.Set("User-Agent", r.UserAgent) + req.Header.Set("Content-Type", "application/json; charset=UTF-8") + resp, err := r.Client.Do(req) + if err != nil { + c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "no http response", Error: err}) + return + } + defer resp.Body.Close() + + // TODO: handle ue failure + + c.Status(http.StatusNoContent) +} diff --git a/internal/app/radio_daemon.go b/internal/app/radio_daemon.go new file mode 100644 index 0000000..e823afd --- /dev/null +++ b/internal/app/radio_daemon.go @@ -0,0 +1,100 @@ +// Copyright 2024 Louis Royer and the NextMN contributors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. +// SPDX-License-Identifier: MIT + +package app + +import ( + "context" + "fmt" + "net" + "net/netip" + + "github.com/nextmn/json-api/jsonapi" + + "github.com/sirupsen/logrus" +) + +const ( + TUN_MTU = 1400 +) + +type RadioDaemon struct { + DlQueue chan DLPkt + radio *Radio + gnbRanAddr netip.AddrPort + PduSessionsManager *PduSessionsManager + srv *net.UDPConn +} + +func NewRadioDaemon(radio *Radio, psMan *PduSessionsManager, gnbRanAddr netip.AddrPort) *RadioDaemon { + return &RadioDaemon{ + DlQueue: make(chan DLPkt), + radio: radio, + PduSessionsManager: psMan, + gnbRanAddr: gnbRanAddr, + } +} + +func (r *RadioDaemon) runUplinkDaemon(ctx context.Context, srv *net.UDPConn) error { + if srv == nil { + logrus.Error("nil server") + return fmt.Errorf("nil srv") + } + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + buf := make([]byte, TUN_MTU) + n, err := srv.Read(buf) + if err != nil { + logrus.WithError(err).Trace("error reading udp packet") + return err + } + logrus.Trace("received new packet from ue") + r.PduSessionsManager.WriteUplink(ctx, buf[:n]) + } + } + return nil +} + +type DLPkt struct { + Ue jsonapi.ControlURI + Payload []byte +} + +func (r *RadioDaemon) WriteDownlink(payload []byte, ue jsonapi.ControlURI) error { + if r.srv == nil { + return fmt.Errorf("nil srv") + } + return r.radio.Write(payload, r.srv, ue) +} + +func (r *RadioDaemon) Start(ctx context.Context) error { + srv, err := net.ListenUDP("udp", net.UDPAddrFromAddrPort(r.gnbRanAddr)) + if err != nil { + return err + } + r.srv = srv + logrus.WithFields(logrus.Fields{ + "bind-addr": r.gnbRanAddr, + }).Info("Starting Radio Simulatior") + go func(ctx context.Context, srv *net.UDPConn) error { + if srv == nil { + return fmt.Errorf("nil srv") + } + select { + case <-ctx.Done(): + srv.Close() + return ctx.Err() + } + return nil + }(ctx, srv) + go func(ctx context.Context, srv *net.UDPConn) { + defer srv.Close() + r.runUplinkDaemon(ctx, srv) + }(ctx, srv) + return nil +} diff --git a/internal/app/setup.go b/internal/app/setup.go index ab33e5b..44ff1fd 100644 --- a/internal/app/setup.go +++ b/internal/app/setup.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a MIT-style license that can be // found in the LICENSE file. // SPDX-License-Identifier: MIT + package app import ( @@ -13,15 +14,31 @@ import ( type Setup struct { config *config.GNBConfig httpServerEntity *HttpServerEntity + radio *Radio + rDaemon *RadioDaemon + psMan *PduSessionsManager } func NewSetup(config *config.GNBConfig) *Setup { + radio := NewRadio(config.Control.Uri, config.Ran.BindAddr, "go-github-nextmn-gnb-lite") + psMan := NewPduSessionsManager(config.Gtp) + rDaemon := NewRadioDaemon(radio, psMan, config.Ran.BindAddr) + ps := NewPduSessions(config.Control.Uri, config.Cp.Uri, psMan, "go-github-nextmn-gnb-lite", config.Gtp) return &Setup{ config: config, - httpServerEntity: NewHttpServerEntity(config.Control.BindAddr), + httpServerEntity: NewHttpServerEntity(config.Control.BindAddr, radio, ps), + radio: radio, + rDaemon: rDaemon, + psMan: psMan, } } func (s *Setup) Init(ctx context.Context) error { + if err := s.rDaemon.Start(ctx); err != nil { + return err + } + if err := s.StartGtpUProtocolEntity(ctx, s.config.Gtp); err != nil { + return err + } if err := s.httpServerEntity.Start(); err != nil { return err } diff --git a/internal/config/config.go b/internal/config/config.go index 510de0c..b5e3984 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -6,6 +6,7 @@ package config import ( "io/ioutil" + "net/netip" "path/filepath" "github.com/nextmn/json-api/jsonapi" @@ -31,11 +32,22 @@ func ParseConf(file string) (*GNBConfig, error) { } type GNBConfig struct { - Control Control `yaml:"control"` - Logger *Logger `yaml:"logger,omitempty"` + Control Control `yaml:"control"` + Ran Ran `yaml:"ran"` + Cp Cp `yaml:"cp"` + Logger *Logger `yaml:"logger,omitempty"` + Gtp netip.Addr `yaml:"gtp"` } type Control struct { Uri jsonapi.ControlURI `yaml:"uri"` // may contain domain name instead of ip address - BindAddr string `yaml:"bind-addr"` // in the form `ip:port` + BindAddr netip.AddrPort `yaml:"bind-addr"` // in the form `ip:port` +} + +type Ran struct { + BindAddr netip.AddrPort `yaml:"bind-addr"` +} + +type Cp struct { + Uri jsonapi.ControlURI `yaml:"uri"` // uri of the control plane }