Skip to content

Commit

Permalink
Make rules configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
louisroyer committed Oct 23, 2024
1 parent bd16a2b commit e597828
Show file tree
Hide file tree
Showing 8 changed files with 355 additions and 348 deletions.
18 changes: 18 additions & 0 deletions config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,21 @@ http-address: "192.0.2.2"
http-port: "8080"
logger:
level: info
uplink:
- control-uri: "http://[fd00:0:0:0:2:8000:0:2]:8080"
enabled: true
segments-list:
- "fc00:2:1::"
- control-uri: "http://[fd00:0:0:0:2:8000:0:2]:8080"
enabled: false
segments-list:
- "fc00:3:1::"
downlink:
- control-uri: "http://[fd00:0:0:0:2:8000:0:4]:8080"
enabled: true
segments-list:
- "fc00:1:1::/48"
- control-uri: "http://[fd00:0:0:0:2:8000:0:5]:8080"
enabled: true
segments-list:
- "fc00:1:1::/48"
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ require (
github.com/gin-gonic/gin v1.10.0
github.com/gofrs/uuid v4.4.0+incompatible
github.com/nextmn/go-pfcp-networking v0.0.38
github.com/nextmn/json-api v0.0.10
github.com/nextmn/json-api v0.0.11
github.com/nextmn/logrus-formatter v0.0.1
github.com/nextmn/rfc9433 v0.0.2
github.com/sirupsen/logrus v1.9.3
github.com/urfave/cli/v2 v2.27.5
github.com/wmnsk/go-pfcp v0.0.24
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,12 @@ 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/go-pfcp-networking v0.0.38 h1:C7HgLh6UUbqfoTHl/uvUcYNZUBPSur68rOHdLUnpx9Q=
github.com/nextmn/go-pfcp-networking v0.0.38/go.mod h1:KYoKLiltDmHL2YMU5mz2k/E1xMoz4TpmzTz6Nr5u5gA=
github.com/nextmn/json-api v0.0.10 h1:/7WCtGaLEKFKGstOrssac6QgPL0MeGqpkRWU3hepS1A=
github.com/nextmn/json-api v0.0.10/go.mod h1:0py63IYCOBp1ZtLkMjNCNnOwbwhOmkh+ymJ0/OrxYx8=
github.com/nextmn/json-api v0.0.11 h1:wrx5IfWntdCmyGdSsFc31RyuKktAvqe9Un+DcxuSfi8=
github.com/nextmn/json-api v0.0.11/go.mod h1:0py63IYCOBp1ZtLkMjNCNnOwbwhOmkh+ymJ0/OrxYx8=
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/nextmn/rfc9433 v0.0.2 h1:6FjMY+Qy8MNXQ0PPxezUsyXDxJiCbTp5j3OcXQgIQh8=
github.com/nextmn/rfc9433 v0.0.2/go.mod h1:uVEEXunVOe3dMDV8eHr8ViPT/RnJ5WxjeQhycgaqFh4=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down
308 changes: 308 additions & 0 deletions internal/app/rules-pusher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
// Copyright 2024 Louis Royer and the NextMN-SRv6-ctrl 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"
"context"
"encoding/json"
"fmt"
"net/http"
"net/netip"
"sync"

"github.com/sirupsen/logrus"
"github.com/wmnsk/go-pfcp/ie"

pfcp_networking "github.com/nextmn/go-pfcp-networking/pfcp"
pfcpapi "github.com/nextmn/go-pfcp-networking/pfcp/api"
"github.com/nextmn/go-pfcp-networking/pfcputil"
"github.com/nextmn/json-api/jsonapi"
"github.com/nextmn/rfc9433/encoding"
"github.com/nextmn/srv6-ctrl/internal/config"
)

const UserAgent = "go-github-nextmn-srv6-ctrl"

type RulesPusher struct {
uplink []config.Rule
downlink []config.Rule
}

func NewRulesPusher(config *config.CtrlConfig) *RulesPusher {
return &RulesPusher{
uplink: config.Uplink,
downlink: config.Downlink,
}
}

func (pusher *RulesPusher) pushSingleRule(client http.Client, uri string, data []byte) error {
req, err := http.NewRequest(http.MethodPost, uri+"/rules", bytes.NewBuffer(data))
if err != nil {
logrus.WithError(err).Error("could not create http request")
return err
}
req.Header.Add("User-Agent", UserAgent)
req.Header.Set("Content-Type", "application/json; charset=UTF-8")
resp, err := client.Do(req)
if err != nil {
logrus.WithError(err).Error("Could not push rules: server not responding")
return fmt.Errorf("Could not push rules: server not responding")
}
defer resp.Body.Close()
if resp.StatusCode == 400 {
logrus.WithError(err).Error("HTTP Bad Request")
return fmt.Errorf("HTTP Bad request")
} else if resp.StatusCode >= 500 {
logrus.WithError(err).Error("HTTP internal error")
return fmt.Errorf("HTTP internal error")
}
//else if resp.StatusCode == 201{
//OK: store resource
//_ := resp.Header.Get("Location")
//}
return nil
}

