Skip to content

Commit

Permalink
Handover command; close #10
Browse files Browse the repository at this point in the history
  • Loading branch information
louisroyer committed Jan 9, 2025
1 parent bbe3e25 commit e1a0020
Show file tree
Hide file tree
Showing 14 changed files with 290 additions and 182 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ 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.14
github.com/nextmn/json-api v0.0.15
github.com/nextmn/logrus-formatter v0.0.1
github.com/sirupsen/logrus v1.9.3
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ 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.14 h1:m4uHOVcXsxkXoxbrhqemLTRG4T86eYkejjirew1nDUU=
github.com/nextmn/json-api v0.0.14/go.mod h1:CQXeNPj9MDGsEExtnqJFIGjLgZAKsmOoO2fy+mep7Ak=
github.com/nextmn/json-api v0.0.15 h1:ZSFr06omGsXtTHT6SWCy7Sc9csNQgwpOibkkdUm0pEA=
github.com/nextmn/json-api v0.0.15/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.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
Expand Down
2 changes: 1 addition & 1 deletion internal/app/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func NewHttpServerEntity(bindAddr netip.AddrPort, r *radio.Radio, ps *session.Pd
r.Register(h)

// Pdu Session
h.POST("/ps/establishment-accept", ps.EstablishmentAccept)
ps.Register(h)

logrus.WithFields(logrus.Fields{"http-addr": bindAddr}).Info("HTTP Server created")
e := HttpServerEntity{
Expand Down
12 changes: 8 additions & 4 deletions internal/app/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"github.com/nextmn/ue-lite/internal/radio"
"github.com/nextmn/ue-lite/internal/session"
"github.com/nextmn/ue-lite/internal/tun"

"github.com/sirupsen/logrus"
)

type Setup struct {
Expand All @@ -24,14 +26,13 @@ type Setup struct {
}

func NewSetup(config *config.UEConfig) *Setup {
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")
r := radio.NewRadio(config.Control.Uri, tunMan, config.Ran.BindAddr, "go-github-nextmn-ue-lite")
ps := session.NewPduSessions(config.Control.Uri, r, config.Ran.PDUSessions, "go-github-nextmn-ue-lite")
return &Setup{
config: config,
httpServerEntity: NewHttpServerEntity(config.Control.BindAddr, r, ps),
radioDaemon: radio.NewRadioDaemon(config.Control.Uri, config.Ran.Gnbs, r, psMan, tunMan, config.Ran.BindAddr),
radioDaemon: radio.NewRadioDaemon(config.Control.Uri, config.Ran.Gnbs, r, config.Ran.BindAddr),
ps: ps,
tunMan: tunMan,
}
Expand All @@ -44,12 +45,15 @@ func (s *Setup) Run(ctx context.Context) error {
if err := s.tunMan.Start(ctx); err != nil {
return err
}
logrus.Debug("TunMan started")
if err := s.radioDaemon.Start(ctx); err != nil {
return err
}
logrus.Debug("Radio Daemon started")
if err := s.ps.Start(ctx); err != nil {
return err
}
logrus.Debug("PsMan started")
select {
case <-ctx.Done():
ctxShutdown, cancel := context.WithTimeout(ctx, 1*time.Second)
Expand Down
11 changes: 7 additions & 4 deletions internal/radio/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ import (
)

var (
ErrNilCtx = errors.New("nil context")
ErrNilTunIface = errors.New("nil TUN interface")
ErrNilUdpConn = errors.New("nil UDP Connection")
ErrUnknownGnb = errors.New("Unknown gNB")
ErrNilCtx = errors.New("nil context")
ErrNilTunIface = errors.New("nil TUN interface")
ErrNilUdpConn = errors.New("nil UDP Connection")
ErrUnknownGnb = errors.New("Unknown gNB")
ErrUnexpectedGnb = errors.New("PDU session do not use the expected gNB")
ErrPduSessionNotFound = errors.New("No PDU Session found for this IP Address")
ErrPduSessionAlreadyExists = errors.New("PDU session already exists")

ErrUnsupportedPDUType = errors.New("Unsupported PDU Type")
ErrMalformedPDU = errors.New("Malformed PDU")
Expand Down
2 changes: 1 addition & 1 deletion internal/radio/peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func (r *Radio) Peer(c *gin.Context) {
}

func (r *Radio) HandlePeer(peer n1n2.RadioPeerMsg) {
r.peerMap.Store(peer.Control, peer.Data)
r.peerMap.Store(peer.Control.String(), peer.Data)
logrus.WithFields(logrus.Fields{
"peer-control": peer.Control.String(),
"peer-ran": peer.Data,
Expand Down
73 changes: 60 additions & 13 deletions internal/radio/radio.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"net/netip"
"sync"

"github.com/nextmn/ue-lite/internal/tun"

"github.com/nextmn/json-api/jsonapi"
"github.com/nextmn/json-api/jsonapi/n1n2"

Expand All @@ -22,28 +24,73 @@ import (
)

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
Client http.Client
peerMap sync.Map // key: gnb control uri (string); value: gnb ran ip address
routingTable sync.Map // key: ueIp; value gnb control uri
Tun *tun.TunManager
Control jsonapi.ControlURI
Data netip.AddrPort
UserAgent string

// not exported because must not be modified
ctx context.Context
}

func NewRadio(control jsonapi.ControlURI, data netip.AddrPort, userAgent string) *Radio {
func NewRadio(control jsonapi.ControlURI, tunMan *tun.TunManager, data netip.AddrPort, userAgent string) *Radio {
return &Radio{
peerMap: sync.Map{},
Client: http.Client{},
Control: control,
Data: data,
UserAgent: userAgent,
peerMap: sync.Map{},
routingTable: sync.Map{},
Client: http.Client{},
Control: control,
Data: data,
UserAgent: userAgent,
Tun: tunMan,
}
}

// AddRoute creates a route to the gNB for this PDU session, including configuration of iproute2 interface
func (r *Radio) AddRoute(ueIp netip.Addr, gnb jsonapi.ControlURI) error {
if _, ok := r.peerMap.Load(gnb.String()); !ok {
return ErrUnknownGnb
}
if _, loaded := r.routingTable.LoadOrStore(ueIp, gnb); loaded {
return ErrPduSessionAlreadyExists
}
return r.Tun.AddIp(ueIp)
}

// DelRoute remove the route to the gNB for this PDU session, including (de-)configuration of iproute2 interface
func (r *Radio) DelRoute(ueIp netip.Addr) error {
r.routingTable.Delete(ueIp)
return r.Tun.DelIp(ueIp)
}

func (r *Radio) Write(pkt []byte, srv *net.UDPConn, gnb jsonapi.ControlURI) error {
gnbRan, ok := r.peerMap.Load(gnb)
// UpdateRoute updates the route to the gNB for this PDU Session
func (r *Radio) UpdateRoute(ueIp netip.Addr, oldGnb jsonapi.ControlURI, newGnb jsonapi.ControlURI) error {
if _, ok := r.peerMap.Load(newGnb.String()); !ok {
return ErrUnknownGnb
}
old, ok := r.routingTable.Load(ueIp)
if !ok {
return ErrPduSessionNotFound
}
oldT := old.(jsonapi.ControlURI)
if oldT.String() != oldGnb.String() {
return ErrUnexpectedGnb
}

r.routingTable.Store(ueIp, newGnb)
return nil
}

func (r *Radio) Write(pkt []byte, srv *net.UDPConn, ue netip.Addr) error {
gnb, ok := r.routingTable.Load(ue)
if !ok {
logrus.Trace("PDU Session not found for this IP Address")
return ErrPduSessionNotFound
}
gnbT := gnb.(jsonapi.ControlURI)
gnbRan, ok := r.peerMap.Load(gnbT.String())
if !ok {
logrus.Trace("Unknown gnb")
return ErrUnknownGnb
Expand Down
74 changes: 38 additions & 36 deletions internal/radio/radio_daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"net"
"net/netip"

"github.com/nextmn/ue-lite/internal/session"
"github.com/nextmn/ue-lite/internal/tun"

"github.com/nextmn/json-api/jsonapi"
Expand All @@ -24,20 +23,16 @@ type RadioDaemon struct {
Control jsonapi.ControlURI
Gnbs []jsonapi.ControlURI
Radio *Radio
PsMan *session.PduSessionsManager
UeRanAddr netip.AddrPort
tunMan *tun.TunManager
closed chan struct{}
}

func NewRadioDaemon(control jsonapi.ControlURI, gnbs []jsonapi.ControlURI, radio *Radio, psMan *session.PduSessionsManager, tunMan *tun.TunManager, ueRanAddr netip.AddrPort) *RadioDaemon {
func NewRadioDaemon(control jsonapi.ControlURI, gnbs []jsonapi.ControlURI, radio *Radio, ueRanAddr netip.AddrPort) *RadioDaemon {
return &RadioDaemon{
Control: control,
Gnbs: gnbs,
Radio: radio,
PsMan: psMan,
UeRanAddr: ueRanAddr,
tunMan: tunMan,
closed: make(chan struct{}),
}
}
Expand Down Expand Up @@ -77,44 +72,47 @@ func (r *RadioDaemon) runUplinkDaemon(ctx context.Context, srv *net.UDPConn, ifa
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 ErrUnsupportedPDUType
}
src, ok := netip.AddrFromSlice(waterutil.IPv4Source(buf[:n]).To4())
if !ok {
return ErrMalformedPDU
}

// 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")
if err := r.handleUplinkPDU(srv, ifacetun); err != nil {
logrus.WithError(err).Trace("Packet dropped")
}
}
}
return nil
}

func (r *RadioDaemon) handleUplinkPDU(srv *net.UDPConn, ifacetun *water.Interface) error {
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 ErrUnsupportedPDUType
}
src, ok := netip.AddrFromSlice(waterutil.IPv4Source(buf[:n]).To4())
if !ok {
return ErrMalformedPDU
}

if err := r.Radio.Write(buf[:n], srv, src); err == nil {
logrus.WithFields(
logrus.Fields{
"ip-addr": src,
}).Trace("Packet forwarded")
}
return err

}

func (r *RadioDaemon) Start(ctx context.Context) error {
if err := r.Radio.Init(ctx); err != nil {
return err
}
ifacetun := r.tunMan.OpenTun()
defer func(ctx context.Context) {
defer r.tunMan.CloseTun()
ifacetun := r.Radio.Tun.OpenTun()
go func(ctx context.Context) {
defer r.Radio.Tun.CloseTun()
select {
case <-ctx.Done():
close(r.closed)
Expand All @@ -137,10 +135,14 @@ func (r *RadioDaemon) Start(ctx context.Context) error {
return nil
}(ctx, srv)
go func(ctx context.Context, srv *net.UDPConn, ifacetun *water.Interface) {
r.runDownlinkDaemon(ctx, srv, ifacetun)
if err := r.runDownlinkDaemon(ctx, srv, ifacetun); err != nil {
logrus.WithError(err).Error("Radio Downlink Daemon stopped")
}
}(ctx, srv, ifacetun)
go func(ctx context.Context, srv *net.UDPConn, ifacetun *water.Interface) {
r.runUplinkDaemon(ctx, srv, ifacetun)
if err := r.runUplinkDaemon(ctx, srv, ifacetun); err != nil {
logrus.WithError(err).Error("Radio Uplink Daemon stopped")
}
}(ctx, srv, ifacetun)

for _, gnb := range r.Gnbs {
Expand Down
19 changes: 8 additions & 11 deletions internal/radio/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,24 @@
package radio

import (
"encoding/json"
"net/http"
"net/netip"

"github.com/nextmn/json-api/jsonapi"

"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)

func (r *Radio) Status(c *gin.Context) {
peers := make(map[jsonapi.ControlURI]netip.Addr)
peers := make(map[string]netip.AddrPort)
r.peerMap.Range(func(key, value any) bool {
peers[key.(jsonapi.ControlURI)] = value.(netip.Addr)
peers[key.(string)] = value.(netip.AddrPort)
logrus.WithFields(logrus.Fields{
"key": key.(string),
"value": value.(netip.AddrPort),
}).Trace("Creating radio/status response")
return true
})
j, err := json.Marshal(peers)
if err != nil {
c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "could not marshal peers map", Error: err})
return
}

c.Header("Cache-Control", "no-cache")
c.JSON(http.StatusOK, j)
c.JSON(http.StatusOK, peers)
}
3 changes: 1 addition & 2 deletions internal/session/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,5 @@ import (
)

var (
ErrNilCtx = errors.New("nil context")
ErrPduSessionNotFound = errors.New("No PDU Session found for this IP Address")
ErrNilCtx = errors.New("nil context")
)
35 changes: 35 additions & 0 deletions internal/session/establishment-accept.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 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 session

import (
"net/http"

"github.com/nextmn/json-api/jsonapi"
"github.com/nextmn/json-api/jsonapi/n1n2"

"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)

// 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
}

logrus.WithFields(logrus.Fields{
"gnb": ps.Header.Gnb.String(),
"ip-addr": ps.Addr,
}).Info("New PDU Session")

go p.CreatePduSession(ps.Addr, ps.Header.Gnb)

c.JSON(http.StatusAccepted, jsonapi.Message{Message: "please refer to logs for more information"})
}
Loading

0 comments on commit e1a0020

Please sign in to comment.