diff --git a/proto/context.proto b/proto/context.proto index 62b21b4497a2ac000ddc9405c8693e7490c35ce2..3eebb54d48726e9d77c82f31c850105b9eecb591 100644 --- a/proto/context.proto +++ b/proto/context.proto @@ -359,6 +359,7 @@ enum ServiceTypeEnum { SERVICETYPE_IP_LINK = 11; SERVICETYPE_TAPI_LSP = 12; SERVICETYPE_IPOWDM = 13; + SERVICETYPE_UPF = 14; } enum ServiceStatusEnum { diff --git a/src/common/DeviceTypes.py b/src/common/DeviceTypes.py index 948de0e98b42a4de9762d65c51690ea5e6dd96ef..8e1cf6758082dd408a8e05d49dd053f3f218ec8c 100644 --- a/src/common/DeviceTypes.py +++ b/src/common/DeviceTypes.py @@ -38,6 +38,9 @@ class DeviceTypeEnum(Enum): EMULATED_PACKET_ROUTER = 'emu-packet-router' EMULATED_PACKET_SWITCH = 'emu-packet-switch' EMULATED_XR_CONSTELLATION = 'emu-xr-constellation' + EMULATED_ORAN_DU = 'oran-du' + EMULATED_ORAN_CU = 'oran-cu' + EMULATED_ORAN_CN = 'oran-cn' # Real device types CLIENT = 'client' diff --git a/src/context/service/database/models/enums/ServiceType.py b/src/context/service/database/models/enums/ServiceType.py index c672d160ff194f35b085971a0639f8d6c8470307..cf24a000f7a7dbedf58ac96800965e84b3081c40 100644 --- a/src/context/service/database/models/enums/ServiceType.py +++ b/src/context/service/database/models/enums/ServiceType.py @@ -36,6 +36,7 @@ class ORM_ServiceTypeEnum(enum.Enum): IP_LINK = ServiceTypeEnum.SERVICETYPE_IP_LINK IPOWDM = ServiceTypeEnum.SERVICETYPE_IPOWDM TAPI_LSP = ServiceTypeEnum.SERVICETYPE_TAPI_LSP + UPF = ServiceTypeEnum.SERVICETYPE_UPF grpc_to_enum__service_type = functools.partial( grpc_to_enum, ServiceTypeEnum, ORM_ServiceTypeEnum) diff --git a/src/service/service/service_handler_api/FilterFields.py b/src/service/service/service_handler_api/FilterFields.py index b0a5666a60663b5949445b6dbb10c922981004a6..2ce77608ab3d4c22f1fd9120d12b1bfb5940e463 100644 --- a/src/service/service/service_handler_api/FilterFields.py +++ b/src/service/service/service_handler_api/FilterFields.py @@ -20,8 +20,50 @@ class FilterFieldEnum(Enum): SERVICE_TYPE = 'service_type' DEVICE_DRIVER = 'device_driver' -# Map allowed filter fields to allowed values per Filter field. -# If no restriction (free text) None is specified +SERVICE_TYPE_VALUES = { + ServiceTypeEnum.SERVICETYPE_UNKNOWN, + ServiceTypeEnum.SERVICETYPE_L3NM, + ServiceTypeEnum.SERVICETYPE_L2NM, + ServiceTypeEnum.SERVICETYPE_L1NM, + ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE, + ServiceTypeEnum.SERVICETYPE_TE, + ServiceTypeEnum.SERVICETYPE_E2E, + ServiceTypeEnum.SERVICETYPE_OPTICAL_CONNECTIVITY, + ServiceTypeEnum.SERVICETYPE_QKD, + ServiceTypeEnum.SERVICETYPE_INT, + ServiceTypeEnum.SERVICETYPE_ACL, + ServiceTypeEnum.SERVICETYPE_IP_LINK, + ServiceTypeEnum.SERVICETYPE_IPOWDM, + ServiceTypeEnum.SERVICETYPE_TAPI_LSP, + ServiceTypeEnum.SERVICETYPE_UPF, +} + +DEVICE_DRIVER_VALUES = { + DeviceDriverEnum.DEVICEDRIVER_UNDEFINED, + DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG, + DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API, + DeviceDriverEnum.DEVICEDRIVER_P4, + DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY, + DeviceDriverEnum.DEVICEDRIVER_ONF_TR_532, + DeviceDriverEnum.DEVICEDRIVER_XR, + DeviceDriverEnum.DEVICEDRIVER_IETF_L2VPN, + DeviceDriverEnum.DEVICEDRIVER_GNMI_OPENCONFIG, + DeviceDriverEnum.DEVICEDRIVER_OPTICAL_TFS, + DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN, + DeviceDriverEnum.DEVICEDRIVER_OC, + DeviceDriverEnum.DEVICEDRIVER_QKD, + DeviceDriverEnum.DEVICEDRIVER_IETF_L3VPN, + DeviceDriverEnum.DEVICEDRIVER_IETF_SLICE, + DeviceDriverEnum.DEVICEDRIVER_NCE, + DeviceDriverEnum.DEVICEDRIVER_SMARTNIC, + DeviceDriverEnum.DEVICEDRIVER_MORPHEUS, + DeviceDriverEnum.DEVICEDRIVER_RYU, + DeviceDriverEnum.DEVICEDRIVER_GNMI_NOKIA_SRLINUX, + DeviceDriverEnum.DEVICEDRIVER_OPENROADM, + DeviceDriverEnum.DEVICEDRIVER_RESTCONF_OPENCONFIG, +} + +# Map allowed filter fields to allowed values per Filter field. If no restriction (free text) None is specified FILTER_FIELD_ALLOWED_VALUES = { FilterFieldEnum.SERVICE_TYPE.value : set(ServiceTypeEnum.values()), FilterFieldEnum.DEVICE_DRIVER.value : set(DeviceDriverEnum.values()), diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py index c30d5c308c6893198bc88f37198a229d6d1e192d..7a607acddff625b4413e8f3ae172523a8853fdd3 100644 --- a/src/service/service/service_handlers/__init__.py +++ b/src/service/service/service_handlers/__init__.py @@ -32,6 +32,7 @@ from .p4_fabric_tna_int.p4_fabric_tna_int_service_handler import P4FabricINTServ from .p4_fabric_tna_l2_simple.p4_fabric_tna_l2_simple_service_handler import P4FabricL2SimpleServiceHandler from .p4_fabric_tna_l3.p4_fabric_tna_l3_service_handler import P4FabricL3ServiceHandler from .p4_fabric_tna_acl.p4_fabric_tna_acl_service_handler import P4FabricACLServiceHandler +from .p4_fabric_tna_upf.p4_fabric_tna_upf_service_handler import P4FabricUPFServiceHandler from .tapi_lsp.Tapi_LSPServiceHandler import Tapi_LSPServiceHandler from .tapi_tapi.TapiServiceHandler import TapiServiceHandler from .tapi_xr.TapiXrServiceHandler import TapiXrServiceHandler @@ -153,6 +154,12 @@ SERVICE_HANDLERS = [ FilterFieldEnum.DEVICE_DRIVER: DeviceDriverEnum.DEVICEDRIVER_P4, } ]), + (P4FabricUPFServiceHandler, [ + { + FilterFieldEnum.SERVICE_TYPE: ServiceTypeEnum.SERVICETYPE_UPF, + FilterFieldEnum.DEVICE_DRIVER: DeviceDriverEnum.DEVICEDRIVER_P4, + } + ]), (L2NM_IETFL2VPN_ServiceHandler, [ { FilterFieldEnum.SERVICE_TYPE : ServiceTypeEnum.SERVICETYPE_L2NM, diff --git a/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_service_handler.py b/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_service_handler.py index 0b09825da0af8a63f76a2c5d1f88b72d42e76954..466f37601a3c97d1a2095e12833229bf74453f1a 100644 --- a/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_service_handler.py +++ b/src/service/service/service_handlers/p4_fabric_tna_acl/p4_fabric_tna_acl_service_handler.py @@ -448,7 +448,7 @@ class P4FabricACLServiceHandler(_ServiceHandler): if IPV4_SRC in acl_entry: try: rules += rules_set_up_acl_filter_host( - ingress_port=port_id, + port_id=port_id, ip_address=acl_entry[IPV4_SRC], prefix_len=acl_entry[IPV4_PREFIX_LEN], ip_direction="src", @@ -461,7 +461,7 @@ class P4FabricACLServiceHandler(_ServiceHandler): if IPV4_DST in acl_entry: try: rules += rules_set_up_acl_filter_host( - ingress_port=port_id, + port_id=port_id, ip_address=acl_entry[IPV4_DST], prefix_len=acl_entry[IPV4_PREFIX_LEN], ip_direction="dst", @@ -474,7 +474,7 @@ class P4FabricACLServiceHandler(_ServiceHandler): if TRN_PORT_SRC in acl_entry: try: rules += rules_set_up_acl_filter_port( - ingress_port=port_id, + port_id=port_id, transport_port=acl_entry[TRN_PORT_SRC], transport_direction="src", action=action @@ -486,7 +486,7 @@ class P4FabricACLServiceHandler(_ServiceHandler): if TRN_PORT_DST in acl_entry: try: rules += rules_set_up_acl_filter_port( - ingress_port=port_id, + port_id=port_id, transport_port=acl_entry[TRN_PORT_DST], transport_direction="dst", action=action diff --git a/src/service/service/service_handlers/p4_fabric_tna_commons/p4_fabric_tna_commons.py b/src/service/service/service_handlers/p4_fabric_tna_commons/p4_fabric_tna_commons.py index b003ceef8528b2bfb6bdcb19d206eba818277808..b03cfc908690de537dca1e451ab5c4baaafbbc52 100644 --- a/src/service/service/service_handlers/p4_fabric_tna_commons/p4_fabric_tna_commons.py +++ b/src/service/service/service_handlers/p4_fabric_tna_commons/p4_fabric_tna_commons.py @@ -48,7 +48,11 @@ VLAN_ID = "vlan_id" RECIRCULATION_PORT_LIST = "recirculation_port_list" PORT_LIST = "port_list" PORT_PREFIX = "port-" +FORWARDING_LIST = "fwd_list" ROUTING_LIST = "routing_list" +HOST_LIST = "host_list" +HOST_MAC = "host_mac" +HOST_LABEL = "host_label" MAC_SRC = "mac_src" MAC_DST = "mac_dst" IPV4_SRC = "ipv4_src" @@ -72,6 +76,7 @@ TABLE_EGRESS_VLAN = "FabricEgress.egress_next.egress_vlan" TABLE_FWD_CLASSIFIER = "FabricIngress.filtering.fwd_classifier" TABLE_BRIDGING = "FabricIngress.forwarding.bridging" TABLE_ROUTING_V4 = "FabricIngress.forwarding.routing_v4" +TABLE_PRE_NEXT_VLAN = "FabricIngress.pre_next.next_vlan" TABLE_NEXT_SIMPLE = "FabricIngress.next.simple" TABLE_NEXT_HASHED = "FabricIngress.next.hashed" TABLE_ACL = "FabricIngress.acl.acl" @@ -136,9 +141,6 @@ DEF_VLAN = 4094 ETHER_TYPE_IPV4 = "0x0800" ETHER_TYPE_IPV6 = "0x86DD" -# Member ID -NEXT_MEMBER_ID = 1 - # Time interval in seconds for consecutive rule management (insert/delete) operations RULE_CONF_INTERVAL_SEC = 0.1 @@ -208,11 +210,11 @@ def find_port_id_in_endpoint_list(endpoint_list : List, target_endpoint_uuid : s ################################### def rules_set_up_port_ingress( - ingress_port : int, + port_id : int, port_type : str, vlan_id: int, action : ConfigActionEnum) -> List [Tuple]: # type: ignore - assert ingress_port >= 0, "Invalid ingress port to configure ingress port" + assert port_id >= 0, "Invalid port ID to configure ingress port" assert port_type.lower() in PORT_TYPES_STR_VALID, "Invalid port type to configure ingress port" assert chk_vlan_id(vlan_id), "Invalid VLAN ID to configure ingress port" @@ -224,6 +226,12 @@ def rules_set_up_port_ingress( port_type_int = PORT_TYPE_MAP[port_type.lower()] assert port_type_int in PORT_TYPES_INT_VALID, "Invalid port type to configure ingress filtering" + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure ingress port") + LOGGER.info(f"================== Port ID {port_id}") + LOGGER.info(f"================== VLAN is valid {vlan_is_valid}") + LOGGER.info(f"================== Port type {port_type_int}") + rules_filtering_vlan_ingress = [] rules_filtering_vlan_ingress.append( json_config_rule( @@ -234,7 +242,7 @@ def rules_set_up_port_ingress( 'match-fields': [ { 'match-field': 'ig_port', - 'match-value': str(ingress_port) + 'match-value': str(port_id) }, { 'match-field': 'vlan_is_valid', @@ -266,6 +274,11 @@ def rules_set_up_port_egress( assert egress_port >= 0, "Invalid egress port to configure egress vlan" assert chk_vlan_id(vlan_id), "Invalid VLAN ID to configure egress vlan" + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure egress port") + LOGGER.info(f"================== Port ID {egress_port}") + LOGGER.info(f"================== VLAN ID {vlan_id}") + rule_no = cache_rule(TABLE_EGRESS_VLAN, action) rules_vlan_egress = [] @@ -294,13 +307,19 @@ def rules_set_up_port_egress( return rules_vlan_egress def rules_set_up_fwd_classifier( - ingress_port : int, + port_id : int, fwd_type : int, eth_type: str, action : ConfigActionEnum) -> List [Tuple]: # type: ignore - assert ingress_port >= 0, "Invalid ingress port to configure forwarding classifier" + assert port_id >= 0, "Invalid port ID to configure forwarding classifier" assert fwd_type in FORWARDING_TYPES_VALID, "Invalid forwarding type to configure forwarding classifier" + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure FWD classifier") + LOGGER.info(f"================== Port ID {port_id}") + LOGGER.info(f"================== Ethernet type {eth_type}") + LOGGER.info(f"================== FWD type {fwd_type}") + rule_no = cache_rule(TABLE_FWD_CLASSIFIER, action) rules_filtering_fwd_classifier = [] @@ -313,7 +332,7 @@ def rules_set_up_fwd_classifier( 'match-fields': [ { 'match-field': 'ig_port', - 'match-value': str(ingress_port) + 'match-value': str(port_id) }, { 'match-field': 'ip_eth_type', @@ -335,7 +354,7 @@ def rules_set_up_fwd_classifier( return rules_filtering_fwd_classifier def rules_set_up_port( - port : int, + port_id : int, port_type : str, fwd_type : int, vlan_id : int, @@ -345,7 +364,7 @@ def rules_set_up_port( rules_list.extend( rules_set_up_port_ingress( - ingress_port=port, + port_id=port_id, port_type=port_type, vlan_id=vlan_id, action=action @@ -353,7 +372,7 @@ def rules_set_up_port( ) rules_list.extend( rules_set_up_fwd_classifier( - ingress_port=port, + port_id=port_id, fwd_type=fwd_type, eth_type=eth_type, action=action @@ -361,15 +380,51 @@ def rules_set_up_port( ) rules_list.extend( rules_set_up_port_egress( - egress_port=port, + egress_port=port_id, vlan_id=vlan_id, action=action ) ) - LOGGER.debug(f"Port configured:{port}") + LOGGER.debug(f"Port configured:{port_id}") return rules_list +def rules_set_up_port_host( + port : int, + vlan_id : int, + action : ConfigActionEnum, # type: ignore + fwd_type=FORWARDING_TYPE_BRIDGING, + eth_type=ETHER_TYPE_IPV4): + # This is a host facing port + port_type = PORT_TYPE_HOST + + return rules_set_up_port( + port_id=port, + port_type=port_type, + fwd_type=fwd_type, + vlan_id=vlan_id, + action=action, + eth_type=eth_type + ) + +def rules_set_up_port_switch( + port : int, + vlan_id : int, + action : ConfigActionEnum, # type: ignore + fwd_type=FORWARDING_TYPE_BRIDGING, + eth_type=ETHER_TYPE_IPV4): + # This is a switch facing port + port_type = PORT_TYPE_SWITCH + + return rules_set_up_port( + port_id=port, + port_type=port_type, + fwd_type=fwd_type, + vlan_id=vlan_id, + action=action, + eth_type=eth_type + ) + ################################### ### A. End of port setup ################################### @@ -380,13 +435,22 @@ def rules_set_up_port( ################################### def rules_set_up_fwd_bridging( + port_id : int, vlan_id: int, eth_dst : str, - egress_port : int, + next_id : int, action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert port_id >= 0, "Invalid port ID to configure bridging" assert chk_vlan_id(vlan_id), "Invalid VLAN ID to configure bridging" assert chk_address_mac(eth_dst), "Invalid destination Ethernet address to configure bridging" - assert egress_port >= 0, "Invalid outport to configure bridging" + assert next_id >= 0, "Invalid next ID to configure bridging" + + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure bridging") + LOGGER.info(f"================== Port ID {port_id}") + LOGGER.info(f"================== VLAN ID {vlan_id}") + LOGGER.info(f"================== Ethernet Dst {eth_dst}") + LOGGER.info(f"================== Next ID {next_id}") rule_no = cache_rule(TABLE_BRIDGING, action) @@ -411,7 +475,7 @@ def rules_set_up_fwd_bridging( 'action-params': [ { 'action-param': 'next_id', - 'action-value': str(egress_port) + 'action-value': str(next_id) } ], 'priority': 1 @@ -421,80 +485,147 @@ def rules_set_up_fwd_bridging( return rules_fwd_bridging -def rules_set_up_next_output_simple( - egress_port : int, - action : ConfigActionEnum) -> List [Tuple]: # type: ignore - assert egress_port >= 0, "Invalid outport to configure next output simple" +def rules_set_up_pre_next_vlan( + port_id : int, + next_id : int, + vlan_id : int, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert port_id >= 0, "Invalid port ID to configure pre-next VLAN" + assert next_id >= 0, "Invalid next ID to configure pre-next VLAN" + assert chk_vlan_id(vlan_id), "Invalid VLAN ID to configure pre-next VLAN" - rule_no = cache_rule(TABLE_NEXT_SIMPLE, action) + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure pre-next VLAN") + LOGGER.info(f"================== Port ID {port_id}") + LOGGER.info(f"================== Next ID {next_id}") + LOGGER.info(f"================== VLAN ID {vlan_id}") - rules_next_output_simple = [] - rules_next_output_simple.append( + rule_no = cache_rule(TABLE_PRE_NEXT_VLAN, action) + + rules_pre_next_vlan = [] + rules_pre_next_vlan.append( json_config_rule( action, - '/tables/table/'+TABLE_NEXT_SIMPLE+'['+str(rule_no)+']', + '/tables/table/'+TABLE_PRE_NEXT_VLAN+'['+str(rule_no)+']', { - 'table-name': TABLE_NEXT_SIMPLE, + 'table-name': TABLE_PRE_NEXT_VLAN, 'match-fields': [ { 'match-field': 'next_id', - 'match-value': str(egress_port) + 'match-value': str(next_id) } ], - 'action-name': 'FabricIngress.next.output_simple', + 'action-name': 'FabricIngress.pre_next.set_vlan', 'action-params': [ { - 'action-param': 'port_num', - 'action-value': str(egress_port) + 'action-param': 'vlan_id', + 'action-value': str(vlan_id) } - ] + ], + 'priority': 0 } ) ) - return rules_next_output_simple - -def rules_set_up_next_output_hashed( - egress_port : int, - action : ConfigActionEnum, # type: ignore - next_id = None) -> List [Tuple]: - assert egress_port >= 0, "Invalid outport to configure next output hashed" + return rules_pre_next_vlan - if next_id is None: - next_id = egress_port +def rules_set_up_next_profile_hashed_output( + port_id : int, + next_id : int, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert port_id >= 0, "Invalid port ID to configure next profile for hashed output" + assert next_id >=0, "Invalid next ID to configure next profile for hashed output" - global NEXT_MEMBER_ID + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure next hashed profile for output") + LOGGER.info(f"================== Port ID {port_id}") + LOGGER.info(f"================== Next ID {next_id}") rule_no = cache_rule(ACTION_PROFILE_NEXT_HASHED, action) - rules_next_output_hashed = [] - rules_next_output_hashed.append( + rules_next_profile_hashed_out = [] + rules_next_profile_hashed_out.append( json_config_rule( action, '/action_profiles/action_profile/'+ACTION_PROFILE_NEXT_HASHED+'['+str(rule_no)+']', { 'action-profile-name': ACTION_PROFILE_NEXT_HASHED, - 'member-id': NEXT_MEMBER_ID, + 'member-id': next_id, 'action-name': 'FabricIngress.next.output_hashed', 'action-params': [ { 'action-param': 'port_num', - 'action-value': str(egress_port) + 'action-value': str(next_id) } ] } ) ) + return rules_next_profile_hashed_out + +def rules_set_up_next_output_simple( + port_id : int, + next_id : int, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert port_id >= 0, "Invalid port ID to configure next output simple" + assert next_id >=0, "Invalid next ID to configure next output simple" + + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure next output - simple") + LOGGER.info(f"================== Port ID {port_id}") + LOGGER.info(f"================== Next ID {next_id}") + + rule_no = cache_rule(TABLE_NEXT_SIMPLE, action) + + rules_next_output_simple = [] + rules_next_output_simple.append( + json_config_rule( + action, + '/tables/table/'+TABLE_NEXT_SIMPLE+'['+str(rule_no)+']', + { + 'table-name': TABLE_NEXT_SIMPLE, + 'match-fields': [ + { + 'match-field': 'next_id', + 'match-value': str(next_id) + } + ], + 'action-name': 'FabricIngress.next.output_simple', + 'action-params': [ + { + 'action-param': 'port_num', + 'action-value': str(next_id) + } + ] + } + ) + ) + + return rules_next_output_simple + +def rules_set_up_next_hashed( + port_id : int, + next_id : int, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert port_id >= 0, "Invalid port ID to configure next routing hashed" + assert next_id >=0, "Invalid next ID to configure next routing hashed" + + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure next output - hashed routing") + LOGGER.info(f"================== Port ID {port_id}") + LOGGER.info(f"================== Next ID {next_id}") + rule_no = cache_rule(TABLE_NEXT_HASHED, action) - rules_next_output_hashed.append( + rules_next_hashed = [] + rules_next_hashed.append( json_config_rule( action, '/tables/table/'+TABLE_NEXT_HASHED+'['+str(rule_no)+']', { 'table-name': TABLE_NEXT_HASHED, - 'member-id': NEXT_MEMBER_ID, + 'member-id': next_id, 'match-fields': [ { 'match-field': 'next_id', @@ -505,9 +636,7 @@ def rules_set_up_next_output_hashed( ) ) - NEXT_MEMBER_ID += 1 - - return rules_next_output_hashed + return rules_next_hashed ################################### ### B. End of L2 setup @@ -518,14 +647,71 @@ def rules_set_up_next_output_hashed( ### C. L3 setup ################################### +def rules_set_up_next_profile_hashed_routing( + port_id : int, + next_id : int, + eth_src : str, + eth_dst : str, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert port_id >= 0, "Invalid port ID to configure next profile for hashed routing" + assert next_id >=0, "Invalid next ID to configure next profile for hashed routing" + assert chk_address_mac(eth_src), "Invalid source Ethernet address to configure next profile for hashed routing" + assert chk_address_mac(eth_dst), "Invalid destination Ethernet address to configure next profile for hashed routing" + + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure next hashed profile for routing") + LOGGER.info(f"================== Port ID {port_id}") + LOGGER.info(f"================== Next ID {next_id}") + LOGGER.info(f"================== Ethernet Src {eth_src}") + LOGGER.info(f"================== Ethernet Dst {eth_dst}") + + rule_no = cache_rule(ACTION_PROFILE_NEXT_HASHED, action) + + rules_next_profile_hashed_rt = [] + rules_next_profile_hashed_rt.append( + json_config_rule( + action, + '/action_profiles/action_profile/'+ACTION_PROFILE_NEXT_HASHED+'['+str(rule_no)+']', + { + 'action-profile-name': ACTION_PROFILE_NEXT_HASHED, + 'member-id': next_id, + 'action-name': 'FabricIngress.next.routing_hashed', + 'action-params': [ + { + 'action-param': 'port_num', + 'action-value': str(next_id) + }, + { + 'action-param': 'smac', + 'action-value': eth_src + }, + { + 'action-param': 'dmac', + 'action-value': eth_dst + } + ] + } + ) + ) + + return rules_next_profile_hashed_rt + def rules_set_up_routing( + port_id : int, ipv4_dst : str, ipv4_prefix_len : int, - egress_port : int, + next_id : int, action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert port_id >= 0, "Invalid port ID to configure routing" assert chk_address_ipv4(ipv4_dst), "Invalid destination IPv4 address to configure routing" - assert chk_prefix_len_ipv4(ipv4_prefix_len), "Invalid IPv4 prefix length" - assert egress_port >= 0, "Invalid outport to configure routing" + assert chk_prefix_len_ipv4(ipv4_prefix_len), "Invalid IPv4 prefix length to configure routing" + assert next_id >= 0, "Invalid next ID to configure routing" + + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure routing") + LOGGER.info(f"================== Port ID {port_id}") + LOGGER.info(f"================== IPv4 dst {ipv4_dst}/{ipv4_prefix_len}") + LOGGER.info(f"================== Next ID {next_id}") rule_no = cache_rule(TABLE_ROUTING_V4, action) @@ -546,7 +732,7 @@ def rules_set_up_routing( 'action-params': [ { 'action-param': 'next_id', - 'action-value': str(egress_port) + 'action-value': str(next_id) } ] } @@ -556,14 +742,23 @@ def rules_set_up_routing( return rules_routing def rules_set_up_next_routing_simple( - egress_port : int, + port_id : int, + next_id : int, eth_src : str, eth_dst : str, action : ConfigActionEnum) -> List [Tuple]: # type: ignore - assert egress_port >= 0, "Invalid outport to configure next routing simple" + assert port_id >= 0, "Invalid port ID to configure next routing simple" + assert next_id >=0, "Invalid next ID to configure next routing simple" assert chk_address_mac(eth_src), "Invalid source Ethernet address to configure next routing simple" assert chk_address_mac(eth_dst), "Invalid destination Ethernet address to configure next routing simple" + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure next routing - simple") + LOGGER.info(f"================== Port {port_id}") + LOGGER.info(f"================== Next ID {next_id}") + LOGGER.info(f"================== MAC src {eth_src}") + LOGGER.info(f"================== MAC dst {eth_dst}") + rule_no = cache_rule(TABLE_NEXT_SIMPLE, action) rules_next_routing_simple = [] @@ -576,14 +771,14 @@ def rules_set_up_next_routing_simple( 'match-fields': [ { 'match-field': 'next_id', - 'match-value': str(egress_port) + 'match-value': str(next_id) } ], 'action-name': 'FabricIngress.next.routing_simple', 'action-params': [ { 'action-param': 'port_num', - 'action-value': str(egress_port) + 'action-value': str(next_id) }, { 'action-param': 'smac', @@ -600,68 +795,6 @@ def rules_set_up_next_routing_simple( return rules_next_routing_simple -def rules_set_up_next_routing_hashed( - egress_port : int, - action : ConfigActionEnum, # type: ignore - next_id = None) -> List [Tuple]: - assert egress_port >= 0, "Invalid outport to configure next routing hashed" - random_mac_src = generate_random_mac() - random_mac_dst = generate_random_mac() - if next_id is None: - next_id = egress_port - - global NEXT_MEMBER_ID - - rule_no = cache_rule(ACTION_PROFILE_NEXT_HASHED, action) - - rules_next_routing_hashed = [] - rules_next_routing_hashed.append( - json_config_rule( - action, - '/action_profiles/action_profile/'+ACTION_PROFILE_NEXT_HASHED+'['+str(rule_no)+']', - { - 'action-profile-name': ACTION_PROFILE_NEXT_HASHED, - 'member-id': NEXT_MEMBER_ID, - 'action-name': 'FabricIngress.next.routing_hashed', - 'action-params': [ - { - 'action-param': 'port_num', - 'action-value': str(egress_port) - }, - { - 'action-param': 'smac', - 'action-value': random_mac_src - }, - { - 'action-param': 'dmac', - 'action-value': random_mac_dst - } - ] - } - ) - ) - - rule_no = cache_rule(TABLE_NEXT_HASHED, action) - - rules_next_routing_hashed.append( - json_config_rule( - action, - '/tables/table/'+TABLE_NEXT_HASHED+'['+str(rule_no)+']', - { - 'table-name': TABLE_NEXT_HASHED, - 'member-id': NEXT_MEMBER_ID, - 'match-fields': [ - { - 'match-field': 'next_id', - 'match-value': str(next_id) - } - ] - } - ) - ) - - return rules_next_routing_hashed - ################################### ### C. End of L3 setup ################################### @@ -699,6 +832,12 @@ def rules_set_up_clone_session( assert egress_port >= 0, "Invalid egress port number to configure clone session" assert instance >= 0, "Invalid instance number to configure clone session" + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure clone session") + LOGGER.info(f"================== Session ID {session_id}") + LOGGER.info(f"================== Egress port {egress_port}") + LOGGER.info(f"================== Instance {instance}") + rule_no = cache_rule(CLONE_SESSION, action) #TODO: For TNA pass also: packet_length_bytes = 128 @@ -734,21 +873,27 @@ def rules_set_up_clone_session( ################################### def rules_set_up_acl_filter_host( - ingress_port : int, + port_id : int, ip_address : str, prefix_len : int, ip_direction : str, action : ConfigActionEnum) -> List [Tuple]: # type: ignore - assert ingress_port >= 0, "Invalid ingress port to configure ACL" + assert port_id >= 0, "Invalid port ID to configure ACL" assert chk_address_ipv4(ip_address), "Invalid IP address to configure ACL" assert 0 < prefix_len <= 32, "Invalid IP address prefix length to configure ACL" ip_match = "ipv4_src" if ip_direction == "src" else "ipv4_dst" - prefix_len_hex = prefix_to_hex_mask(prefix_len) rule_no = cache_rule(TABLE_ACL, action) + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure host ACL rule") + LOGGER.info(f"================== Port ID {port_id}") + LOGGER.info(f"================== IP address {ip_address}") + LOGGER.info(f"================== IP address type {ip_match}") + LOGGER.info(f"================== IP prefix length {prefix_len}") + rules_acl = [] rules_acl.append( json_config_rule( @@ -759,7 +904,7 @@ def rules_set_up_acl_filter_host( 'match-fields': [ { 'match-field': 'ig_port', - 'match-value': str(ingress_port) + 'match-value': str(port_id) }, { 'match-field': ip_match, @@ -776,17 +921,23 @@ def rules_set_up_acl_filter_host( return rules_acl def rules_set_up_acl_filter_port( - ingress_port : int, + port_id : int, transport_port : int, transport_direction : str, action : ConfigActionEnum) -> List [Tuple]: # type: ignore - assert ingress_port >= 0, "Invalid ingress port to configure ACL" + assert port_id >= 0, "Invalid port ID to configure ACL" assert chk_transport_port(transport_port), "Invalid transport port to configure ACL" trn_match = "l4_sport" if transport_direction == "src" else "l4_dport" rule_no = cache_rule(TABLE_ACL, action) + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure port ACL rule") + LOGGER.info(f"================== Port ID {port_id}") + LOGGER.info(f"================== Transport port {transport_port}") + LOGGER.info(f"================== Transport port type {transport_direction}") + rules_acl = [] rules_acl.append( json_config_rule( @@ -797,7 +948,7 @@ def rules_set_up_acl_filter_port( 'match-fields': [ { 'match-field': 'ig_port', - 'match-value': str(ingress_port) + 'match-value': str(port_id) }, { 'match-field': trn_match, diff --git a/src/service/service/service_handlers/p4_fabric_tna_int/p4_fabric_tna_int_config.py b/src/service/service/service_handlers/p4_fabric_tna_int/p4_fabric_tna_int_config.py index da5d6db070278b1e607bee6d92198aa4c5ddf677..dc7b6e6bad00c7ddb65efe84f171037ba345a384 100644 --- a/src/service/service/service_handlers/p4_fabric_tna_int/p4_fabric_tna_int_config.py +++ b/src/service/service/service_handlers/p4_fabric_tna_int/p4_fabric_tna_int_config.py @@ -124,7 +124,7 @@ def rules_set_up_int_recirculation_ports( for port in recirculation_port_list: rules_list.extend( rules_set_up_port( - port=port, + port_id=port, port_type=port_type, fwd_type=fwd_type, vlan_id=vlan_id, diff --git a/src/service/service/service_handlers/p4_fabric_tna_int/p4_fabric_tna_int_service_handler.py b/src/service/service/service_handlers/p4_fabric_tna_int/p4_fabric_tna_int_service_handler.py index 5cc6e24c8af7eab80f7830cf160304939564800e..f3d38d593579e943a5b02af7e135c2a81283df32 100644 --- a/src/service/service/service_handlers/p4_fabric_tna_int/p4_fabric_tna_int_service_handler.py +++ b/src/service/service/service_handlers/p4_fabric_tna_int/p4_fabric_tna_int_service_handler.py @@ -410,6 +410,13 @@ class P4FabricINTServiceHandler(_ServiceHandler): dev_name = device_obj.name rules = [] + port_id = self.__switch_info[dev_name][PORT_INT][PORT_ID] + next_id = port_id + mac_src = self.__switch_info[dev_name][MAC] + collector_mac_dst = self.__int_collector_mac + collector_ip_dst = self.__int_collector_ip + vlan_id = self.__int_vlan_id + ### INT reporting rules try: rules += rules_set_up_int_watchlist(action=action) @@ -422,7 +429,7 @@ class P4FabricINTServiceHandler(_ServiceHandler): recirculation_port_list=self.__switch_info[dev_name][RECIRCULATION_PORT_LIST], port_type=PORT_TYPE_INT, fwd_type=FORWARDING_TYPE_UNICAST_IPV4, - vlan_id=self.__int_vlan_id, + vlan_id=vlan_id, action=action ) except Exception as ex: @@ -463,10 +470,10 @@ class P4FabricINTServiceHandler(_ServiceHandler): ### INT port setup rules try: rules += rules_set_up_port( - port=self.__switch_info[dev_name][PORT_INT][PORT_ID], + port_id=port_id, port_type=PORT_TYPE_HOST, fwd_type=FORWARDING_TYPE_BRIDGING, - vlan_id=self.__int_vlan_id, + vlan_id=vlan_id, action=action ) except Exception as ex: @@ -476,13 +483,17 @@ class P4FabricINTServiceHandler(_ServiceHandler): ### INT port forwarding rules try: rules += rules_set_up_fwd_bridging( - vlan_id=self.__int_vlan_id, - eth_dst=self.__int_collector_mac, - egress_port=self.__switch_info[dev_name][PORT_INT][PORT_ID], + port_id=port_id, + vlan_id=vlan_id, + eth_dst=collector_mac_dst, + next_id=next_id, action=action ) - rules += rules_set_up_next_output_simple( - egress_port=self.__switch_info[dev_name][PORT_INT][PORT_ID], + rules += rules_set_up_next_profile_hashed_routing( + port_id=port_id, + next_id=next_id, + eth_src=mac_src, + eth_dst=collector_mac_dst, action=action ) except Exception as ex: @@ -491,16 +502,16 @@ class P4FabricINTServiceHandler(_ServiceHandler): ### INT packet routing rules try: - rules += rules_set_up_next_routing_simple( - egress_port=self.__switch_info[dev_name][PORT_INT][PORT_ID], - eth_src=self.__switch_info[dev_name][MAC], - eth_dst=self.__int_collector_mac, + rules += rules_set_up_next_hashed( + port_id=port_id, + next_id=next_id, action=action ) rules += rules_set_up_routing( - ipv4_dst=self.__int_collector_ip, + port_id=port_id, + ipv4_dst=collector_ip_dst, ipv4_prefix_len=32, - egress_port=self.__switch_info[dev_name][PORT_INT][PORT_ID], + next_id=next_id, action=action ) except Exception as ex: @@ -587,13 +598,14 @@ class P4FabricINTServiceHandler(_ServiceHandler): return # Start the INT collector - c_id = None + c_uuid = None try: telemetry_frontend_client = TelemetryFrontendClient() c_id: CollectorId = telemetry_frontend_client.StartCollector(collect_int) # type: ignore - assert c_id.collector_id.uuid, "INT collector failed to start" + c_uuid = c_id.collector_id.uuid + assert c_uuid, "INT collector failed to start" except Exception as ex: LOGGER.error(f"INT collector cannot be initialized: Failed to start the collector {ex}") return - LOGGER.info(f"INT collector with ID {c_id.collector_id.uuid} is successfully invoked") + LOGGER.info(f"INT collector with ID {c_uuid} is successfully invoked") diff --git a/src/service/service/service_handlers/p4_fabric_tna_l2_simple/p4_fabric_tna_l2_simple_config.py b/src/service/service/service_handlers/p4_fabric_tna_l2_simple/p4_fabric_tna_l2_simple_config.py deleted file mode 100644 index 0fd1b7101f7f74809c118b6a2cee3e7c265e6c03..0000000000000000000000000000000000000000 --- a/src/service/service/service_handlers/p4_fabric_tna_l2_simple/p4_fabric_tna_l2_simple_config.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Common objects and methods for L2 forwarding based on the SD-Fabric dataplane model. -This dataplane covers both software based and hardware-based Stratum-enabled P4 switches, -such as the BMv2 software switch and Intel's Tofino/Tofino-2 switches. - -SD-Fabric repo: https://github.com/stratum/fabric-tna -SD-Fabric docs: https://docs.sd-fabric.org/master/index.html -""" - -from common.proto.context_pb2 import ConfigActionEnum - -from service.service.service_handlers.p4_fabric_tna_commons.p4_fabric_tna_commons import * - -# L2 simple service handler settings -FORWARDING_LIST = "fwd_list" -HOST_MAC = "host_mac" - -def rules_set_up_port_host( - port : int, - vlan_id : int, - action : ConfigActionEnum, # type: ignore - fwd_type=FORWARDING_TYPE_BRIDGING, - eth_type=ETHER_TYPE_IPV4): - # This is a host facing port - port_type = PORT_TYPE_HOST - - return rules_set_up_port( - port=port, - port_type=port_type, - fwd_type=fwd_type, - vlan_id=vlan_id, - action=action, - eth_type=eth_type - ) - -def rules_set_up_port_switch( - port : int, - vlan_id : int, - action : ConfigActionEnum, # type: ignore - fwd_type=FORWARDING_TYPE_BRIDGING, - eth_type=ETHER_TYPE_IPV4): - # This is a switch facing port - port_type = PORT_TYPE_SWITCH - - return rules_set_up_port( - port=port, - port_type=port_type, - fwd_type=fwd_type, - vlan_id=vlan_id, - action=action, - eth_type=eth_type - ) diff --git a/src/service/service/service_handlers/p4_fabric_tna_l2_simple/p4_fabric_tna_l2_simple_service_handler.py b/src/service/service/service_handlers/p4_fabric_tna_l2_simple/p4_fabric_tna_l2_simple_service_handler.py index aa8bd6209dec3be2dee3b7146a61f926e3d2f7c8..1c1818e56462715c2faeac588d308df97dae41ce 100644 --- a/src/service/service/service_handlers/p4_fabric_tna_l2_simple/p4_fabric_tna_l2_simple_service_handler.py +++ b/src/service/service/service_handlers/p4_fabric_tna_l2_simple/p4_fabric_tna_l2_simple_service_handler.py @@ -28,8 +28,6 @@ from service.service.service_handler_api.SettingsHandler import SettingsHandler from service.service.service_handlers.p4_fabric_tna_commons.p4_fabric_tna_commons import * from service.service.task_scheduler.TaskExecutor import TaskExecutor -from .p4_fabric_tna_l2_simple_config import * - LOGGER = logging.getLogger(__name__) METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'p4_fabric_tna_l2_simple'}) @@ -411,6 +409,9 @@ class P4FabricL2SimpleServiceHandler(_ServiceHandler): def _create_rules(self, device_obj : Device, port_id : int, action : ConfigActionEnum): # type: ignore dev_name = device_obj.name + # TODO: Fix + next_id = 2 if port_id == 1 else 1 + host_facing_port = self._is_host_facing_port(dev_name, port_id) LOGGER.info(f"\t | Service endpoint is host facing: {"True" if host_facing_port else "False"}") @@ -434,29 +435,46 @@ class P4FabricL2SimpleServiceHandler(_ServiceHandler): LOGGER.error("Error while creating port setup rules") raise Exception(ex) - fwd_list = self._get_fwd_list_of_switch_port(switch_name=dev_name, port_id=port_id) - for mac in fwd_list: - LOGGER.info(f"Switch {dev_name} - Port {port_id} - Creating rule for host MAC: {mac}") + fwd_list = self._get_fwd_list_of_switch_port(switch_name=dev_name, port_id=next_id) + for mac_dst in fwd_list: + LOGGER.info(f"Switch {dev_name} - Port {port_id} - Creating rule for destination MAC: {mac_dst}") try: ### Bridging rules rules += rules_set_up_fwd_bridging( + port_id=port_id, vlan_id=self._get_vlan_id_of_switch_port(switch_name=dev_name, port_id=port_id), - eth_dst=mac, - egress_port=port_id, + eth_dst=mac_dst, + next_id=next_id, action=action ) except Exception as ex: - LOGGER.error("Error while creating bridging rules") + LOGGER.error("Error while creating rule for bridging") raise Exception(ex) - try: - ### Next output rule - rules += rules_set_up_next_output_simple( - egress_port=port_id, - action=action - ) - except Exception as ex: - LOGGER.error("Error while creating next output L2 rules") - raise Exception(ex) + src_list = self._get_fwd_list_of_switch_port(switch_name=dev_name, port_id=port_id) + for mac_src in src_list: + try: + ### Next profile for hashed routing + rules += rules_set_up_next_profile_hashed_routing( + port_id=port_id, + next_id=next_id, + eth_src=mac_src, + eth_dst=mac_dst, + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating rule for next hashed profile") + raise Exception(ex) + + try: + ### Next hashed port + rules += rules_set_up_next_hashed( + port_id=port_id, + next_id=next_id, + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating rule for next hashed port") + raise Exception(ex) return rules diff --git a/src/service/service/service_handlers/p4_fabric_tna_l3/p4_fabric_tna_l3_service_handler.py b/src/service/service/service_handlers/p4_fabric_tna_l3/p4_fabric_tna_l3_service_handler.py index 4f6927829a20defb0fd02fcadf03794f64fa6794..730c7c79058c6b50f95a609fde8a12d451cf0004 100644 --- a/src/service/service/service_handlers/p4_fabric_tna_l3/p4_fabric_tna_l3_service_handler.py +++ b/src/service/service/service_handlers/p4_fabric_tna_l3/p4_fabric_tna_l3_service_handler.py @@ -341,6 +341,8 @@ class P4FabricL3ServiceHandler(_ServiceHandler): assert port_id >= 0, f"Switch {switch_name} - Invalid P4 switch port ID" port_type = port[PORT_TYPE] assert port_type in PORT_TYPES_STR_VALID, f"Switch {switch_name} - Valid P4 switch port types are: {','.join(PORT_TYPES_STR_VALID)}" + vlan_id = port[VLAN_ID] + assert chk_vlan_id(vlan_id), f"Switch {switch_name} - Invalid VLAN ID for port {port_id}" if switch_name not in self.__port_map: self.__port_map[switch_name] = {} @@ -349,6 +351,7 @@ class P4FabricL3ServiceHandler(_ServiceHandler): self.__port_map[switch_name][port_key] = {} self.__port_map[switch_name][port_key][PORT_ID] = port_id self.__port_map[switch_name][port_key][PORT_TYPE] = port_type + self.__port_map[switch_name][port_key][VLAN_ID] = vlan_id self.__port_map[switch_name][port_key][ROUTING_LIST] = [] # Routing list @@ -401,29 +404,97 @@ class P4FabricL3ServiceHandler(_ServiceHandler): return switch_entry[port_key] + def _get_port_type_of_switch_port(self, switch_name : str, port_id : int) -> str: + switch_port_entry = self._get_switch_port_in_port_map(switch_name, port_id) + return switch_port_entry[PORT_TYPE] + + def _get_vlan_id_of_switch_port(self, switch_name : str, port_id : int) -> int: + switch_port_entry = self._get_switch_port_in_port_map(switch_name, port_id) + return switch_port_entry[VLAN_ID] + def _get_routing_list_of_switch_port(self, switch_name : str, port_id : int) -> List [Tuple]: switch_port_entry = self._get_switch_port_in_port_map(switch_name, port_id) return switch_port_entry[ROUTING_LIST] + def _is_host_facing_port(self, switch_name : str, port_id : int) -> bool: + return self._get_port_type_of_switch_port(switch_name, port_id) == PORT_TYPE_HOST + def _create_rules(self, device_obj : Device, port_id : int, action : ConfigActionEnum): # type: ignore dev_name = device_obj.name + # TODO: Fix + next_id = 2 if port_id ==1 else 1 + + host_facing_port = self._is_host_facing_port(dev_name, port_id) + LOGGER.info(f"\t | Service endpoint is host facing: {"True" if host_facing_port else "False"}") + rules = [] + try: + ### Port setup rules + if host_facing_port: + rules += rules_set_up_port_host( + port=port_id, + vlan_id=self._get_vlan_id_of_switch_port(switch_name=dev_name, port_id=port_id), + action=action + ) + else: + rules += rules_set_up_port_switch( + port=port_id, + vlan_id=self._get_vlan_id_of_switch_port(switch_name=dev_name, port_id=port_id), + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating port setup rules") + raise Exception(ex) + ### Static routing rules - routing_list = self._get_routing_list_of_switch_port(switch_name=dev_name, port_id=port_id) + routing_list = self._get_routing_list_of_switch_port(switch_name=dev_name, port_id=next_id) for rt_entry in routing_list: try: - rules += rules_set_up_next_routing_simple( - egress_port=port_id, + ### Bridging rules + rules += rules_set_up_fwd_bridging( + port_id=port_id, + vlan_id=self._get_vlan_id_of_switch_port(switch_name=dev_name, port_id=port_id), + eth_dst=rt_entry[MAC_DST], + next_id=next_id, + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating rule for bridging") + raise Exception(ex) + + try: + ### Next profile for hashed routing + rules += rules_set_up_next_profile_hashed_routing( + port_id=port_id, + next_id=next_id, eth_src=rt_entry[MAC_SRC], eth_dst=rt_entry[MAC_DST], action=action ) + except Exception as ex: + LOGGER.error("Error while creating rule for next hashed profile") + raise Exception(ex) + + try: + ### Next hashed port + rules += rules_set_up_next_hashed( + port_id=port_id, + next_id=next_id, + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating rule for next hashed port") + raise Exception(ex) + + try: + # Routing destination rules += rules_set_up_routing( + port_id=port_id, ipv4_dst=rt_entry[IPV4_DST], ipv4_prefix_len=rt_entry[IPV4_PREFIX_LEN], - egress_port=port_id, + next_id=next_id, action=action ) except Exception as ex: diff --git a/src/service/service/service_handlers/p4_fabric_tna_upf/__init__.py b/src/service/service/service_handlers/p4_fabric_tna_upf/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..023830645e0fcb60e3f8583674a954810af222f2 --- /dev/null +++ b/src/service/service/service_handlers/p4_fabric_tna_upf/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/src/service/service/service_handlers/p4_fabric_tna_upf/p4_fabric_tna_upf_config.py b/src/service/service/service_handlers/p4_fabric_tna_upf/p4_fabric_tna_upf_config.py new file mode 100644 index 0000000000000000000000000000000000000000..faea1d3d692a86fd31b726ee96c3d929ef5c2b71 --- /dev/null +++ b/src/service/service/service_handlers/p4_fabric_tna_upf/p4_fabric_tna_upf_config.py @@ -0,0 +1,540 @@ +# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Common objects and methods for 5G User Plane Function (UPF) offloading in P4 +based on the SD-Fabric dataplane model. +This dataplane covers both software based and hardware-based Stratum-enabled P4 switches, +such as the BMv2 software switch and Intel's Tofino/Tofino-2 switches. + +SD-Fabric repo: https://github.com/stratum/fabric-tna +SD-Fabric docs: https://docs.sd-fabric.org/master/index.html +""" + +from typing import List, Tuple +from common.proto.context_pb2 import ConfigActionEnum +from common.tools.object_factory.ConfigRule import json_config_rule +from common.type_checkers.Checkers import chk_address_ipv4, chk_prefix_len_ipv4, chk_transport_port + +from service.service.service_handlers.p4_fabric_tna_commons.p4_fabric_tna_commons import * + +# UPF service handler settings +UPF = "upf" +UPLINK_PORT = "uplink_port" +DOWNLINK_PORT = "downlink_port" +UPLINK_IP = "uplink_ip" +DOWNLINK_IP = "downlink_ip" +UPLINK_MAC = "uplink_mac" +DOWNLINK_MAC = "downlink_mac" +TEID = "teid" +SLICE_ID = "slice_id" +APP_ID = "app_id" +APP_METER_ID = "app_meter_id" +CTR_ID = "ctr_id" +TC_ID = "tc_id" +TUNNEL_PEER_ID = "tunnel_peer_id" +GNB = "gnb" +DATA_NETWORK = "data_network" +UE_LIST = "ue_list" +UE_ID = "ue_id" +UE_IP = "ue_ip" +PDU_LIST = "pdu_sessions" +QOS_FLOWS = "qos_flows" +PDU_SESSION_ID = "pdu_session_id" +DNN = "dnn" +PDU_SESSION_TYPE = "pdu_session_type" +GTPU_TUNNEL = "gtpu_tunnel" +UPLINK = "uplink" +DOWNLINK = "downlink" +SRC = "src" +DST = "dst" +QFI = "qfi" +FIVEQI = "5qi" +QOS_TYPE = "qos_type" +QOS_DESC = "qos_desc" + +# Tables +TABLE_UPF_INTERFACES = "FabricIngress.upf.interfaces" +TABLE_UPF_UL_SESSIONS = "FabricIngress.upf.uplink_sessions" +TABLE_UPF_UL_TERM = "FabricIngress.upf.uplink_terminations" +TABLE_UPF_UL_RECIRC_RULES = "FabricIngress.upf.uplink_recirc_rules" # No need for recirculation +TABLE_UPF_DL_SESSIONS = "FabricIngress.upf.downlink_sessions" +TABLE_UPF_DL_TERM = "FabricIngress.upf.downlink_terminations" +TABLE_UPF_DL_IG_TUN_PEERS = "FabricIngress.upf.ig_tunnel_peers" +TABLE_UPF_DL_EG_TUN_PEERS = "FabricEgress.upf.eg_tunnel_peers" +TABLE_UPF_DL_GTPU_ENCAP = "FabricEgress.upf.gtpu_encap" # This table has no key, thus auto-applies actions + +TABLE_QOS_SLICE_TC = "FabricIngress.qos.set_slice_tc" # This table is accessed automatically (no rule applied) +TABLE_QOS_DEF_TC = "FabricIngress.qos.default_tc" # Miss. No QoS applied so far +TABLE_QOS_QUEUES = "FabricIngress.qos.queues" # Miss. No QoS applied so far + +# UPF settings +GTP_PORT = 2152 + +GTPU_VALID = 1 +GTPU_INVALID = 0 + +## Default values +DEF_APP_ID = 0 +DEF_APP_METER_ID = 0 +DEF_CTR_ID = 401 +DEF_SLICE_ID = 0 +DEF_TC_ID = 3 +DEF_TEID = 1 +DEF_TUN_PEER_ID = 1 +DEF_SESSION_METER_ID = 0 +DEF_QFI = 0 + +# 5QI +FIVEQI_NON_GBR = 9 +FIVEQI_GBR = 1 +FIVEQI_DELAY_CRITICAL_GBR = 82 + +# QoS +QOS_TYPE_NON_GBR = "Non-GBR" +QOS_TYPE_GBR = "GBR" +QOS_TYPE_DELAY_CRITICAL_GBR = "Delay-Critical GBR" +QOS_TYPES_STR_VALID = [QOS_TYPE_NON_GBR, QOS_TYPE_GBR, QOS_TYPE_DELAY_CRITICAL_GBR] + +QOS_TYPE_TO_5QI_MAP = { + QOS_TYPE_NON_GBR: FIVEQI_NON_GBR, + QOS_TYPE_GBR: FIVEQI_GBR, + QOS_TYPE_DELAY_CRITICAL_GBR: FIVEQI_DELAY_CRITICAL_GBR +} + +QOS_TYPE_TO_DESC_MAP = { + QOS_TYPE_NON_GBR: "Best effort", + QOS_TYPE_GBR: "Low latency", + QOS_TYPE_DELAY_CRITICAL_GBR: "Ultra-low latency" +} + + +def rules_set_up_upf_interface( + port_id : int, + ipv4_dst : str, + ipv4_prefix_len : int, + gtpu_value : int, + slice_id : int, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert chk_address_ipv4(ipv4_dst), "Invalid destination IPv4 address to configure UPF interface" + assert chk_prefix_len_ipv4(ipv4_prefix_len), "Invalid destination IPv4 address prefix length to configure UPF interface" + assert gtpu_value >= 0, "Invalid slice identifier to configure UPF interface" + assert slice_id >= 0, "Invalid slice identifier to configure UPF interface" + + action_name = None + + if gtpu_value == GTPU_VALID: # Packet carries a GTP header (UL packet) + action_name = "FabricIngress.upf.iface_access" + else: # Packet does not carry a GTP header (DL packet) + action_name = "FabricIngress.upf.iface_core" + + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure UPF interface") + LOGGER.info(f"================== Port ID {port_id}") + LOGGER.info(f"================== IPv4 dst {ipv4_dst}") + LOGGER.info(f"================== IPv4 prefix {ipv4_prefix_len}") + LOGGER.info(f"================== GTP-U value {gtpu_value}") + LOGGER.info(f"================== Slice ID {slice_id}") + LOGGER.info(f"================== Action {action_name}") + + rule_no = cache_rule(TABLE_UPF_INTERFACES, action) + + rules_upf_interfaces = [] + rules_upf_interfaces.append( + json_config_rule( + action, + '/tables/table/'+TABLE_UPF_INTERFACES+'['+str(rule_no)+']', + { + 'table-name': TABLE_UPF_INTERFACES, + 'match-fields': [ + { + 'match-field': 'ipv4_dst_addr', + 'match-value': ipv4_dst + "/" + str(ipv4_prefix_len) + }, + { + 'match-field': 'gtpu_is_valid', + 'match-value': str(gtpu_value) + } + ], + 'action-name': action_name, + 'action-params': [ + { + 'action-param': 'slice_id', + 'action-value': str(slice_id) + } + ], + 'priority': 0 + } + ) + ) + + return rules_upf_interfaces + +################################### +### A. Uplink (UL) setup +################################### + +def rules_set_up_upf_uplink_sessions( + port_id : int, + tun_ip_address : str, + teid : int, + session_meter_id : int, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert chk_address_ipv4(tun_ip_address), "Invalid tunnel IPv4 address to configure UPF uplink session" + assert teid >= 0, "Invalid tunnel endpoint identifier (TEID) to configure UPF uplink session" + assert session_meter_id >= 0, "Invalid session meter identifier to configure UPF uplink session" + + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure UL session") + LOGGER.info(f"================== Port ID {port_id}") + LOGGER.info(f"================== Tunnel IP {tun_ip_address}") + LOGGER.info(f"================== TEID {teid}") + LOGGER.info(f"================== Session meter ID {session_meter_id}") + + rule_no = cache_rule(TABLE_UPF_UL_SESSIONS, action) + + rules_upf_ul_session = [] + rules_upf_ul_session.append( + json_config_rule( + action, + '/tables/table/'+TABLE_UPF_UL_SESSIONS+'['+str(rule_no)+']', + { + 'table-name': TABLE_UPF_UL_SESSIONS, + 'match-fields': [ + { + 'match-field': 'tunnel_ipv4_dst', + 'match-value': tun_ip_address + }, + { + 'match-field': 'teid', + 'match-value': str(teid) + } + ], + 'action-name': "FabricIngress.upf.set_uplink_session", + 'action-params': [ + { + 'action-param': 'session_meter_idx', + 'action-value': str(session_meter_id) + } + ], + 'priority': 0 + } + ) + ) + + return rules_upf_ul_session + +def rules_set_up_upf_uplink_terminations( + port_id : int, + ue_session_id : str, + app_id : int, + ctr_id : int, + app_meter_id : int, + tc_id : int, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert chk_address_ipv4(ue_session_id), "Invalid UE IPv4 address (UE session ID) to configure UPF uplink termination" + assert app_id >= 0, "Invalid application identifier to configure UPF uplink termination" + assert ctr_id >= 0, "Invalid ctr identifier to configure UPF uplink termination" + assert app_meter_id >= 0, "Invalid app meter identifier to configure UPF uplink termination" + assert tc_id >= 0, "Invalid tc identifier to configure UPF uplink termination" + + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure UL termination") + LOGGER.info(f"================== Port ID {port_id}") + LOGGER.info(f"================== UE session ID {ue_session_id}") + LOGGER.info(f"================== App ID {app_id}") + LOGGER.info(f"================== Ctr ID {ctr_id}") + LOGGER.info(f"================== App meter ID {app_meter_id}") + LOGGER.info(f"================== TC ID {tc_id}") + + rule_no = cache_rule(TABLE_UPF_UL_TERM, action) + + rules_upf_ul_termination = [] + rules_upf_ul_termination.append( + json_config_rule( + action, + '/tables/table/'+TABLE_UPF_UL_TERM+'['+str(rule_no)+']', + { + 'table-name': TABLE_UPF_UL_TERM, + 'match-fields': [ + { + 'match-field': 'ue_session_id', + 'match-value': ue_session_id + }, + { + 'match-field': 'app_id', + 'match-value': str(app_id) + } + ], + 'action-name': "FabricIngress.upf.app_fwd", + 'action-params': [ + { + 'action-param': 'ctr_id', + 'action-value': str(ctr_id) + }, + { + 'action-param': 'app_meter_idx', + 'action-value': str(app_meter_id) + }, + { + 'action-param': 'tc', + 'action-value': str(tc_id) + } + ], + 'priority': 0 + } + ) + ) + + return rules_upf_ul_termination + +################################### +### A. End of Uplink (UL) setup +################################### + +################################### +### B. Downlink (DL) setup +################################### + +def rules_set_up_upf_downlink_sessions( + port_id : int, + ipv4_dst : str, + session_meter_id : int, + tun_peer_id : int, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert chk_address_ipv4(ipv4_dst), "Invalid destination IPv4 address to configure UPF downlink session" + assert session_meter_id >= 0, "Invalid session meter identifier to configure UPF downlink session" + assert tun_peer_id >= 0, "Invalid tunnel peer identifier to configure UPF downlink session" + + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure DL session") + LOGGER.info(f"================== Port ID {port_id}") + LOGGER.info(f"================== IP dst {ipv4_dst}") + LOGGER.info(f"================== Session meter ID {session_meter_id}") + LOGGER.info(f"================== Tunnel peer ID {tun_peer_id}") + + rule_no = cache_rule(TABLE_UPF_DL_SESSIONS, action) + + rules_upf_dl_session = [] + rules_upf_dl_session.append( + json_config_rule( + action, + '/tables/table/'+TABLE_UPF_DL_SESSIONS+'['+str(rule_no)+']', + { + 'table-name': TABLE_UPF_DL_SESSIONS, + 'match-fields': [ + { + 'match-field': 'ue_addr', + 'match-value': ipv4_dst + } + ], + 'action-name': "FabricIngress.upf.set_downlink_session", + 'action-params': [ + { + 'action-param': 'session_meter_idx', + 'action-value': str(session_meter_id) + }, + { + 'action-param': 'tun_peer_id', + 'action-value': str(tun_peer_id) + } + ], + 'priority': 0 + } + ) + ) + + return rules_upf_dl_session + +def rules_set_up_upf_downlink_terminations( + port_id : int, + ue_session_id : str, + app_id : int, + ctr_id : int, + app_meter_id : int, + tc_id : int, + teid : int, + qfi : int, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert chk_address_ipv4(ue_session_id), "Invalid UE IPv4 address (UE session ID) to configure UPF downlink termination" + assert app_id >= 0, "Invalid application identifier to configure downlink termination" + assert ctr_id >= 0, "Invalid ctr identifier to configure UPF downlink termination" + assert app_meter_id >= 0, "Invalid app meter identifier to configure UPF downlink termination" + assert tc_id >= 0, "Invalid tc identifier to configure UPF downlink termination" + assert teid >= 0, "Invalid tunnel endpoint identifier (TEID) to configure UPF downlink termination" + assert qfi >= 0, "Invalid QoS flow identifier (QFI) to configure UPF downlink termination" + + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure DL termination") + LOGGER.info(f"================== Port ID {port_id}") + LOGGER.info(f"================== UE session ID {ue_session_id}") + LOGGER.info(f"================== App ID {app_id}") + LOGGER.info(f"================== Ctr ID {ctr_id}") + LOGGER.info(f"================== App meter ID {app_meter_id}") + LOGGER.info(f"================== TC ID {tc_id}") + LOGGER.info(f"================== TEID {teid}") + LOGGER.info(f"================== QFI {qfi}") + + rule_no = cache_rule(TABLE_UPF_DL_TERM, action) + + rules_upf_dl_termination = [] + rules_upf_dl_termination.append( + json_config_rule( + action, + '/tables/table/'+TABLE_UPF_DL_TERM+'['+str(rule_no)+']', + { + 'table-name': TABLE_UPF_DL_TERM, + 'match-fields': [ + { + 'match-field': 'ue_session_id', + 'match-value': ue_session_id + }, + { + 'match-field': 'app_id', + 'match-value': str(app_id) + } + ], + 'action-name': "FabricIngress.upf.downlink_fwd_encap", + 'action-params': [ + { + 'action-param': 'ctr_id', + 'action-value': str(ctr_id) + }, + { + 'action-param': 'app_meter_idx', + 'action-value': str(app_meter_id) + }, + { + 'action-param': 'tc', + 'action-value': str(tc_id) + }, + { + 'action-param': 'teid', + 'action-value': str(teid) + }, + { + 'action-param': 'qfi', + 'action-value': str(qfi) + } + ], + 'priority': 0 + } + ) + ) + + return rules_upf_dl_termination + +def rules_set_up_upf_downlink_ig_tunnel_peers( + port_id : int, + tun_peer_id : int, + tun_dst_addr : str, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert tun_peer_id >= 0, "Invalid tunnel peer identifier to configure UPF downlink ingress tunnel peers" + assert chk_address_ipv4(tun_dst_addr), "Invalid tunnel destination IPv4 address to configure UPF downlink ingress tunnel peers" + + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure IG tunnel peer") + LOGGER.info(f"================== Port ID {port_id}") + LOGGER.info(f"================== Tunnel peer ID {tun_peer_id}") + LOGGER.info(f"================== Tunnel IP dst {tun_dst_addr}") + + rule_no = cache_rule(TABLE_UPF_DL_IG_TUN_PEERS, action) + + rules_upf_dl_ig_tun_peers = [] + rules_upf_dl_ig_tun_peers.append( + json_config_rule( + action, + '/tables/table/'+TABLE_UPF_DL_IG_TUN_PEERS+'['+str(rule_no)+']', + { + 'table-name': TABLE_UPF_DL_IG_TUN_PEERS, + 'match-fields': [ + { + 'match-field': 'tun_peer_id', + 'match-value': str(tun_peer_id) + } + ], + 'action-name': "FabricIngress.upf.set_routing_ipv4_dst", + 'action-params': [ + { + 'action-param': 'tun_dst_addr', + 'action-value': tun_dst_addr + } + ], + 'priority': 0 + } + ) + ) + + return rules_upf_dl_ig_tun_peers + +def rules_set_up_upf_downlink_eg_tunnel_peers( + port_id : int, + tun_peer_id : int, + tun_src_addr : str, + tun_dst_addr : str, + tun_src_port : int, + action : ConfigActionEnum) -> List [Tuple]: # type: ignore + assert tun_peer_id >= 0, "Invalid tunnel peer identifier to configure UPF downlink egress tunnel peers" + assert chk_address_ipv4(tun_src_addr), "Invalid tunnel source IPv4 address to configure UPF downlink egress tunnel peers" + assert chk_address_ipv4(tun_dst_addr), "Invalid tunnel destination IPv4 address to configure UPF downlink egress tunnel peers" + assert chk_transport_port(tun_src_port), "Invalid tunnel source transport port to configure UPF downlink egress tunnel peers" + + LOGGER.info("==================================================================================") + LOGGER.info("================== About to configure EG tunnel peer") + LOGGER.info(f"================== Port ID {port_id}") + LOGGER.info(f"================== Tunnel peer ID {tun_peer_id}") + LOGGER.info(f"================== Tunnel IP src {tun_src_addr}") + LOGGER.info(f"================== Tunnel IP dst {tun_dst_addr}") + LOGGER.info(f"================== Tunnel Port src {tun_src_port}") + + rule_no = cache_rule(TABLE_UPF_DL_EG_TUN_PEERS, action) + + rules_upf_dl_eg_tun_peers = [] + rules_upf_dl_eg_tun_peers.append( + json_config_rule( + action, + '/tables/table/'+TABLE_UPF_DL_EG_TUN_PEERS+'['+str(rule_no)+']', + { + 'table-name': TABLE_UPF_DL_EG_TUN_PEERS, + 'match-fields': [ + { + 'match-field': 'tun_peer_id', + 'match-value': str(tun_peer_id) + } + ], + 'action-name': "FabricEgress.upf.load_tunnel_params", + 'action-params': [ + { + 'action-param': 'tunnel_src_addr', + 'action-value': tun_src_addr + }, + { + 'action-param': 'tunnel_dst_addr', + 'action-value': tun_dst_addr + }, + { + 'action-param': 'tunnel_src_port', + 'action-value': str(tun_src_port) + } + ], + 'priority': 0 + } + ) + ) + + return rules_upf_dl_eg_tun_peers + +################################### +### B. End of Downlink (DL) setup +################################### diff --git a/src/service/service/service_handlers/p4_fabric_tna_upf/p4_fabric_tna_upf_service_handler.py b/src/service/service/service_handlers/p4_fabric_tna_upf/p4_fabric_tna_upf_service_handler.py new file mode 100644 index 0000000000000000000000000000000000000000..f9d4ca20fb81235f728e21aec017ba95119884e0 --- /dev/null +++ b/src/service/service/service_handlers/p4_fabric_tna_upf/p4_fabric_tna_upf_service_handler.py @@ -0,0 +1,946 @@ +# Copyright 2022-2024 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Service handler for P4-based UPF offloading using the SD-Fabric P4 dataplane +for BMv2 and Intel Tofino switches. +""" + +import logging +from typing import Any, List, Dict, Optional, Tuple, Union +from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method +from common.proto.context_pb2 import ConfigActionEnum, DeviceId, Service, Device +from common.tools.object_factory.Device import json_device_id +from common.type_checkers.Checkers import chk_type, chk_address_mac, chk_address_ipv4, chk_prefix_len_ipv4 +from service.service.service_handler_api._ServiceHandler import _ServiceHandler +from service.service.service_handler_api.SettingsHandler import SettingsHandler +from service.service.service_handlers.p4_fabric_tna_commons.p4_fabric_tna_commons import * +from service.service.task_scheduler.TaskExecutor import TaskExecutor + +from .p4_fabric_tna_upf_config import * + +LOGGER = logging.getLogger(__name__) + +METRICS_POOL = MetricsPool('Service', 'Handler', labels={'handler': 'p4_fabric_tna_upf'}) + +class P4FabricUPFServiceHandler(_ServiceHandler): + def __init__( # pylint: disable=super-init-not-called + self, service : Service, task_executor : TaskExecutor, **settings # type: ignore + ) -> None: + """ Initialize Driver. + Parameters: + service + The service instance (gRPC message) to be managed. + task_executor + An instance of Task Executor providing access to the + service handlers factory, the context and device clients, + and an internal cache of already-loaded gRPC entities. + **settings + Extra settings required by the service handler. + + """ + self.__service_label = "P4 UPF offloading service" + self.__service = service + self.__task_executor = task_executor + self.__settings_handler = SettingsHandler(self.__service.service_config, **settings) + + self._init_settings() + self._parse_settings() + self._print_settings() + + @metered_subclass_method(METRICS_POOL) + def SetEndpoint( + self, endpoints : List[Tuple[str, str, Optional[str]]], + connection_uuid : Optional[str] = None + ) -> List[Union[bool, Exception]]: + """ Create/Update service endpoints from a list. + Parameters: + endpoints: List[Tuple[str, str, Optional[str]]] + List of tuples, each containing a device_uuid, + endpoint_uuid and, optionally, the topology_uuid + of the endpoint to be added. + connection_uuid : Optional[str] + If specified, is the UUID of the connection this endpoint is associated to. + Returns: + results: List[Union[bool, Exception]] + List of results for endpoint changes requested. + Return values must be in the same order as the requested + endpoints. If an endpoint is properly added, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('endpoints', endpoints, list) + if len(endpoints) == 0: return [] + + LOGGER.info(f"{self.__service_label} - Provision service configuration") + + visited = set() + results = [] + for endpoint in endpoints: + device_uuid, endpoint_uuid = endpoint[0:2] + device = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) + device_name = device.name + + LOGGER.info(f"Device {device_name}") + LOGGER.info(f"\t | Service endpoint UUID: {endpoint_uuid}") + + port_id = find_port_id_in_endpoint_list(device.device_endpoints, endpoint_uuid) + LOGGER.info(f"\t | Service port ID: {port_id}") + + dev_port_key = device_name + "-" + PORT_PREFIX + str(port_id) + + # Skip already visited device ports + if dev_port_key in visited: + continue + + # Skip non-dataplane ports + if port_id not in [self.__upf[UPLINK_PORT], self.__upf[DOWNLINK_PORT]]: + LOGGER.info(f"\t | Port ID {port_id} is not an UL or DP port; skipping...") + continue + + rules = [] + actual_rules = -1 + applied_rules, failed_rules = 0, -1 + label = "" + + # Create and apply rules + try: + # Uplink (UL) rules + if port_id == self.__upf[UPLINK_PORT]: + rules = self._create_rules_uplink( + device_obj=device, + port_id=port_id, + next_id=self.__upf[DOWNLINK_PORT], + action=ConfigActionEnum.CONFIGACTION_SET) + label = "uplink (UL)" + # Downlink (DL) rules + elif port_id == self.__upf[DOWNLINK_PORT]: + rules = self._create_rules_downlink( + device_obj=device, + port_id=port_id, + next_id=self.__upf[UPLINK_PORT], + action=ConfigActionEnum.CONFIGACTION_SET) + label = "downlink (DL)" + actual_rules = len(rules) + applied_rules, failed_rules = apply_rules( + task_executor=self.__task_executor, + device_obj=device, + json_config_rules=rules + ) + except Exception as ex: + LOGGER.error(f"Failed to insert {label} UPF rules on device {device.name} due to {ex}") + results.append(ex) + finally: + rules.clear() + + # Ensure correct status + if (failed_rules == 0) and (applied_rules == actual_rules): + LOGGER.info(f"Installed {applied_rules}/{actual_rules} {label} UPF rules on device {device_name} and port {port_id}") + results.append(True) + + # You should no longer visit this device port again + visited.add(dev_port_key) + + return results + + @metered_subclass_method(METRICS_POOL) + def DeleteEndpoint( + self, endpoints : List[Tuple[str, str, Optional[str]]], + connection_uuid : Optional[str] = None + ) -> List[Union[bool, Exception]]: + """ Delete service endpoints from a list. + Parameters: + endpoints: List[Tuple[str, str, Optional[str]]] + List of tuples, each containing a device_uuid, + endpoint_uuid, and the topology_uuid of the endpoint + to be removed. + connection_uuid : Optional[str] + If specified, is the UUID of the connection this endpoint is associated to. + Returns: + results: List[Union[bool, Exception]] + List of results for endpoint deletions requested. + Return values must be in the same order as the requested + endpoints. If an endpoint is properly deleted, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('endpoints', endpoints, list) + if len(endpoints) == 0: return [] + + LOGGER.info(f"{self.__service_label} - Deprovision service configuration") + + visited = set() + results = [] + for endpoint in endpoints: + device_uuid, endpoint_uuid = endpoint[0:2] + device = self.__task_executor.get_device(DeviceId(**json_device_id(device_uuid))) + device_name = device.name + + LOGGER.info(f"Device {device_name}") + LOGGER.info(f"\t | Service endpoint UUID: {endpoint_uuid}") + + port_id = find_port_id_in_endpoint_list(device.device_endpoints, endpoint_uuid) + LOGGER.info(f"\t | Service port ID: {port_id}") + + dev_port_key = device_name + "-" + PORT_PREFIX + str(port_id) + + # Skip already visited device ports + if dev_port_key in visited: + continue + + # Skip non-dataplane ports + if port_id not in [self.__upf[UPLINK_PORT], self.__upf[DOWNLINK_PORT]]: + LOGGER.info(f"\t | Port ID {port_id} is not an UL or DP port; skipping...") + continue + + rules = [] + actual_rules = -1 + applied_rules, failed_rules = 0, -1 + label = "" + + # Create and apply rules + try: + # Uplink (UL) rules + if port_id == self.__upf[UPLINK_PORT]: + rules = self._create_rules_uplink( + device_obj=device, + port_id=port_id, + next_id=self.__upf[DOWNLINK_PORT], + action=ConfigActionEnum.CONFIGACTION_DELETE) + label = "uplink (UL)" + # Downlink (DL) rules + elif port_id == self.__upf[DOWNLINK_PORT]: + rules = self._create_rules_downlink( + device_obj=device, + port_id=port_id, + next_id=self.__upf[UPLINK_PORT], + action=ConfigActionEnum.CONFIGACTION_DELETE) + label = "downlink (DL)" + actual_rules = len(rules) + applied_rules, failed_rules = apply_rules( + task_executor=self.__task_executor, + device_obj=device, + json_config_rules=rules + ) + except Exception as ex: + LOGGER.error(f"Failed to delete {label} UPF rules from device {device.name} due to {ex}") + results.append(ex) + finally: + rules.clear() + + # Ensure correct status + if (failed_rules == 0) and (applied_rules == actual_rules): + LOGGER.info(f"Deleted {applied_rules}/{actual_rules} {label} UPF rules from device {device_name} and port {port_id}") + results.append(True) + + # You should no longer visit this device port again + visited.add(dev_port_key) + + return results + + @metered_subclass_method(METRICS_POOL) + def SetConstraint(self, constraints: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Create/Update service constraints. + Parameters: + constraints: List[Tuple[str, Any]] + List of tuples, each containing a constraint_type and the + new constraint_value to be set. + Returns: + results: List[Union[bool, Exception]] + List of results for constraint changes requested. + Return values must be in the same order as the requested + constraints. If a constraint is properly set, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('constraints', constraints, list) + if len(constraints) == 0: return [] + + msg = '[SetConstraint] Method not implemented. Constraints({:s}) are being ignored.' + LOGGER.warning(msg.format(str(constraints))) + return [True for _ in range(len(constraints))] + + @metered_subclass_method(METRICS_POOL) + def DeleteConstraint(self, constraints: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Delete service constraints. + Parameters: + constraints: List[Tuple[str, Any]] + List of tuples, each containing a constraint_type pointing + to the constraint to be deleted, and a constraint_value + containing possible additionally required values to locate + the constraint to be removed. + Returns: + results: List[Union[bool, Exception]] + List of results for constraint deletions requested. + Return values must be in the same order as the requested + constraints. If a constraint is properly deleted, True must + be returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('constraints', constraints, list) + if len(constraints) == 0: return [] + + msg = '[DeleteConstraint] Method not implemented. Constraints({:s}) are being ignored.' + LOGGER.warning(msg.format(str(constraints))) + return [True for _ in range(len(constraints))] + + @metered_subclass_method(METRICS_POOL) + def SetConfig(self, resources: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Create/Update configuration for a list of service resources. + Parameters: + resources: List[Tuple[str, Any]] + List of tuples, each containing a resource_key pointing to + the resource to be modified, and a resource_value + containing the new value to be set. + Returns: + results: List[Union[bool, Exception]] + List of results for resource key changes requested. + Return values must be in the same order as the requested + resource keys. If a resource is properly set, True must be + returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('resources', resources, list) + if len(resources) == 0: return [] + + msg = '[SetConfig] Method not implemented. Resources({:s}) are being ignored.' + LOGGER.warning(msg.format(str(resources))) + return [True for _ in range(len(resources))] + + @metered_subclass_method(METRICS_POOL) + def DeleteConfig(self, resources: List[Tuple[str, Any]]) \ + -> List[Union[bool, Exception]]: + """ Delete configuration for a list of service resources. + Parameters: + resources: List[Tuple[str, Any]] + List of tuples, each containing a resource_key pointing to + the resource to be modified, and a resource_value containing + possible additionally required values to locate the value + to be removed. + Returns: + results: List[Union[bool, Exception]] + List of results for resource key deletions requested. + Return values must be in the same order as the requested + resource keys. If a resource is properly deleted, True must + be returned; otherwise, the Exception that is raised during + the processing must be returned. + """ + chk_type('resources', resources, list) + if len(resources) == 0: return [] + + msg = '[DeleteConfig] Method not implemented. Resources({:s}) are being ignored.' + LOGGER.warning(msg.format(str(resources))) + return [True for _ in range(len(resources))] + + def _init_settings(self): + self.__switch_info = {} + self.__port_map = {} + self.__upf = {} + self.__gnb = {} + self.__ue_map = {} + + try: + self.__settings = self.__settings_handler.get('/settings') + LOGGER.info(f"{self.__service_label} with settings: {self.__settings}") + except Exception as ex: + LOGGER.error(f"Failed to retrieve service settings: {ex}") + raise Exception(ex) + + def _parse_settings(self): + try: + switch_info = self.__settings.value[SWITCH_INFO] + except Exception as ex: + LOGGER.error(f"Failed to parse service settings: {ex}") + raise Exception(ex) + assert isinstance(switch_info, list), "Switch info object must be a list" + + for switch in switch_info: + for switch_name, sw_info in switch.items(): + assert switch_name, "Invalid P4 switch name" + assert isinstance(sw_info, dict), \ + "Switch {} info must be a map with arch, dpid, port_list, fwd_list, routing_list, upf, gnb, and ue_list items)" + assert sw_info[ARCH] in SUPPORTED_TARGET_ARCH_LIST, \ + f"Switch {switch_name} - Supported P4 architectures are: {','.join(SUPPORTED_TARGET_ARCH_LIST)}" + switch_dpid = sw_info[DPID] + assert switch_dpid > 0, f"Switch {switch_name} - P4 switch dataplane ID {sw_info[DPID]} must be a positive integer" + + # Port list + port_list = sw_info[PORT_LIST] + assert isinstance(port_list, list),\ + f"Switch {switch_name} port list must be a list with port_id, port_type, and vlan_id items" + for port in port_list: + port_id = port[PORT_ID] + assert port_id >= 0, f"Switch {switch_name} - Invalid P4 switch port ID" + port_type = port[PORT_TYPE] + assert port_type in PORT_TYPES_STR_VALID, f"Switch {switch_name} - Valid P4 switch port types are: {','.join(PORT_TYPES_STR_VALID)}" + vlan_id = port[VLAN_ID] + assert chk_vlan_id(vlan_id), f"Switch {switch_name} - Invalid VLAN ID for port {port_id}" + + if switch_name not in self.__port_map: + self.__port_map[switch_name] = {} + port_key = PORT_PREFIX + str(port_id) + if port_key not in self.__port_map[switch_name]: + self.__port_map[switch_name][port_key] = {} + self.__port_map[switch_name][port_key][PORT_ID] = port_id + self.__port_map[switch_name][port_key][PORT_TYPE] = port_type + self.__port_map[switch_name][port_key][VLAN_ID] = vlan_id + self.__port_map[switch_name][port_key][FORWARDING_LIST] = [] + self.__port_map[switch_name][port_key][ROUTING_LIST] = [] + + # Forwarding list + fwd_list = sw_info[FORWARDING_LIST] + assert isinstance(fwd_list, list), f"Switch {switch_name} forwarding list must be a list" + for fwd_entry in fwd_list: + port_id = fwd_entry[PORT_ID] + assert port_id >= 0, f"Invalid port ID: {port_id}" + host_mac = fwd_entry[HOST_MAC] + assert chk_address_mac(host_mac), f"Invalid host MAC address {host_mac}" + host_label = "" + if HOST_LABEL in fwd_entry: + host_label = fwd_entry[HOST_LABEL] + + # Retrieve entry from the port map + switch_port_entry = self._get_switch_port_in_port_map(switch_name, port_id) + + host_facing_port = self._is_host_facing_port(switch_name, port_id) + LOGGER.info(f"Switch {switch_name} - Port {port_id}: Is host facing: {"True" if host_facing_port else "False"}") + switch_port_entry[FORWARDING_LIST].append( + { + HOST_MAC: host_mac, + HOST_LABEL: host_label + } + ) + + # Routing list + routing_list = sw_info[ROUTING_LIST] + assert isinstance(routing_list, list), f"Switch {switch_name} routing list must be a list" + for rt_entry in routing_list: + port_id = rt_entry[PORT_ID] + assert port_id >= 0, f"Invalid port ID: {port_id}" + ipv4_dst = rt_entry[IPV4_DST] + assert chk_address_ipv4(ipv4_dst), f"Invalid destination IPv4 address {ipv4_dst}" + ipv4_prefix_len = rt_entry[IPV4_PREFIX_LEN] + assert chk_prefix_len_ipv4(ipv4_prefix_len), f"Invalid IPv4 address prefix length {ipv4_prefix_len}" + mac_src = rt_entry[MAC_SRC] + assert chk_address_mac(mac_src), f"Invalid source MAC address {mac_src}" + mac_dst = rt_entry[MAC_DST] + assert chk_address_mac(mac_dst), f"Invalid destination MAC address {mac_dst}" + + # Retrieve entry from the port map + switch_port_entry = self._get_switch_port_in_port_map(switch_name, port_id) + + # Add routing entry + switch_port_entry[ROUTING_LIST].append( + { + PORT_ID: port_id, + IPV4_DST: ipv4_dst, + IPV4_PREFIX_LEN: ipv4_prefix_len, + MAC_SRC: mac_src, + MAC_DST: mac_dst + } + ) + + # UPF + upf = sw_info[UPF] + + self.__upf[UPLINK_PORT] = upf[UPLINK_PORT] + assert self.__upf[UPLINK_PORT] >= 0, f"Invalid uplink UPF port: {self.__upf[UPLINK_PORT]}" + + self.__upf[UPLINK_IP] = upf[UPLINK_IP] + assert chk_address_ipv4(self.__upf[UPLINK_IP]), f"Invalid uplink UPF IPv4 address {self.__upf[UPLINK_IP]}" + + self.__upf[UPLINK_MAC] = upf[UPLINK_MAC] + assert chk_address_mac(self.__upf[UPLINK_MAC]), f"Invalid uplink UPF MAC address {self.__upf[UPLINK_MAC]}" + + self.__upf[DOWNLINK_PORT] = upf[DOWNLINK_PORT] + assert self.__upf[DOWNLINK_PORT] >= 0, f"Invalid downlink UPF port: {self.__upf[DOWNLINK_PORT]}" + + self.__upf[DOWNLINK_IP] = upf[DOWNLINK_IP] + assert chk_address_ipv4(self.__upf[DOWNLINK_IP]), f"Invalid downlink UPF IPv4 address {self.__upf[DOWNLINK_IP]}" + + self.__upf[DOWNLINK_MAC] = upf[DOWNLINK_MAC] + assert chk_address_mac(self.__upf[DOWNLINK_MAC]), f"Invalid downlink UPF MAC address {self.__upf[DOWNLINK_MAC]}" + + slice_id = upf[SLICE_ID] + assert slice_id >= 0, "Slice ID must be a non-negative integer" + self.__upf[SLICE_ID] = slice_id + + teid = upf[TEID] + assert teid >= 0, "TEID must be a non-negative integer" + self.__upf[TEID] = teid + + app_id = upf[APP_ID] + assert app_id >= 0, "App ID must be a non-negative integer" + self.__upf[APP_ID] = app_id + + app_meter_id = upf[APP_METER_ID] + assert app_meter_id >= 0, "App meter ID must be a non-negative integer" + self.__upf[APP_METER_ID] = app_meter_id + + ctr_id = upf[CTR_ID] + assert ctr_id >= 0, "Ctr ID must be a non-negative integer" + self.__upf[CTR_ID] = ctr_id + + tc_id = upf[TC_ID] + assert tc_id >= 0, "TC ID must be a non-negative integer" + self.__upf[TC_ID] = tc_id + + tunnel_peer_id = upf[TUNNEL_PEER_ID] + assert tunnel_peer_id >= 0, "Tunnel peer ID must be a non-negative integer" + self.__upf[TUNNEL_PEER_ID] = tunnel_peer_id + + # gNB configuration + gnb = sw_info[GNB] + self.__gnb[IP] = gnb[IP] + assert chk_address_ipv4(self.__gnb[IP]), f"Invalid 5G gNB IPv4 address {self.__gnb[IP]}" + + self.__gnb[MAC] = gnb[MAC] + assert chk_address_mac(self.__gnb[MAC]), f"Invalid 5G gNB MAC address {self.__gnb[MAC]}" + + # UE list + ue_list = sw_info[UE_LIST] + assert isinstance(ue_list, list), f"Switch {switch_name} UE list must be a list" + for ue in ue_list: + ue_id = ue[UE_ID] + assert ue_id, f"Empty UE ID: {UE_ID}" + ue_ip = ue[UE_IP] + assert chk_address_ipv4(ue_ip), "Invalid UE IPv4 address" + self.__ue_map[ue_ip] = {} + self.__ue_map[ue_ip][UE_ID] = ue_id + self.__ue_map[ue_ip][UE_IP] = ue_ip + + # PDU list per UE + self.__ue_map[ue_ip][PDU_LIST] = [] + pdu_session_list = ue[PDU_LIST] + assert isinstance(pdu_session_list, list), f"UE {ue_id} PDU session list must be a list" + for pdu in pdu_session_list: + pdu_id = pdu[PDU_SESSION_ID] + assert pdu_id >= 0, "PDU ID must be a non-negative integer" + assert pdu_id == DEF_SESSION_METER_ID, "Better use PDU session ID = 0, as only this is supported for now" + dnn = pdu[DNN] + assert dnn, "Data network name is invalid" + session_type_str = pdu[PDU_SESSION_TYPE] + assert session_type_str == ETHER_TYPE_IPV4, f"Only supported PDU session type for now is {ETHER_TYPE_IPV4}" + gtpu_tunnel = pdu[GTPU_TUNNEL] + assert isinstance(gtpu_tunnel, dict), "GTP-U tunnel info must be a map with uplink and downlink items)" + + gtpu_ul = gtpu_tunnel[UPLINK] + assert isinstance(gtpu_ul, dict), "GTP-U tunnel UL info must be a map with src and dst IP items)" + assert chk_address_ipv4(gtpu_ul[SRC]), f"Invalid GTP-U UL src IPv4 address {gtpu_ul[SRC]}" + assert chk_address_ipv4(gtpu_ul[DST]), f"Invalid GTP-U UL dst IPv4 address {gtpu_ul[DST]}" + + gtpu_dl = gtpu_tunnel[DOWNLINK] + assert isinstance(gtpu_dl, dict), "GTP-U tunnel DL info must be a map with src and dst IP items)" + assert chk_address_ipv4(gtpu_dl[SRC]), f"Invalid GTP-U DL src IPv4 address {gtpu_dl[SRC]}" + assert chk_address_ipv4(gtpu_dl[DST]), f"Invalid GTP-U DL dst IPv4 address {gtpu_dl[DST]}" + + self.__ue_map[ue_ip][PDU_LIST].append(pdu) + + # QoS flows per UE + self.__ue_map[ue_ip][QOS_FLOWS] = [] + qos_flows_list = ue[QOS_FLOWS] + assert isinstance(qos_flows_list, list), f"UE {ue_id} QoS flows' list must be a list" + for flow in qos_flows_list: + qfi = flow[QFI] + assert qfi >= 0, "QFI must be a non-negative integer" + fiveqi = flow[FIVEQI] + assert fiveqi >= 0, "5QI must be a non-negative integer" + qos_type = flow[QOS_TYPE] + assert qos_type.casefold() in (s.casefold() for s in QOS_TYPES_STR_VALID), \ + f"UE {ue_id} - Valid QoS types are: {','.join(QOS_TYPES_STR_VALID)}" + self.__ue_map[ue_ip][QOS_FLOWS].append(flow) + + self.__switch_info[switch_name] = sw_info + + def _print_settings(self): + LOGGER.info(f"--------------- {self.__service.name} settings ---------------") + LOGGER.info("--- Topology info") + for switch_name, switch_info in self.__switch_info.items(): + LOGGER.info(f"\t Device {switch_name}") + LOGGER.info(f"\t\t| Target P4 architecture: {switch_info[ARCH]}") + LOGGER.info(f"\t\t| Data plane ID: {switch_info[DPID]}") + LOGGER.info("\t\t| 5G UPF Configuration:") + LOGGER.info(f"\t\t\t| Uplink Port: {self.__upf[UPLINK_PORT]}") + LOGGER.info(f"\t\t\t| Uplink IP: {self.__upf[UPLINK_IP]}") + LOGGER.info(f"\t\t\t| Uplink MAC: {self.__upf[UPLINK_MAC]}") + LOGGER.info(f"\t\t\t| Downlink Port: {self.__upf[DOWNLINK_PORT]}") + LOGGER.info(f"\t\t\t| Downlink IP: {self.__upf[DOWNLINK_IP]}") + LOGGER.info(f"\t\t\t| Downlink MAC: {self.__upf[DOWNLINK_MAC]}") + LOGGER.info(f"\t\t\t| Slice ID: {self.__upf[SLICE_ID]}") + LOGGER.info(f"\t\t\t| Tunnel Endpoint ID: {self.__upf[TEID]}") + LOGGER.info(f"\t\t\t| App ID: {self.__upf[APP_ID]}") + LOGGER.info(f"\t\t\t| App Meter ID: {self.__upf[APP_METER_ID]}") + LOGGER.info(f"\t\t\t| Ctr ID: {self.__upf[CTR_ID]}") + LOGGER.info(f"\t\t\t| TC ID: {self.__upf[TC_ID]}") + LOGGER.info(f"\t\t\t| Tunnel Peer ID: {self.__upf[TUNNEL_PEER_ID]}\n") + # LOGGER.info("\n") + LOGGER.info("\t\t| 5G gNB Configuration:") + LOGGER.info(f"\t\t\t| 5G gNB IP: {self.__gnb[IP]}") + LOGGER.info(f"\t\t\t| 5G gNB MAC: {self.__gnb[MAC]}\n") + LOGGER.info("\t\t| Port Map:") + for _, port_map in self.__port_map[switch_name].items(): + LOGGER.info(f"\t\t\t| Port ID: {port_map[PORT_ID]}") + LOGGER.info(f"\t\t\t| Port type: {port_map[PORT_TYPE]}") + LOGGER.info(f"\t\t\t| Port VLAN ID: {port_map[VLAN_ID]}") + LOGGER.info(f"\t\t\t| FWD list: {port_map[FORWARDING_LIST]}") + LOGGER.info(f"\t\t\t| Routing list: {port_map[ROUTING_LIST]}\n") + LOGGER.info("\t\t| UE List:") + for ue_key, ue_info in self.__ue_map.items(): + assert ue_key == ue_info[UE_IP], "UE key is not the UE IPv4 address" + ue_ip = ue_info[UE_IP] + LOGGER.info(f"\t\t\t| UE ID: {ue_info[UE_ID]}") + LOGGER.info(f"\t\t\t| UE IP: {ue_info[UE_IP]}") + for pdu in self.__ue_map[ue_ip][PDU_LIST]: + LOGGER.info(f"\t\t\t\t| PDU session ID: {pdu[PDU_SESSION_ID]}") + LOGGER.info(f"\t\t\t\t| DNN: {pdu[DNN]}") + LOGGER.info(f"\t\t\t\t| PDU session type: {pdu[PDU_SESSION_TYPE]}") + LOGGER.info(f"\t\t\t\t| GTP-U tunnel UL Src. IP: {pdu[GTPU_TUNNEL][UPLINK][SRC]}") + LOGGER.info(f"\t\t\t\t| GTP-U tunnel UL Dst. IP: {pdu[GTPU_TUNNEL][UPLINK][DST]}") + LOGGER.info(f"\t\t\t\t| GTP-U tunnel DL Src. IP: {pdu[GTPU_TUNNEL][DOWNLINK][SRC]}") + LOGGER.info(f"\t\t\t\t| GTP-U tunnel DL Dst. IP: {pdu[GTPU_TUNNEL][DOWNLINK][DST]}\n") + for flow in self.__ue_map[ue_ip][QOS_FLOWS]: + LOGGER.info(f"\t\t\t\t| QoS QFI: {flow[QFI]}") + LOGGER.info(f"\t\t\t\t| QoS 5QI: {flow[FIVEQI]}") + LOGGER.info(f"\t\t\t\t| QoS Type: {flow[QOS_TYPE]}\n") + LOGGER.info("-------------------------------------------------------") + + def _get_switch_port_in_port_map(self, switch_name : str, port_id : int) -> Dict: + assert switch_name, "A valid switch name must be used as a key to the port map" + assert port_id > 0, "A valid switch port ID must be used as a key to a switch's port map" + switch_entry = self.__port_map[switch_name] + assert switch_entry, f"Switch {switch_name} does not exist in the port map" + port_key = PORT_PREFIX + str(port_id) + assert switch_entry[port_key], f"Port with ID {port_id} does not exist in the switch map" + + return switch_entry[port_key] + + def _get_port_type_of_switch_port(self, switch_name : str, port_id : int) -> str: + switch_port_entry = self._get_switch_port_in_port_map(switch_name, port_id) + return switch_port_entry[PORT_TYPE] + + def _get_vlan_id_of_switch_port(self, switch_name : str, port_id : int) -> int: + switch_port_entry = self._get_switch_port_in_port_map(switch_name, port_id) + return switch_port_entry[VLAN_ID] + + def _get_fwd_list_of_switch_port(self, switch_name : str, port_id : int) -> List [Tuple]: + switch_port_entry = self._get_switch_port_in_port_map(switch_name, port_id) + return switch_port_entry[FORWARDING_LIST] + + def _get_routing_list_of_switch_port(self, switch_name : str, port_id : int) -> List [Tuple]: + switch_port_entry = self._get_switch_port_in_port_map(switch_name, port_id) + return switch_port_entry[ROUTING_LIST] + + def _is_host_facing_port(self, switch_name : str, port_id : int) -> bool: + return self._get_port_type_of_switch_port(switch_name, port_id) == PORT_TYPE_HOST + + def _create_rules_uplink( + self, + device_obj : Device, # type: ignore + port_id : int, + next_id : int, + action : ConfigActionEnum): # type: ignore + dev_name = device_obj.name + vlan_id = self._get_vlan_id_of_switch_port(switch_name=dev_name, port_id=port_id) + + rules = [] + + # Port setup rules + try: + rules += rules_set_up_port( + port_id=port_id, + port_type=PORT_TYPE_HOST, + fwd_type=FORWARDING_TYPE_BRIDGING, + vlan_id=vlan_id, + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating port setup rules") + raise Exception(ex) + + ### UPF rules + try: + rules += rules_set_up_upf_interface( + port_id=port_id, + ipv4_dst=self.__upf[UPLINK_IP], # UPF's N3 interface + ipv4_prefix_len=32, + gtpu_value=GTPU_VALID, + slice_id=self.__upf[SLICE_ID], + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating UPF interface rule") + raise Exception(ex) + + try: + rules += rules_set_up_upf_uplink_sessions( + port_id=port_id, + tun_ip_address=self.__upf[UPLINK_IP], # UPF's N3 interface + teid=self.__upf[TEID], + session_meter_id=DEF_SESSION_METER_ID, + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating UPF UL session rule") + raise Exception(ex) + + # UE-specific rules + for _, ue_info in self.__ue_map.items(): + try: + rules += rules_set_up_upf_uplink_terminations( + port_id=port_id, + ue_session_id=ue_info[UE_IP], # UE's IPv4 address + app_id=self.__upf[APP_ID], + ctr_id=self.__upf[CTR_ID], + app_meter_id=self.__upf[APP_METER_ID], + tc_id=self.__upf[TC_ID], + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating UPF termination rule") + raise Exception(ex) + + # L2 Forwarding rules + fwd_list = self._get_fwd_list_of_switch_port(switch_name=dev_name, port_id=next_id) + for host_map in fwd_list: + mac_dst = host_map[HOST_MAC] + label = host_map[HOST_LABEL] + LOGGER.info(f"\t | Switch {dev_name} - Port {port_id} - Creating rule for host MAC: {mac_dst} - label: {label}") + try: + ### Bridging rules + rules += rules_set_up_fwd_bridging( + port_id=port_id, + vlan_id=vlan_id, + eth_dst=mac_dst, + next_id=next_id, + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating bridging rule") + raise Exception(ex) + + try: + ### Pre-next VLAN rule + rules += rules_set_up_pre_next_vlan( + port_id=port_id, + next_id=next_id, + vlan_id=vlan_id, + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating pre-next VLAN rule") + raise Exception(ex) + + ### Static routing rules + routing_list = self._get_routing_list_of_switch_port(switch_name=dev_name, port_id=port_id) + for rt_entry in routing_list: + LOGGER.info(f"\t | Switch {dev_name} - Port {port_id} - Route to dst {rt_entry[IPV4_DST]}/{rt_entry[IPV4_PREFIX_LEN]}, with MAC src {rt_entry[MAC_SRC]} and dst {rt_entry[MAC_DST]}") + + try: + ### Next profile for hashed routing + rules += rules_set_up_next_profile_hashed_routing( + port_id=port_id, + next_id=next_id, + eth_src=rt_entry[MAC_SRC], # UPF's N6 interface (self.__upf[DOWNLINK_MAC]) + eth_dst=rt_entry[MAC_DST], # Data network's N6 interface + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating next profile for hashed routing") + raise Exception(ex) + + try: + ### Next hashed for routing + rules += rules_set_up_next_hashed( + port_id=port_id, + next_id=next_id, + action=action + ) + ### Route towards destination + rules += rules_set_up_routing( + port_id=port_id, + ipv4_dst=rt_entry[IPV4_DST], + ipv4_prefix_len=rt_entry[IPV4_PREFIX_LEN], + next_id=next_id, + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating static L3 routing rules") + raise Exception(ex) + + return rules + + def _create_rules_downlink( + self, + device_obj : Device, # type: ignore + port_id : int, + next_id : int, + action : ConfigActionEnum): # type: ignore + dev_name = device_obj.name + vlan_id = self._get_vlan_id_of_switch_port(switch_name=dev_name, port_id=port_id) + + rules = [] + + # Port setup + try: + rules += rules_set_up_port( + port_id=port_id, + port_type=PORT_TYPE_HOST, + fwd_type=FORWARDING_TYPE_BRIDGING, + vlan_id=vlan_id, + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating port setup rules") + raise Exception(ex) + + ### UPF + for _, ue_info in self.__ue_map.items(): + try: + rules += rules_set_up_upf_interface( + port_id=port_id, + ipv4_dst=ue_info[UE_IP], + ipv4_prefix_len=32, + gtpu_value=GTPU_INVALID, + slice_id=self.__upf[SLICE_ID], + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating UPF interface rule") + raise Exception(ex) + + try: + rules += rules_set_up_upf_downlink_sessions( + port_id=port_id, + ipv4_dst=ue_info[UE_IP], + session_meter_id=ue_info[PDU_LIST][0][PDU_SESSION_ID], # Should match DEF_SESSION_METER_ID + tun_peer_id=self.__upf[TUNNEL_PEER_ID], + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating UPF DL session rule") + raise Exception(ex) + + try: + rules += rules_set_up_upf_downlink_terminations( + port_id=port_id, + ue_session_id=ue_info[UE_IP], # UE's IPv4 address + app_id=self.__upf[APP_ID], + ctr_id=self.__upf[CTR_ID], + app_meter_id=self.__upf[APP_METER_ID], + tc_id=self.__upf[TC_ID], + teid=self.__upf[TEID], + qfi=ue_info[QOS_FLOWS][0][QFI], + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating UPF DL termination rule") + raise Exception(ex) + + try: + rules += rules_set_up_upf_downlink_ig_tunnel_peers( + port_id=port_id, + tun_peer_id=self.__upf[TUNNEL_PEER_ID], + tun_dst_addr=self.__upf[UPLINK_IP], # UPF's N3 interface + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating UPF DL ingress tunnel peers rule") + raise Exception(ex) + + try: + rules += rules_set_up_upf_downlink_eg_tunnel_peers( + port_id=port_id, + tun_peer_id=self.__upf[TUNNEL_PEER_ID], + tun_src_addr=self.__upf[UPLINK_IP], # UPF's N3 interface + tun_dst_addr=self.__gnb[IP], # gNB's N3 interface + tun_src_port=GTP_PORT, # GTP-U port + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating UPF DL egress tunnel peers rule") + raise Exception(ex) + + # L2 Forwarding + fwd_list = self._get_fwd_list_of_switch_port(switch_name=dev_name, port_id=next_id) + for host_map in fwd_list: + mac_dst = host_map[HOST_MAC] + label = host_map[HOST_LABEL] + LOGGER.info(f"\t | Switch {dev_name} - Port {port_id} - Creating rule for host MAC: {mac_dst} - label: {label}") + try: + ### Bridging + rules += rules_set_up_fwd_bridging( + port_id=port_id, + vlan_id=vlan_id, + eth_dst=mac_dst, + next_id=next_id, + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating bridging rule") + raise Exception(ex) + + try: + ### Pre-next VLAN + rules += rules_set_up_pre_next_vlan( + port_id=port_id, + next_id=next_id, + vlan_id=vlan_id, + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating pre-next VLAN rule") + raise Exception(ex) + + ### Static routing + routing_list = self._get_routing_list_of_switch_port(switch_name=dev_name, port_id=port_id) + for rt_entry in routing_list: + LOGGER.info(f"\t | Switch {dev_name} - Port {port_id} - Route to dst {rt_entry[IPV4_DST]}/{rt_entry[IPV4_PREFIX_LEN]}, with MAC src {rt_entry[MAC_SRC]} and dst {rt_entry[MAC_DST]}") + + try: + ### Next profile for hashed routing + rules += rules_set_up_next_profile_hashed_routing( + port_id=port_id, + next_id=next_id, + eth_src=rt_entry[MAC_SRC], # UPF's N3 interface (self.__upf[UPLINK_MAC]) + eth_dst=rt_entry[MAC_DST], # gNB's N3 interface (self.__gnb[MAC]) + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating next profile for hashed routing") + raise Exception(ex) + + try: + ### Next hashed for routing + rules += rules_set_up_next_hashed( + port_id=port_id, + next_id=next_id, + action=action + ) + ### Route towards destination + rules += rules_set_up_routing( + port_id=port_id, + ipv4_dst=rt_entry[IPV4_DST], + ipv4_prefix_len=rt_entry[IPV4_PREFIX_LEN], + next_id=next_id, + action=action + ) + except Exception as ex: + LOGGER.error("Error while creating static L3 routing rules") + raise Exception(ex) + + return rules + diff --git a/src/telemetry/backend/service/collectors/int_collector/INTCollector.py b/src/telemetry/backend/service/collectors/int_collector/INTCollector.py index 0f35882f024070ce2ea3c27f524e64992999bc35..84352f417e9bea4765ff80f62cbe9cd5488f3389 100644 --- a/src/telemetry/backend/service/collectors/int_collector/INTCollector.py +++ b/src/telemetry/backend/service/collectors/int_collector/INTCollector.py @@ -23,8 +23,6 @@ from datetime import datetime from telemetry.backend.service.collector_api._Collector import _Collector from scapy.all import * -import struct -import socket import ipaddress from .INTCollectorCommon import IntDropReport, IntLocalReport, IntFixedReport, FlowInfo, IPPacket, UDPPacket diff --git a/src/webui/service/static/topology_icons/oran-cn.png b/src/webui/service/static/topology_icons/oran-cn.png new file mode 100644 index 0000000000000000000000000000000000000000..f1555434f9db8e4268b6544933069601bb79eeab Binary files /dev/null and b/src/webui/service/static/topology_icons/oran-cn.png differ diff --git a/src/webui/service/static/topology_icons/oran-cu.png b/src/webui/service/static/topology_icons/oran-cu.png new file mode 100644 index 0000000000000000000000000000000000000000..aff6986e3800b4184d942f9a8edb24a406db1ccc Binary files /dev/null and b/src/webui/service/static/topology_icons/oran-cu.png differ diff --git a/src/webui/service/static/topology_icons/oran-du.png b/src/webui/service/static/topology_icons/oran-du.png new file mode 100644 index 0000000000000000000000000000000000000000..d486305cf02b0fe1ed6c92f38ce1cb21887cd3bc Binary files /dev/null and b/src/webui/service/static/topology_icons/oran-du.png differ