From ccf4e4843d7d851bf4ce052d081066f4523efbf2 Mon Sep 17 00:00:00 2001 From: Lennart Rosam Date: Sat, 27 Dec 2025 00:48:01 +0100 Subject: [PATCH 1/2] feat: implement inbound roaming reject Implements inbound roaming reject cause configuration. This is useful for private networks and public SIMs from other carriers try to join. In that case, you want to respond with ROAMING_NOT_ALLOWED as reject cause in 2G/3G/4G. Otherwise, the UE may believe that its subscription is terminated. Defaults to ROAMING_NOT_ALLOWED for a safe OOBE. Production carriers are advised to change this to IMSI_UNKNOWN. --- config.yaml | 9 +++++++++ docker/.env | 1 + docker/config.yaml | 9 +++++++++ lib/diameter.py | 11 +++++++++-- lib/gsup/controller/air.py | 10 +++++++++- tests/config.yaml | 2 ++ 6 files changed, 39 insertions(+), 3 deletions(-) diff --git a/config.yaml b/config.yaml index caf56988..ce63d19d 100644 --- a/config.yaml +++ b/config.yaml @@ -79,6 +79,15 @@ hss: - 'scscf.ims.mnc001.mcc001.3gppnetwork.org' roaming: + inbound: + # How tofi handle unknown IMSIs when inbound roaming. Valid options are: + # IMSI_UNKNOWN - Reject the request with the "IMSI_UNKNOWN" error code. + # ROAMING_NOT_ALLOWED - Reject the request with the "ROAMING_NOT_ALLOWED" error code. + # The latter is useful when operating a private network and public SIMs should not be allowed to connect. + # "Roaming not allowed" will cause the UE to not try again on this network. + # "Imsi unknown" may cause the UE to believe that its subscription is invalid. + # For private networks, "ROAMING_NOT_ALLOWED" is recommended. Otherwise, use "IMSI_UNKNOWN". + reject_unknown_imsis_with: "ROAMING_NOT_ALLOWED" outbound: # Whether or not to a subscriber to connect to an undefined network when outbound roaming. allow_undefined_networks: True diff --git a/docker/.env b/docker/.env index 318b63c4..0c9a49a6 100644 --- a/docker/.env +++ b/docker/.env @@ -26,6 +26,7 @@ HSS_DSR_EXTERNAL_IDENTIFIER=example HSS_IGNORE_PURGE_UE_REQUEST=False HSS_SCSCF_POOL=scscf.ims.mnc001.mcc001.3gppnetwork.org HSS_ROAMING_ALLOW_UNDEFINED_NETWORKS=True +HSS_ROAMING_REJECT_UNKNOWN_IMSIS_WITH="ROAMING_NOT_ALLOWED" HSS_SCTP_RTO_MAX=5000 HSS_SCTP_RTO_MIN=500 HSS_SCTP_RTO_INITIAL=1000 diff --git a/docker/config.yaml b/docker/config.yaml index 3644cc99..74ff10c7 100644 --- a/docker/config.yaml +++ b/docker/config.yaml @@ -79,6 +79,15 @@ hss: - "${HSS_SCSCF_POOL:-scscf.ims.mnc001.mcc001.3gppnetwork.org}" roaming: + inbound: + # How to handle unknown IMSIs when inbound roaming. Valid options are: + # IMSI_UNKNOWN - Reject the request with the "IMSI_UNKNOWN" error code. + # ROAMING_NOT_ALLOWED - Reject the request with the "ROAMING_NOT_ALLOWED" error code. + # The latter is useful when operating a private network and public SIMs should not be allowed to connect. + # "Roaming not allowed" will cause the UE to not try again on this network. + # "Imsi unknown" may cause the UE to believe that its subscription is invalid. + # For private networks, "ROAMING_NOT_ALLOWED" is recommended. Otherwise, use "IMSI_UNKNOWN". + reject_unknown_imsis_with: "${HSS_ROAMING_REJECT_UNKNOWN_IMSIS_WITH:-ROAMING_NOT_ALLOWED}" outbound: # Whether or not to a subscriber to connect to an undefined network when outbound roaming. allow_undefined_networks: ${HSS_ROAMING_ALLOW_UNDEFINED_NETWORKS:-True} diff --git a/lib/diameter.py b/lib/diameter.py index e220d03d..01963194 100755 --- a/lib/diameter.py +++ b/lib/diameter.py @@ -130,6 +130,13 @@ def __init__( ] + @staticmethod + def get_unknown_imsi_reject_cause() -> int: + if config['hss']['roaming']['inbound']['reject_unknown_imsis_with'] == 'ROAMING_NOT_ALLOWED': + return 5004 # DIAMETER_ERROR_ROAMING_NOT_ALLOWED + return 5001 # DIAMETER_ERROR_USER_UNKNOWN + + #Generates rounding for calculating padding def myround(self, n, base=4): if(n > 0): @@ -2244,7 +2251,7 @@ def Answer_16777251_318(self, packet_vars, avps): usePrefix=True, prefixHostname=self.hostname, prefixServiceName='metric') - #Handle if the subscriber is not present in HSS return "DIAMETER_ERROR_USER_UNKNOWN" + #Handle if the subscriber is not present in HSS return the appropriate error self.logTool.log(service='HSS', level='debug', message="Subscriber " + str(imsi) + " is unknown in database", redisClient=self.redisMessaging) avp = '' session_id = self.get_avp_data(avps, 263)[0] #Get Session-ID @@ -2255,7 +2262,7 @@ def Answer_16777251_318(self, packet_vars, avps): #Experimental Result AVP(Response Code for Failure) avp_experimental_result = '' avp_experimental_result += self.generate_vendor_avp(266, 40, 10415, '') #AVP Vendor ID - avp_experimental_result += self.generate_avp(298, 40, self.int_to_hex(5001, 4)) #AVP Experimental-Result-Code: DIAMETER_ERROR_USER_UNKNOWN (5001) + avp_experimental_result += self.generate_avp(298, 40, self.int_to_hex(self.get_unknown_imsi_reject_cause(), 4)) #AVP Experimental-Result-Code: DIAMETER_ERROR_USER_UNKNOWN (5001) or DIAMETER_ERROR_ROAMING_NOT_ALLOWED (5004) avp += self.generate_avp(297, 40, avp_experimental_result) #AVP Experimental-Result(297) avp += self.generate_avp(277, 40, "00000001") #Auth-Session-State diff --git a/lib/gsup/controller/air.py b/lib/gsup/controller/air.py index f1e6e210..49655e87 100644 --- a/lib/gsup/controller/air.py +++ b/lib/gsup/controller/air.py @@ -13,6 +13,7 @@ from logtool import LogTool from utils import validate_imsi, InvalidIMSI +from pyhss_config import config class AIRController(GsupController): def __init__(self, logger: LogTool, database: Database): @@ -27,6 +28,13 @@ def get_num_vectors_req(self, message: dict): return max_num return ret + @staticmethod + def _get_unknown_subscriber_reject_cause() -> GMMCause: + if config['hss']['roaming']['inbound']['reject_unknown_imsis_with'] == 'ROAMING_NOT_ALLOWED': + return GMMCause.ROAMING_NOTALLOWED + else: + return GMMCause.IMSI_UNKNOWN + async def handle_message(self, peer: IPAPeer, message: GsupMessage): request_dict = message.to_dict() imsi = GsupMessageUtil.get_first_ie_by_name(GsupMessageUtil.GSUP_MSG_IE_IMSI, request_dict) @@ -79,7 +87,7 @@ async def handle_message(self, peer: IPAPeer, message: GsupMessage): peer, GsupMessageBuilder().with_msg_type(MsgType.SEND_AUTH_INFO_ERROR) .with_ie('imsi', imsi) - .with_ie('cause', GMMCause.IMSI_UNKNOWN.value) + .with_ie('cause', self._get_unknown_subscriber_reject_cause().value) .build(), ) except Exception as e: diff --git a/tests/config.yaml b/tests/config.yaml index d400a6bc..8e957ee5 100644 --- a/tests/config.yaml +++ b/tests/config.yaml @@ -30,6 +30,8 @@ hss: - 'scscf.ims.mnc001.mcc001.3gppnetwork.org' roaming: + inbound: + reject_unknown_imsis_with: "ROAMING_NOT_ALLOWED" outbound: allow_undefined_networks: True From dc2cc5605abfcf8dc47d1e2ca022e81443264a2b Mon Sep 17 00:00:00 2001 From: Lennart Rosam Date: Tue, 27 Jan 2026 11:32:33 +0100 Subject: [PATCH 2/2] fix: Typo Co-authored-by: Oliver Smith --- config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.yaml b/config.yaml index ce63d19d..d7580585 100644 --- a/config.yaml +++ b/config.yaml @@ -80,7 +80,7 @@ hss: roaming: inbound: - # How tofi handle unknown IMSIs when inbound roaming. Valid options are: + # How to handle unknown IMSIs when inbound roaming. Valid options are: # IMSI_UNKNOWN - Reject the request with the "IMSI_UNKNOWN" error code. # ROAMING_NOT_ALLOWED - Reject the request with the "ROAMING_NOT_ALLOWED" error code. # The latter is useful when operating a private network and public SIMs should not be allowed to connect.