@@ -36,6 +36,11 @@ type RulesPusher struct {
3636 ues sync.Map
3737}
3838
39+ type RuleAction struct {
40+ Url * url.URL
41+ Action * n4tosrv6.Action
42+ GtpDstPrefix netip.Prefix
43+ }
3944type ueInfos struct {
4045 sync.Mutex
4146
@@ -44,11 +49,11 @@ type ueInfos struct {
4449 Gnb string
4550 Pushed bool
4651
47- AnchorsRules []* url. URL
48- AnchorsLock sync.Mutex
52+ AnchorsRules []* RuleAction
53+ AnchorsLock sync.RWMutex
4954
50- SRGWRules []* url. URL
51- SRGWLock sync.Mutex
55+ SRGWRules []* RuleAction
56+ SRGWLock sync.RWMutex
5257}
5358
5459func NewRulesPusher (config * config.CtrlConfig ) * RulesPusher {
@@ -59,6 +64,29 @@ func NewRulesPusher(config *config.CtrlConfig) *RulesPusher {
5964 }
6065}
6166
67+ func (pusher * RulesPusher ) pushUpdateAction (ctx context.Context , client http.Client , url * url.URL , data []byte ) error {
68+ req , err := http .NewRequestWithContext (ctx , http .MethodPatch , url .String (), bytes .NewBuffer (data ))
69+ if err != nil {
70+ logrus .WithError (err ).Error ("could not create http request" )
71+ return err
72+ }
73+ req .Header .Add ("User-Agent" , UserAgent )
74+ req .Header .Set ("Content-Type" , "application/json; charset=UTF-8" )
75+ resp , err := client .Do (req )
76+ if err != nil {
77+ logrus .WithError (err ).Error ("Could not push update action: server not responding" )
78+ return fmt .Errorf ("Could not push update action: server not responding" )
79+ }
80+ defer resp .Body .Close ()
81+ if resp .StatusCode == 400 {
82+ logrus .WithError (err ).Error ("HTTP Bad Request" )
83+ return fmt .Errorf ("HTTP Bad request" )
84+ } else if resp .StatusCode >= 500 {
85+ logrus .WithError (err ).Error ("HTTP internal error" )
86+ return fmt .Errorf ("HTTP internal error" )
87+ }
88+ return nil
89+ }
6290func (pusher * RulesPusher ) pushSingleRule (ctx context.Context , client http.Client , uri jsonapi.ControlURI , data []byte ) (* url.URL , error ) {
6391 req , err := http .NewRequestWithContext (ctx , http .MethodPost , uri .JoinPath ("rules" ).String (), bytes .NewBuffer (data ))
6492 if err != nil {
@@ -92,16 +120,16 @@ func (pusher *RulesPusher) pushSingleRule(ctx context.Context, client http.Clien
92120
93121func (pusher * RulesPusher ) pushRTRRule (ctx context.Context , ue_ip string ) error {
94122 i , ok := pusher .ues .Load (ue_ip )
123+ if ! ok {
124+ return fmt .Errorf ("UE not in ue list" )
125+ }
95126 infos := i .(* ueInfos )
96127 infos .Lock ()
97128 defer infos .Unlock ()
98129 if infos .Pushed {
99130 return nil // already pushed, nothing to do
100131 }
101132 infos .Pushed = true
102- if ! ok {
103- return fmt .Errorf ("UE not in ue list" )
104- }
105133 service_ip := "10.4.0.1"
106134 logrus .WithFields (logrus.Fields {
107135 "ue-ip" : ue_ip ,
@@ -134,6 +162,9 @@ func (pusher *RulesPusher) pushRTRRule(ctx context.Context, ue_ip string) error
134162 // if no area is defined, create a new-one with only this gnb
135163 area = []netip.Prefix {netip .PrefixFrom (gnb_addr , 32 )}
136164 }
165+ action := n4tosrv6.Action {
166+ SRH : * srh ,
167+ }
137168 rule := n4tosrv6.Rule {
138169 Enabled : r .Enabled ,
139170 Type : "uplink" ,
@@ -147,9 +178,7 @@ func (pusher *RulesPusher) pushRTRRule(ctx context.Context, ue_ip string) error
147178 Dst : service_addr ,
148179 },
149180 },
150- Action : n4tosrv6.Action {
151- SRH : * srh ,
152- },
181+ Action : action ,
153182 }
154183 rule_json , err := json .Marshal (rule )
155184 if err != nil {
@@ -163,7 +192,10 @@ func (pusher *RulesPusher) pushRTRRule(ctx context.Context, ue_ip string) error
163192 defer infos .SRGWLock .Unlock ()
164193 url , err := pusher .pushSingleRule (ctx , client , r .ControlURI , rule_json )
165194 if err == nil {
166- infos .SRGWRules = append (infos .SRGWRules , url )
195+ infos .SRGWRules = append (infos .SRGWRules , & RuleAction {
196+ Url : url ,
197+ Action : & action ,
198+ })
167199 }
168200 return err
169201 }()
@@ -190,6 +222,8 @@ func (pusher *RulesPusher) pushRTRRule(ctx context.Context, ue_ip string) error
190222 if ! ok {
191223 return fmt .Errorf ("could not convert MGTP4IPv6Dst to netip.Addr" )
192224 }
225+ // note: in srv6, segment[n] is the first segment of the path, and segment[0] is the last segment in the path
226+ // because Segment-Left is a pointer to the current segment and is decremented each SR-hop
193227 segList [0 ] = dstIp .String ()
194228
195229 srh , err := n4tosrv6 .NewSRH (segList )
@@ -199,6 +233,9 @@ func (pusher *RulesPusher) pushRTRRule(ctx context.Context, ue_ip string) error
199233 }).WithError (err ).Error ("Creation of SRH downlink failed" )
200234 return err
201235 }
236+ action := n4tosrv6.Action {
237+ SRH : * srh ,
238+ }
202239 rule := n4tosrv6.Rule {
203240 Enabled : true ,
204241 Type : "downlink" ,
@@ -207,9 +244,7 @@ func (pusher *RulesPusher) pushRTRRule(ctx context.Context, ue_ip string) error
207244 Dst : ue_addr ,
208245 },
209246 },
210- Action : n4tosrv6.Action {
211- SRH : * srh ,
212- },
247+ Action : action ,
213248 }
214249 rule_json , err := json .Marshal (rule )
215250 if err != nil {
@@ -223,18 +258,86 @@ func (pusher *RulesPusher) pushRTRRule(ctx context.Context, ue_ip string) error
223258 defer infos .AnchorsLock .Unlock ()
224259 url , err := pusher .pushSingleRule (ctx , client , r .ControlURI , rule_json )
225260 if err == nil {
226- infos .AnchorsRules = append (infos .AnchorsRules , url )
261+ infos .AnchorsRules = append (infos .AnchorsRules , & RuleAction {
262+ Url : url ,
263+ Action : & action ,
264+ GtpDstPrefix : prefix ,
265+ })
227266 }
228267 return err
229268 }()
230269
231270 }
232271 wg .Wait ()
272+ pusher .ues .Store (ue_ip , infos )
273+
233274 return nil
234275}
235276
236- func (pusher * RulesPusher ) pushHandover (ctx context.Context , ue string , handoverTo jsonapi.Fteid ) {
237- //TODO Handover
277+ func (pusher * RulesPusher ) pushHandover (ctx context.Context , ue_ip string , handoverTo jsonapi.Fteid ) error {
278+ i , ok := pusher .ues .Load (ue_ip )
279+ if ! ok {
280+ return fmt .Errorf ("UE not in ue list" )
281+ }
282+ infos := i .(* ueInfos )
283+ infos .Lock ()
284+ defer infos .Unlock ()
285+
286+ client := http.Client {}
287+ var wg sync.WaitGroup
288+
289+ infos .AnchorsLock .RLock ()
290+ defer infos .AnchorsLock .RUnlock ()
291+
292+ logrus .WithFields (logrus.Fields {
293+ "nb-uplink" : len (infos .AnchorsRules ),
294+ "ue-ip" : ue_ip ,
295+ "handover-to-addr" : handoverTo .Addr ,
296+ "handover-to-teid" : handoverTo .Teid ,
297+ }).Debug ("Pushing new downlink rules for handover" )
298+
299+ for _ , r := range infos .AnchorsRules {
300+ dst := encoding .NewMGTP4IPv6Dst (r .GtpDstPrefix , handoverTo .Addr .As4 (), encoding .NewArgsMobSession (0 , false , false , handoverTo .Teid ))
301+ dstB , err := dst .Marshal ()
302+ if err != nil {
303+ return err
304+ }
305+ dstIp , ok := netip .AddrFromSlice (dstB )
306+ if ! ok {
307+ return fmt .Errorf ("could not convert MGTP4IPv6Dst to netip.Addr" )
308+ }
309+ seg0 , err := n4tosrv6 .NewSegment (dstIp .String ())
310+ if err != nil {
311+ return err
312+ }
313+ // note: in srv6, segment[n] is the first segment of the path, and segment[0] is the last segment in the path
314+ // because Segment-Left is a pointer to the current segment and is decremented each SR-hop
315+ r .Action .SRH [0 ] = seg0
316+
317+ action_json , err := json .Marshal (r .Action )
318+ if err != nil {
319+ logrus .WithError (err ).Error ("Could not marshal json" )
320+ return err
321+ }
322+ wg .Add (1 )
323+ go func () error {
324+ defer wg .Done ()
325+ err := pusher .pushUpdateAction (ctx , client , r .Url .JoinPath ("update-action" ), action_json )
326+ if err != nil {
327+ logrus .WithError (err ).Error ("Could not push update action" )
328+ } else {
329+ logrus .WithFields (logrus.Fields {
330+ "path" : r .Url .JoinPath ("update-action" ).String (),
331+ }).Debug ("pushed update action" )
332+ }
333+ return err
334+ }()
335+ }
336+ wg .Wait ()
337+ infos .Gnb = handoverTo .Addr .String ()
338+ infos .DownlinkTeid = handoverTo .Teid
339+ pusher .ues .Store (ue_ip , infos )
340+ return nil
238341
239342}
240343
@@ -320,7 +423,12 @@ func (pusher *RulesPusher) updateRoutersRules(ctx context.Context, msgType pfcpu
320423 "pdrid" : pdrid ,
321424 "ue" : ue .IPv4Address .String (),
322425 }).Debug ("UE identified for handover" )
323- pusher .pushHandover (ctx , ue .IPv4Address .String (), handoverTo )
426+ go func () {
427+ err := pusher .pushHandover (ctx , ue .IPv4Address .String (), handoverTo )
428+ if err != nil {
429+ logrus .WithError (err ).Error ("Could not push handover rule" )
430+ }
431+ }()
324432 handoverDone = true
325433 return nil
326434 })
@@ -369,8 +477,8 @@ func (pusher *RulesPusher) updateRoutersRules(ctx context.Context, msgType pfcpu
369477 }
370478 if ue , loaded := pusher .ues .LoadOrStore (ue_ipv4 , & ueInfos {
371479 UplinkFTeid : jsonapi.Fteid {Teid : fteid .TEID , Addr : addr },
372- AnchorsRules : make ([]* url. URL , 0 ),
373- SRGWRules : make ([]* url. URL , 0 ),
480+ AnchorsRules : make ([]* RuleAction , 0 ),
481+ SRGWRules : make ([]* RuleAction , 0 ),
374482 }); loaded {
375483 logrus .WithFields (logrus.Fields {
376484 "teid-uplink" : fteid .TEID ,
@@ -405,8 +513,8 @@ func (pusher *RulesPusher) updateRoutersRules(ctx context.Context, msgType pfcpu
405513 if ue , loaded := pusher .ues .LoadOrStore (ue_ipv4 , & ueInfos {
406514 DownlinkTeid : teid_downlink ,
407515 Gnb : gnb_ipv4 ,
408- AnchorsRules : make ([]* url. URL , 0 ),
409- SRGWRules : make ([]* url. URL , 0 ),
516+ AnchorsRules : make ([]* RuleAction , 0 ),
517+ SRGWRules : make ([]* RuleAction , 0 ),
410518 }); loaded {
411519 logrus .WithFields (logrus.Fields {
412520 "gnb-ipv4" : gnb_ipv4 ,
0 commit comments