Skip to content

Commit cdd5d68

Browse files
committed
Add handover across areas
1 parent 196fb2a commit cdd5d68

File tree

1 file changed

+248
-45
lines changed

1 file changed

+248
-45
lines changed

internal/app/rules-pusher.go

Lines changed: 248 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ type RuleAction struct {
4141
Action *n4tosrv6.Action
4242
GtpDstPrefix netip.Prefix
4343
}
44+
45+
type HandoverInfos struct {
46+
UlTargetSrgw jsonapi.Fteid
47+
DlTargetGnb *jsonapi.Fteid
48+
}
49+
4450
type ueInfos struct {
4551
sync.Mutex
4652

@@ -49,8 +55,9 @@ type ueInfos struct {
4955
Gnb string
5056
Pushed bool
5157

52-
AnchorsRules []*RuleAction
53-
AnchorsLock sync.RWMutex
58+
HandoverInfos *HandoverInfos
59+
AnchorsRules []*RuleAction
60+
AnchorsLock sync.RWMutex
5461

5562
SRGWRules []*RuleAction
5663
SRGWLock sync.RWMutex
@@ -127,6 +134,193 @@ func gnbInArea(gnb netip.Addr, area []netip.Prefix) bool {
127134
return false
128135
}
129136

137+
func (pusher *RulesPusher) pushHandoverAcrossAreas(ctx context.Context, ue_ip string) error {
138+
i, ok := pusher.ues.Load(ue_ip)
139+
if !ok {
140+
return fmt.Errorf("UE not in ue list")
141+
}
142+
infos := i.(*ueInfos)
143+
infos.Lock()
144+
defer infos.Unlock()
145+
if !infos.Pushed {
146+
return nil // not pushed, nothing to do
147+
}
148+
if infos.HandoverInfos == nil {
149+
return nil // no handover
150+
}
151+
if infos.HandoverInfos.DlTargetGnb == nil {
152+
return nil // not enough info, yet
153+
}
154+
gnb_addr := infos.HandoverInfos.DlTargetGnb.Addr
155+
ue_addr, err := netip.ParseAddr(ue_ip)
156+
if err != nil {
157+
return err
158+
}
159+
client := http.Client{}
160+
logrus.WithFields(logrus.Fields{
161+
"dl-teid": infos.HandoverInfos.DlTargetGnb.Teid,
162+
"dl-addr": infos.HandoverInfos.DlTargetGnb.Addr,
163+
"ul-teid": infos.HandoverInfos.UlTargetSrgw.Teid,
164+
"ul-addr": infos.HandoverInfos.UlTargetSrgw.Addr,
165+
"ue-ip": ue_ip,
166+
}).Info("Pushing Handover Rules")
167+
var wg sync.WaitGroup
168+
169+
for _, r := range pusher.uplink {
170+
if r.Service == nil {
171+
return fmt.Errorf("service configurated is nil for uplink rule")
172+
}
173+
//TODO: add ArgMobSession
174+
srh, err := n4tosrv6.NewSRH(r.SegmentsList)
175+
if err != nil {
176+
logrus.WithFields(logrus.Fields{
177+
"segments-list": r.SegmentsList,
178+
}).WithError(err).Error("Creation of SRH uplink failed")
179+
return err
180+
}
181+
var area []netip.Prefix
182+
if r.Area != nil {
183+
area = *r.Area
184+
} else {
185+
// if no area is defined, create a new-one with only this gnb
186+
area = []netip.Prefix{netip.PrefixFrom(gnb_addr, 32)}
187+
}
188+
// check infos.Gnb in area
189+
if !gnbInArea(gnb_addr, area) {
190+
continue
191+
}
192+
193+
action := n4tosrv6.Action{
194+
SRH: *srh,
195+
}
196+
rule := n4tosrv6.Rule{
197+
Enabled: r.Enabled,
198+
Type: "uplink",
199+
Match: n4tosrv6.Match{
200+
Header: &n4tosrv6.GtpHeader{
201+
OuterIpSrc: area,
202+
FTeid: infos.HandoverInfos.UlTargetSrgw,
203+
InnerIpSrc: &ue_addr,
204+
},
205+
Payload: &n4tosrv6.Payload{
206+
// TODO: allow multiple services
207+
Dst: *r.Service,
208+
},
209+
},
210+
Action: action,
211+
}
212+
rule_json, err := json.Marshal(rule)
213+
if err != nil {
214+
logrus.WithError(err).Error("Could not marshal json")
215+
return err
216+
}
217+
wg.Add(1)
218+
// TODO: remove old rules
219+
go func() error {
220+
defer wg.Done()
221+
//infos.SRGWLock.Lock()
222+
//defer infos.SRGWLock.Unlock()
223+
_, _ = pusher.pushSingleRule(ctx, client, r.ControlURI, rule_json)
224+
// TODO: store this (after old rules are removed)
225+
//if err == nil {
226+
// infos.SRGWRules = append(infos.SRGWRules, &RuleAction{
227+
// Url: url,
228+
// Action: &action,
229+
// })
230+
//}
231+
return err
232+
}()
233+
234+
}
235+
236+
for _, r := range pusher.downlink {
237+
var area []netip.Prefix
238+
if r.Area != nil {
239+
area = *r.Area
240+
} else {
241+
// if no area is defined, create a new-one with only this gnb
242+
area = []netip.Prefix{netip.PrefixFrom(gnb_addr, 32)}
243+
}
244+
// check infos.Gnb in area
245+
if !gnbInArea(gnb_addr, area) {
246+
continue
247+
}
248+
if len(r.SegmentsList) == 0 {
249+
logrus.Error("Empty segments list for downlink")
250+
return fmt.Errorf("Empty segments list for downlink")
251+
}
252+
segList := make([]string, len(r.SegmentsList))
253+
copy(segList, r.SegmentsList)
254+
prefix, err := netip.ParsePrefix(r.SegmentsList[0])
255+
if err != nil {
256+
return err
257+
}
258+
dst := encoding.NewMGTP4IPv6Dst(prefix, gnb_addr.As4(), encoding.NewArgsMobSession(0, false, false, infos.HandoverInfos.DlTargetGnb.Teid))
259+
dstB, err := dst.Marshal()
260+
if err != nil {
261+
return err
262+
}
263+
dstIp, ok := netip.AddrFromSlice(dstB)
264+
if !ok {
265+
return fmt.Errorf("could not convert MGTP4IPv6Dst to netip.Addr")
266+
}
267+
// note: in srv6, segment[n] is the first segment of the path, and segment[0] is the last segment in the path
268+
// because Segment-Left is a pointer to the current segment and is decremented each SR-hop
269+
segList[0] = dstIp.String()
270+
271+
srh, err := n4tosrv6.NewSRH(segList)
272+
if err != nil {
273+
logrus.WithFields(logrus.Fields{
274+
"segments-list": r.SegmentsList,
275+
}).WithError(err).Error("Creation of SRH downlink failed")
276+
return err
277+
}
278+
action := n4tosrv6.Action{
279+
SRH: *srh,
280+
SourceGtp4: r.SrgwGtp4,
281+
}
282+
rule := n4tosrv6.Rule{
283+
Enabled: true,
284+
Type: "downlink",
285+
Match: n4tosrv6.Match{
286+
Payload: &n4tosrv6.Payload{
287+
Dst: ue_addr,
288+
},
289+
},
290+
Action: action,
291+
}
292+
rule_json, err := json.Marshal(rule)
293+
if err != nil {
294+
logrus.WithError(err).Error("Could not marshal json")
295+
return err
296+
}
297+
wg.Add(1)
298+
// TODO: remove old rules
299+
go func() error {
300+
defer wg.Done()
301+
//infos.AnchorsLock.Lock()
302+
//defer infos.AnchorsLock.Unlock()
303+
_, _ = pusher.pushSingleRule(ctx, client, r.ControlURI, rule_json)
304+
// TODO: store this (after old rules are removed)
305+
//if err == nil {
306+
// infos.AnchorsRules = append(infos.AnchorsRules, &RuleAction{
307+
// Url: url,
308+
// Action: &action,
309+
// GtpDstPrefix: prefix,
310+
// })
311+
//}
312+
return err
313+
}()
314+
315+
}
316+
wg.Wait()
317+
infos.HandoverInfos = nil // reset handover state
318+
pusher.ues.Store(ue_ip, infos)
319+
320+
return nil
321+
322+
}
323+
130324
func (pusher *RulesPusher) pushRTRRule(ctx context.Context, ue_ip string) error {
131325
i, ok := pusher.ues.Load(ue_ip)
132326
if !ok {
@@ -376,33 +570,6 @@ func (pusher *RulesPusher) pushHandover(ctx context.Context, ue_ip string, hando
376570

377571
func (pusher *RulesPusher) updateRoutersRules(ctx context.Context, msgType pfcputil.MessageType, msg pfcp_networking.ReceivedMessage, e *pfcp_networking.PFCPEntityUP) {
378572
logrus.Debug("Into updateRoutersRules")
379-
// detect handover with indirect forwarding
380-
381-
// 1. Establishment request:
382-
// 1.1. new PDR UL (srgw1+teid)
383-
// 1.2 FAR to edge
384-
// -> we have UE ip and UL fteid, but we don't have gnb DL fteid yet
385-
// => we don't know the area of the UE
386-
// * store ul_fteids[ue_ip] = this ul fteid
387-
388-
// 2. Modification request:
389-
// 2.1 PDR new fteid srgw1+teid (forw)
390-
// 2.2 FAR gnbl3
391-
// -> we don't have UE ip, we have a forw fteid
392-
393-
// 3. Modification request:
394-
// 3.1 PDR new fteid srgw0+teid (forw)
395-
// 3.2 FAR srgw1+teid
396-
// -> we don't have UE ip, we have the forw fteid from step 2, we have the gnb fteid
397-
398-
// 4. Modification request:
399-
// 4.1. PDR match UE
400-
// 4.2. FAR to gnbl3
401-
// -> we have the UE ip, we have the gnb DL fteid
402-
// => we know the area of the UE
403-
// * establish UL using fteid from step 1
404-
// * establish DL using fteid from step 4
405-
406573
if msgType == message.MsgTypeSessionModificationRequest {
407574
logrus.Debug("session modification request")
408575
// check if handover
@@ -542,14 +709,31 @@ func (pusher *RulesPusher) updateRoutersRules(ctx context.Context, msgType pfcpu
542709
AnchorsRules: make([]*RuleAction, 0),
543710
SRGWRules: make([]*RuleAction, 0),
544711
}); loaded {
545-
logrus.WithFields(logrus.Fields{
546-
"teid-uplink": fteid.TEID,
547-
"ue-ipv4": ue_ipv4,
548-
}).Debug("Updating UeInfos")
712+
if ue.(*ueInfos).Pushed {
713+
if addr == ue.(*ueInfos).UplinkFTeid.Addr && fteid.TEID == ue.(*ueInfos).UplinkFTeid.Teid {
714+
return nil // old data
715+
}
716+
logrus.WithFields(logrus.Fields{
717+
"teid-uplink": fteid.TEID,
718+
"addr-uplink": addr,
719+
"ue-ipv4": ue_ipv4,
720+
}).Debug("Updating UeInfos with handoverInfos for uplink")
721+
ue.(*ueInfos).Lock()
722+
ue.(*ueInfos).HandoverInfos = &HandoverInfos{
723+
UlTargetSrgw: jsonapi.Fteid{Teid: fteid.TEID, Addr: addr},
724+
}
725+
ue.(*ueInfos).Unlock()
549726

550-
ue.(*ueInfos).Lock()
551-
ue.(*ueInfos).UplinkFTeid = jsonapi.Fteid{Teid: fteid.TEID, Addr: addr}
552-
ue.(*ueInfos).Unlock()
727+
} else {
728+
logrus.WithFields(logrus.Fields{
729+
"teid-uplink": fteid.TEID,
730+
"ue-ipv4": ue_ipv4,
731+
}).Debug("Updating UeInfos")
732+
733+
ue.(*ueInfos).Lock()
734+
ue.(*ueInfos).UplinkFTeid = jsonapi.Fteid{Teid: fteid.TEID, Addr: addr}
735+
ue.(*ueInfos).Unlock()
736+
}
553737
} else if logrus.IsLevelEnabled(logrus.DebugLevel) {
554738
logrus.WithFields(logrus.Fields{
555739
"teid-uplink": fteid.TEID,
@@ -578,15 +762,33 @@ func (pusher *RulesPusher) updateRoutersRules(ctx context.Context, msgType pfcpu
578762
AnchorsRules: make([]*RuleAction, 0),
579763
SRGWRules: make([]*RuleAction, 0),
580764
}); loaded {
581-
logrus.WithFields(logrus.Fields{
582-
"gnb-ipv4": gnb_ipv4,
583-
"teid-downlink": teid_downlink,
584-
"ue-ipv4": ue_ipv4,
585-
}).Debug("Updating UeInfos")
586-
ue.(*ueInfos).Lock()
587-
ue.(*ueInfos).Gnb = gnb_ipv4
588-
ue.(*ueInfos).DownlinkTeid = teid_downlink
589-
ue.(*ueInfos).Unlock()
765+
if ue.(*ueInfos).Pushed {
766+
if gnb_ipv4 == ue.(*ueInfos).Gnb && teid_downlink == ue.(*ueInfos).DownlinkTeid {
767+
return nil // old info
768+
}
769+
addr, err := netip.ParseAddr(gnb_ipv4)
770+
if err != nil {
771+
return nil
772+
}
773+
logrus.WithFields(logrus.Fields{
774+
"teid-downlink": teid_downlink,
775+
"addr-downlink": gnb_ipv4,
776+
"ue-ipv4": ue_ipv4,
777+
}).Debug("Updating UeInfos with handoverInfos for downlink")
778+
ue.(*ueInfos).Lock()
779+
ue.(*ueInfos).HandoverInfos.DlTargetGnb = &jsonapi.Fteid{Teid: teid_downlink, Addr: addr}
780+
ue.(*ueInfos).Unlock()
781+
} else {
782+
logrus.WithFields(logrus.Fields{
783+
"gnb-ipv4": gnb_ipv4,
784+
"teid-downlink": teid_downlink,
785+
"ue-ipv4": ue_ipv4,
786+
}).Debug("Updating UeInfos")
787+
ue.(*ueInfos).Lock()
788+
ue.(*ueInfos).Gnb = gnb_ipv4
789+
ue.(*ueInfos).DownlinkTeid = teid_downlink
790+
ue.(*ueInfos).Unlock()
791+
}
590792
} else if logrus.IsLevelEnabled(logrus.DebugLevel) {
591793
logrus.WithFields(logrus.Fields{
592794
"gnb-ipv4": gnb_ipv4,
@@ -628,6 +830,7 @@ func (pusher *RulesPusher) updateRoutersRules(ctx context.Context, msgType pfcpu
628830
go func() {
629831
defer wg.Done()
630832
pusher.pushRTRRule(ctx, ip.(string))
833+
pusher.pushHandoverAcrossAreas(ctx, ip.(string))
631834
// TODO: check pushRTRRule return code and send pfcp error on failure
632835
}()
633836
return true

0 commit comments

Comments
 (0)