Skip to content

Commit

Permalink
(wip) Add PSE Procedure
Browse files Browse the repository at this point in the history
  • Loading branch information
louisroyer committed Nov 26, 2024
1 parent 871c9d1 commit d54bd85
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 4 deletions.
6 changes: 6 additions & 0 deletions config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,11 @@ control:
uri: "http://192.0.2.3:8080"
bind-addr: "192.0.2.3:8080"

slices:
nextmn-lite:
pool: "10.0.0.0/24"
upfs:
- "203.0.113.1" # only the first upf is used for now

logger:
level: "debug"
9 changes: 8 additions & 1 deletion internal/app/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,25 @@ import (

type HttpServerEntity struct {
srv *http.Server
ps *PduSessions
}

func NewHttpServerEntity(bindAddr string) *HttpServerEntity {
func NewHttpServerEntity(bindAddr string, ps *PduSessions) *HttpServerEntity {
// TODO: gin.SetMode(gin.DebugMode) / gin.SetMode(gin.ReleaseMode) depending on log level
r := gin.Default()
r.GET("/status", Status)

// PDU Sessions
r.POST("/ps/establishment-request", ps.EstablishmentRequest)
r.POST("/ps/n2-establishment-response", ps.N2EstablishmentResponse)

logrus.WithFields(logrus.Fields{"http-addr": bindAddr}).Info("HTTP Server created")
e := HttpServerEntity{
srv: &http.Server{
Addr: bindAddr,
Handler: r,
},
ps: ps,
}
return &e
}
Expand Down
189 changes: 189 additions & 0 deletions internal/app/pdu_session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// 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"
"encoding/json"
"fmt"
"net/http"
"net/netip"
"sync"

"github.com/nextmn/cp-lite/internal/config"

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

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

// TODO: move to jsonapi
type PduSessionEstabReqMsg struct {
Ue jsonapi.ControlURI `json:"ue"`
Gnb jsonapi.ControlURI `json:"gnb"`
Dnn string `json:"dnn"`
}

// TODO: move to jsonapi
type PduSessionEstabAcceptMsg struct {
Header PduSessionEstabReqMsg `json:"header"`
Addr netip.Addr `json:"address"`
}

// TODO: move to jsonapi
type N2PduSessionReqMsg struct {
Cp jsonapi.ControlURI `json:"cp"`
UeInfo PduSessionEstabAcceptMsg `json:"ue-info"`
Upf netip.Addr `json:"upf"`
UplinkTeid uint32 `json:"uplink-teid"`
}

// TODO: move to jsonapi
type N2PduSessionRespMsg struct {
UeInfo PduSessionEstabAcceptMsg `json:"ue-info"`
DownlinkTeid uint32 `json:"downlink-teid"`
Gnb netip.Addr `json:"gnb"`
}

type Pool struct {
pool netip.Prefix
current netip.Addr
}

func NewPool(pool netip.Prefix) *Pool {
return &Pool{
pool: pool,
current: pool.Addr(),
}
}

func (p *Pool) Next() (netip.Addr, error) {
addr := p.current.Next()
p.current = addr
if !p.pool.Contains(addr) {
return addr, fmt.Errorf("out of range")
}
return addr, nil
}

type PduSessions struct {
PduSessionsMap sync.Map // key: UE 5G IP ; value: PduSession
Client http.Client
Control jsonapi.ControlURI
UserAgent string
Slices map[string]config.Slice
Pools map[string]*Pool
}

type PduSession struct {
Upf netip.Addr
UplinkTeid uint32
Gnb netip.Addr
DownlinkTeid uint32
}

func NewPduSessions(control jsonapi.ControlURI, slices map[string]config.Slice, userAgent string) *PduSessions {
var pools map[string]*Pool
for name, p := range slices {
pools[name] = NewPool(p.Pool)
}
return &PduSessions{
PduSessionsMap: sync.Map{},
Client: http.Client{},
Control: control,
UserAgent: userAgent,
Slices: slices,
Pools: pools,
}
}

func (p *PduSessions) EstablishmentRequest(c *gin.Context) {
var ps PduSessionEstabReqMsg
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{
"ue": ps.Ue.String(),
"gnb": ps.Gnb.String(),
}).Info("New PDU Session establishment Request")

// allocate new ue ip addr
UeIpAddr, err := p.Pools[ps.Dnn].Next()
if err != nil {
logrus.WithError(err).Error("no address available in pool")
c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "no address available in pool", Error: err})
return
}
// allocate uplink teid
pduSession := PduSession{
Upf: p.Slices[ps.Dnn].Upfs[0],
UplinkTeid: 0x4321, // TODO: change me
}

