From e4f9bcb2172e7f85e22f2824d55e405f128a308c Mon Sep 17 00:00:00 2001 From: wiwi878 Date: Wed, 24 Jun 2026 07:05:27 +0000 Subject: [PATCH 1/2] fix: preserve UE security capability on path switch --- internal/ngap/handler.go | 35 ++++++++++++++++++++++++------ internal/ngap/handler_test.go | 40 +++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/internal/ngap/handler.go b/internal/ngap/handler.go index 6174efaa..1b69679c 100644 --- a/internal/ngap/handler.go +++ b/internal/ngap/handler.go @@ -1300,13 +1300,13 @@ func handlePathSwitchRequestMain(ran *context.AmfRan, } if uESecurityCapabilities != nil { - amfUe.UESecurityCapability.SetEA1_128_5G(uESecurityCapabilities.NRencryptionAlgorithms.Value.Bytes[0] & 0x80) - amfUe.UESecurityCapability.SetEA2_128_5G(uESecurityCapabilities.NRencryptionAlgorithms.Value.Bytes[0] & 0x40) - amfUe.UESecurityCapability.SetEA3_128_5G(uESecurityCapabilities.NRencryptionAlgorithms.Value.Bytes[0] & 0x20) - amfUe.UESecurityCapability.SetIA1_128_5G(uESecurityCapabilities.NRintegrityProtectionAlgorithms.Value.Bytes[0] & 0x80) - amfUe.UESecurityCapability.SetIA2_128_5G(uESecurityCapabilities.NRintegrityProtectionAlgorithms.Value.Bytes[0] & 0x40) - amfUe.UESecurityCapability.SetIA3_128_5G(uESecurityCapabilities.NRintegrityProtectionAlgorithms.Value.Bytes[0] & 0x20) - // not support any E-UTRA algorithms + storedEA, storedIA := storedNRUESecurityCapability(amfUe) + receivedEA, receivedIA := receivedNRUESecurityCapability(uESecurityCapabilities) + if storedEA != receivedEA || storedIA != receivedIA { + // TODO: Include E-UTRA capability once AmfUe stores it; current NGAP builders only preserve NR bits. + ranUe.Log.Warnf("UESecurityCapabilities mismatch in PathSwitchRequest: stored NR(EA=0x%02x, IA=0x%02x), received NR(EA=0x%02x, IA=0x%02x); keep stored UE security capability", + storedEA, storedIA, receivedEA, receivedIA) + } } if rANUENGAPID != nil { @@ -1410,6 +1410,27 @@ func handlePathSwitchRequestMain(ran *context.AmfRan, } } +func storedNRUESecurityCapability(amfUe *context.AmfUe) (nrEncryptionAlgorithm, nrIntegrityAlgorithm byte) { + nrEncryptionAlgorithm |= amfUe.UESecurityCapability.GetEA1_128_5G() << 7 + nrEncryptionAlgorithm |= amfUe.UESecurityCapability.GetEA2_128_5G() << 6 + nrEncryptionAlgorithm |= amfUe.UESecurityCapability.GetEA3_128_5G() << 5 + + nrIntegrityAlgorithm |= amfUe.UESecurityCapability.GetIA1_128_5G() << 7 + nrIntegrityAlgorithm |= amfUe.UESecurityCapability.GetIA2_128_5G() << 6 + nrIntegrityAlgorithm |= amfUe.UESecurityCapability.GetIA3_128_5G() << 5 + return +} + +func receivedNRUESecurityCapability(uESecurityCapabilities *ngapType.UESecurityCapabilities) (nrEncryptionAlgorithm, nrIntegrityAlgorithm byte) { + if len(uESecurityCapabilities.NRencryptionAlgorithms.Value.Bytes) > 0 { + nrEncryptionAlgorithm = uESecurityCapabilities.NRencryptionAlgorithms.Value.Bytes[0] & 0xe0 + } + if len(uESecurityCapabilities.NRintegrityProtectionAlgorithms.Value.Bytes) > 0 { + nrIntegrityAlgorithm = uESecurityCapabilities.NRintegrityProtectionAlgorithms.Value.Bytes[0] & 0xe0 + } + return +} + func handleHandoverRequestAcknowledgeMain(ran *context.AmfRan, targetUe *context.RanUe, rANUENGAPID *ngapType.RANUENGAPID, diff --git a/internal/ngap/handler_test.go b/internal/ngap/handler_test.go index 52bfbb35..c0ae6b65 100644 --- a/internal/ngap/handler_test.go +++ b/internal/ngap/handler_test.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "fmt" "net" + "strings" "testing" "github.com/google/uuid" @@ -67,6 +68,45 @@ func NewAmfRan(conn net.Conn) *amf_context.AmfRan { return &ran } +func TestHandlePathSwitchRequestKeepsStoredUESecurityCapabilityOnMismatch(t *testing.T) { + amfSelf := amf_context.GetSelf() + NewAmfContext(amfSelf) + + sourceRan := NewAmfRan(nil) + sourceRan.AnType = "3GPP_ACCESS" + ranUe, err := sourceRan.NewRanUe(1) + require.NoError(t, err) + + amfUe := amfSelf.NewAmfUe("imsi-208930000007487") + amfUe.SecurityContextAvailable = true + amfUe.NgKsi.Ksi = 1 + amfUe.Kamf = strings.Repeat("11", 32) + amfUe.NH = []byte(strings.Repeat("\x22", 32)) + amfUe.UESecurityCapability = nasType.UESecurityCapability{ + Iei: nasMessage.RegistrationRequestUESecurityCapabilityType, + Len: 2, + Buffer: []uint8{0x00, 0x00}, + } + amfUe.UESecurityCapability.SetIA2_128_5G(1) + + ranUe.AmfUe = amfUe + amfUe.RanUe["3GPP_ACCESS"] = ranUe + + targetRan := NewAmfRan(nil) + targetRan.AnType = "3GPP_ACCESS" + sourceAMFUeNgapID := ngapType.AMFUENGAPID{Value: ranUe.AmfUeNgapId} + ranUeNgapID := ngapType.RANUENGAPID{Value: 2} + received := &ngapType.UESecurityCapabilities{} + received.NRencryptionAlgorithms.Value = aper.BitString{Bytes: []byte{0x00, 0x00}, BitLength: 16} + received.NRintegrityProtectionAlgorithms.Value = aper.BitString{Bytes: []byte{0x00, 0x00}, BitLength: 16} + + handlePathSwitchRequestMain(targetRan, &ranUeNgapID, &sourceAMFUeNgapID, nil, received, nil, nil) + + require.Equal(t, uint8(1), amfUe.UESecurityCapability.GetIA2_128_5G()) + require.Equal(t, uint8(0), amfUe.UESecurityCapability.GetIA1_128_5G()) + require.Equal(t, uint8(0), amfUe.UESecurityCapability.GetIA3_128_5G()) +} + func NewAmfContext(amfCtx *amf_context.AMFContext) { *amfCtx = amf_context.AMFContext{ NfId: uuid.New().String(), From dedba7695c18a27c2c911edb458c5497b9415b7d Mon Sep 17 00:00:00 2001 From: wiwi878 Date: Wed, 24 Jun 2026 07:18:08 +0000 Subject: [PATCH 2/2] fix: address path switch CI failures --- internal/ngap/handler.go | 8 +++- internal/ngap/handler_test.go | 84 +++++++++++++++++++---------------- 2 files changed, 51 insertions(+), 41 deletions(-) diff --git a/internal/ngap/handler.go b/internal/ngap/handler.go index 1b69679c..49ab52a7 100644 --- a/internal/ngap/handler.go +++ b/internal/ngap/handler.go @@ -1304,7 +1304,9 @@ func handlePathSwitchRequestMain(ran *context.AmfRan, receivedEA, receivedIA := receivedNRUESecurityCapability(uESecurityCapabilities) if storedEA != receivedEA || storedIA != receivedIA { // TODO: Include E-UTRA capability once AmfUe stores it; current NGAP builders only preserve NR bits. - ranUe.Log.Warnf("UESecurityCapabilities mismatch in PathSwitchRequest: stored NR(EA=0x%02x, IA=0x%02x), received NR(EA=0x%02x, IA=0x%02x); keep stored UE security capability", + ranUe.Log.Warnf("UESecurityCapabilities mismatch in PathSwitchRequest: "+ + "stored NR(EA=0x%02x, IA=0x%02x), received NR(EA=0x%02x, IA=0x%02x); "+ + "keep stored UE security capability", storedEA, storedIA, receivedEA, receivedIA) } } @@ -1421,7 +1423,9 @@ func storedNRUESecurityCapability(amfUe *context.AmfUe) (nrEncryptionAlgorithm, return } -func receivedNRUESecurityCapability(uESecurityCapabilities *ngapType.UESecurityCapabilities) (nrEncryptionAlgorithm, nrIntegrityAlgorithm byte) { +func receivedNRUESecurityCapability( + uESecurityCapabilities *ngapType.UESecurityCapabilities, +) (nrEncryptionAlgorithm, nrIntegrityAlgorithm byte) { if len(uESecurityCapabilities.NRencryptionAlgorithms.Value.Bytes) > 0 { nrEncryptionAlgorithm = uESecurityCapabilities.NRencryptionAlgorithms.Value.Bytes[0] & 0xe0 } diff --git a/internal/ngap/handler_test.go b/internal/ngap/handler_test.go index c0ae6b65..77b8bb12 100644 --- a/internal/ngap/handler_test.go +++ b/internal/ngap/handler_test.go @@ -68,45 +68,6 @@ func NewAmfRan(conn net.Conn) *amf_context.AmfRan { return &ran } -func TestHandlePathSwitchRequestKeepsStoredUESecurityCapabilityOnMismatch(t *testing.T) { - amfSelf := amf_context.GetSelf() - NewAmfContext(amfSelf) - - sourceRan := NewAmfRan(nil) - sourceRan.AnType = "3GPP_ACCESS" - ranUe, err := sourceRan.NewRanUe(1) - require.NoError(t, err) - - amfUe := amfSelf.NewAmfUe("imsi-208930000007487") - amfUe.SecurityContextAvailable = true - amfUe.NgKsi.Ksi = 1 - amfUe.Kamf = strings.Repeat("11", 32) - amfUe.NH = []byte(strings.Repeat("\x22", 32)) - amfUe.UESecurityCapability = nasType.UESecurityCapability{ - Iei: nasMessage.RegistrationRequestUESecurityCapabilityType, - Len: 2, - Buffer: []uint8{0x00, 0x00}, - } - amfUe.UESecurityCapability.SetIA2_128_5G(1) - - ranUe.AmfUe = amfUe - amfUe.RanUe["3GPP_ACCESS"] = ranUe - - targetRan := NewAmfRan(nil) - targetRan.AnType = "3GPP_ACCESS" - sourceAMFUeNgapID := ngapType.AMFUENGAPID{Value: ranUe.AmfUeNgapId} - ranUeNgapID := ngapType.RANUENGAPID{Value: 2} - received := &ngapType.UESecurityCapabilities{} - received.NRencryptionAlgorithms.Value = aper.BitString{Bytes: []byte{0x00, 0x00}, BitLength: 16} - received.NRintegrityProtectionAlgorithms.Value = aper.BitString{Bytes: []byte{0x00, 0x00}, BitLength: 16} - - handlePathSwitchRequestMain(targetRan, &ranUeNgapID, &sourceAMFUeNgapID, nil, received, nil, nil) - - require.Equal(t, uint8(1), amfUe.UESecurityCapability.GetIA2_128_5G()) - require.Equal(t, uint8(0), amfUe.UESecurityCapability.GetIA1_128_5G()) - require.Equal(t, uint8(0), amfUe.UESecurityCapability.GetIA3_128_5G()) -} - func NewAmfContext(amfCtx *amf_context.AMFContext) { *amfCtx = amf_context.AMFContext{ NfId: uuid.New().String(), @@ -196,6 +157,51 @@ func NewAmfContext(amfCtx *amf_context.AMFContext) { } } +func TestHandlePathSwitchRequestKeepsStoredUESecurityCapabilityOnMismatch(t *testing.T) { + amfSelf := amf_context.GetSelf() + NewAmfContext(amfSelf) + + sourceRan := NewAmfRan(nil) + sourceRan.AnType = "3GPP_ACCESS" + ranUe := &amf_context.RanUe{ + Ran: sourceRan, + RanUeNgapId: 1, + AmfUeNgapId: 1, + Log: sourceRan.Log, + } + sourceRan.RanUeList.Store(ranUe.RanUeNgapId, ranUe) + amfSelf.RanUePool.Store(ranUe.AmfUeNgapId, ranUe) + + amfUe := amfSelf.NewAmfUe("imsi-208930000007487") + amfUe.SecurityContextAvailable = true + amfUe.NgKsi.Ksi = 1 + amfUe.Kamf = strings.Repeat("11", 32) + amfUe.NH = []byte(strings.Repeat("\x22", 32)) + amfUe.UESecurityCapability = nasType.UESecurityCapability{ + Iei: nasMessage.RegistrationRequestUESecurityCapabilityType, + Len: 2, + Buffer: []uint8{0x00, 0x00}, + } + amfUe.UESecurityCapability.SetIA2_128_5G(1) + + ranUe.AmfUe = amfUe + amfUe.RanUe["3GPP_ACCESS"] = ranUe + + targetRan := NewAmfRan(nil) + targetRan.AnType = "3GPP_ACCESS" + sourceAMFUeNgapID := ngapType.AMFUENGAPID{Value: ranUe.AmfUeNgapId} + ranUeNgapID := ngapType.RANUENGAPID{Value: 2} + received := &ngapType.UESecurityCapabilities{} + received.NRencryptionAlgorithms.Value = aper.BitString{Bytes: []byte{0x00, 0x00}, BitLength: 16} + received.NRintegrityProtectionAlgorithms.Value = aper.BitString{Bytes: []byte{0x00, 0x00}, BitLength: 16} + + handlePathSwitchRequestMain(targetRan, &ranUeNgapID, &sourceAMFUeNgapID, nil, received, nil, nil) + + require.Equal(t, uint8(1), amfUe.UESecurityCapability.GetIA2_128_5G()) + require.Equal(t, uint8(0), amfUe.UESecurityCapability.GetIA1_128_5G()) + require.Equal(t, uint8(0), amfUe.UESecurityCapability.GetIA3_128_5G()) +} + func BuildInitialUEMessage(ranUeNgapID int64, nasPdu []byte, fiveGSTmsi string) ngapType.NGAPPDU { var TestPlmn ngapType.PLMNIdentity var tmsi aper.OctetString