func (pusher *RulesPusher) pushRTRRule(ue_ip string, gnb_ip string, teid_downlink uint32, teid_uplink uint32) error {
service_ip := "10.4.0.1"
logrus.WithFields(logrus.Fields{
"ue-ip": ue_ip,
"gnb-ip": gnb_ip,
"teid-downlink": teid_downlink,
"teid-uplink": teid_uplink,
"service-ip": service_ip,
}).Info("Pushing Router Rules")
ue_addr := netip.MustParseAddr(ue_ip) // FIXME: don't trust user input => ParseAddr
gnb_addr := netip.MustParseAddr(gnb_ip) // FIXME: don't trust user input => ParseAddr
service_addr := netip.MustParseAddr(service_ip) // FIXME: don't trust user input => ParseAddr

client := http.Client{}
var wg sync.WaitGroup

for _, r := range pusher.uplink {
//TODO: add ArgMobSession
srh, err := jsonapi.NewSRH(r.SegmentsList)
if err != nil {
logrus.WithFields(logrus.Fields{
"segments-list": r.SegmentsList,
}).WithError(err).Error("Creation of SRH uplink failed")
return err
}
rule := jsonapi.Rule{
Enabled: r.Enabled,
Type: "uplink",
Match: jsonapi.Match{
Header: &jsonapi.GtpHeader{
OuterIpSrc: gnb_addr,
Teid: teid_uplink,
InnerIpSrc: &ue_addr,
},
Payload: &jsonapi.Payload{
Dst: service_addr,
},
},
Action: jsonapi.Action{
SRH: *srh,
},
}
rule_json, err := json.Marshal(rule)
if err != nil {
logrus.WithError(err).Error("Could not marshal json")
return err
}
wg.Add(1)
go func() error {
defer wg.Done()
return pusher.pushSingleRule(client, r.ControlURI, rule_json)
}()

}

for _, r := range pusher.downlink {
if len(r.SegmentsList) == 0 {
logrus.Error("Empty segments list for downlink")
return fmt.Errorf("Empty segments list for downlink")
}
segList := make([]string, len(r.SegmentsList))
copy(segList, r.SegmentsList)
prefix, err := netip.ParsePrefix(r.SegmentsList[0])
if err != nil {
return err
}
dst := encoding.NewMGTP4IPv6Dst(prefix, gnb_addr.As4(), encoding.NewArgsMobSession(0, false, false, teid_downlink))
dstB, err := dst.Marshal()
if err != nil {
return err
}
dstIp, ok := netip.AddrFromSlice(dstB)
if !ok {
return fmt.Errorf("could not convert MGTP4IPv6Dst to netip.Addr")
}
segList[0] = dstIp.String()

srh, err := jsonapi.NewSRH(segList)
if err != nil {
logrus.WithFields(logrus.Fields{
"segments-list": r.SegmentsList,
}).WithError(err).Error("Creation of SRH downlink failed")
return err
}
rule := jsonapi.Rule{
Enabled: true,
Type: "downlink",
Match: jsonapi.Match{
Payload: &jsonapi.Payload{
Dst: ue_addr,
},
},
Action: jsonapi.Action{
SRH: *srh,
},
}
rule_json, err := json.Marshal(rule)
if err != nil {
logrus.WithError(err).Error("Could not marshal json")
return err
}
wg.Add(1)
go func() error {
defer wg.Done()
return pusher.pushSingleRule(client, r.ControlURI, rule_json)
}()

}
wg.Wait()
return nil
}

type ueInfos struct {
UplinkTeid uint32
DownlinkTeid uint32
Gnb string
}

