@@ -36,6 +36,11 @@ type RulesPusher struct {
36
36
ues sync.Map
37
37
}
38
38
39
+ type RuleAction struct {
40
+ Url * url.URL
41
+ Action * n4tosrv6.Action
42
+ GtpDstPrefix netip.Prefix
43
+ }
39
44
type ueInfos struct {
40
45
sync.Mutex
41
46
@@ -44,11 +49,11 @@ type ueInfos struct {
44
49
Gnb string
45
50
Pushed bool
46
51
47
- AnchorsRules []* url. URL
48
- AnchorsLock sync.Mutex
52
+ AnchorsRules []* RuleAction
53
+ AnchorsLock sync.RWMutex
49
54
50
- SRGWRules []* url. URL
51
- SRGWLock sync.Mutex
55
+ SRGWRules []* RuleAction
56
+ SRGWLock sync.RWMutex
52
57
}
53
58
54
59
func NewRulesPusher (config * config.CtrlConfig ) * RulesPusher {
@@ -59,6 +64,29 @@ func NewRulesPusher(config *config.CtrlConfig) *RulesPusher {
59
64
}
60
65
}
61
66
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
+ }
62
90
func (pusher * RulesPusher ) pushSingleRule (ctx context.Context , client http.Client , uri jsonapi.ControlURI , data []byte ) (* url.URL , error ) {
63
91
req , err := http .NewRequestWithContext (ctx , http .MethodPost , uri .JoinPath ("rules" ).String (), bytes .NewBuffer (data ))
64
92
if err != nil {
@@ -92,16 +120,16 @@ func (pusher *RulesPusher) pushSingleRule(ctx context.Context, client http.Clien
92
120
93
121
func (pusher * RulesPusher ) pushRTRRule (ctx context.Context , ue_ip string ) error {
94
122
i , ok := pusher .ues .Load (ue_ip )
123
+ if ! ok {
124
+ return fmt .Errorf ("UE not in ue list" )
125
+ }
95
126
infos := i .(* ueInfos )
96
127
infos .Lock ()
97
128
defer infos .Unlock ()
98
129
if infos .Pushed {
99
130
return nil // already pushed, nothing to do
100
131
}
101
132
infos .Pushed = true
102
- if ! ok {
103
- return fmt .Errorf ("UE not in ue list" )
104
- }
105
133
service_ip := "10.4.0.1"
106
134
logrus .WithFields (logrus.Fields {
107
135
"ue-ip" : ue_ip ,
@@ -134,6 +162,9 @@ func (pusher *RulesPusher) pushRTRRule(ctx context.Context, ue_ip string) error
134
162
// if no area is defined, create a new-one with only this gnb
135
163
area = []netip.Prefix {netip .PrefixFrom (gnb_addr , 32 )}
136
164
}
165
+ action := n4tosrv6.Action {
166
+ SRH : * srh ,
167
+ }
137
168
rule := n4tosrv6.Rule {
138
169
Enabled : r .Enabled ,
139
170
Type : "uplink" ,
@@ -147,9 +178,7 @@ func (pusher *RulesPusher) pushRTRRule(ctx context.Context, ue_ip string) error
147
178
Dst : service_addr ,
148
179
},
149
180
},
150
- Action : n4tosrv6.Action {
151
- SRH : * srh ,
152
- },
181
+ Action : action ,
153
182
}
154
183
rule_json , err := json .Marshal (rule )
155
184
if err != nil {
@@ -163,7 +192,10 @@ func (pusher *RulesPusher) pushRTRRule(ctx context.Context, ue_ip string) error
163
192
defer infos .SRGWLock .Unlock ()
164
193
url , err := pusher .pushSingleRule (ctx , client , r .ControlURI , rule_json )
165
194
if err == nil {
166
- infos .SRGWRules = append (infos .SRGWRules , url )
195
+ infos .SRGWRules = append (infos .SRGWRules , & RuleAction {
196
+ Url : url ,
197
+ Action : & action ,
198
+ })
167
199
}
168
200
return err
169
201
}()
@@ -190,6 +222,8 @@ func (pusher *RulesPusher) pushRTRRule(ctx context.Context, ue_ip string) error
190
222
if ! ok {
191
223
return fmt .Errorf ("could not convert MGTP4IPv6Dst to netip.Addr" )
192
224
}
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
193
227
segList [0 ] = dstIp .String ()
194
228
195
229
srh , err := n4tosrv6 .NewSRH (segList )
@@ -199,6 +233,9 @@ func (pusher *RulesPusher) pushRTRRule(ctx context.Context, ue_ip string) error
199
233
}).WithError (err ).Error ("Creation of SRH downlink failed" )
200
234
return err
201
235
}
236
+ action := n4tosrv6.Action {
237
+ SRH : * srh ,
238
+ }
202
239
rule := n4tosrv6.Rule {
203
240
Enabled : true ,
204
241
Type : "downlink" ,
@@ -207,9 +244,7 @@ func (pusher *RulesPusher) pushRTRRule(ctx context.Context, ue_ip string) error
207
244
Dst : ue_addr ,
208
245
},
209
246
},
210
- Action : n4tosrv6.Action {
211
- SRH : * srh ,
212
- },
247
+ Action : action ,
213
248
}
214
249
rule_json , err := json .Marshal (rule )
215
250
if err != nil {
@@ -223,18 +258,86 @@ func (pusher *RulesPusher) pushRTRRule(ctx context.Context, ue_ip string) error
223
258
defer infos .AnchorsLock .Unlock ()
224
259
url , err := pusher .pushSingleRule (ctx , client , r .ControlURI , rule_json )
225
260
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
+ })
227
266
}
228
267
return err
229
268
}()
230
269
231
270
}
232
271
wg .Wait ()
272
+ pusher .ues .Store (ue_ip , infos )
273
+
233
274
return nil
234
275
}
235
276
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
238
341
239
342
}
240
343
@@ -320,7 +423,12 @@ func (pusher *RulesPusher) updateRoutersRules(ctx context.Context, msgType pfcpu
320
423
"pdrid" : pdrid ,
321
424
"ue" : ue .IPv4Address .String (),
322
425
}).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
+ }()
324
432
handoverDone = true
325
433
return nil
326
434
})
@@ -369,8 +477,8 @@ func (pusher *RulesPusher) updateRoutersRules(ctx context.Context, msgType pfcpu
369
477
}
370
478
if ue , loaded := pusher .ues .LoadOrStore (ue_ipv4 , & ueInfos {
371
479
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 ),
374
482
}); loaded {
375
483
logrus .WithFields (logrus.Fields {
376
484
"teid-uplink" : fteid .TEID ,
@@ -405,8 +513,8 @@ func (pusher *RulesPusher) updateRoutersRules(ctx context.Context, msgType pfcpu
405
513
if ue , loaded := pusher .ues .LoadOrStore (ue_ipv4 , & ueInfos {
406
514
DownlinkTeid : teid_downlink ,
407
515
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 ),
410
518
}); loaded {
411
519
logrus .WithFields (logrus.Fields {
412
520
"gnb-ipv4" : gnb_ipv4 ,
0 commit comments