From 2b865c6635b561d85a5332614880ee68af5dc93b Mon Sep 17 00:00:00 2001 From: Louis Royer Date: Tue, 26 Nov 2024 16:29:47 +0100 Subject: [PATCH] Add PSE procedure; close #4 --- README.md | 3 + config/config.yaml | 9 +- go.mod | 30 +++---- go.sum | 66 +++++++------- internal/app/cli.go | 71 +++++++++++++++ internal/app/control.go | 25 +++++- internal/app/pdu_session.go | 86 ++++++++++++++++++ internal/app/pdu_sessions_manager.go | 108 ++++++++++++++++++++++ internal/app/radio.go | 98 ++++++++++++++++++++ internal/app/radio_daemon.go | 128 +++++++++++++++++++++++++++ internal/app/setup.go | 16 +++- internal/app/tun.go | 59 ++++++++++++ internal/app/utils.go | 36 ++++++++ internal/config/config.go | 15 +++- 14 files changed, 693 insertions(+), 57 deletions(-) create mode 100644 internal/app/cli.go create mode 100644 internal/app/pdu_session.go create mode 100644 internal/app/pdu_sessions_manager.go create mode 100644 internal/app/radio.go create mode 100644 internal/app/radio_daemon.go create mode 100644 internal/app/tun.go create mode 100644 internal/app/utils.go diff --git a/README.md b/README.md index 58c9154..0ac9372 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,15 @@ Simply run `make build` and `make install`. ### Docker If you plan using NextMN-UE Lite with Docker: - The container required the `NET_ADMIN` capability; +- 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" ``` ## Author diff --git a/config/config.yaml b/config/config.yaml index 49078f2..8c65ff2 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -1,6 +1,13 @@ control: uri: "http://192.0.2.1:8080" bind-addr: "192.0.2.1:8080" +ran: + bind-addr: "198.51.100.1:1234" + gnbs: + - "http://192.0.2.2:8080" + pdu-sessions: + - gnb: "http://192.0.2.2:8080" + dnn: "nextmn-lite" logger: - level: "debug" + level: "trace" diff --git a/go.mod b/go.mod index 8292aac..dfe29d4 100644 --- a/go.mod +++ b/go.mod @@ -5,41 +5,41 @@ 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/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 github.com/urfave/cli/v2 v2.27.5 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/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.27.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 90025c1..19db350 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/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.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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,30 +48,28 @@ 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.12 h1:QIg+wmCBhti5hzvh2mtQ6sJ3XayFrOusvsnuHOd9fdU= -github.com/nextmn/json-api v0.0.12/go.mod h1:CQXeNPj9MDGsEExtnqJFIGjLgZAKsmOoO2fy+mep7Ak= -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/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= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8= +github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= 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= @@ -83,28 +80,25 @@ 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/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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.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/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= 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= +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/cli.go b/internal/app/cli.go new file mode 100644 index 0000000..987dff2 --- /dev/null +++ b/internal/app/cli.go @@ -0,0 +1,71 @@ +// 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 ( + "net/http" + + "github.com/nextmn/json-api/jsonapi" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" +) + +type Cli struct { + Radio *Radio + PduSessions *PduSessions +} + +func NewCli(radio *Radio, pduSessions *PduSessions) *Cli { + return &Cli{ + Radio: radio, + PduSessions: pduSessions, + } +} + +type CliPeerMsg struct { + Gnb jsonapi.ControlURI `json:"gnb"` + Dnn string `json:"dnn"` +} + +// Allow to peer to a gNB +func (cli *Cli) RadioPeer(c *gin.Context) { + var peer CliPeerMsg + 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 + } + if err := cli.Radio.InitPeer(c, peer.Gnb); err != nil { + logrus.WithError(err).Error("could not perform radio peer init") + c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "could not perform radio peer init", Error: err}) + return + } + + // TODO: handle gnb failure + + c.Status(http.StatusNoContent) +} + +func (cli *Cli) PsEstablish(c *gin.Context) { + var peer CliPeerMsg + 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 + } + // TODO: first, check if radio link is established + + if err := cli.PduSessions.InitEstablish(c, peer.Gnb, peer.Dnn); err != nil { + logrus.WithError(err).Error("could not perform pdu session establishment") + c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "could not perform pdu session establishment", Error: err}) + return + } + + // TODO: handle gnb failure + + c.Status(http.StatusNoContent) +} diff --git a/internal/app/control.go b/internal/app/control.go index b47f6be..74dc9f1 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,37 @@ import ( ) type HttpServerEntity struct { - srv *http.Server + srv *http.Server + ps *PduSessions + radio *Radio + cli *Cli } -func NewHttpServerEntity(bindAddr string) *HttpServerEntity { +func NewHttpServerEntity(bindAddr netip.AddrPort, radio *Radio, ps *PduSessions) *HttpServerEntity { + cli := NewCli(radio, ps) // TODO: gin.SetMode(gin.DebugMode) / gin.SetMode(gin.ReleaseMode) depending on log level r := gin.Default() r.GET("/status", Status) + + // CLI + r.POST("/cli/radio/peer", cli.RadioPeer) + r.POST("/cli/ps/establish", cli.PsEstablish) + + // Radio + r.POST("/radio/peer", radio.Peer) + + // Pdu Session + r.POST("/ps/establishment-accept", ps.EstablishmentAccept) + 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, + cli: cli, } return &e } diff --git a/internal/app/pdu_session.go b/internal/app/pdu_session.go new file mode 100644 index 0000000..f020bc8 --- /dev/null +++ b/internal/app/pdu_session.go @@ -0,0 +1,86 @@ +// 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" + "context" + "encoding/json" + "net/http" + "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: overlay ip address, value: gnb control uri + Control jsonapi.ControlURI + Client http.Client + UserAgent string + PduSessionsManager *PduSessionsManager +} + +func NewPduSessions(control jsonapi.ControlURI, pduSessionsManager *PduSessionsManager, userAgent string) *PduSessions { + return &PduSessions{ + Client: http.Client{}, + PduSessionsMap: sync.Map{}, + Control: control, + UserAgent: userAgent, + PduSessionsManager: pduSessionsManager, + } +} + +func (p *PduSessions) InitEstablish(ctx context.Context, gnb jsonapi.ControlURI, dnn string) error { + logrus.WithFields(logrus.Fields{ + "gnb": gnb.String(), + }).Info("Creating new PDU Session") + + msg := n1n2.PduSessionEstabReqMsg{ + Ue: p.Control, + Gnb: gnb, + Dnn: dnn, + } + reqBody, err := json.Marshal(msg) + if err != nil { + return err + } + req, err := http.NewRequestWithContext(ctx, http.MethodPost, gnb.JoinPath("ps/establishment-request").String(), bytes.NewBuffer(reqBody)) + if err != nil { + return err + } + 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 { + return err + } + defer resp.Body.Close() + return nil +} + +// get status of the controller +func (p *PduSessions) EstablishmentAccept(c *gin.Context) { + var ps n1n2.PduSessionEstabAcceptMsg + 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 + } + p.PduSessionsMap.Store(ps.Addr, ps.Header.Gnb) + + logrus.WithFields(logrus.Fields{ + "gnb": ps.Header.Gnb.String(), + "ip-addr": ps.Addr, + }).Info("New PDU Session") + + p.PduSessionsManager.CreatePduSession(ps.Addr, ps.Header.Gnb) + + c.Status(http.StatusNoContent) +} diff --git a/internal/app/pdu_sessions_manager.go b/internal/app/pdu_sessions_manager.go new file mode 100644 index 0000000..1a2edb0 --- /dev/null +++ b/internal/app/pdu_sessions_manager.go @@ -0,0 +1,108 @@ +// 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 ( + "fmt" + "net" + "net/netip" + "sync" + + "github.com/nextmn/json-api/jsonapi" + + "github.com/sirupsen/logrus" + "github.com/songgao/water/waterutil" +) + +type PduSessionsManager struct { + Links map[netip.Addr]jsonapi.ControlURI // UeIpAddr : Gnb control URI + sync.Mutex + isInit bool + radio *Radio +} + +func NewPduSessionsManager(radio *Radio) *PduSessionsManager { + return &PduSessionsManager{ + Links: make(map[netip.Addr]jsonapi.ControlURI), + isInit: false, + radio: radio, + } +} + +func (p *PduSessionsManager) Write(pkt []byte, srv *net.UDPConn) error { + if !waterutil.IsIPv4(pkt) { + return fmt.Errorf("not an IPv4 packet") + } + src, ok := netip.AddrFromSlice(waterutil.IPv4Source(pkt).To4()) + if !ok { + return fmt.Errorf("error while retrieving ip addr") + } + gnb, ok := p.Links[src] + if !ok { + logrus.WithFields( + logrus.Fields{ + "ip-addr": src, + }).Trace("no pdu session found for this ip address") + return fmt.Errorf("no pdu session found for this ip address") + } + ret := p.radio.Write(pkt, srv, gnb) + if ret == nil { + logrus.WithFields( + logrus.Fields{ + "ip-addr": src, + }).Trace("packet forwarded") + } + return ret + +} + +func (p *PduSessionsManager) DeletePduSession(ueIpAddr netip.Addr) error { + logrus.WithFields(logrus.Fields{ + "ue-ip-addr": ueIpAddr, + }).Info("Removing link for PDU Session") + p.Lock() + defer p.Unlock() + delete(p.Links, ueIpAddr) + logrus.WithFields(logrus.Fields{ + "ue-ip-addr": ueIpAddr, + }).Debug("Creating ip address for new PDU Session") + if err := runIP("addr", "del", fmt.Sprintf("%s/%d", ueIpAddr.String(), ueIpAddr.BitLen()), "dev", TUN_NAME); err != nil { + logrus.WithError(err).WithFields(logrus.Fields{ + "ue-ip-addr": ueIpAddr, + "dev": TUN_NAME, + }).Error("Could not add ip address for new PDU Session") + return err + } + return nil +} + +func (p *PduSessionsManager) UpdatePduSession(ueIpAddr netip.Addr, newGnb jsonapi.ControlURI) { + logrus.WithFields(logrus.Fields{ + "ue-ip-addr": ueIpAddr, + "new-gnb": newGnb, + }).Info("Updating link for PDU Session") + p.Lock() + defer p.Unlock() + p.Links[ueIpAddr] = newGnb +} + +func (p *PduSessionsManager) CreatePduSession(ueIpAddr netip.Addr, gnb jsonapi.ControlURI) error { + p.Lock() + defer p.Unlock() + p.Links[ueIpAddr] = gnb + + logrus.WithFields(logrus.Fields{ + "ue-ip-addr": ueIpAddr, + }).Debug("Creating ip address for new PDU Session") + if err := runIP("addr", "add", fmt.Sprintf("%s/%d", ueIpAddr.String(), ueIpAddr.BitLen()), "dev", TUN_NAME); err != nil { + logrus.WithError(err).WithFields(logrus.Fields{ + "ue-ip-addr": ueIpAddr, + "dev": TUN_NAME, + }).Error("Could not add ip address for new PDU Session") + return err + } + return nil +} diff --git a/internal/app/radio.go b/internal/app/radio.go new file mode 100644 index 0000000..cdb1bfa --- /dev/null +++ b/internal/app/radio.go @@ -0,0 +1,98 @@ +// 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" + "context" + "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 { + Client http.Client + peerMap sync.Map // key: gnb control uri ; value: gnb ran ip address + 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, gnb jsonapi.ControlURI) error { + gnbRan, ok := r.peerMap.Load(gnb) + if !ok { + logrus.Trace("Unknown gnb") + return fmt.Errorf("Unknown gnb") + } + + _, err := srv.WriteToUDPAddrPort(pkt, gnbRan.(netip.AddrPort)) + + return err +} + +func (r *Radio) InitPeer(ctx context.Context, gnb jsonapi.ControlURI) error { + logrus.WithFields(logrus.Fields{ + "gnb": gnb.String(), + }).Info("Creating radio link with a new gNB") + + msg := n1n2.RadioPeerMsg{ + Control: r.Control, + Data: r.Data, + } + + reqBody, err := json.Marshal(msg) + if err != nil { + return err + } + req, err := http.NewRequestWithContext(ctx, http.MethodPost, gnb.JoinPath("radio/peer").String(), bytes.NewBuffer(reqBody)) + if err != nil { + return err + } + 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 { + return err + } + defer resp.Body.Close() + return nil +} + +// Allow to peer to a gNB +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) +} diff --git a/internal/app/radio_daemon.go b/internal/app/radio_daemon.go new file mode 100644 index 0000000..c38b00c --- /dev/null +++ b/internal/app/radio_daemon.go @@ -0,0 +1,128 @@ +// 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/ue-lite/internal/config" + + "github.com/nextmn/json-api/jsonapi" + + "github.com/songgao/water" +) + +type RadioDaemon struct { + Control jsonapi.ControlURI + Gnbs []jsonapi.ControlURI + ReqPS []config.PDUSession + Radio *Radio + PduSessions *PduSessions + PduSessionsManager *PduSessionsManager + UeRanAddr netip.AddrPort +} + +func NewRadioDaemon(control jsonapi.ControlURI, gnbs []jsonapi.ControlURI, reqPS []config.PDUSession, radio *Radio, pduSessions *PduSessions, psMan *PduSessionsManager, ueRanAddr netip.AddrPort) *RadioDaemon { + return &RadioDaemon{ + Control: control, + Gnbs: gnbs, + ReqPS: reqPS, + Radio: radio, + PduSessions: pduSessions, + PduSessionsManager: psMan, + UeRanAddr: ueRanAddr, + } +} + +func (r *RadioDaemon) runDownlinkDaemon(ctx context.Context, srv *net.UDPConn, tun *water.Interface) error { + if srv == nil { + return fmt.Errorf("nil srv") + } + if tun == nil { + return fmt.Errorf("nil tun iface") + } + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + buf := make([]byte, TUN_MTU) + n, err := srv.Read(buf) + if err != nil { + return err + } + tun.Write(buf[:n]) + } + } + return nil +} + +func (r *RadioDaemon) runUplinkDaemon(ctx context.Context, srv *net.UDPConn, tun *water.Interface) error { + if srv == nil { + return fmt.Errorf("nil srv") + } + if tun == nil { + return fmt.Errorf("nil tun iface") + } + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + buf := make([]byte, TUN_MTU) + n, err := tun.Read(buf) + if err != nil { + return err + } + r.PduSessionsManager.Write(buf[:n], srv) + } + } + return nil +} + +func (r *RadioDaemon) Start(ctx context.Context, tun *water.Interface) error { + srv, err := net.ListenUDP("udp", net.UDPAddrFromAddrPort(r.UeRanAddr)) + if err != nil { + return err + } + 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, tun *water.Interface) { + r.runDownlinkDaemon(ctx, srv, tun) + }(ctx, srv, tun) + go func(ctx context.Context, srv *net.UDPConn, tun *water.Interface) { + r.runUplinkDaemon(ctx, srv, tun) + }(ctx, srv, tun) + + for _, gnb := range r.Gnbs { + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := r.Radio.InitPeer(ctx, gnb); err != nil { + return err + } + } + } + for _, ps := range r.ReqPS { + if err := r.PduSessions.InitEstablish(ctx, ps.Gnb, ps.Dnn); err != nil { + return err + } + } + return nil +} diff --git a/internal/app/setup.go b/internal/app/setup.go index aa7f465..09f24a1 100644 --- a/internal/app/setup.go +++ b/internal/app/setup.go @@ -14,12 +14,19 @@ import ( type Setup struct { config *config.UEConfig httpServerEntity *HttpServerEntity + radioDaemon *RadioDaemon + psMan *PduSessionsManager } func NewSetup(config *config.UEConfig) *Setup { + radio := NewRadio(config.Control.Uri, config.Ran.BindAddr, "go-github-nextmn-ue-lite") + psMan := NewPduSessionsManager(radio) + ps := NewPduSessions(config.Control.Uri, psMan, "go-github-nextmn-ue-lite") return &Setup{ config: config, - httpServerEntity: NewHttpServerEntity(config.Control.BindAddr), + httpServerEntity: NewHttpServerEntity(config.Control.BindAddr, radio, ps), + radioDaemon: NewRadioDaemon(config.Control.Uri, config.Ran.Gnbs, config.Ran.PDUSessions, radio, ps, psMan, config.Ran.BindAddr), + psMan: psMan, } } @@ -27,6 +34,13 @@ func (s *Setup) Init(ctx context.Context) error { if err := s.httpServerEntity.Start(); err != nil { return err } + tun, err := NewTunIface() + if err != nil { + return err + } + if err := s.radioDaemon.Start(ctx, tun); err != nil { + return err + } return nil } diff --git a/internal/app/tun.go b/internal/app/tun.go new file mode 100644 index 0000000..f6ef663 --- /dev/null +++ b/internal/app/tun.go @@ -0,0 +1,59 @@ +// 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 ( + "strconv" + + "github.com/sirupsen/logrus" + "github.com/songgao/water" +) + +const ( + TUN_NAME = "nextmn-ue-lite" + TUN_MTU = 1400 +) + +func NewTunIface() (*water.Interface, error) { + config := water.Config{ + DeviceType: water.TUN, + } + config.Name = TUN_NAME + iface, err := water.New(config) + if nil != err { + logrus.WithError(err).Error("Unable to allocate TUN interface") + return nil, err + } + err = runIP("link", "set", "dev", iface.Name(), "mtu", strconv.Itoa(TUN_MTU)) + if nil != err { + logrus.WithError(err).WithFields(logrus.Fields{ + "mtu": TUN_MTU, + "interface": iface.Name(), + }).Error("Unable to set MTU") + return nil, err + } + err = runIP("link", "set", "dev", iface.Name(), "up") + if nil != err { + logrus.WithError(err).WithFields(logrus.Fields{ + "interface": iface.Name(), + }).Error("Unable to set interface up") + return nil, err + } + // TODO: add proto "nextmn-lite-ue" + err = runIP("route", "replace", "default", "dev", iface.Name()) + if nil != err { + logrus.WithError(err).WithFields(logrus.Fields{ + "interface": iface.Name(), + }).Error("Unable to set default route") + return nil, err + } + err = runIPTables("-A", "OUTPUT", "-o", iface.Name(), "-p", "icmp", "--icmp-type", "redirect", "-j", "DROP") + if err != nil { + logrus.WithError(err).WithFields(logrus.Fields{"interface": iface.Name()}).Error("Error while setting iptable rule to drop icmp redirects") + return nil, err + } + return iface, nil +} diff --git a/internal/app/utils.go b/internal/app/utils.go new file mode 100644 index 0000000..aae2c77 --- /dev/null +++ b/internal/app/utils.go @@ -0,0 +1,36 @@ +// 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 ( + "fmt" + "os" + "os/exec" +) + +// Run ip command +func runIP(args ...string) error { + cmd := exec.Command("ip", args...) + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + cmd.Stdin = os.Stdin + if err := cmd.Run(); err != nil { + return fmt.Errorf("Error running %s: %s", cmd.Args, err) + } + return nil +} + +// Run iptables command +func runIPTables(args ...string) error { + cmd := exec.Command("iptables", args...) + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + cmd.Stdin = os.Stdin + if err := cmd.Run(); err != nil { + return fmt.Errorf("Error running %s: %s", cmd.Args, err) + } + return nil +} diff --git a/internal/config/config.go b/internal/config/config.go index 64719ef..f7a1768 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -7,6 +7,7 @@ package config import ( "io/ioutil" + "net/netip" "path/filepath" "github.com/nextmn/json-api/jsonapi" @@ -33,10 +34,22 @@ func ParseConf(file string) (*UEConfig, error) { type UEConfig struct { Control Control `yaml:"control"` + Ran Ran `yaml:"ran"` Logger *Logger `yaml:"logger,omitempty"` } 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"` // in the form ip:port + Gnbs []jsonapi.ControlURI `yaml:"gnbs"` // list of gnb used + PDUSessions []PDUSession `yaml:"pdu-sessions"` // list of pdu sessions that will be established +} + +type PDUSession struct { + Gnb jsonapi.ControlURI `yaml:"gnb"` + Dnn string `yaml:"dnn"` }