diff --git a/config/config.yaml b/config/config.yaml index facd925..c703069 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -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" diff --git a/go.mod b/go.mod index d9cf45d..6a95f06 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index fb14e40..7bdd7de 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/app/rules-pusher.go b/internal/app/rules-pusher.go new file mode 100644 index 0000000..21e9ade --- /dev/null +++ b/internal/app/rules-pusher.go @@ -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") +} diff --git a/internal/app/setup.go b/internal/app/setup.go index 2763bb1..84f9f75 100644 --- a/internal/app/setup.go +++ b/internal/app/setup.go @@ -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) diff --git a/internal/app/upf.go b/internal/app/upf.go index d9c766d..7c4094d 100644 --- a/internal/app/upf.go +++ b/internal/app/upf.go @@ -5,362 +5,25 @@ package app import ( - "bytes" "context" - "encoding/json" - "fmt" - "net/http" - "net/netip" - "sync" "github.com/nextmn/srv6-ctrl/internal/config" 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/sirupsen/logrus" - "github.com/wmnsk/go-pfcp/ie" "github.com/wmnsk/go-pfcp/message" ) -const UserAgent = "go-github-nextmn-srv6-ctrl" - -func 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 pushRTRRule(ue_ip string, gnb_ip string, teid_downlink uint32, teid_uplink uint32) error { - srgw_uri := "http://[fd00:0:0:0:2:8000:0:2]:8080" //FIXME: dont use hardcoded value - edgertr0 := "http://[fd00:0:0:0:2:8000:0:4]:8080" //FIXME: dont use hardcoded value - edgertr1 := "http://[fd00:0:0:0:2:8000:0:5]:8080" //FIXME: dont use hardcoded value - logrus.WithFields(logrus.Fields{ - "ue-ip": ue_ip, - "gnb-ip": gnb_ip, - "teid-downlink": teid_downlink, - "teid-uplink": teid_uplink, - }).Info("Pushing Router Rule") - - ue_addr := netip.MustParseAddr(ue_ip) // FIXME: don't trust input => ParseAddr - gnb_addr := netip.MustParseAddr(gnb_ip) // FIXME: don't trust user input => ParseAddr - service_addr := netip.MustParseAddr("10.4.0.1") // FIXME: don't trust user input => ParseAddr - - // FIXME: don't hardcode! - srh_downlink := "" - srh_uplink_1 := "fc00:2:1::" // FIXME - srh_uplink_2 := "fc00:3:1::" // FIXME - rr := "fc00:4:1::" //FIXME - if teid_downlink != 1 { - logrus.WithFields(logrus.Fields{ - "hardcoded-teid-downlink": 1, - "actual-teid-downlink": teid_downlink, - }).Error("downlink TEID different than hardcoded one! It's time to write more code :(") - return fmt.Errorf("Not implemented with this teid") - } - switch gnb_ip { - case "10.1.4.129": // gnb1 - srh_downlink = "fc00:1:1:0A01:0481:0:0:0100" - break - - case "10.1.4.130": // gnb2 - srh_downlink = "fc00:1:1:0A01:0482:0:0:0100" - break - default: - logrus.WithFields(logrus.Fields{"gnb-ip": gnb_ip}).Error("Wrong gnb ip") - return fmt.Errorf("Not implemented with this gnb ip") - } - nh_downlink, err := jsonapi.NewNextHop(rr) - if err != nil { - logrus.WithError(err).Error("Creation of NextHop downlink failed") - return err - } - - nh_uplink1, err := jsonapi.NewNextHop(rr) - if err != nil { - logrus.WithError(err).Error("Creation of NextHop uplink 1 failed") - return err - } - nh_uplink2, err := jsonapi.NewNextHop(rr) - if err != nil { - logrus.WithError(err).Error("Creation of NextHop uplink 2 failed") - return err - } - srh_downlink_json, err := jsonapi.NewSRH([]string{srh_downlink}) - if err != nil { - logrus.WithError(err).Error("Creation of SRH downlink failed") - return err - } - srh_uplink1_json, err := jsonapi.NewSRH([]string{srh_uplink_1}) - if err != nil { - logrus.WithError(err).Error("Creation of SRH uplink 1 failed") - return err - } - srh_uplink2_json, err := jsonapi.NewSRH([]string{srh_uplink_2}) - if err != nil { - logrus.WithError(err).Error("Creation of SRH uplink 2 failed") - return err - } - - data_edge := jsonapi.Rule{ - Enabled: true, - Type: "downlink", - Match: jsonapi.Match{ - Payload: &jsonapi.Payload{ - Dst: ue_addr, - }, - }, - Action: jsonapi.Action{ - NextHop: *nh_downlink, - SRH: *srh_downlink_json, - }, - } - - data_gw1 := jsonapi.Rule{ - Enabled: true, - Type: "uplink", - Match: jsonapi.Match{ - Header: &jsonapi.GtpHeader{ - OuterIpSrc: gnb_addr, // TODO - Teid: teid_uplink, - InnerIpSrc: &ue_addr, - }, - Payload: &jsonapi.Payload{ - Dst: service_addr, - }, - }, - Action: jsonapi.Action{ - NextHop: *nh_uplink1, - SRH: *srh_uplink1_json, - }, - } - json_data_gw1, err := json.Marshal(data_gw1) - if err != nil { - logrus.WithError(err).Error("Could not marshal json") - return err - } - data_gw2 := jsonapi.Rule{ - Enabled: false, - Type: "uplink", - Match: jsonapi.Match{ - Header: &jsonapi.GtpHeader{ - OuterIpSrc: gnb_addr, // TODO - Teid: teid_uplink, - InnerIpSrc: &ue_addr, - }, - Payload: &jsonapi.Payload{ - Dst: service_addr, - }, - }, - Action: jsonapi.Action{ - NextHop: *nh_uplink2, - SRH: *srh_uplink2_json, - }, - } - json_data_gw2, err := json.Marshal(data_gw2) - if err != nil { - logrus.WithError(err).Error("Could not marshal json") - return err - } - json_data_edge, err := json.Marshal(data_edge) - if err != nil { - logrus.WithError(err).Error("Could not marshal json") - return err - } - - //FIXME: dont send to every node, only to relevant ones - client := http.Client{} - var wg sync.WaitGroup - - wg.Add(1) - go func() error { - defer wg.Done() - return pushSingleRule(client, srgw_uri, json_data_gw1) - }() - - wg.Add(1) - go func() error { - defer wg.Done() - return pushSingleRule(client, srgw_uri, json_data_gw2) - }() - - wg.Add(1) - go func() error { - defer wg.Done() - return pushSingleRule(client, edgertr0, json_data_edge) - }() - - wg.Add(1) - go func() error { - defer wg.Done() - return pushSingleRule(client, edgertr1, json_data_edge) - }() - wg.Wait() - return nil -} - -type ueInfos struct { - UplinkTeid uint32 - DownlinkTeid uint32 - Gnb string -} - -func 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() - 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") -} - func NewPFCPNode(conf *config.CtrlConfig) *pfcp_networking.PFCPEntityUP { return pfcp_networking.NewPFCPEntityUP(conf.PFCPAddress.String(), conf.PFCPAddress.String()) } -func PFCPServerAddHooks(s *pfcp_networking.PFCPEntityUP) error { +func PFCPServerAddHooks(s *pfcp_networking.PFCPEntityUP, pusher *RulesPusher) error { if err := s.AddHandler(message.MsgTypeSessionEstablishmentRequest, func(ctx context.Context, msg pfcp_networking.ReceivedMessage) (*pfcp_networking.OutcomingMessage, error) { out, err := pfcp_networking.DefaultSessionEstablishmentRequestHandler(ctx, msg) if err == nil { go s.LogPFCPRules() - updateRoutersRules(ctx, message.MsgTypeSessionEstablishmentRequest, msg, s) + pusher.updateRoutersRules(ctx, message.MsgTypeSessionEstablishmentRequest, msg, s) } return out, err }); err != nil { @@ -370,7 +33,7 @@ func PFCPServerAddHooks(s *pfcp_networking.PFCPEntityUP) error { out, err := pfcp_networking.DefaultSessionModificationRequestHandler(ctx, msg) if err == nil { go s.LogPFCPRules() - updateRoutersRules(ctx, message.MsgTypeSessionModificationRequest, msg, s) + pusher.updateRoutersRules(ctx, message.MsgTypeSessionModificationRequest, msg, s) } return out, err }); err != nil { diff --git a/internal/config/config.go b/internal/config/config.go index fc05588..51f5ba3 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -33,4 +33,6 @@ type CtrlConfig struct { HTTPAddress netip.Addr `yaml:"http-address"` HTTPPort *string `yaml:"http-port,omitempty"` // default: 80 Logger *Logger `yaml:"logger,omitempty"` + Uplink []Rule `yaml:"uplink"` + Downlink []Rule `yaml:"downlink"` } diff --git a/internal/config/rule.go b/internal/config/rule.go new file mode 100644 index 0000000..515eee4 --- /dev/null +++ b/internal/config/rule.go @@ -0,0 +1,11 @@ +// Copyright 2023 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 config + +type Rule struct { + ControlURI string `yaml:"control-uri"` // e.g. http://srgw.local:8080 + Enabled bool `yaml:"enabled"` + SegmentsList []string `yaml:"segments-list"` // Segment[0] is the ultimate node, Segment[n-1] is the next hop ; Segment[0] can be a prefix (for downlink) +}