From e44d96a3566c48b3ceee36e0d30eaf6bdd3fd0d1 Mon Sep 17 00:00:00 2001 From: Louis Royer Date: Wed, 13 Nov 2024 17:23:45 +0100 Subject: [PATCH] Initial commit --- .github/dependabot.yml | 15 ++++++ .github/workflows/create-release.yml | 23 +++++++++ .github/workflows/main.yml | 26 +++++++++++ .github/workflows/pre-commit.yml | 14 ++++++ .gitignore | 2 + .pre-commit-config.yaml | 8 ++++ LICENSE | 21 +++++++++ Makefile | 26 +++++++++++ README.md | 44 +++++++++++++++++ bash-completion/completions/gnb-lite | 21 +++++++++ config/config.yaml | 2 + go.mod | 18 +++++++ go.sum | 31 ++++++++++++ internal/app/setup.go | 39 ++++++++++++++++ internal/config/config.go | 33 +++++++++++++ internal/config/logger.go | 11 +++++ main.go | 70 ++++++++++++++++++++++++++++ 17 files changed, 404 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/create-release.yml create mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/pre-commit.yml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 bash-completion/completions/gnb-lite create mode 100644 config/config.yaml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/app/setup.go create mode 100644 internal/config/config.go create mode 100644 internal/config/logger.go create mode 100644 main.go diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a288f58 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 +updates: + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + assignees: + - "louisroyer" + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "daily" + assignees: + - "louisroyer" diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 0000000..c696cbc --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,23 @@ +name: Release +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" + +jobs: + release: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Get version number + id: version + run: echo "version=$(echo ${{ github.ref_name }} | cut -c2- -)" >> $GITHUB_OUTPUT + - name: Release + uses: ncipollo/release-action@v1 + with: + generateReleaseNotes: true + makeLatest: "legacy" + name: "Version ${{ steps.version.outputs.version }}" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..96599dd --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,26 @@ +# This is a basic workflow to help you get started with Actions + +name: CI + +# Controls when the workflow will run +on: + # Triggers the workflow on push or pull request events but only for the "master" branch + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: +jobs: + go_build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Go + uses: actions/setup-go@v5 + - name: Install depends + run: go get . + - name: Build + run: go build -v ./... diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..7f579af --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,14 @@ +name: pre-commit + +on: + pull_request: + push: + branches: [master] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + - uses: pre-commit/action@v3.0.1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e77db11 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +gnb-lite +!bash-completion/completions/gnb-lite diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..416533b --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,8 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace + - id: mixed-line-ending +exclude: ^.github/.*$ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0b734ba --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright © 2023 Louis Royer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e444038 --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +prefix = /usr/local +exec_prefix = $(prefix) +bindir = $(exec_prefix)/bin +BASHCOMPLETIONSDIR = $(exec_prefix)/share/bash-completion/completions + + +RM = rm -f +INSTALL = install -D + +.PHONY: install uninstall build clean default +default: build +build: + go build +clean: + go clean +reinstall: uninstall install +install: + $(INSTALL) gnb-lite $(DESTDIR)$(bindir)/gnb-lite + $(INSTALL) bash-completion/completions/gnb-lite $(DESTDIR)$(BASHCOMPLETIONSDIR)/gnb-lite + @echo "=================================" + @echo ">> Now run the following command:" + @echo -e "\tsource $(DESTDIR)$(BASHCOMPLETIONSDIR)/gnb-lite" + @echo "=================================" +uninstall: + $(RM) $(DESTDIR)$(bindir)/gnb-lite + $(RM) $(DESTDIR)$(BASHCOMPLETIONSDIR)/gnb-lite diff --git a/README.md b/README.md new file mode 100644 index 0000000..c12da2d --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# NextMN-gNB Lite +**NextMN-gNB Lite** is an experimental gNB simulator designed to be used along with **NextMN-UE Lite** and **NextMN-CP Lite** to mimic from an UPF point-of-view a 5G & beyond Control Plane and a RAN. + +3GPP N1/N2 interfaces are not (and will not be) implemented, and Control Plane is minimalistic on purpose. + +This allow to test N3 and N4 interfaces of an UPF, and in particular to test handover procedures. + +If you don't need to use handover procedures, consider using [UERANSIM](https://github.com/aligungr/UERANSIM) along with a real Control Plane (e.g. [free5GC](https://github.com/free5GC)'s NFs) instead. + +## Getting started +### Build dependencies +- 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 + +## Licence +MIT diff --git a/bash-completion/completions/gnb-lite b/bash-completion/completions/gnb-lite new file mode 100644 index 0000000..f0f6241 --- /dev/null +++ b/bash-completion/completions/gnb-lite @@ -0,0 +1,21 @@ +#! /bin/bash + +: ${PROG:=$(basename ${BASH_SOURCE})} + +_cli_bash_autocomplete() { + if [[ "${COMP_WORDS[0]}" != "source" ]]; then + local cur opts base + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + if [[ "$cur" == "-"* ]]; then + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion ) + else + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) + fi + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi +} + +complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete $PROG +unset PROG diff --git a/config/config.yaml b/config/config.yaml new file mode 100644 index 0000000..3770bfe --- /dev/null +++ b/config/config.yaml @@ -0,0 +1,2 @@ +logger: + level: "debug" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7e53722 --- /dev/null +++ b/go.mod @@ -0,0 +1,18 @@ +module github.com/nextmn/gnb-lite + +go 1.22.7 + +require ( + github.com/adrg/xdg v0.5.3 + github.com/nextmn/logrus-formatter v0.0.1 + github.com/sirupsen/logrus v1.9.3 + github.com/urfave/cli/v2 v2.27.5 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + golang.org/x/sys v0.26.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a5c4fc6 --- /dev/null +++ b/go.sum @@ -0,0 +1,31 @@ +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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/nextmn/logrus-formatter v0.0.1 h1:Bsf78jjiEESc+rV8xE6IyKj4frDPGMwXFNrLQzm6A1E= +github.com/nextmn/logrus-formatter v0.0.1/go.mod h1:vdSZ+sIcSna8vjbXkSFxsnsKHqRwaUEed4JCPcXoGyM= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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/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/sys v0.0.0-20220715151400-c0bba94af5f8/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= +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= diff --git a/internal/app/setup.go b/internal/app/setup.go new file mode 100644 index 0000000..b79e019 --- /dev/null +++ b/internal/app/setup.go @@ -0,0 +1,39 @@ +// 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" + + "github.com/nextmn/gnb-lite/internal/config" +) + +type Setup struct { + config *config.GNBConfig +} + +func NewSetup(config *config.GNBConfig) *Setup { + return &Setup{ + config: config, + } +} +func (s *Setup) Init(ctx context.Context) error { + return nil +} + +func (s *Setup) Run(ctx context.Context) error { + defer s.Exit() + if err := s.Init(ctx); err != nil { + return err + } + select { + case <-ctx.Done(): + return nil + } +} + +func (s *Setup) Exit() error { + return nil +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..1cfbbdd --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,33 @@ +// 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 config + +import ( + "io/ioutil" + "path/filepath" + + "gopkg.in/yaml.v3" +) + +func ParseConf(file string) (*GNBConfig, error) { + var conf GNBConfig + path, err := filepath.Abs(file) + if err != nil { + return nil, err + } + yamlFile, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + err = yaml.Unmarshal(yamlFile, &conf) + if err != nil { + return nil, err + } + return &conf, nil +} + +type GNBConfig struct { + Logger *Logger `yaml:"logger,omitempty"` +} diff --git a/internal/config/logger.go b/internal/config/logger.go new file mode 100644 index 0000000..ed70e33 --- /dev/null +++ b/internal/config/logger.go @@ -0,0 +1,11 @@ +// 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 config + +import "github.com/sirupsen/logrus" + +type Logger struct { + Level logrus.Level `yaml:"level"` +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..3706345 --- /dev/null +++ b/main.go @@ -0,0 +1,70 @@ +// 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 main + +import ( + "context" + "os" + "os/signal" + "syscall" + + "github.com/nextmn/logrus-formatter/logger" + + "github.com/nextmn/gnb-lite/internal/app" + "github.com/nextmn/gnb-lite/internal/config" + + "github.com/adrg/xdg" + "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" +) + +func main() { + logger.Init("NextMN-gNB Lite") + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT) + defer cancel() + app := &cli.App{ + Name: "NextMN-gNB Lite", + Usage: "Experimental gNB Simulator", + EnableBashCompletion: true, + Authors: []*cli.Author{ + {Name: "Louis Royer"}, + }, + Flags: []cli.Flag{ + &cli.PathFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "Load configuration from `FILE`", + Required: false, + DefaultText: "${XDG_CONFIG_DIRS}/nextmn-gnb-lite/config.yaml", + EnvVars: []string{"CONFIG_FILE"}, + }, + }, + Action: func(ctx *cli.Context) error { + if ctx.Path("config") == "" { + if xdgPath, err := xdg.SearchConfigFile("nextmn-gnb-lite/config.yaml"); err != nil { + cli.ShowAppHelp(ctx) + logrus.WithError(err).Fatal("No configuration file defined") + } else { + ctx.Set("config", xdgPath) + } + } + conf, err := config.ParseConf(ctx.Path("config")) + if err != nil { + logrus.WithContext(ctx.Context).WithError(err).Fatal("Error loading config, exiting…") + } + if conf.Logger != nil { + logrus.SetLevel(conf.Logger.Level) + } + + if err := app.NewSetup(conf).Run(ctx.Context); err != nil { + logrus.WithError(err).Fatal("Error while running, exiting…") + } + return nil + }, + } + if err := app.RunContext(ctx, os.Args); err != nil { + logrus.Fatal(err) + } +}