Skip to content

Commit bc56e5b

Browse files
committed
Add GTP4
1 parent 5a9db84 commit bc56e5b

File tree

4 files changed

+263
-31
lines changed

4 files changed

+263
-31
lines changed

mup/args-mob-session.go

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright 2023 Louis Royer and the NextMN-SRv6 contributors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style license that can be
3+
// found in the LICENSE file.
4+
// SPDX-License-Identifier: MIT
5+
package mup
6+
7+
import "encoding/binary"
8+
9+
// Args.Mob.Session as defined in draft-ietf-dmm-srv6-mobile-uplane-24, section 6.1
10+
type ArgsMobSession struct {
11+
qfi uint8 // QoS Flow Identifier (6 bits)
12+
r uint8 // Reflective QoS Indication (1 bit)
13+
u uint8 // Unused and for future use (1 bit)
14+
pduSessionID uint32 // Identifier of PDU Session. The GTP-U equivalent is TEID (32 bits)
15+
}
16+
17+
// NewArgsMobSession creates an ArgsMobSession.
18+
func NewArgsMobSession(qfi uint8, r bool, u bool, pduSessionID uint32) *ArgsMobSession {
19+
var ruint uint8 = 0
20+
if r {
21+
ruint = 1
22+
}
23+
var uuint uint8 = 0
24+
if u {
25+
uuint = 1
26+
}
27+
return &ArgsMobSession{
28+
qfi: qfi,
29+
r: ruint,
30+
u: uuint,
31+
pduSessionID: pduSessionID,
32+
}
33+
}
34+
35+
// ParseArgsMobSession parses given byte sequence as an ArgsMobSession.
36+
func ParseArgsMobSession(b []byte) (*ArgsMobSession, error) {
37+
a := &ArgsMobSession{}
38+
if err := a.UnmarshalBinary(b); err != nil {
39+
return nil, err
40+
}
41+
return a, nil
42+
}
43+
44+
// QFI returns the Qos Flow Identifier for this ArgsMobSession.
45+
func (a *ArgsMobSession) QFI() uint8 {
46+
return a.qfi
47+
}
48+
49+
// R returns the Reflective QoS Indication for this ArgsMobSession.
50+
func (a *ArgsMobSession) R() bool {
51+
if a.r == 0 {
52+
return false
53+
}
54+
return true
55+
}
56+
57+
// U returns the U bit for this ArgsMobSession.
58+
func (a *ArgsMobSession) U() bool {
59+
if a.u == 0 {
60+
return false
61+
}
62+
return true
63+
}
64+
65+
// PDUSessionID returns the PDU Session Identifier for this ArgsMobSession. The GTP-U equivalent is TEID.
66+
func (a *ArgsMobSession) PDUSessionID() uint32 {
67+
return a.pduSessionID
68+
}
69+
70+
// MarshalLen returns the serial length of Args.Mob.Session.
71+
func (a *ArgsMobSession) MarshalLen() int {
72+
return 5
73+
}
74+
75+
// Marshal returns the byte sequence generated from ArgsMobSession.
76+
func (a *ArgsMobSession) Marshal() ([]byte, error) {
77+
b := make([]byte, a.MarshalLen())
78+
if err := a.MarshalTo(b); err != nil {
79+
return nil, err
80+
}
81+
return b, nil
82+
}
83+
84+
// MarshalTo puts the byte sequence in the byte array given as b.
85+
func (a *ArgsMobSession) MarshalTo(b []byte) error {
86+
if len(b) < a.MarshalLen() {
87+
return ErrTooShortToMarshal
88+
}
89+
b[0] = ((0x3F & a.qfi) << 2) | ((0x01 & a.r) << 1) | (0x01 & a.u)
90+
binary.BigEndian.PutUint32(b[1:5], a.pduSessionID)
91+
return nil
92+
}
93+
94+
// UnmarshalBinary sets the values retrieved from byte sequence in an ArgsMobSession.
95+
func (a *ArgsMobSession) UnmarshalBinary(b []byte) error {
96+
if len(b) < 5 {
97+
return ErrTooShortToParse
98+
}
99+
a.qfi = (0x3F & (b[0] >> 2))
100+
a.r = (0x01 & (b[0] >> 1))
101+
a.u = (0x01 & b[0])
102+
a.pduSessionID = binary.BigEndian.Uint32(b[1:5])
103+
return nil
104+
}