func (pusher *RulesPusher) updateRoutersRules(ctx context.Context, msgType pfcputil.MessageType, message pfcp_networking.ReceivedMessage, e *pfcp_networking.PFCPEntityUP) {
logrus.Debug("Into updateRoutersRules")
ues := sync.Map{}
var wg0 sync.WaitGroup
for _, session := range e.GetPFCPSessions() {
logrus.Debug("In for loop…")
wg0.Add(1)
go func() error {
defer wg0.Done()
session.RLock()
defer session.RUnlock()
session.ForeachUnsortedPDR(func(pdr pfcpapi.PDRInterface) error {
farid, err := pdr.FARID()
if err != nil {
logrus.WithError(err).Debug("skip: error getting FARid")
return nil
}
ue_ip_addr, err := pdr.UEIPAddress()
if err != nil {
logrus.WithError(err).Debug("skip: error getting ueipaddr")
return nil
}

// FIXME: temporary hack, no IPv6 support
ue_ipv4 := ue_ip_addr.IPv4Address.String()
if source_iface, err := pdr.SourceInterface(); err != nil {
logrus.WithError(err).Debug("skip: error getting source-iface")
return nil
} else if source_iface == ie.SrcInterfaceAccess {
fteid, err := pdr.FTEID()
if err != nil {
logrus.WithError(err).Debug("skip: no fteid")
return nil
}
if ue, loaded := ues.LoadOrStore(ue_ipv4, &ueInfos{
UplinkTeid: fteid.TEID,
}); loaded {
logrus.WithFields(logrus.Fields{
"teid-uplink": fteid.TEID,
"ue-ipv4": ue_ipv4,
}).Debug("Updating UeInfos")
ue.(*ueInfos).UplinkTeid = fteid.TEID
} else if logrus.IsLevelEnabled(logrus.DebugLevel) {
logrus.WithFields(logrus.Fields{
"teid-uplink": fteid.TEID,
"ue-ipv4": ue_ipv4,
}).Debug("Adding new ue to UeInfos")
}

} else if (source_iface == ie.SrcInterfaceCore) || (source_iface == ie.SrcInterfaceSGiLANN6LAN) {
far, err := session.GetFAR(farid)
if err != nil {
logrus.WithError(err).Debug("skip: error getting far")
return nil
}
ForwardingParametersIe, err := far.ForwardingParameters()
if err != nil {
// no forwarding prameters (maybe because hasn't FORW ?)
return nil
}
if ohc, err := ForwardingParametersIe.OuterHeaderCreation(); err == nil {
// FIXME: temporary hack, no IPv6 support
gnb_ipv4 := ohc.IPv4Address.String()
teid_downlink := ohc.TEID
if ue, loaded := ues.LoadOrStore(ue_ipv4, &ueInfos{
DownlinkTeid: teid_downlink,
Gnb: gnb_ipv4,
}); loaded {
logrus.WithFields(logrus.Fields{
"gnb-ipv4": gnb_ipv4,
"teid-downlink": teid_downlink,
"ue-ipv4": ue_ipv4,
}).Debug("Updating UeInfos")
ue.(*ueInfos).Gnb = gnb_ipv4
ue.(*ueInfos).DownlinkTeid = teid_downlink
} else if logrus.IsLevelEnabled(logrus.DebugLevel) {
logrus.WithFields(logrus.Fields{
"gnb-ipv4": gnb_ipv4,
"teid-downlink": teid_downlink,
"ue-ipv4": ue_ipv4,
}).Debug("Adding new ue to UeInfos")
}

} else {
logrus.WithError(err).Debug("skip: error getting ohc")
return nil
}
} else {
return nil
}
return nil
})
return nil
}()
}
wg0.Wait()
var wg sync.WaitGroup
ues.Range(func(ip any, ue any) bool {
if ue.(*ueInfos).DownlinkTeid == 0 {
// no set yet => session will be modified
logrus.WithFields(logrus.Fields{
"ue-ipv4": ip,
}).Debug("Downlink TEID is null")
return true
}
logrus.WithFields(logrus.Fields{
"ue-ipv4": ip,
"gnb-ipv4": ue.(*ueInfos).Gnb,
"teid-downlink": ue.(*ueInfos).DownlinkTeid,
"teid-uplink": ue.(*ueInfos).UplinkTeid,
}).Debug("PushRTRRule")
wg.Add(1)
go func() {
defer wg.Done()
pusher.pushRTRRule(ip.(string), ue.(*ueInfos).Gnb, ue.(*ueInfos).DownlinkTeid, ue.(*ueInfos).UplinkTeid)
// TODO: check pushRTRRule return code and send pfcp error on failure
}()
return true
})
wg.Wait()
logrus.Debug("Exit updateRoutersRules")
}
12 changes: 7 additions & 5 deletions internal/app/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,21 @@ import (
)

type Setup struct {
HTTPServer *HttpServerEntity
PFCPServer *pfcp_networking.PFCPEntityUP
HTTPServer *HttpServerEntity
PFCPServer *pfcp_networking.PFCPEntityUP
RulesPusher *RulesPusher
}

func NewSetup(conf *config.CtrlConfig) Setup {
return Setup{
HTTPServer: NewHttpServer(conf),
PFCPServer: NewPFCPNode(conf),
HTTPServer: NewHttpServer(conf),
PFCPServer: NewPFCPNode(conf),
RulesPusher: NewRulesPusher(conf),
}
}

func (s Setup) Run(ctx context.Context) error {
if err := PFCPServerAddHooks(s.PFCPServer); err != nil {
if err := PFCPServerAddHooks(s.PFCPServer, s.RulesPusher); err != nil {
return err
}
StartPFCPServer(ctx, s.PFCPServer)
Expand Down
Loading

0 comments on commit e597828

Please sign in to comment.