From cf7b690d9231014436594d7925791ec6db16513c Mon Sep 17 00:00:00 2001 From: Guillaume Belanger Date: Sat, 30 Dec 2023 18:29:28 -0500 Subject: [PATCH] feat: Adds pfcp session establishment --- ie/fseid.go | 94 ++++++++++++++++++++++++ ie/fseid_test.go | 99 ++++++++++++++++++++++++++ ie/ie.go | 3 + ie/nodeId.go | 42 ++++------- ie/nodeId_test.go | 6 +- ie/sourceIPAddress.go | 48 ++++++------- ie/sourceIPAddress_test.go | 12 ++-- messages/pfcp_association_setup.go | 3 - messages/pfcp_session_establishment.go | 10 +++ tests/heartbeat_test.go | 2 +- tests/pfcp_association_release_test.go | 4 +- tests/pfcp_association_setup_test.go | 4 +- tests/pfcp_association_update_test.go | 4 +- tests/pfcp_node_report_test.go | 4 +- 14 files changed, 260 insertions(+), 75 deletions(-) create mode 100644 ie/fseid.go create mode 100644 ie/fseid_test.go create mode 100644 messages/pfcp_session_establishment.go diff --git a/ie/fseid.go b/ie/fseid.go new file mode 100644 index 0000000..2435f1c --- /dev/null +++ b/ie/fseid.go @@ -0,0 +1,94 @@ +package ie + +import ( + "bytes" + "encoding/binary" + "net" +) + +type FSEID struct { + IEType uint16 + Length uint16 + V4 bool + V6 bool + SEID uint64 + IPv4 []byte + IPv6 []byte +} + +func NewFSEID(seid uint64, ipv4Address string, ipv6Address string) (FSEID, error) { + fseid := FSEID{ + IEType: 57, + SEID: seid, + } + var length uint16 = 9 + + ipv4 := net.ParseIP(ipv4Address) + ipv6 := net.ParseIP(ipv6Address) + + if ipv4.To4() != nil { + fseid.V4 = true + fseid.IPv4 = ipv4.To4() + length += 4 + } + if ipv6.To16() != nil { + fseid.V6 = true + fseid.IPv6 = ipv6.To16() + length += 16 + } + fseid.Length = length + return fseid, nil +} + +func (fseid FSEID) Serialize() []byte { + buf := new(bytes.Buffer) + + // Octets 1 to 2: Type (57) + binary.Write(buf, binary.BigEndian, uint16(fseid.IEType)) + + // Octets 3 to 4: Length + binary.Write(buf, binary.BigEndian, uint16(fseid.Length)) + + // Octet 5: Spare (6 bits) + V4 (1 bit) + V6 (1 bit) + var flags byte + if fseid.V4 { + flags |= 1 << 1 // Set the second bit from the right if V4 is true + } + if fseid.V6 { + flags |= 1 << 0 // Set the first bit from the right if V6 is true + } + buf.WriteByte(flags) + + // Octets 6 13: SEID + binary.Write(buf, binary.BigEndian, fseid.SEID) + + // Octet m to (m+3) IPv4 address + if fseid.V4 { + buf.Write(fseid.IPv4) + } + + // Octet p to (p+15): IPv6 address + if fseid.V6 { + buf.Write(fseid.IPv6) + } + + return buf.Bytes() +} + +func DeserializeFSEID(ieType uint16, ieLength uint16, ieValue []byte) FSEID { + v4 := ieValue[0]&0x02 > 0 + v6 := ieValue[0]&0x01 > 0 + seid := binary.BigEndian.Uint64(ieValue[1:9]) + ipv4 := ieValue[9:13] + ipv6 := ieValue[13:29] + + return FSEID{ + IEType: ieType, + Length: ieLength, + V4: v4, + V6: v6, + SEID: seid, + IPv4: ipv4, + IPv6: ipv6, + } +} diff --git a/ie/fseid_test.go b/ie/fseid_test.go new file mode 100644 index 0000000..4dbdae9 --- /dev/null +++ b/ie/fseid_test.go @@ -0,0 +1,99 @@ +package ie_test + +import ( + "fmt" + "testing" + + "github.com/dot-5g/pfcp/ie" +) + +func TestGivenValidIPAddressWhenNewFSEIDThenFieldsAreSetCorrectly(t *testing.T) { + seid := uint64(0x1234567890ABCDEF) + + fseid, err := ie.NewFSEID(seid, "1.2.3.4", "") + + if err != nil { + t.Fatalf("Error creating FSEID: %v", err) + } + + if fseid.IEType != 57 { + t.Errorf("Expected FSEID IEType 97, got %d", fseid.IEType) + } + + if fseid.Length != 13 { + t.Errorf("Expected FSEID length 12, got %d", fseid.Length) + } + + if fseid.V4 != true { + t.Errorf("Expected FSEID V4 true, got %v", fseid.V4) + } + + if fseid.V6 != false { + t.Errorf("Expected FSEID V6 false, got %v", fseid.V6) + } + + if fseid.SEID != seid { + t.Errorf("Expected FSEID SEID %d, got %d", seid, fseid.SEID) + } + + expectedIPv4 := []byte{1, 2, 3, 4} + for i := range fseid.IPv4 { + if fseid.IPv4[i] != expectedIPv4[i] { + t.Errorf("Expected FSEID IPv4 %v, got %v", expectedIPv4, fseid.IPv4) + } + } + +} + +func TestGivenSerializedWhenDeserializeThenFieldsSetCorrectly(t *testing.T) { + seid := uint64(0x1234567890ABCDEF) + ipv4 := "2.3.4.5" + ipv6 := "2001:db8::68" + + fseid, err := ie.NewFSEID(seid, ipv4, ipv6) + + if err != nil { + t.Fatalf("Error creating FSEID: %v", err) + } + + serialized := fseid.Serialize() + + fmt.Printf("Serialized FSEID: %v\n", serialized) + + deserialized := ie.DeserializeFSEID(57, 12, serialized[4:]) + + if deserialized.IEType != 57 { + t.Errorf("Expected FSEID IEType 57, got %d", deserialized.IEType) + } + + if deserialized.Length != 12 { + t.Errorf("Expected FSEID length 12, got %d", deserialized.Length) + } + + if deserialized.V4 != true { + t.Errorf("Expected FSEID V4 true, got %v", deserialized.V4) + } + + if deserialized.V6 != true { + t.Errorf("Expected FSEID V6 true, got %v", deserialized.V6) + } + + if deserialized.SEID != seid { + t.Errorf("Expected FSEID SEID %d, got %d", seid, deserialized.SEID) + } + + expectedIPv4 := []byte{2, 3, 4, 5} + for i := range deserialized.IPv4 { + if deserialized.IPv4[i] != expectedIPv4[i] { + t.Errorf("Expected FSEID IPv4 %v, got %v", expectedIPv4, deserialized.IPv4) + } + } + + expectedIPv6 := []byte{32, 1, 13, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x00, 0x00, 0x68} + for i := range deserialized.IPv6 { + if deserialized.IPv6[i] != expectedIPv6[i] { + t.Errorf("Expected FSEID IPv6 %v, got %v", expectedIPv6, deserialized.IPv6) + } + } + +} diff --git a/ie/ie.go b/ie/ie.go index 6c4348a..093b695 100644 --- a/ie/ie.go +++ b/ie/ie.go @@ -16,6 +16,7 @@ const ( NodeReportTypeIEType IEType = 101 SourceIPAddressIEType IEType = 192 UPFunctionFeaturesIEType IEType = 43 + FSEIDIEType IEType = 57 ) type InformationElement interface { @@ -56,6 +57,8 @@ func ParseInformationElements(b []byte) ([]InformationElement, error) { ie, err = DeserializeSourceIPAddress(uint16(ieType), ieLength, ieValue) case UPFunctionFeaturesIEType: ie, err = DeserializeUPFunctionFeatures(uint16(ieType), ieLength, ieValue) + // case FSEIDIEType: + // ie, err = DeserializeFSEID(uint16(ieType), ieLength, ieValue) default: err = fmt.Errorf("unknown IE type %d", ieType) } diff --git a/ie/nodeId.go b/ie/nodeId.go index 517df2f..90a46da 100644 --- a/ie/nodeId.go +++ b/ie/nodeId.go @@ -3,7 +3,6 @@ package ie import ( "bytes" "encoding/binary" - "fmt" "net" ) @@ -22,44 +21,31 @@ type NodeID struct { NodeIDValue []byte } -func NewNodeID(nodeIDType NodeIDType, nodeIDValue string) NodeID { +func NewNodeID(nodeID string) NodeID { var nodeIDValueBytes []byte var length uint16 + var nodeIDType NodeIDType - switch nodeIDType { - case IPv4: - ip := net.ParseIP(nodeIDValue) - if ip == nil { - panic("invalid IPv4 address") - } - ipv4 := ip.To4() - if ipv4 == nil { - panic("invalid IPv4 address") - } - nodeIDValueBytes = ipv4 + ip := net.ParseIP(nodeID) + + if ip.To4() != nil { + nodeIDValueBytes = ip.To4() length = uint16(len(nodeIDValueBytes)) + 1 - case IPv6: - ip := net.ParseIP(nodeIDValue) - if ip == nil { - panic("invalid IPv6 address") - } - ipv6 := ip.To16() - if ipv6 == nil { - panic("invalid IPv6 address") - } - nodeIDValueBytes = ipv6 + nodeIDType = IPv4 + } else if ip.To16() != nil { + nodeIDValueBytes = ip.To16() length = uint16(len(nodeIDValueBytes)) + 1 - case FQDN: - fqdn := []byte(nodeIDValue) + nodeIDType = IPv6 + } else { + fqdn := []byte(nodeID) if len(fqdn) > 255 { panic("FQDN too long") } nodeIDValueBytes = fqdn length = uint16(len(nodeIDValueBytes)) + 1 - - default: - panic(fmt.Sprintf("invalid NodeIDType %d", nodeIDType)) + nodeIDType = FQDN } + return NodeID{ IEtype: 60, Length: length, diff --git a/ie/nodeId_test.go b/ie/nodeId_test.go index 4cc0a49..c366ae5 100644 --- a/ie/nodeId_test.go +++ b/ie/nodeId_test.go @@ -7,7 +7,7 @@ import ( ) func TestNewNodeIDIPv4(t *testing.T) { - nodeID := ie.NewNodeID(ie.IPv4, "1.2.3.4") + nodeID := ie.NewNodeID("1.2.3.4") if nodeID.IEtype != 60 { t.Errorf("Expected NodeID, got %d", nodeID.IEtype) @@ -35,7 +35,7 @@ func TestNewNodeIDIPv4(t *testing.T) { } func TestNewNodeIDIPv6(t *testing.T) { - nodeID := ie.NewNodeID(ie.IPv6, "2001:db8::68") + nodeID := ie.NewNodeID("2001:db8::68") if nodeID.IEtype != 60 { t.Errorf("Expected NodeID, got %d", nodeID.IEtype) @@ -63,7 +63,7 @@ func TestNewNodeIDIPv6(t *testing.T) { } func TestNewNodeIDFQDN(t *testing.T) { - nodeID := ie.NewNodeID(ie.FQDN, "www.example.com") + nodeID := ie.NewNodeID("www.example.com") if nodeID.IEtype != 60 { t.Errorf("Expected NodeID, got %d", nodeID.IEtype) diff --git a/ie/sourceIPAddress.go b/ie/sourceIPAddress.go index f07c5a2..f87a1cf 100644 --- a/ie/sourceIPAddress.go +++ b/ie/sourceIPAddress.go @@ -1,7 +1,6 @@ package ie import ( - "fmt" "net" ) @@ -16,31 +15,36 @@ type SourceIPAddress struct { MaskPrefixLength uint8 } -func NewSourceIPAddress(cidr string) (SourceIPAddress, error) { +func NewSourceIPAddress(ipv4Address string, ipv6Address string) (SourceIPAddress, error) { sourceIPAddress := SourceIPAddress{ IEtype: 192, } - ip, ipnet, err := net.ParseCIDR(cidr) - if err != nil { - return sourceIPAddress, fmt.Errorf("invalid CIDR") - } + length := 2 + + ipv4, ipv4net, _ := net.ParseCIDR(ipv4Address) + ipv6, ipv6net, _ := net.ParseCIDR(ipv6Address) - if ip.To4() != nil { + if ipv4.To4() != nil { sourceIPAddress.V4 = true - sourceIPAddress.IPv4Address = ip.To4() - sourceIPAddress.Length = 6 - } else { - sourceIPAddress.V6 = true - sourceIPAddress.IPv6Address = ip.To16() - sourceIPAddress.Length = 18 + sourceIPAddress.IPv4Address = ipv4.To4() + length += 4 + sourceIPAddress.MPL = true + ones, _ := ipv4net.Mask.Size() + sourceIPAddress.MaskPrefixLength = uint8(ones) } - if ipnet != nil { + if ipv6.To16() != nil { + sourceIPAddress.V6 = true + sourceIPAddress.IPv6Address = ipv6.To16() + sourceIPAddress.Length = 18 + length += 16 sourceIPAddress.MPL = true - ones, _ := ipnet.Mask.Size() + ones, _ := ipv6net.Mask.Size() sourceIPAddress.MaskPrefixLength = uint8(ones) } + sourceIPAddress.Length = uint16(length) + return sourceIPAddress, nil } @@ -49,19 +53,11 @@ func (sourceIPAddress SourceIPAddress) IsZeroValue() bool { } func (sourceIPAddress SourceIPAddress) Serialize() []byte { - var length uint16 - - if sourceIPAddress.V4 { - length = 6 - } - if sourceIPAddress.V6 { - length = 18 - } - bytes := make([]byte, 4+length) + bytes := make([]byte, 4+sourceIPAddress.Length) bytes[0] = byte(sourceIPAddress.IEtype >> 8) bytes[1] = byte(sourceIPAddress.IEtype) - bytes[2] = byte(length >> 8) - bytes[3] = byte(length) + bytes[2] = byte(sourceIPAddress.Length >> 8) + bytes[3] = byte(sourceIPAddress.Length) if sourceIPAddress.MPL { bytes[4] = 0x80 } diff --git a/ie/sourceIPAddress_test.go b/ie/sourceIPAddress_test.go index 7df9174..a76451c 100644 --- a/ie/sourceIPAddress_test.go +++ b/ie/sourceIPAddress_test.go @@ -8,7 +8,7 @@ import ( func TestGivenCorrectIPv4AddressWhenSourceIPAddressThenFieldsSetCorrectly(t *testing.T) { - sourceIPAddress, err := ie.NewSourceIPAddress("1.2.3.4/24") + sourceIPAddress, err := ie.NewSourceIPAddress("1.2.3.4/24", "") if err != nil { t.Fatalf("Error creating SourceIPAddress: %v", err) @@ -41,7 +41,7 @@ func TestGivenCorrectIPv4AddressWhenSourceIPAddressThenFieldsSetCorrectly(t *tes func TestGivenCorrectIPv6AddressWhenSourceIPAddressThenFieldsSetCorrectly(t *testing.T) { - sourceIPAddress, err := ie.NewSourceIPAddress("2001:db8::/32") + sourceIPAddress, err := ie.NewSourceIPAddress("", "2001:db8::/32") if err != nil { t.Fatalf("Error creating SourceIPAddress: %v", err) @@ -72,9 +72,9 @@ func TestGivenCorrectIPv6AddressWhenSourceIPAddressThenFieldsSetCorrectly(t *tes } } -func TestGivenSerializedIPV4AddressWhenDeserializeThenFieldsSetCorrectly(t *testing.T) { +func TestGivenSerializedAddressWhenDeserializeThenFieldsSetCorrectly(t *testing.T) { - sourceIPAddress, err := ie.NewSourceIPAddress("2.2.3.1/24") + sourceIPAddress, err := ie.NewSourceIPAddress("2.2.3.1/24", "2001:db8::/32") if err != nil { t.Fatalf("Error creating SourceIPAddress: %v", err) @@ -104,8 +104,8 @@ func TestGivenSerializedIPV4AddressWhenDeserializeThenFieldsSetCorrectly(t *test t.Errorf("Expected NodeID V4 true, got %v", deserializedSourceIPAddress.V4) } - if deserializedSourceIPAddress.V6 != false { - t.Errorf("Expected NodeID V6 false, got %v", deserializedSourceIPAddress.V6) + if deserializedSourceIPAddress.V6 != true { + t.Errorf("Expected NodeID V6 true, got %v", deserializedSourceIPAddress.V6) } } diff --git a/messages/pfcp_association_setup.go b/messages/pfcp_association_setup.go index 88099a2..b1daf02 100644 --- a/messages/pfcp_association_setup.go +++ b/messages/pfcp_association_setup.go @@ -1,8 +1,6 @@ package messages import ( - "fmt" - "github.com/dot-5g/pfcp/ie" ) @@ -33,7 +31,6 @@ func ParsePFCPAssociationSetupRequest(data []byte) (PFCPAssociationSetupRequest, continue } if upfeaturesIE, ok := elem.(ie.UPFunctionFeatures); ok { - fmt.Printf("upfeaturesIE: %v\n", upfeaturesIE) upfeatures = upfeaturesIE continue } diff --git a/messages/pfcp_session_establishment.go b/messages/pfcp_session_establishment.go new file mode 100644 index 0000000..3b9d004 --- /dev/null +++ b/messages/pfcp_session_establishment.go @@ -0,0 +1,10 @@ +package messages + +import "github.com/dot-5g/pfcp/ie" + +type PFCPSessionEstablishmentRequest struct { + NodeID ie.NodeID // Mandatory + CPFSEID ie.FSEID // Mandatory + // CreatePDR ie.CreatePDR // Mandatory + // CreateFAR ie.CreateFAR // Mandatory +} diff --git a/tests/heartbeat_test.go b/tests/heartbeat_test.go index d2df0ee..c9a538b 100644 --- a/tests/heartbeat_test.go +++ b/tests/heartbeat_test.go @@ -105,7 +105,7 @@ func HeartbeatRequestWithSourceIPAddress(t *testing.T) { pfcpServer.HeartbeatRequest(HandleHeartbeatRequestWithSourceIP) sentSequenceNumber := uint32(32) recoveryTimeStamp := ie.NewRecoveryTimeStamp(time.Now()) - sourceIPAddress, _ := ie.NewSourceIPAddress("2.3.2.3/24") + sourceIPAddress, _ := ie.NewSourceIPAddress("2.3.2.3/24", "") heartbeatRequestMsg := messages.HeartbeatRequest{ RecoveryTimeStamp: recoveryTimeStamp, SourceIPAddress: sourceIPAddress, diff --git a/tests/pfcp_association_release_test.go b/tests/pfcp_association_release_test.go index bcb12d4..7c9031e 100644 --- a/tests/pfcp_association_release_test.go +++ b/tests/pfcp_association_release_test.go @@ -58,7 +58,7 @@ func PFCPAssociationReleaseRequest(t *testing.T) { time.Sleep(time.Second) pfcpClient := client.New("127.0.0.1:8805") - nodeID := ie.NewNodeID(ie.IPv4, "12.23.34.45") + nodeID := ie.NewNodeID("12.23.34.45") sequenceNumber := uint32(32) PFCPAssociationReleaseRequestMsg := messages.PFCPAssociationReleaseRequest{ NodeID: nodeID, @@ -108,7 +108,7 @@ func PFCPAssociationReleaseResponse(t *testing.T) { time.Sleep(time.Second) pfcpClient := client.New("127.0.0.1:8805") - nodeID := ie.NewNodeID(ie.IPv4, "3.4.5.6") + nodeID := ie.NewNodeID("3.4.5.6") sequenceNumber := uint32(32) cause := ie.NewCause(2) diff --git a/tests/pfcp_association_setup_test.go b/tests/pfcp_association_setup_test.go index 72d79c6..22c085e 100644 --- a/tests/pfcp_association_setup_test.go +++ b/tests/pfcp_association_setup_test.go @@ -64,7 +64,7 @@ func PFCPAssociationSetupRequest(t *testing.T) { time.Sleep(time.Second) pfcpClient := client.New("127.0.0.1:8805") - nodeID := ie.NewNodeID(ie.IPv4, "12.23.34.45") + nodeID := ie.NewNodeID("12.23.34.45") recoveryTimeStamp := ie.NewRecoveryTimeStamp(time.Now()) sequenceNumber := uint32(32) features := [](ie.UPFeature){ @@ -143,7 +143,7 @@ func PFCPAssociationSetupResponse(t *testing.T) { time.Sleep(time.Second) pfcpClient := client.New("127.0.0.1:8805") - nodeID := ie.NewNodeID(ie.IPv4, "1.2.3.4") + nodeID := ie.NewNodeID("1.2.3.4") cause := ie.NewCause(2) recoveryTimeStamp := ie.NewRecoveryTimeStamp(time.Now()) sequenceNumber := uint32(32) diff --git a/tests/pfcp_association_update_test.go b/tests/pfcp_association_update_test.go index 3dd0c87..0f766ed 100644 --- a/tests/pfcp_association_update_test.go +++ b/tests/pfcp_association_update_test.go @@ -58,7 +58,7 @@ func PFCPAssociationUpdateRequest(t *testing.T) { time.Sleep(time.Second) pfcpClient := client.New("127.0.0.1:8805") - nodeID := ie.NewNodeID(ie.IPv4, "12.23.34.45") + nodeID := ie.NewNodeID("12.23.34.45") sequenceNumber := uint32(32) PFCPAssociationUpdateRequestMsg := messages.PFCPAssociationUpdateRequest{ NodeID: nodeID, @@ -108,7 +108,7 @@ func PFCPAssociationUpdateResponse(t *testing.T) { time.Sleep(time.Second) pfcpClient := client.New("127.0.0.1:8805") - nodeID := ie.NewNodeID(ie.IPv4, "3.4.5.6") + nodeID := ie.NewNodeID("3.4.5.6") sequenceNumber := uint32(32) cause := ie.NewCause(2) PFCPAssociationUpdateResponseMsg := messages.PFCPAssociationUpdateResponse{ diff --git a/tests/pfcp_node_report_test.go b/tests/pfcp_node_report_test.go index 3fbcc3c..497d456 100644 --- a/tests/pfcp_node_report_test.go +++ b/tests/pfcp_node_report_test.go @@ -60,7 +60,7 @@ func PFCPNodeReportRequest(t *testing.T) { time.Sleep(time.Second) pfcpClient := client.New("127.0.0.1:8805") - nodeID := ie.NewNodeID(ie.IPv4, "12.23.34.45") + nodeID := ie.NewNodeID("12.23.34.45") gpqr := false ckdr := false uprr := true @@ -141,7 +141,7 @@ func PFCPNodeReportResponse(t *testing.T) { time.Sleep(time.Second) pfcpClient := client.New("127.0.0.1:8805") - nodeID := ie.NewNodeID(ie.IPv4, "3.4.5.6") + nodeID := ie.NewNodeID("3.4.5.6") sequenceNumber := uint32(32) cause := ie.NewCause(2) PFCPNodeReportResponseMsg := messages.PFCPNodeReportResponse{