p.PduSessionsMap.Store(UeIpAddr, pduSession)

// send PseAccept to UE
n2PsReq := N2PduSessionReqMsg{
Cp: p.Control,
UeInfo: PduSessionEstabAcceptMsg{
Header: ps,
Addr: UeIpAddr,
},
Upf: pduSession.Upf,
UplinkTeid: pduSession.UplinkTeid,
}
reqBody, err := json.Marshal(n2PsReq)
if err != nil {
c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "could not marshal json", Error: err})
return
}
req, err := http.NewRequestWithContext(c, http.MethodPost, ps.Gnb.JoinPath("ps/n2-establishment-request").String(), bytes.NewBuffer(reqBody))
if err != nil {
c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "could not create request", Error: err})
return
}
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 {
c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "no http response", Error: err})
return
}
defer resp.Body.Close()
}

func (p *PduSessions) N2EstablishmentResponse(c *gin.Context) {
var ps N2PduSessionRespMsg
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
}
pduSession, ok := p.PduSessionsMap.LoadAndDelete(ps.UeInfo.Addr)
if !ok {
logrus.Error("No PDU Session establishment procedure started for this UE")
c.JSON(http.StatusInternalServerError, jsonapi.MessageWithError{Message: "no pdu session establishment procedure started for this UE", Error: nil})
return
}

psStruct := pduSession.(PduSession)

psStruct.DownlinkTeid = ps.DownlinkTeid
psStruct.Gnb = ps.Gnb
p.PduSessionsMap.Store(ps.UeInfo.Addr, psStruct)
logrus.WithFields(logrus.Fields{
"ue": ps.UeInfo.Header.Ue.String(),
"gnb": ps.UeInfo.Header.Gnb.String(),
"ip-addr": ps.UeInfo.Addr,
"gtp-upf": psStruct.Upf,
"gtp-uplink-teid": psStruct.UplinkTeid,
"gtp-downlink-teid": psStruct.DownlinkTeid,
"gtp-gnb": psStruct.Gnb,
}).Info("New PDU Session Established")
}
4 changes: 3 additions & 1 deletion internal/app/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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 (
Expand All @@ -16,9 +17,10 @@ type Setup struct {
}

func NewSetup(config *config.CPConfig) *Setup {
ps := NewPduSessions(config.Control.Uri, config.Slices, "go-github-nextmn-cp-lite")
return &Setup{
config: config,
httpServerEntity: NewHttpServerEntity(config.Control.BindAddr),
httpServerEntity: NewHttpServerEntity(config.Control.BindAddr, ps),
}
}
func (s *Setup) Init(ctx context.Context) error {
Expand Down
11 changes: 9 additions & 2 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package config

import (
"io/ioutil"
"net/netip"
"path/filepath"

"github.com/nextmn/json-api/jsonapi"
Expand All @@ -31,11 +32,17 @@ func ParseConf(file string) (*CPConfig, error) {
}

type CPConfig struct {
Control Control `yaml:"control"`
Logger *Logger `yaml:"logger,omitempty"`
Control Control `yaml:"control"`
Logger *Logger `yaml:"logger,omitempty"`
Slices map[string]Slice `yaml:"slices"`
}

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

type Slice struct {
Pool netip.Prefix `yaml:"pool"`
Upfs []netip.Addr `yaml:"upfs"`
}

0 comments on commit d54bd85

Please sign in to comment.