From 06405f291f5b8be6a5d47dcde7558001097b848d Mon Sep 17 00:00:00 2001 From: Louis Royer Date: Tue, 10 Dec 2024 17:31:08 +0100 Subject: [PATCH] Refactor --- internal/app/control.go | 32 ++-- internal/app/radio_daemon.go | 128 ---------------- internal/app/setup.go | 32 ++-- internal/{app => cli}/cli.go | 11 +- internal/{app => radio}/radio.go | 2 +- internal/radio/radio_daemon.go | 145 ++++++++++++++++++ internal/{app => session}/pdu_session.go | 39 +++-- .../{app => session}/pdu_sessions_manager.go | 44 ++---- internal/{app => tun}/tun.go | 40 ++++- internal/{app => tun}/utils.go | 2 +- 10 files changed, 260 insertions(+), 215 deletions(-) delete mode 100644 internal/app/radio_daemon.go rename internal/{app => cli}/cli.go (88%) rename internal/{app => radio}/radio.go (99%) create mode 100644 internal/radio/radio_daemon.go rename internal/{app => session}/pdu_session.go (71%) rename internal/{app => session}/pdu_sessions_manager.go (57%) rename internal/{app => tun}/tun.go (68%) rename internal/{app => tun}/utils.go (98%) diff --git a/internal/app/control.go b/internal/app/control.go index 74dc9f1..effc4aa 100644 --- a/internal/app/control.go +++ b/internal/app/control.go @@ -12,6 +12,10 @@ import ( "net/netip" "time" + "github.com/nextmn/ue-lite/internal/cli" + "github.com/nextmn/ue-lite/internal/radio" + "github.com/nextmn/ue-lite/internal/session" + "github.com/nextmn/json-api/healthcheck" "github.com/gin-gonic/gin" @@ -20,36 +24,36 @@ import ( type HttpServerEntity struct { srv *http.Server - ps *PduSessions - radio *Radio - cli *Cli + ps *session.PduSessions + radio *radio.Radio + cli *cli.Cli } -func NewHttpServerEntity(bindAddr netip.AddrPort, radio *Radio, ps *PduSessions) *HttpServerEntity { - cli := NewCli(radio, ps) +func NewHttpServerEntity(bindAddr netip.AddrPort, r *radio.Radio, ps *session.PduSessions) *HttpServerEntity { + c := cli.NewCli(r, ps) // TODO: gin.SetMode(gin.DebugMode) / gin.SetMode(gin.ReleaseMode) depending on log level - r := gin.Default() - r.GET("/status", Status) + h := gin.Default() + h.GET("/status", Status) // CLI - r.POST("/cli/radio/peer", cli.RadioPeer) - r.POST("/cli/ps/establish", cli.PsEstablish) + h.POST("/cli/radio/peer", c.RadioPeer) + h.POST("/cli/ps/establish", c.PsEstablish) // Radio - r.POST("/radio/peer", radio.Peer) + h.POST("/radio/peer", r.Peer) // Pdu Session - r.POST("/ps/establishment-accept", ps.EstablishmentAccept) + h.POST("/ps/establishment-accept", ps.EstablishmentAccept) logrus.WithFields(logrus.Fields{"http-addr": bindAddr}).Info("HTTP Server created") e := HttpServerEntity{ srv: &http.Server{ Addr: bindAddr.String(), - Handler: r, + Handler: h, }, ps: ps, - radio: radio, - cli: cli, + radio: r, + cli: c, } return &e } diff --git a/internal/app/radio_daemon.go b/internal/app/radio_daemon.go deleted file mode 100644 index c38b00c..0000000 --- a/internal/app/radio_daemon.go +++ /dev/null @@ -1,128 +0,0 @@ -// 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 9b91840..9a780b7 100644 --- a/internal/app/setup.go +++ b/internal/app/setup.go @@ -9,26 +9,30 @@ import ( "context" "github.com/nextmn/ue-lite/internal/config" + "github.com/nextmn/ue-lite/internal/radio" + "github.com/nextmn/ue-lite/internal/session" + "github.com/nextmn/ue-lite/internal/tun" ) type Setup struct { config *config.UEConfig httpServerEntity *HttpServerEntity - radioDaemon *RadioDaemon - psMan *PduSessionsManager - tunMan *TunManager + radioDaemon *radio.RadioDaemon + ps *session.PduSessions + tunMan *tun.TunManager } 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") + r := radio.NewRadio(config.Control.Uri, config.Ran.BindAddr, "go-github-nextmn-ue-lite") + tunMan := tun.NewTunManager() + psMan := session.NewPduSessionsManager(tunMan) + ps := session.NewPduSessions(config.Control.Uri, psMan, config.Ran.PDUSessions, "go-github-nextmn-ue-lite") return &Setup{ config: config, - 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, - tunMan: NewTunManager(), + httpServerEntity: NewHttpServerEntity(config.Control.BindAddr, r, ps), + radioDaemon: radio.NewRadioDaemon(config.Control.Uri, config.Ran.Gnbs, r, psMan, tunMan, config.Ran.BindAddr), + ps: ps, + tunMan: tunMan, } } @@ -36,11 +40,13 @@ func (s *Setup) Init(ctx context.Context) error { if err := s.httpServerEntity.Start(); err != nil { return err } - tun, err := s.tunMan.Start(ctx) - if err != nil { + if err := s.tunMan.Start(ctx); err != nil { return err } - if err := s.radioDaemon.Start(ctx, tun); err != nil { + if err := s.radioDaemon.Start(ctx); err != nil { + return err + } + if err := s.ps.Start(ctx); err != nil { return err } return nil diff --git a/internal/app/cli.go b/internal/cli/cli.go similarity index 88% rename from internal/app/cli.go rename to internal/cli/cli.go index 987dff2..4ab73ce 100644 --- a/internal/app/cli.go +++ b/internal/cli/cli.go @@ -3,23 +3,26 @@ // found in the LICENSE file. // SPDX-License-Identifier: MIT -package app +package cli import ( "net/http" "github.com/nextmn/json-api/jsonapi" + "github.com/nextmn/ue-lite/internal/radio" + "github.com/nextmn/ue-lite/internal/session" + "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" ) type Cli struct { - Radio *Radio - PduSessions *PduSessions + Radio *radio.Radio + PduSessions *session.PduSessions } -func NewCli(radio *Radio, pduSessions *PduSessions) *Cli { +func NewCli(radio *radio.Radio, pduSessions *session.PduSessions) *Cli { return &Cli{ Radio: radio, PduSessions: pduSessions, diff --git a/internal/app/radio.go b/internal/radio/radio.go similarity index 99% rename from internal/app/radio.go rename to internal/radio/radio.go index cdb1bfa..e2e3b32 100644 --- a/internal/app/radio.go +++ b/internal/radio/radio.go @@ -3,7 +3,7 @@ // found in the LICENSE file. // SPDX-License-Identifier: MIT -package app +package radio import ( "bytes" diff --git a/internal/radio/radio_daemon.go b/internal/radio/radio_daemon.go new file mode 100644 index 0000000..c937bd3 --- /dev/null +++ b/internal/radio/radio_daemon.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 radio + +import ( + "context" + "fmt" + "net" + "net/netip" + + "github.com/nextmn/ue-lite/internal/session" + "github.com/nextmn/ue-lite/internal/tun" + + "github.com/nextmn/json-api/jsonapi" + + "github.com/sirupsen/logrus" + "github.com/songgao/water" + "github.com/songgao/water/waterutil" +) + +type RadioDaemon struct { + Control jsonapi.ControlURI + Gnbs []jsonapi.ControlURI + Radio *Radio + PsMan *session.PduSessionsManager + UeRanAddr netip.AddrPort + tunMan *tun.TunManager +} + +func NewRadioDaemon(control jsonapi.ControlURI, gnbs []jsonapi.ControlURI, radio *Radio, psMan *session.PduSessionsManager, tunMan *tun.TunManager, ueRanAddr netip.AddrPort) *RadioDaemon { + return &RadioDaemon{ + Control: control, + Gnbs: gnbs, + Radio: radio, + PsMan: psMan, + UeRanAddr: ueRanAddr, + tunMan: tunMan, + } +} + +func (r *RadioDaemon) runDownlinkDaemon(ctx context.Context, srv *net.UDPConn, ifacetun *water.Interface) error { + if srv == nil { + return fmt.Errorf("nil srv") + } + if ifacetun == nil { + return fmt.Errorf("nil tun iface") + } + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + buf := make([]byte, tun.TUN_MTU) + n, err := srv.Read(buf) + if err != nil { + return err + } + ifacetun.Write(buf[:n]) + } + } + return nil +} + +func (r *RadioDaemon) runUplinkDaemon(ctx context.Context, srv *net.UDPConn, ifacetun *water.Interface) error { + if srv == nil { + return fmt.Errorf("nil srv") + } + if ifacetun == nil { + return fmt.Errorf("nil tun iface") + } + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + buf := make([]byte, tun.TUN_MTU) + n, err := ifacetun.Read(buf) + if err != nil { + return err + } + + // get UE IP Address + if !waterutil.IsIPv4(buf[:n]) { + return fmt.Errorf("not an IPv4 packet") + } + src, ok := netip.AddrFromSlice(waterutil.IPv4Source(buf[:n]).To4()) + if !ok { + return fmt.Errorf("error while retrieving ip addr") + } + + // get gNB linked to UE + gnb, err := r.PsMan.LinkedGnb(src) + if err != nil { + return err + } + if err := r.Radio.Write(buf[:n], srv, gnb); err == nil { + logrus.WithFields( + logrus.Fields{ + "ip-addr": src, + }).Trace("packet forwarded") + } + } + } + return nil +} + +func (r *RadioDaemon) Start(ctx context.Context) error { + ifacetun := r.tunMan.Tun + 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, ifacetun *water.Interface) { + r.runDownlinkDaemon(ctx, srv, ifacetun) + }(ctx, srv, ifacetun) + go func(ctx context.Context, srv *net.UDPConn, ifacetun *water.Interface) { + r.runUplinkDaemon(ctx, srv, ifacetun) + }(ctx, srv, ifacetun) + + for _, gnb := range r.Gnbs { + select { + case <-ctx.Done(): + return ctx.Err() + default: + if err := r.Radio.InitPeer(ctx, gnb); err != nil { + return err + } + } + } + return nil +} diff --git a/internal/app/pdu_session.go b/internal/session/pdu_session.go similarity index 71% rename from internal/app/pdu_session.go rename to internal/session/pdu_session.go index f020bc8..fe5e57a 100644 --- a/internal/app/pdu_session.go +++ b/internal/session/pdu_session.go @@ -3,14 +3,15 @@ // found in the LICENSE file. // SPDX-License-Identifier: MIT -package app +package session import ( "bytes" "context" "encoding/json" "net/http" - "sync" + + "github.com/nextmn/ue-lite/internal/config" "github.com/nextmn/json-api/jsonapi" "github.com/nextmn/json-api/jsonapi/n1n2" @@ -20,20 +21,20 @@ import ( ) type PduSessions struct { - PduSessionsMap sync.Map // key: overlay ip address, value: gnb control uri - Control jsonapi.ControlURI - Client http.Client - UserAgent string - PduSessionsManager *PduSessionsManager + Control jsonapi.ControlURI + Client http.Client + UserAgent string + psMan *PduSessionsManager + reqPs []config.PDUSession } -func NewPduSessions(control jsonapi.ControlURI, pduSessionsManager *PduSessionsManager, userAgent string) *PduSessions { +func NewPduSessions(control jsonapi.ControlURI, psMan *PduSessionsManager, reqPs []config.PDUSession, userAgent string) *PduSessions { return &PduSessions{ - Client: http.Client{}, - PduSessionsMap: sync.Map{}, - Control: control, - UserAgent: userAgent, - PduSessionsManager: pduSessionsManager, + Client: http.Client{}, + Control: control, + UserAgent: userAgent, + psMan: psMan, + reqPs: reqPs, } } @@ -73,14 +74,22 @@ func (p *PduSessions) EstablishmentAccept(c *gin.Context) { 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) + p.psMan.CreatePduSession(ps.Addr, ps.Header.Gnb) c.Status(http.StatusNoContent) } + +func (p *PduSessions) Start(ctx context.Context) error { + for _, ps := range p.reqPs { + if err := p.InitEstablish(ctx, ps.Gnb, ps.Dnn); err != nil { + return err + } + } + return nil +} diff --git a/internal/app/pdu_sessions_manager.go b/internal/session/pdu_sessions_manager.go similarity index 57% rename from internal/app/pdu_sessions_manager.go rename to internal/session/pdu_sessions_manager.go index 1a2edb0..00e4c9a 100644 --- a/internal/app/pdu_sessions_manager.go +++ b/internal/session/pdu_sessions_manager.go @@ -3,59 +3,45 @@ // found in the LICENSE file. // SPDX-License-Identifier: MIT -package app +package session import ( "fmt" - "net" "net/netip" "sync" + "github.com/nextmn/ue-lite/internal/tun" + "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 + tun *tun.TunManager } -func NewPduSessionsManager(radio *Radio) *PduSessionsManager { +func NewPduSessionsManager(tunMan *tun.TunManager) *PduSessionsManager { return &PduSessionsManager{ Links: make(map[netip.Addr]jsonapi.ControlURI), isInit: false, - radio: radio, + tun: tunMan, } } -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") - } +func (p *PduSessionsManager) LinkedGnb(src netip.Addr) (jsonapi.ControlURI, error) { 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 jsonapi.ControlURI{}, fmt.Errorf("no pdu session found for this ip address") } - return ret + return gnb, nil } @@ -69,11 +55,7 @@ func (p *PduSessionsManager) DeletePduSession(ueIpAddr netip.Addr) error { 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") + if err := p.tun.DelIp(ueIpAddr); err != nil { return err } return nil @@ -97,11 +79,7 @@ func (p *PduSessionsManager) CreatePduSession(ueIpAddr netip.Addr, gnb jsonapi.C 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") + if err := p.tun.AddIp(ueIpAddr); err != nil { return err } return nil diff --git a/internal/app/tun.go b/internal/tun/tun.go similarity index 68% rename from internal/app/tun.go rename to internal/tun/tun.go index 8524579..0582d7b 100644 --- a/internal/app/tun.go +++ b/internal/tun/tun.go @@ -3,10 +3,12 @@ // found in the LICENSE file. // SPDX-License-Identifier: MIT -package app +package tun import ( "context" + "fmt" + "net/netip" "strconv" "github.com/sirupsen/logrus" @@ -21,16 +23,21 @@ const ( type TunManager struct { ready bool name string + Tun *water.Interface } func NewTunManager() *TunManager { return &TunManager{} } -func (t *TunManager) Start(ctx context.Context) (*water.Interface, error) { - iface, err := NewTunIface() +func (t *TunManager) Start(ctx context.Context) error { + tun, err := newTunIface() + t.Tun = tun + if err != nil { + return err + } t.ready = true - t.name = iface.Name() + t.name = t.Tun.Name() go func(ctx context.Context) { select { case <-ctx.Done(): @@ -41,10 +48,10 @@ func (t *TunManager) Start(ctx context.Context) (*water.Interface, error) { } } }(ctx) - return iface, err + return err } -func NewTunIface() (*water.Interface, error) { +func newTunIface() (*water.Interface, error) { config := water.Config{ DeviceType: water.TUN, } @@ -84,3 +91,24 @@ func NewTunIface() (*water.Interface, error) { } return iface, nil } + +func (t *TunManager) DelIp(ip netip.Addr) error { + if err := runIP("addr", "del", fmt.Sprintf("%s/%d", ip.String(), ip.BitLen()), "dev", TUN_NAME); err != nil { + logrus.WithError(err).WithFields(logrus.Fields{ + "ue-ip-addr": ip, + "dev": TUN_NAME, + }).Error("Could not remove ip address") + return err + } + return nil +} +func (t *TunManager) AddIp(ip netip.Addr) error { + if err := runIP("addr", "add", fmt.Sprintf("%s/%d", ip.String(), ip.BitLen()), "dev", TUN_NAME); err != nil { + logrus.WithError(err).WithFields(logrus.Fields{ + "ue-ip-addr": ip, + "dev": TUN_NAME, + }).Error("Could not add ip address for new PDU Session") + return err + } + return nil +} diff --git a/internal/app/utils.go b/internal/tun/utils.go similarity index 98% rename from internal/app/utils.go rename to internal/tun/utils.go index aae2c77..707b1ff 100644 --- a/internal/app/utils.go +++ b/internal/tun/utils.go @@ -3,7 +3,7 @@ // found in the LICENSE file. // SPDX-License-Identifier: MIT -package app +package tun import ( "fmt"