mup/errors.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2023 Louis Royer and the NextMN-SRv6 contributors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style license that can be
3+
// found in the LICENSE file.
4+
// SPDX-License-Identifier: MIT
5+
package mup
6+
7+
import "errors"
8+
9+
var (
10+
ErrTooShortToMarshal = errors.New("too short to serialize")
11+
ErrTooShortToParse = errors.New("too short to parse")
12+
)

runtime/nextmn-tun.go

+111-15
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,25 @@ func goSRExit() error {
6363
return nil
6464
}
6565

66-
func createGTPUEntity(ipAddress string) (*gtpv1.UPlaneConn, error) {
67-
// add SID as ip address (padded with zeros) to iface LinuxSRLinkName
68-
if !strings.HasSuffix(ipAddress, "/128") {
69-
return nil, fmt.Errorf("IP Address for GTP Entity must be an IPv6 address with a /128 mask")
70-
}
71-
if err := runIP("address", "replace", ipAddress, "dev", LinuxSRLinkName); err != nil {
72-
return nil, err
66+
func createGTPUEntity(ipAddress string, ipversion int) (*gtpv1.UPlaneConn, error) {
67+
// TODO: replace the GTP entity with a creation of GTP packet by gopacket (don't forget response to GTP Echo (option))
68+
switch ipversion {
69+
case 4:
70+
if !strings.HasSuffix(ipAddress, "/32") {
71+
return nil, fmt.Errorf("IP Address for this GTP Entity must be an IPv4 address with a /32 mask")
72+
}
73+
// we don't add ip address in ipv4 case because the address is expected to already exist
74+
case 6:
75+
if !strings.HasSuffix(ipAddress, "/128") {
76+
return nil, fmt.Errorf("IP Address for this GTP Entity must be an IPv6 address with a /128 mask")
77+
}
78+
// add ip address (padded with zeros) to iface LinuxSRLinkName
79+
// (we expect to have no address set in the ip range of the SID)
80+
if err := runIP("address", "replace", ipAddress, "dev", LinuxSRLinkName); err != nil {
81+
return nil, err
82+
}
83+
default:
84+
return nil, fmt.Errorf("IP version should be 4 or 6")
7385
}
7486

7587
ipAddressNoMask := strings.SplitN(ipAddress, "/", 2)[0]
@@ -193,18 +205,24 @@ func createEndpoints(iface *water.Interface) error {
193205
gtpNodes = make(map[string](*gtpv1.UPlaneConn))
194206
for _, e := range SRv6.Endpoints {
195207
switch e.Behavior {
196-
case "End.M.GTP6.D": // we receive GTP packet and send SRv6 packets
208+
case "End.MAP":
209+
return fmt.Errorf("Not implemented")
210+
case "End.M.GTP6.D": // we receive GTP packet and send SRv6 packets with ArgsMobSession stored in arguments of SRH[0]
211+
return fmt.Errorf("Not implemented")
212+
case "End.M.GTP6.D.Di": // we receive GTP packet and send SRv6 packets, no ArgsMobSession is stored
197213
if e.Options == nil || e.Options.SourceAddress == nil {
198214
return fmt.Errorf("Options field must contain a set-source-address parameter")
215+
// TODO: after replacement of GTPU-Entity creation by gopacket, this parameter should become optional (default: dst addr of the received packet)
199216
}
200217
if !strings.HasSuffix(e.Sid, "/128") {
201-
return fmt.Errorf("SID of End.M.GTP6.D must be a /128")
218+
return fmt.Errorf("SID of End.M.GTP6.Di must be a /128")
202219
}
220+
// FIXME: canonize gtpentityAddr
203221
gtpentityAddr := e.Sid // we receive GTP packets having this destination address
204222
srAddr := net.ParseIP(strings.SplitN(*e.Options.SourceAddress, "/", 2)[0]) // we send SR packets using this source address
205223
// add a GTP Node to be able to receive GTP packets
206224
if gtpNodes[gtpentityAddr] == nil {
207-
entity, err := createGTPUEntity(gtpentityAddr)
225+
entity, err := createGTPUEntity(gtpentityAddr, 6)
208226
if err != nil {
209227
return err
210228
}
@@ -220,8 +238,9 @@ func createEndpoints(iface *water.Interface) error {
220238
gtpNodes[gtpentityAddr].AddHandler(message.MsgTypeTPDU, func(c gtpv1.Conn, senderAddr net.Addr, msg message.Message) error {
221239
return tpduHandler(iface, srAddr, c, senderAddr, msg, hoplimit)
222240
})
223-
case "End.M.GTP6.E": // we receive SRv6 packets and send GTP packets
241+
case "End.M.GTP6.E": // we receive SRv6 packets and send GTP6 packets
224242
if e.Options == nil || e.Options.SourceAddress == nil {
243+
// TODO: after replacement of GTPU-Entity creation by gopacket, this parameter should become optional (default: dst addr of the received packet)
225244
return fmt.Errorf("Options field must contain a set-source-address parameter")
226245
}
227246
if !strings.HasSuffix(*e.Options.SourceAddress, "/128") {
@@ -236,25 +255,102 @@ func createEndpoints(iface *water.Interface) error {
236255
if err != nil {
237256
return err
238257
}
239-
if netSize > (128 - 8 - 8*4) {
258+
if netSize > maxNetSize {
259+
return fmt.Errorf("Maximum network size for SID is /%d", maxNetSize)
260+
}
261+
if netSize%8 != 0 {
262+
return fmt.Errorf("Network size for SID must be multiple of 8") // FIXME: handle bit shifts
263+
}
264+
// FIXME: canonize srAddr
265+
srAddr := e.Sid // we receive SR packets having this destination address
266+
gtpentityAddr := *e.Options.SourceAddress // we send GTP packets using this source address
267+
// add a GTP Node to be able to respond to GTP Echo Requests
268+
if gtpNodes[gtpentityAddr] == nil {
269+
entity, err := createGTPUEntity(gtpentityAddr, 6)
270+
if err != nil {
271+
return err
272+
}
273+
gtpNodes[gtpentityAddr] = entity
274+
}
275+
// create SRToGTPNode
276+
n, err := NewSRToGTPNode(srAddr, gtpentityAddr, 6)
277+
if err != nil {
278+
return err
279+
} else {
280+
srNodes[srAddr] = n
281+
}
282+
case "End.M.GTP4.E": // we receive SRv6 packets and send GTP4 packets
283+
if e.Options == nil || e.Options.SourceAddress == nil {
284+
// TODO: after replacement of GTPU-Entity creation by gopacket, check the IPv4 source address from IPv6 dest addr argument space
285+
return fmt.Errorf("Options field must contain a set-source-address parameter")
286+
}
287+
if !strings.HasSuffix(*e.Options.SourceAddress, "/32") {
288+
return fmt.Errorf("set-source-address parameter of End.M.GTP4.E must be explicitly a /32 address")
289+
}
290+
if err := runIP("-6", "route", "add", e.Sid, "dev", NextmnSRTunName, "table", RTTableName, "proto", RTProtoName); err != nil {
291+
return err
292+
}
293+
maxNetSize := 128 - (32 + 8 + 8*4) // [ SID + IPv4 DA + QFI + R + U + TEID ]
294+
netSize, err := strconv.Atoi(strings.SplitN(e.Sid, "/", 2)[1])
295+
296+
if err != nil {
297+
return err
298+
}
299+
if netSize > maxNetSize {
240300
return fmt.Errorf("Maximum network size for SID is /%d", maxNetSize)
241301
}
242302
if netSize%8 != 0 {
243-
return fmt.Errorf("Network size for SID must be multiple of 8") // FIXME
303+
return fmt.Errorf("Network size for SID must be multiple of 8") // FIXME: handle bit shifts
244304
}
245305
srAddr := e.Sid // we receive SR packets having this destination address
246306
gtpentityAddr := *e.Options.SourceAddress // we send GTP packets using this source address
247307
// add a GTP Node to be able to respond to GTP Echo Requests
248308
if gtpNodes[gtpentityAddr] == nil {
249-
entity, err := createGTPUEntity(gtpentityAddr)
309+
entity, err := createGTPUEntity(gtpentityAddr, 4)
250310
if err != nil {
251311
return err
252312
}
253313
gtpNodes[gtpentityAddr] = entity
254314
}
255315
// create SRToGTPNode
256-
srNodes[srAddr] = NewSRToGTPNode(srAddr, gtpentityAddr)
316+
n, err := NewSRToGTPNode(srAddr, gtpentityAddr, 4)
317+
if err != nil {
318+
return err
319+
} else {
320+
srNodes[srAddr] = n
321+
}
322+
case "H.M.GTP4.D":
323+
if e.Options == nil || e.Options.SourceAddress == nil {
324+
// TODO: this parameter should be optional (default: sid + dst addr of the received packet)
325+
return fmt.Errorf("Options field must contain a set-source-address parameter")
326+
}
327+
if !strings.HasSuffix(e.Sid, "/32") {
328+
return fmt.Errorf("SID of H.GTP4.d must be a /32")
329+
}
330+
gtpentityAddr := e.Sid // we receive GTP packets having this destination address
331+
srAddr := net.ParseIP(strings.SplitN(*e.Options.SourceAddress, "/", 2)[0]) // we send SR packets using this source address
332+
// add a GTP Node to be able to receive GTP packets
333+
if gtpNodes[gtpentityAddr] == nil {
334+
entity, err := createGTPUEntity(gtpentityAddr, 4)
335+
if err != nil {
336+
return err
337+
}
338+
gtpNodes[gtpentityAddr] = entity
339+
}
340+
// hop limit is set at start of the server, to avoid reading it at each packet reception
341+
hoplimit, err := getipv6hoplimit(NextmnSRTunName)
342+
if err != nil {
343+
return err
344+
}
345+
346+
// add handler that will allow GTP decap & SR encap
347+
gtpNodes[gtpentityAddr].AddHandler(message.MsgTypeTPDU, func(c gtpv1.Conn, senderAddr net.Addr, msg message.Message) error {
348+
return tpduHandler(iface, srAddr, c, senderAddr, msg, hoplimit)
349+
})
350+
case "End.Limit":
351+
return fmt.Errorf("Not implemented")
257352
default:
353+
// pass: other Behaviors can be implemented on linux side (see linux-sr.go)
258354
}
259355
}
260356
return nil

runtime/sr-node.go

+36-16
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package srv6
66

77
import (
88
"context"
9-
"encoding/binary"
109
"fmt"
1110
"log"
1211
"net"
@@ -17,6 +16,7 @@ import (
1716
gopacket "github.com/google/gopacket"
1817
layers "github.com/google/gopacket/layers"
1918
gopacket_srv6 "github.com/louisroyer/gopacket-srv6"
19+
"github.com/louisroyer/nextmn-srv6/mup"
2020
"github.com/wmnsk/go-gtp/gtpv1"
2121
)
2222

@@ -27,13 +27,16 @@ type SRToGTPNode struct {
2727
done chan bool
2828
netsize int
2929
gtpEntityAddr string // CIDR
30+
gtpIPVersion int
3031
}
3132

32-
func NewSRToGTPNode(sid string, gtpentityaddr string) *SRToGTPNode {
33+
func NewSRToGTPNode(sid string, gtpentityaddr string, gtpIPVersion int) (*SRToGTPNode, error) {
34+
if (gtpIPVersion != 4) && (gtpIPVersion != 6) {
35+
return nil, fmt.Errorf("gtpIPVersion should be 6 or 4")
36+
}
3337
netSize, _ := strconv.Atoi(strings.SplitN(sid, "/", 2)[1])
3438
if netSize%8 != 0 {
35-
log.Println("Error: SID networks must be multiple of 8") // FIXME
36-
return nil
39+
return nil, fmt.Errorf("SID networks must be multiple of 8") // FIXME
3740
}
3841
return &SRToGTPNode{
3942
queue: make(chan []byte),
@@ -42,7 +45,8 @@ func NewSRToGTPNode(sid string, gtpentityaddr string) *SRToGTPNode {
4245
done: make(chan bool),
4346
netsize: netSize,
4447
gtpEntityAddr: gtpentityaddr,
45-
}
48+
gtpIPVersion: gtpIPVersion,
49+
}, nil
4650
}
4751

4852
func (s *SRToGTPNode) ListenAndServe() error {
@@ -53,26 +57,42 @@ func (s *SRToGTPNode) ListenAndServe() error {
5357
log.Printf("Received a packet on SID %s\n", s.sid)
5458
pqt := gopacket.NewPacket(packet, layers.LayerTypeIPv6, gopacket.Default)
5559
// extract TEID from destination address
56-
// destination address is formed as follow : [ SID (netsize bits) + QFI (6 bits) + R (1 bit) + U (1 bit) + TEID (32 bits) + spare ]
60+
// destination address is formed as follow : [ SID (netsize bits) + IPv4 DA (only if ipv4) + ArgsMobSession ]
5761
dst := pqt.NetworkLayer().(*layers.IPv6).DstIP.String()
5862
ip, err := netip.ParseAddr(dst)
5963
if err != nil {
6064
return err
6165
}
6266
dstarray := ip.As16()
63-
teidarray := dstarray[(s.netsize/8)+1 : (s.netsize/8)+1+4]
64-
teid := binary.BigEndian.Uint32(teidarray)
67+
offset := 0
68+
if s.gtpIPVersion == 4 {
69+
offset = 32 / 8
70+
}
71+
// TODO: check segments left = 1, and if not send ICMP Parameter Problem to the Source Address (code 0, pointer to SegemntsLeft field), and drop the packet
72+
args, err := mup.ParseArgsMobSession(dstarray[(s.netsize/8)+offset:])
73+
if err != nil {
74+
return err
75+
}
76+
teid := args.PDUSessionID()
6577
// retrieve nextGTPNode (SHR[0])
6678
log.Printf("TEID retreived: %X\n", teid)
6779

68-
// workaround: enforce use of gopacket_srv6 functions
69-
shr := gopacket.NewPacket(pqt.Layers()[1].LayerContents(), gopacket_srv6.LayerTypeIPv6Routing, gopacket.Default).Layers()[0].(*gopacket_srv6.IPv6Routing)
70-
log.Println("layer type", pqt.Layers()[1].LayerType())
71-
log.Println("RoutingType", shr.RoutingType)
72-
log.Println("LastEntry:", shr.LastEntry)
73-
log.Println("sourceRoutingIPs len:", len(shr.SourceRoutingIPs))
74-
log.Println("sourceRoutingIPs[0]:", shr.SourceRoutingIPs[0])
75-
nextGTPNode := fmt.Sprintf("[%s]:%s", shr.SourceRoutingIPs[0].String(), GTPU_PORT)
80+
nextGTPNode := ""
81+
if s.gtpIPVersion == 6 {
82+
// workaround: enforce use of gopacket_srv6 functions
83+
shr := gopacket.NewPacket(pqt.Layers()[1].LayerContents(), gopacket_srv6.LayerTypeIPv6Routing, gopacket.Default).Layers()[0].(*gopacket_srv6.IPv6Routing)
84+
log.Println("layer type", pqt.Layers()[1].LayerType())
85+
log.Println("RoutingType", shr.RoutingType)
86+
log.Println("LastEntry:", shr.LastEntry)
87+
log.Println("sourceRoutingIPs len:", len(shr.SourceRoutingIPs))
88+
log.Println("sourceRoutingIPs[0]:", shr.SourceRoutingIPs[0])
89+
nextGTPNode = fmt.Sprintf("[%s]:%s", shr.SourceRoutingIPs[0].String(), GTPU_PORT)
90+
} else {
91+
// IPv4
92+
ip_arr := dstarray[s.netsize/8 : (s.netsize/8)+4]
93+
ipv4_address := net.IPv4(ip_arr[0], ip_arr[1], ip_arr[2], ip_arr[3])
94+
nextGTPNode = fmt.Sprintf("%s:%s", ipv4_address, GTPU_PORT)
95+
}
7696
raddr, err := net.ResolveUDPAddr("udp", nextGTPNode)
7797
if err != nil {
7898
log.Println("Error while resolving ", nextGTPNode, "(remote node)")

0 commit comments

Comments
 (0)