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 Dec 23, 2024
1 parent bbe3e25 commit e5410d2
Show file tree
Hide file tree
Showing 12 changed files with 236 additions and 145 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-0.20241223192051-3fc04f155386
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
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
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-0.20241217171749-c6d1d71372c1 h1:icv+adEsqhtr8O69ajess/+3dIKl3gMTQ40GlsLcywI=
github.com/nextmn/json-api v0.0.15-0.20241217171749-c6d1d71372c1/go.mod h1:CQXeNPj9MDGsEExtnqJFIGjLgZAKsmOoO2fy+mep7Ak=
github.com/nextmn/json-api v0.0.15-0.20241218142100-3cf6cd948b1a h1:wIvJ21bWiy+E6T/4ecvex7C/gDTNqvY5sNdKHX34wAM=
github.com/nextmn/json-api v0.0.15-0.20241218142100-3cf6cd948b1a/go.mod h1:CQXeNPj9MDGsEExtnqJFIGjLgZAKsmOoO2fy+mep7Ak=
github.com/nextmn/json-api v0.0.15-0.20241218142156-a64418a36b7d h1:lhybNMDI+qjJB+rKDgJiHrXuhkoR7FWhQ4nEfpqZy1g=
github.com/nextmn/json-api v0.0.15-0.20241218142156-a64418a36b7d/go.mod h1:CQXeNPj9MDGsEExtnqJFIGjLgZAKsmOoO2fy+mep7Ak=
github.com/nextmn/json-api v0.0.15-0.20241223150142-2189fb1dc3af h1:/YGOPznGEQ0x/2E1Yk9HZ/3FosWtK5FouI0NDSiN2sE=
github.com/nextmn/json-api v0.0.15-0.20241223150142-2189fb1dc3af/go.mod h1:CQXeNPj9MDGsEExtnqJFIGjLgZAKsmOoO2fy+mep7Ak=
github.com/nextmn/json-api v0.0.15-0.20241223184721-e18ca5cd5f80 h1:bCHBRPROQR9S31nyqTSMuC3GQ0sENbv0976+v28IdnY=
github.com/nextmn/json-api v0.0.15-0.20241223184721-e18ca5cd5f80/go.mod h1:CQXeNPj9MDGsEExtnqJFIGjLgZAKsmOoO2fy+mep7Ak=
github.com/nextmn/json-api v0.0.15-0.20241223190836-9214d6edc562 h1:J7A9dVo41ZUc2Df+Ri58I5fwNdmlPmy0/WfNHi0nmjE=
github.com/nextmn/json-api v0.0.15-0.20241223190836-9214d6edc562/go.mod h1:CQXeNPj9MDGsEExtnqJFIGjLgZAKsmOoO2fy+mep7Ak=
github.com/nextmn/json-api v0.0.15-0.20241223192051-3fc04f155386 h1:RP6Vc+ITbrH6JhBKLGn41eIcttP46gI2g6Inu65BvRY=
github.com/nextmn/json-api v0.0.15-0.20241223192051-3fc04f155386/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
7 changes: 3 additions & 4 deletions internal/app/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,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 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
67 changes: 55 additions & 12 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,27 +24,68 @@ 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 ; 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,
}
}

func (r *Radio) Write(pkt []byte, srv *net.UDPConn, gnb jsonapi.ControlURI) error {
// 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); !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)
}

// 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); !ok {
return ErrUnknownGnb
}
if old, ok := r.routingTable.Load(ueIp); !ok {
return ErrPduSessionNotFound
} else if old != oldGnb {
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
}
gnbRan, ok := r.peerMap.Load(gnb)
if !ok {
logrus.Trace("Unknown gnb")
Expand Down
18 changes: 4 additions & 14 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 @@ -92,12 +87,7 @@ func (r *RadioDaemon) runUplinkDaemon(ctx context.Context, srv *net.UDPConn, ifa
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 {
if err := r.Radio.Write(buf[:n], srv, src); err == nil {
logrus.WithFields(
logrus.Fields{
"ip-addr": src,
Expand All @@ -112,9 +102,9 @@ func (r *RadioDaemon) Start(ctx context.Context) error {
if err := r.Radio.Init(ctx); err != nil {
return err
}
ifacetun := r.tunMan.OpenTun()
ifacetun := r.Radio.Tun.OpenTun()
defer func(ctx context.Context) {
defer r.tunMan.CloseTun()
defer r.Radio.Tun.CloseTun()
select {
case <-ctx.Done():
close(r.closed)
Expand Down
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"})
}
83 changes: 83 additions & 0 deletions internal/session/handover-command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// 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 (
"bytes"
"encoding/json"
"net/http"

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

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

func (p *PduSessions) HandoverCommand(c *gin.Context) {
var ps n1n2.HandoverCommand
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-source": ps.SourceGnb.String(),
"gnb-target": ps.TargetGnb.String(),
}).Info("New Handover Command")

go p.HandleHandoverCommand(ps)

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

func (p *PduSessions) HandleHandoverCommand(m n1n2.HandoverCommand) {
ctx := p.Context()
if m.SourceGnb == m.TargetGnb {
logrus.WithFields(logrus.Fields{
"gnb": m.SourceGnb.String(),
}).Error("Handover Command: source and target gNBs are not different.")
// TODO: notify gNB/CP of failure?
return
}

sessions := make([]n1n2.Session, len(m.Sessions))
// TODO: update pdu sessions atomically
for i, session := range m.Sessions {
if err := p.UpdatePduSession(session.Addr, m.SourceGnb, m.TargetGnb); err != nil {
// TODO: notify gNB/CP of failure?
continue
}
sessions[i] = n1n2.Session{
Addr: session.Addr,
Dnn: session.Dnn,
}
}

// Send Handover Notify
resp := n1n2.HandoverNotify{
UeCtrl: m.UeCtrl,
Cp: m.Cp,
TargetGnb: m.TargetGnb,
Sessions: sessions,
}
reqBody, err := json.Marshal(resp)
if err != nil {
logrus.WithError(err).Error("Could not marshal n1n2.HandoverNotify")
return
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, m.TargetGnb.JoinPath("ps/handover-confirm").String(), bytes.NewBuffer(reqBody))
if err != nil {
logrus.WithError(err).Error("Could not create request for ps/handover-confirm")
return
}
req.Header.Set("User-Agent", p.UserAgent)
req.Header.Set("Content-Type", "application/json; charset=UTF-8")
if _, err := p.Client.Do(req); err != nil {
logrus.WithError(err).Error("Could not send ps/handover-confirm")
}
}
Loading

0 comments on commit e5410d2

Please sign in to comment.