diff --git a/proto/context.proto b/proto/context.proto
index 4d61572df1db6c95b64e9ce1cbfdd9e0db111078..c527078d67c906b8a725b3e1563677f48dd9fb37 100644
--- a/proto/context.proto
+++ b/proto/context.proto
@@ -223,6 +223,7 @@ enum DeviceDriverEnum {
   DEVICEDRIVER_IETF_ACTN = 10;
   DEVICEDRIVER_OC = 11;
   DEVICEDRIVER_QKD = 12;
+  DEVICEDRIVER_IETF_L3VPN = 13;
 }
 
 enum DeviceOperationalStatusEnum {
diff --git a/src/device/service/drivers/__init__.py b/src/device/service/drivers/__init__.py
index b99ee50ca8319ab96f9062a3c58c356fa2ae7ec7..2e53cfa03ed4d3c5b4883edb939f6add79416f3f 100644
--- a/src/device/service/drivers/__init__.py
+++ b/src/device/service/drivers/__init__.py
@@ -81,6 +81,16 @@ DRIVERS.append(
         }
     ]))
 
+
+from .ietf_l3vpn.driver import IetfL3VpnDriver # pylint: disable=wrong-import-position
+DRIVERS.append(
+    (IetfL3VpnDriver, [
+        {
+            FilterFieldEnum.DEVICE_TYPE: DeviceTypeEnum.TERAFLOWSDN_CONTROLLER,
+            FilterFieldEnum.DRIVER: DeviceDriverEnum.DEVICEDRIVER_IETF_L3VPN,
+        }
+    ]))
+
 from .ietf_actn.IetfActnDriver import IetfActnDriver # pylint: disable=wrong-import-position
 DRIVERS.append(
     (IetfActnDriver, [
diff --git a/src/device/service/drivers/emulated/Tools.py b/src/device/service/drivers/emulated/Tools.py
index 9f2a105c0d9735f486f41fab5bc3069ec9327f65..2a6f9c95d351f447f7368584d0ad07889eaecd7c 100644
--- a/src/device/service/drivers/emulated/Tools.py
+++ b/src/device/service/drivers/emulated/Tools.py
@@ -82,6 +82,26 @@ def compose_resource_endpoint(endpoint_data : Dict[str, Any]) -> Optional[Tuple[
     
         if 'location' in endpoint_data:
             endpoint_resource_value['location'] = endpoint_data['location']
+
+        if "site_location" in endpoint_data:
+            endpoint_resource_value["site_location"] = endpoint_data["site_location"]
+
+        if "ce-ip" in endpoint_data:
+            endpoint_resource_value["ce-ip"] = endpoint_data["ce-ip"]
+
+        if "address_ip" in endpoint_data:
+            endpoint_resource_value["address_ip"] = endpoint_data["address_ip"]
+
+        if "address_prefix" in endpoint_data:
+            endpoint_resource_value["address_prefix"] = endpoint_data["address_prefix"]
+
+        if "mtu" in endpoint_data:
+            endpoint_resource_value["mtu"] = endpoint_data["mtu"]
+
+        if "ipv4_lan_prefixes" in endpoint_data:
+            endpoint_resource_value["ipv4_lan_prefixes"] = endpoint_data[
+                "ipv4_lan_prefixes"
+            ]
             
         return endpoint_resource_key, endpoint_resource_value
     except: # pylint: disable=bare-except
diff --git a/src/device/service/drivers/ietf_l3vpn/Constants.py b/src/device/service/drivers/ietf_l3vpn/Constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..df66eb16b3d78c1b388a086011ed6f6b75b8099f
--- /dev/null
+++ b/src/device/service/drivers/ietf_l3vpn/Constants.py
@@ -0,0 +1,25 @@
+# Copyright 2022-2024 ETSI OSG/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.
+
+from device.service.driver_api._Driver import (
+    RESOURCE_ENDPOINTS,
+    RESOURCE_INTERFACES,
+    RESOURCE_NETWORK_INSTANCES,
+)
+
+SPECIAL_RESOURCE_MAPPINGS = {
+    RESOURCE_ENDPOINTS: "/endpoints",
+    RESOURCE_INTERFACES: "/interfaces",
+    RESOURCE_NETWORK_INSTANCES: "/net-instances",
+}
diff --git a/src/device/service/drivers/ietf_l3vpn/TfsApiClient.py b/src/device/service/drivers/ietf_l3vpn/TfsApiClient.py
new file mode 100644
index 0000000000000000000000000000000000000000..1ca965f8777aa23287ad379c8ac2cd0d92d9c28f
--- /dev/null
+++ b/src/device/service/drivers/ietf_l3vpn/TfsApiClient.py
@@ -0,0 +1,187 @@
+# Copyright 2022-2024 ETSI OSG/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.
+
+import logging
+from typing import Dict, List, Optional
+
+import requests
+from requests.auth import HTTPBasicAuth
+
+from device.service.driver_api.ImportTopologyEnum import ImportTopologyEnum
+
+GET_DEVICES_URL = "{:s}://{:s}:{:d}/tfs-api/devices"
+GET_LINKS_URL = "{:s}://{:s}:{:d}/tfs-api/links"
+L3VPN_URL = "{:s}://{:s}:{:d}/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services"
+TIMEOUT = 30
+
+HTTP_OK_CODES = {
+    200,  # OK
+    201,  # Created
+    202,  # Accepted
+    204,  # No Content
+}
+
+MAPPING_STATUS = {
+    "DEVICEOPERATIONALSTATUS_UNDEFINED": 0,
+    "DEVICEOPERATIONALSTATUS_DISABLED": 1,
+    "DEVICEOPERATIONALSTATUS_ENABLED": 2,
+}
+
+MAPPING_DRIVER = {
+    "DEVICEDRIVER_UNDEFINED": 0,
+    "DEVICEDRIVER_OPENCONFIG": 1,
+    "DEVICEDRIVER_TRANSPORT_API": 2,
+    "DEVICEDRIVER_P4": 3,
+    "DEVICEDRIVER_IETF_NETWORK_TOPOLOGY": 4,
+    "DEVICEDRIVER_ONF_TR_532": 5,
+    "DEVICEDRIVER_XR": 6,
+    "DEVICEDRIVER_IETF_L2VPN": 7,
+    "DEVICEDRIVER_GNMI_OPENCONFIG": 8,
+    "DEVICEDRIVER_OPTICAL_TFS": 9,
+    "DEVICEDRIVER_IETF_ACTN": 10,
+    "DEVICEDRIVER_OC": 11,
+}
+
+MSG_ERROR = "Could not retrieve devices in remote TeraFlowSDN instance({:s}). status_code={:s} reply={:s}"
+
+LOGGER = logging.getLogger(__name__)
+
+
+class TfsApiClient:
+    def __init__(
+        self,
+        address: str,
+        port: int,
+        scheme: str = "http",
+        username: Optional[str] = None,
+        password: Optional[str] = None,
+    ) -> None:
+        self._devices_url = GET_DEVICES_URL.format(scheme, address, port)
+        self._links_url = GET_LINKS_URL.format(scheme, address, port)
+        self._l3vpn_url = L3VPN_URL.format(scheme, address, port)
+        self._auth = None
+        # (
+        #     HTTPBasicAuth(username, password)
+        #     if username is not None and password is not None
+        #     else None
+        # )
+
+    def get_devices_endpoints(
+        self, import_topology: ImportTopologyEnum = ImportTopologyEnum.DEVICES
+    ) -> List[Dict]:
+        LOGGER.debug("[get_devices_endpoints] begin")
+        LOGGER.debug(
+            "[get_devices_endpoints] import_topology={:s}".format(str(import_topology))
+        )
+
+        reply = requests.get(self._devices_url, timeout=TIMEOUT, auth=self._auth)
+        if reply.status_code not in HTTP_OK_CODES:
+            msg = MSG_ERROR.format(
+                str(self._devices_url), str(reply.status_code), str(reply)
+            )
+            LOGGER.error(msg)
+            raise Exception(msg)
+
+        if import_topology == ImportTopologyEnum.DISABLED:
+            raise Exception(
+                "Unsupported import_topology mode: {:s}".format(str(import_topology))
+            )
+
+        result = list()
+        for json_device in reply.json()["devices"]:
+            device_uuid: str = json_device["device_id"]["device_uuid"]["uuid"]
+            device_type: str = json_device["device_type"]
+            device_status = json_device["device_operational_status"]
+            device_url = "/devices/device[{:s}]".format(device_uuid)
+            device_data = {
+                "uuid": json_device["device_id"]["device_uuid"]["uuid"],
+                "name": json_device["name"],
+                "type": device_type,
+                "status": MAPPING_STATUS[device_status],
+                "drivers": [
+                    MAPPING_DRIVER[driver] for driver in json_device["device_drivers"]
+                ],
+            }
+            result.append((device_url, device_data))
+
+            for json_endpoint in json_device["device_endpoints"]:
+                endpoint_uuid = json_endpoint["endpoint_id"]["endpoint_uuid"]["uuid"]
+                endpoint_url = "/endpoints/endpoint[{:s}]".format(endpoint_uuid)
+                endpoint_data = {
+                    "device_uuid": device_uuid,
+                    "uuid": endpoint_uuid,
+                    "name": json_endpoint["name"],
+                    "type": json_endpoint["endpoint_type"],
+                }
+                result.append((endpoint_url, endpoint_data))
+
+        if import_topology == ImportTopologyEnum.DEVICES:
+            LOGGER.debug("[get_devices_endpoints] devices only; returning")
+            return result
+
+        reply = requests.get(self._links_url, timeout=TIMEOUT, auth=self._auth)
+        if reply.status_code not in HTTP_OK_CODES:
+            msg = MSG_ERROR.format(
+                str(self._links_url), str(reply.status_code), str(reply)
+            )
+            LOGGER.error(msg)
+            raise Exception(msg)
+
+        for json_link in reply.json()["links"]:
+            link_uuid: str = json_link["link_id"]["link_uuid"]["uuid"]
+            link_url = "/links/link[{:s}]".format(link_uuid)
+            link_endpoint_ids = [
+                (
+                    json_endpoint_id["device_id"]["device_uuid"]["uuid"],
+                    json_endpoint_id["endpoint_uuid"]["uuid"],
+                )
+                for json_endpoint_id in json_link["link_endpoint_ids"]
+            ]
+            link_data = {
+                "uuid": json_link["link_id"]["link_uuid"]["uuid"],
+                "name": json_link["name"],
+                "endpoints": link_endpoint_ids,
+            }
+            result.append((link_url, link_data))
+
+        LOGGER.debug("[get_devices_endpoints] topology; returning")
+        return result
+
+    def create_connectivity_service(self, l3vpn_data: dict) -> None:
+        try:
+            requests.post(self._l3vpn_url, json=l3vpn_data)
+            LOGGER.debug(
+                "[create_connectivity_service] l3vpn_data={:s}".format(str(l3vpn_data))
+            )
+        except requests.exceptions.ConnectionError:
+            raise Exception("faild to send post request to TFS L3VPN NBI")
+
+    def update_connectivity_service(self, l3vpn_data: dict) -> None:
+        vpn_id = l3vpn_data['ietf-l3vpn-svc:l3vpn-svc']["vpn-services"]["vpn-service"][0]["vpn-id"]
+        url = self._l3vpn_url + f"/vpn-service={vpn_id}"
+        try:
+            requests.put(url, json=l3vpn_data)
+            LOGGER.debug(
+                "[update_connectivity_service] l3vpn_data={:s}".format(str(l3vpn_data))
+            )
+        except requests.exceptions.ConnectionError:
+            raise Exception("faild to send post request to TFS L3VPN NBI")
+
+    def delete_connectivity_service(self, service_uuid: str) -> None:
+        url = self._l3vpn_url + f"/vpn-service={service_uuid}"
+        try:
+            requests.delete(url, auth=self._auth)
+            LOGGER.debug("[delete_connectivity_service] url={:s}".format(str(url)))
+        except requests.exceptions.ConnectionError:
+            raise Exception("faild to send delete request to TFS L3VPN NBI")
diff --git a/src/device/service/drivers/ietf_l3vpn/Tools.py b/src/device/service/drivers/ietf_l3vpn/Tools.py
new file mode 100644
index 0000000000000000000000000000000000000000..7caaa27a52cacf03d9822e933f0a6b74fd0e4db8
--- /dev/null
+++ b/src/device/service/drivers/ietf_l3vpn/Tools.py
@@ -0,0 +1,198 @@
+# Copyright 2022-2024 ETSI OSG/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.
+import logging
+from typing import Any, Dict, Optional, Tuple, TypedDict
+
+import requests
+
+from common.proto.kpi_sample_types_pb2 import KpiSampleType
+from common.type_checkers.Checkers import chk_attribute, chk_string, chk_type
+from device.service.driver_api._Driver import RESOURCE_ENDPOINTS
+
+from .Constants import SPECIAL_RESOURCE_MAPPINGS
+
+
+class LANPrefixesDict(TypedDict):
+    lan: str
+    lan_tag: str
+
+
+LOGGER = logging.getLogger(__name__)
+
+SITE_NETWORK_ACCESS_TYPE = "ietf-l3vpn-svc:multipoint"
+
+
+def service_exists(wim_url: str, auth, service_uuid: str) -> bool:
+    try:
+        get_connectivity_service(wim_url, auth, service_uuid)
+        return True
+    except:  # pylint: disable=bare-except
+        return False
+
+
+def get_all_active_connectivity_services(wim_url: str, auth):
+    try:
+        LOGGER.info("Sending get all connectivity services")
+        servicepoint = f"{wim_url}/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services"
+        response = requests.get(servicepoint, auth=auth)
+
+        if response.status_code != requests.codes.ok:
+            raise Exception(
+                "Unable to get all connectivity services",
+                http_code=response.status_code,
+            )
+
+        return response
+    except requests.exceptions.ConnectionError:
+        raise Exception("Request Timeout", http_code=408)
+
+
+def get_connectivity_service(wim_url, auth, service_uuid):
+    try:
+        LOGGER.info("Sending get connectivity service")
+        servicepoint = f"{wim_url}/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services/vpn-service={service_uuid}"
+
+        response = requests.get(servicepoint)
+
+        if response.status_code != requests.codes.ok:
+            raise Exception(
+                "Unable to get connectivity service{:s}".format(str(service_uuid)),
+                http_code=response.status_code,
+            )
+
+        return response
+    except requests.exceptions.ConnectionError:
+        raise Exception("Request Timeout", http_code=408)
+
+
+def process_optional_string_field(
+    endpoint_data: Dict[str, Any],
+    field_name: str,
+    endpoint_resource_value: Dict[str, Any],
+) -> None:
+    field_value = chk_attribute(
+        field_name, endpoint_data, "endpoint_data", default=None
+    )
+    if field_value is None:
+        return
+    chk_string("endpoint_data.{:s}".format(field_name), field_value)
+    if len(field_value) > 0:
+        endpoint_resource_value[field_name] = field_value
+
+
+def compose_resource_endpoint(
+    endpoint_data: Dict[str, Any],
+) -> Optional[Tuple[str, Dict]]:
+    try:
+        # Check type of endpoint_data
+        chk_type("endpoint_data", endpoint_data, dict)
+
+        # Check endpoint UUID (mandatory)
+        endpoint_uuid = chk_attribute("uuid", endpoint_data, "endpoint_data")
+        chk_string("endpoint_data.uuid", endpoint_uuid, min_length=1)
+        endpoint_resource_path = SPECIAL_RESOURCE_MAPPINGS.get(RESOURCE_ENDPOINTS)
+        endpoint_resource_key = "{:s}/endpoint[{:s}]".format(
+            endpoint_resource_path, endpoint_uuid
+        )
+        endpoint_resource_value = {"uuid": endpoint_uuid}
+
+        # Check endpoint optional string fields
+        process_optional_string_field(endpoint_data, "name", endpoint_resource_value)
+        process_optional_string_field(
+            endpoint_data, "site_location", endpoint_resource_value
+        )
+        process_optional_string_field(endpoint_data, "ce-ip", endpoint_resource_value)
+        process_optional_string_field(
+            endpoint_data, "address_ip", endpoint_resource_value
+        )
+        process_optional_string_field(
+            endpoint_data, "address_prefix", endpoint_resource_value
+        )
+        process_optional_string_field(endpoint_data, "mtu", endpoint_resource_value)
+        process_optional_string_field(
+            endpoint_data, "ipv4_lan_prefixes", endpoint_resource_value
+        )
+        process_optional_string_field(endpoint_data, "type", endpoint_resource_value)
+        process_optional_string_field(
+            endpoint_data, "context_uuid", endpoint_resource_value
+        )
+        process_optional_string_field(
+            endpoint_data, "topology_uuid", endpoint_resource_value
+        )
+
+        # Check endpoint sample types (optional)
+        endpoint_sample_types = chk_attribute(
+            "sample_types", endpoint_data, "endpoint_data", default=[]
+        )
+        chk_type("endpoint_data.sample_types", endpoint_sample_types, list)
+        sample_types = {}
+        sample_type_errors = []
+        for i, endpoint_sample_type in enumerate(endpoint_sample_types):
+            field_name = "endpoint_data.sample_types[{:d}]".format(i)
+            try:
+                chk_type(field_name, endpoint_sample_type, (int, str))
+                if isinstance(endpoint_sample_type, int):
+                    metric_name = KpiSampleType.Name(endpoint_sample_type)
+                    metric_id = endpoint_sample_type
+                elif isinstance(endpoint_sample_type, str):
+                    metric_id = KpiSampleType.Value(endpoint_sample_type)
+                    metric_name = endpoint_sample_type
+                else:
+                    str_type = str(type(endpoint_sample_type))
+                    raise Exception("Bad format: {:s}".format(str_type))  # pylint: disable=broad-exception-raised
+            except Exception as e:  # pylint: disable=broad-exception-caught
+                MSG = "Unsupported {:s}({:s}) : {:s}"
+                sample_type_errors.append(
+                    MSG.format(field_name, str(endpoint_sample_type), str(e))
+                )
+
+            metric_name = metric_name.lower().replace("kpisampletype_", "")
+            monitoring_resource_key = "{:s}/state/{:s}".format(
+                endpoint_resource_key, metric_name
+            )
+            sample_types[metric_id] = monitoring_resource_key
+
+        if len(sample_type_errors) > 0:
+            # pylint: disable=broad-exception-raised
+            raise Exception(
+                "Malformed Sample Types:\n{:s}".format("\n".join(sample_type_errors))
+            )
+
+        if len(sample_types) > 0:
+            endpoint_resource_value["sample_types"] = sample_types
+
+        if "site_location" in endpoint_data:
+            endpoint_resource_value["site_location"] = endpoint_data["site_location"]
+
+        if "ce-ip" in endpoint_data:
+            endpoint_resource_value["ce-ip"] = endpoint_data["ce-ip"]
+
+        if "address_ip" in endpoint_data:
+            endpoint_resource_value["address_ip"] = endpoint_data["address_ip"]
+
+        if "address_prefix" in endpoint_data:
+            endpoint_resource_value["address_prefix"] = endpoint_data["address_prefix"]
+
+        if "mtu" in endpoint_data:
+            endpoint_resource_value["mtu"] = endpoint_data["mtu"]
+
+        if "ipv4_lan_prefixes" in endpoint_data:
+            endpoint_resource_value["ipv4_lan_prefixes"] = endpoint_data[
+                "ipv4_lan_prefixes"
+            ]
+
+        return endpoint_resource_key, endpoint_resource_value
+    except:  # pylint: disable=bare-except
+        LOGGER.exception("Problem composing endpoint({:s})".format(str(endpoint_data)))
+        return None
diff --git a/src/device/service/drivers/ietf_l3vpn/__init__.py b/src/device/service/drivers/ietf_l3vpn/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..bbfc943b68af13a11e562abbc8680ade71db8f02
--- /dev/null
+++ b/src/device/service/drivers/ietf_l3vpn/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2022-2024 ETSI OSG/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/device/service/drivers/ietf_l3vpn/driver.py b/src/device/service/drivers/ietf_l3vpn/driver.py
new file mode 100644
index 0000000000000000000000000000000000000000..2aca83b6a645bf2e793b08841949813f0413a531
--- /dev/null
+++ b/src/device/service/drivers/ietf_l3vpn/driver.py
@@ -0,0 +1,309 @@
+# Copyright 2022-2024 ETSI OSG/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.
+
+import json
+import logging
+import re
+import threading
+from typing import Any, Iterator, List, Optional, Tuple, Union
+
+import anytree
+import requests
+from requests.auth import HTTPBasicAuth
+
+from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method
+from common.type_checkers.Checkers import chk_length, chk_string, chk_type
+from device.service.driver_api._Driver import (
+    RESOURCE_ENDPOINTS,
+    RESOURCE_SERVICES,
+    _Driver,
+)
+from device.service.driver_api.AnyTreeTools import (
+    TreeNode,
+    dump_subtree,
+    get_subnode,
+    set_subnode_value,
+)
+from device.service.driver_api.ImportTopologyEnum import (
+    ImportTopologyEnum,
+    get_import_topology,
+)
+
+from .Constants import SPECIAL_RESOURCE_MAPPINGS
+from .TfsApiClient import TfsApiClient
+from .Tools import compose_resource_endpoint
+
+LOGGER = logging.getLogger(__name__)
+
+
+ALL_RESOURCE_KEYS = [
+    RESOURCE_ENDPOINTS,
+    RESOURCE_SERVICES,
+]
+
+RE_GET_ENDPOINT_FROM_INTERFACE = re.compile(r"^\/interface\[([^\]]+)\].*")
+
+RE_IETF_L3VPN_DATA = re.compile(r"^\/service\[[^\]]+\]\/IETFL3VPN$")
+RE_IETF_L3VPN_OPERATION = re.compile(r"^\/service\[[^\]]+\]\/IETFL3VPN\/operation$")
+
+DRIVER_NAME = "ietf_l3vpn"
+METRICS_POOL = MetricsPool("Device", "Driver", labels={"driver": DRIVER_NAME})
+
+
+class IetfL3VpnDriver(_Driver):
+    def __init__(self, address: str, port: str, **settings) -> None:
+        super().__init__(DRIVER_NAME, address, int(port), **settings)
+        self.__lock = threading.Lock()
+        self.__started = threading.Event()
+        self.__terminate = threading.Event()
+        self.__running = TreeNode(".")
+        scheme = self.settings.get("scheme", "http")
+        username = self.settings.get("username")
+        password = self.settings.get("password")
+        self.tac = TfsApiClient(
+            self.address,
+            self.port,
+            scheme=scheme,
+            username=username,
+            password=password,
+        )
+        self.__auth = None
+        # (
+        #     HTTPBasicAuth(username, password)
+        #     if username is not None and password is not None
+        #     else None
+        # )
+        self.__tfs_nbi_root = "{:s}://{:s}:{:d}".format(
+            scheme, self.address, int(self.port)
+        )
+        self.__timeout = int(self.settings.get("timeout", 120))
+        self.__import_topology = get_import_topology(
+            self.settings, default=ImportTopologyEnum.DEVICES
+        )
+        endpoints = self.settings.get("endpoints", [])
+        endpoint_resources = []
+        for endpoint in endpoints:
+            endpoint_resource = compose_resource_endpoint(endpoint)
+            if endpoint_resource is None:
+                continue
+            endpoint_resources.append(endpoint_resource)
+        self._set_initial_config(endpoint_resources)
+
+    def _set_initial_config(
+        self, resources: List[Tuple[str, Any]]
+    ) -> List[Union[bool, Exception]]:
+        chk_type("resources", resources, list)
+        if len(resources) == 0:
+            return []
+        results = []
+        resolver = anytree.Resolver(pathattr="name")
+        with self.__lock:
+            for i, resource in enumerate(resources):
+                str_resource_name = "resources[#{:d}]".format(i)
+                try:
+                    chk_type(str_resource_name, resource, (list, tuple))
+                    chk_length(str_resource_name, resource, min_length=2, max_length=2)
+                    resource_key, resource_value = resource
+                    chk_string(str_resource_name, resource_key, allow_empty=False)
+                    resource_path = resource_key.split("/")
+                except Exception as e:  # pylint: disable=broad-except
+                    LOGGER.exception(
+                        "Exception validating {:s}: {:s}".format(
+                            str_resource_name, str(resource_key)
+                        )
+                    )
+                    results.append(e)  # if validation fails, store the exception
+                    continue
+
+                try:
+                    resource_value = json.loads(resource_value)
+                except:  # pylint: disable=bare-except
+                    pass
+
+                set_subnode_value(
+                    resolver, self.__running, resource_path, resource_value
+                )
+
+                results.append(True)
+        return results
+
+    def Connect(self) -> bool:
+        url = (
+            self.__tfs_nbi_root + "/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services"
+        )
+        with self.__lock:
+            if self.__started.is_set():
+                return True
+            try:
+                # requests.get(url, timeout=self.__timeout, auth=self.__auth)
+                ...
+            except requests.exceptions.Timeout:
+                LOGGER.exception("Timeout connecting {:s}".format(url))
+                return False
+            except Exception:  # pylint: disable=broad-except
+                LOGGER.exception("Exception connecting {:s}".format(url))
+                return False
+            else:
+                self.__started.set()
+                return True
+
+    def Disconnect(self) -> bool:
+        with self.__lock:
+            self.__terminate.set()
+            return True
+
+    @metered_subclass_method(METRICS_POOL)
+    def GetInitialConfig(self) -> List[Tuple[str, Any]]:
+        with self.__lock:
+            return []
+
+    @metered_subclass_method(METRICS_POOL)
+    def GetConfig(
+        self, resource_keys: List[str] = []
+    ) -> List[Tuple[str, Union[Any, None, Exception]]]:
+        chk_type("resources", resource_keys, list)
+        with self.__lock:
+            if len(resource_keys) == 0:
+                return dump_subtree(self.__running)
+            results = []
+            resolver = anytree.Resolver(pathattr="name")
+            for i, resource_key in enumerate(resource_keys):
+                str_resource_name = "resource_key[#{:d}]".format(i)
+                try:
+                    chk_string(str_resource_name, resource_key, allow_empty=False)
+                    resource_key = SPECIAL_RESOURCE_MAPPINGS.get(
+                        resource_key, resource_key
+                    )
+                    resource_path = resource_key.split("/")
+                except Exception as e:  # pylint: disable=broad-except
+                    LOGGER.exception(
+                        "Exception validating {:s}: {:s}".format(
+                            str_resource_name, str(resource_key)
+                        )
+                    )
+                    results.append(
+                        (resource_key, e)
+                    )  # if validation fails, store the exception
+                    continue
+
+                resource_node = get_subnode(
+                    resolver, self.__running, resource_path, default=None
+                )
+                # if not found, resource_node is None
+                if resource_node is None:
+                    continue
+                results.extend(dump_subtree(resource_node))
+            return results
+        return results
+
+    @metered_subclass_method(METRICS_POOL)
+    def SetConfig(
+        self, resources: List[Tuple[str, Any]]
+    ) -> List[Union[bool, Exception]]:
+        results = []
+        if len(resources) == 0:
+            return results
+        with self.__lock:
+            for resource in resources:
+                resource_key, resource_value = resource
+                if RE_IETF_L3VPN_OPERATION.match(resource_key):
+                    operation_type = json.loads(resource_value)["type"]
+                    results.append((resource_key, True))
+                    break
+            else:
+                raise Exception("operation type not found in resources")
+            for resource in resources:
+                LOGGER.info("resource = {:s}".format(str(resource)))
+                resource_key, resource_value = resource
+                if not RE_IETF_L3VPN_DATA.match(resource_key):
+                    continue
+                try:
+                    resource_value = json.loads(resource_value)
+
+                    # if service_exists(self.__tfs_nbi_root, self.__auth, service_uuid):
+                    #     exc = NotImplementedError(
+                    #         "IETF L3VPN Service Update is still not supported"
+                    #     )
+                    #     results.append((resource[0], exc))
+                    #     continue
+                    if operation_type == "create":
+                        service_id = resource_value["ietf-l3vpn-svc:l3vpn-svc"][
+                            "vpn-services"
+                        ]["vpn-service"][0]["vpn-id"]
+                        self.tac.create_connectivity_service(resource_value)
+                    elif operation_type == "update":
+                        service_id = resource_value["ietf-l3vpn-svc:l3vpn-svc"][
+                            "vpn-services"
+                        ]["vpn-service"][0]["vpn-id"]
+                        self.tac.update_connectivity_service(resource_value)
+                    else:
+                        raise Exception("operation type not supported")
+                    results.append((resource_key, True))
+                except Exception as e:  # pylint: disable=broad-except
+                    LOGGER.exception(
+                        "Unhandled error processing resource_key({:s})".format(
+                            str(resource_key)
+                        )
+                    )
+                    results.append((resource_key, e))
+        return results
+
+    @metered_subclass_method(METRICS_POOL)
+    def DeleteConfig(
+        self, resources: List[Tuple[str, Any]]
+    ) -> List[Union[bool, Exception]]:
+        results = []
+        if len(resources) == 0:
+            return results
+        with self.__lock:
+            for resource in resources:
+                LOGGER.info("resource = {:s}".format(str(resource)))
+                resource_key, resource_value = resource
+                if not RE_IETF_L3VPN_DATA.match(resource_key):
+                    continue
+                try:
+                    resource_value = json.loads(resource_value)
+                    service_id = resource_value["id"]
+
+                    # if service_exists(self.__tfs_nbi_root, self.__auth, service_uuid):
+                    self.tac.delete_connectivity_service(service_id)
+                    results.append((resource_key, True))
+                except Exception as e:  # pylint: disable=broad-except
+                    LOGGER.exception(
+                        "Unhandled error processing resource_key({:s})".format(
+                            str(resource_key)
+                        )
+                    )
+                    results.append((resource_key, e))
+        return results
+
+    @metered_subclass_method(METRICS_POOL)
+    def SubscribeState(
+        self, subscriptions: List[Tuple[str, float, float]]
+    ) -> List[Union[bool, Exception]]:
+        # TODO: IETF L3VPN does not support monitoring by now
+        return [False for _ in subscriptions]
+
+    @metered_subclass_method(METRICS_POOL)
+    def UnsubscribeState(
+        self, subscriptions: List[Tuple[str, float, float]]
+    ) -> List[Union[bool, Exception]]:
+        # TODO: IETF L3VPN does not support monitoring by now
+        return [False for _ in subscriptions]
+
+    def GetState(
+        self, blocking=False, terminate: Optional[threading.Event] = None
+    ) -> Iterator[Tuple[float, str, Any]]:
+        # TODO: IETF L3VPN does not support monitoring by now
+        return []
diff --git a/src/device/tests/test_unitary_ietf_l3vpn.py b/src/device/tests/test_unitary_ietf_l3vpn.py
new file mode 100644
index 0000000000000000000000000000000000000000..728ca691332c8abee7b5d6f5ad6c151240e540ed
--- /dev/null
+++ b/src/device/tests/test_unitary_ietf_l3vpn.py
@@ -0,0 +1,345 @@
+import json
+from json import dumps
+
+import requests
+
+from device.service.drivers.ietf_l3vpn.driver import IetfL3VpnDriver
+from device.service.Tools import RESOURCE_ENDPOINTS
+
+settings = {
+    "endpoints": [
+        {
+            "uuid": "access-pe",
+            "name": "access-pe",
+            "type": "copper",
+            "ce-ip": "1.1.1.1",
+            "address_ip": "3.3.2.1",
+            "address_prefix": 24,
+            "location": "access",
+            "mtu": 1500,
+            "ipv4_lan_prefixes": [
+                {"lan": "128.32.10.0/24", "lan_tag": 10},
+                {"lan": "128.32.20.0/24", "lan_tag": 20},
+            ],
+        },
+        {
+            "uuid": "cloud-pe",
+            "name": "cloud-pe",
+            "type": "copper",
+            "ce-ip": "1.1.1.1",
+            "address_ip": "3.3.2.1",
+            "address_prefix": 24,
+            "location": "cloud",
+            "mtu": 1500,
+            "ipv4_lan_prefixes": [{"lan": "172.1.101.0/24", "lan_tag": 101}],
+        },
+    ],
+    "scheme": "http",
+    "username": "admin",
+    "password": "admin",
+    "base_url": "/restconf/v2/data",
+    "timeout": 120,
+    "verify": False,
+}
+
+post_request_data = []
+get_request_data = []
+
+
+def mock_post(*args, **kwargs):
+    post_request_data.append((args, kwargs))
+
+
+def mock_get(*args, **kwargs):
+    get_request_data.append((args, kwargs))
+
+
+driver = IetfL3VpnDriver(address="1.2.3.4", port=0, **settings)
+
+
+def test_connect(monkeypatch):
+    global post_request_data
+    global get_request_data
+    post_request_data = []
+    get_request_data = []
+    monkeypatch.setattr(requests, "post", mock_post)
+    monkeypatch.setattr(requests, "get", mock_get)
+
+    driver.Connect()
+    assert not post_request_data
+    assert len(get_request_data) == 1
+    assert get_request_data[0][0] == (
+        "http://1.2.3.4:0/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services",
+    )
+    assert list(get_request_data[0][1].keys()) == ["timeout", "verify", "auth"]
+
+
+def test_GetConfig(monkeypatch):
+    global post_request_data
+    global get_request_data
+    post_request_data = []
+    get_request_data = []
+    monkeypatch.setattr(requests, "post", mock_post)
+    monkeypatch.setattr(requests, "get", mock_get)
+
+    resources_to_get = [RESOURCE_ENDPOINTS]
+    result_GetConfig = driver.GetConfig(resources_to_get)
+    assert result_GetConfig == [
+        (
+            "/endpoints/endpoint[access-pe]",
+            {
+                "uuid": "access-pe",
+                "name": "access-pe",
+                "type": "copper",
+                "location": "access",
+                "ce-ip": "1.1.1.1",
+                "address_ip": "3.3.2.1",
+                "address_prefix": 24,
+                "mtu": 1500,
+                "ipv4_lan_prefixes": [
+                    {"lan": "128.32.10.0/24", "lan_tag": 10},
+                    {"lan": "128.32.20.0/24", "lan_tag": 20},
+                ],
+            },
+        ),
+        (
+            "/endpoints/endpoint[cloud-pe]",
+            {
+                "uuid": "cloud-pe",
+                "name": "cloud-pe",
+                "type": "copper",
+                "location": "cloud",
+                "ce-ip": "1.1.1.1",
+                "address_ip": "3.3.2.1",
+                "address_prefix": 24,
+                "mtu": 1500,
+                "ipv4_lan_prefixes": [{"lan": "172.1.101.0/24", "lan_tag": 101}],
+            },
+        ),
+    ]
+
+
+def test_SetConfig(monkeypatch):
+    global post_request_data
+    global get_request_data
+    post_request_data = []
+    get_request_data = []
+    monkeypatch.setattr(requests, "post", mock_post)
+    monkeypatch.setattr(requests, "get", mock_get)
+
+    resources = [
+        (
+            "/services/service[vpn_A]",
+            json.dumps(
+                {
+                    "uuid": "vpn_A",
+                    "src_device_name": "ip-net-controller",
+                    "src_endpoint_name": settings["endpoints"][0]["name"],
+                    "src_site_location": settings["endpoints"][0]["location"],
+                    "src_ipv4_lan_prefixes": settings["endpoints"][0][
+                        "ipv4_lan_prefixes"
+                    ],
+                    "src_ce_address": settings["endpoints"][0]["ce-ip"],
+                    "src_pe_address": settings["endpoints"][0]["address_ip"],
+                    "src_ce_pe_network_prefix": settings["endpoints"][0][
+                        "address_prefix"
+                    ],
+                    "src_mtu": settings["endpoints"][0]["mtu"],
+                    "dst_device_name": "ip-net-controller",
+                    "dst_endpoint_name": settings["endpoints"][1]["name"],
+                    "dst_site_location": settings["endpoints"][1]["location"],
+                    "dst_ipv4_lan_prefixes": settings["endpoints"][1][
+                        "ipv4_lan_prefixes"
+                    ],
+                    "dst_ce_address": settings["endpoints"][1]["ce-ip"],
+                    "dst_pe_address": settings["endpoints"][1]["address_ip"],
+                    "dst_ce_pe_network_prefix": settings["endpoints"][1][
+                        "address_prefix"
+                    ],
+                    "dst_mtu": settings["endpoints"][1]["mtu"],
+                }
+            ),
+        )
+    ]
+    result_SetConfig = driver.SetConfig(resources)
+    assert result_SetConfig == [("/services/service[vpn_A]", True)]
+    assert len(get_request_data) == 1
+    assert get_request_data[0][0] == (
+        "http://1.2.3.4:0/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services/vpn-service=vpn_A",
+    )
+    assert len(post_request_data) == 1
+    assert post_request_data[0][0] == (
+        "http://1.2.3.4:0/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services/vpn-services",
+    )
+    assert post_request_data[0][1]["json"] == {
+        "ietf-l3vpn-svc:l3vpn-svc": {
+            "vpn-services": {"vpn-service": [{"vpn-id": "vpn_A"}]},
+            "sites": {
+                "site": [
+                    {
+                        "site-id": "site_access",
+                        "management": {"type": "ietf-l3vpn-svc:customer-managed"},
+                        "locations": {"location": [{"location-id": "access"}]},
+                        "devices": {
+                            "device": [
+                                {
+                                    "device-id": "ip-net-controller",
+                                    "location": "access",
+                                }
+                            ]
+                        },
+                        "routing-protocols": {
+                            "routing-protocol": [
+                                {
+                                    "type": "ietf-l3vpn-svc:static",
+                                    "static": {
+                                        "cascaded-lan-prefixes": {
+                                            "ipv4-lan-prefixes": [
+                                                {
+                                                    "lan": "128.32.10.0/24",
+                                                    "lan-tag": 10,
+                                                    "next-hop": "3.3.2.1",
+                                                },
+                                                {
+                                                    "lan": "128.32.20.0/24",
+                                                    "lan-tag": 20,
+                                                    "next-hop": "3.3.2.1",
+                                                },
+                                            ]
+                                        }
+                                    },
+                                }
+                            ]
+                        },
+                        "site-network-accesses": {
+                            "site-network-access": [
+                                {
+                                    "site-network-access-id": "access-pe",
+                                    "site-network-access-type": "ietf-l3vpn-svc:multipoint",
+                                    "device-reference": "ip-net-controller",
+                                    "vpn-attachment": {
+                                        "vpn-id": "vpn_A",
+                                        "site-role": "ietf-l3vpn-svc:hub-role",
+                                    },
+                                    "ip-connection": {
+                                        "ipv4": {
+                                            "address-allocation-type": "ietf-l3vpn-svc:static-address",
+                                            "addresses": {
+                                                "provider-address": "3.3.2.1",
+                                                "customer-address": "1.1.1.1",
+                                                "prefix-length": 24,
+                                            },
+                                        }
+                                    },
+                                    "service": {
+                                        "svc-mtu": 1500,
+                                        "svc-input-bandwidth": 1000000000,
+                                        "svc-output-bandwidth": 1000000000,
+                                        "qos": {
+                                            "qos-profile": {
+                                                "classes": {
+                                                    "class": [
+                                                        {
+                                                            "class-id": "src_qos_profile",
+                                                            "direction": (
+                                                                "ietf-l3vpn-svc:both",
+                                                            ),
+                                                            "latency": {
+                                                                "latency-boundary": 10
+                                                            },
+                                                            "bandwidth": {
+                                                                "guaranteed-bw-percent": 100
+                                                            },
+                                                        }
+                                                    ]
+                                                }
+                                            }
+                                        },
+                                    },
+                                }
+                            ]
+                        },
+                    },
+                    {
+                        "site-id": "site_cloud",
+                        "management": {"type": "ietf-l3vpn-svc:customer-managed"},
+                        "locations": {"location": [{"location-id": "cloud"}]},
+                        "devices": {
+                            "device": [
+                                {
+                                    "device-id": "ip-net-controller",
+                                    "location": "cloud",
+                                }
+                            ]
+                        },
+                        "routing-protocols": {
+                            "routing-protocol": [
+                                {
+                                    "type": "ietf-l3vpn-svc:static",
+                                    "static": {
+                                        "cascaded-lan-prefixes": {
+                                            "ipv4-lan-prefixes": [
+                                                {
+                                                    "lan": "172.1.101.0/24",
+                                                    "lan-tag": 101,
+                                                    "next-hop": "3.3.2.1",
+                                                }
+                                            ]
+                                        }
+                                    },
+                                }
+                            ]
+                        },
+                        "site-network-accesses": {
+                            "site-network-access": [
+                                {
+                                    "site-network-access-id": "cloud-pe",
+                                    "site-network-access-type": "ietf-l3vpn-svc:multipoint",
+                                    "device-reference": "ip-net-controller",
+                                    "vpn-attachment": {
+                                        "vpn-id": "vpn_A",
+                                        "site-role": "ietf-l3vpn-svc:spoke-role",
+                                    },
+                                    "ip-connection": {
+                                        "ipv4": {
+                                            "address-allocation-type": "ietf-l3vpn-svc:static-address",
+                                            "addresses": {
+                                                "provider-address": "3.3.2.1",
+                                                "customer-address": "1.1.1.1",
+                                                "prefix-length": 24,
+                                            },
+                                        }
+                                    },
+                                    "service": {
+                                        "svc-mtu": 1500,
+                                        "svc-input-bandwidth": 1000000000,
+                                        "svc-output-bandwidth": 1000000000,
+                                        "qos": {
+                                            "qos-profile": {
+                                                "classes": {
+                                                    "class": [
+                                                        {
+                                                            "class-id": "dst_qos_profile",
+                                                            "direction": (
+                                                                "ietf-l3vpn-svc:both",
+                                                            ),
+                                                            "latency": {
+                                                                "latency-boundary": 10
+                                                            },
+                                                            "bandwidth": {
+                                                                "guaranteed-bw-percent": 100
+                                                            },
+                                                        }
+                                                    ]
+                                                }
+                                            }
+                                        },
+                                    },
+                                }
+                            ]
+                        },
+                    },
+                ]
+            },
+        }
+    }
diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Service.py b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Service.py
index 97364dff8606f1af48bab362b94b968561792411..bf3f8aabca1c04260d32a4163ec2686931f520fc 100644
--- a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Service.py
+++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Service.py
@@ -26,7 +26,7 @@ from ..tools.HttpStatusCodes import HTTP_GATEWAYTIMEOUT, HTTP_NOCONTENT, HTTP_OK
 LOGGER = logging.getLogger(__name__)
 
 class L3VPN_Service(Resource):
-    @HTTP_AUTH.login_required
+    # @HTTP_AUTH.login_required
     def get(self, vpn_id : str):
         LOGGER.debug('VPN_Id: {:s}'.format(str(vpn_id)))
         LOGGER.debug('Request: {:s}'.format(str(request)))
@@ -52,7 +52,7 @@ class L3VPN_Service(Resource):
             response.status_code = HTTP_SERVERERROR
         return response
 
-    @HTTP_AUTH.login_required
+    # @HTTP_AUTH.login_required
     def delete(self, vpn_id : str):
         LOGGER.debug('VPN_Id: {:s}'.format(str(vpn_id)))
         LOGGER.debug('Request: {:s}'.format(str(request)))
diff --git a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Services.py b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Services.py
index 98d950952702d5cf1df8aa29edc50683e56a296e..47f6d5726225350976a67eeaacda64ceb32f0d7f 100644
--- a/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Services.py
+++ b/src/nbi/service/rest_server/nbi_plugins/ietf_l3vpn/L3VPN_Services.py
@@ -26,11 +26,11 @@ from .YangValidator import YangValidator
 LOGGER = logging.getLogger(__name__)
 
 class L3VPN_Services(Resource):
-    @HTTP_AUTH.login_required
+    # @HTTP_AUTH.login_required
     def get(self):
         return {}
 
-    @HTTP_AUTH.login_required
+    # @HTTP_AUTH.login_required
     def post(self):
         if not request.is_json: raise UnsupportedMediaType('JSON payload is required')
         request_data : Dict = request.json
diff --git a/src/nbi/tests/data/agg-net-descriptor.json b/src/nbi/tests/data/agg-net-descriptor.json
new file mode 100644
index 0000000000000000000000000000000000000000..bde4db62844084c0674312a60d7c1cbf8357de52
--- /dev/null
+++ b/src/nbi/tests/data/agg-net-descriptor.json
@@ -0,0 +1,628 @@
+{
+    "contexts": [
+        {
+            "context_id": {
+                "context_uuid": {
+                    "uuid": "admin"
+                }
+            }
+        }
+    ],
+    "topologies": [
+        {
+            "topology_id": {
+                "context_id": {
+                    "context_uuid": {
+                        "uuid": "admin"
+                    }
+                },
+                "topology_uuid": {
+                    "uuid": "admin"
+                }
+            }
+        }
+    ],
+    "devices": [
+        {
+            "device_id": {
+                "device_uuid": {
+                    "uuid": "ip-net-controller"
+                }
+            },
+            "name": "ip-net-controller",
+            "device_type": "ip-sdn-controller",
+            "device_operational_status": 1,
+            "device_drivers": [
+                13
+            ],
+            "device_config": {
+                "config_rules": [
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/address",
+                            "resource_value": "10.10.10.10"
+                        }
+                    },
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/port",
+                            "resource_value": "80"
+                        }
+                    },
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/settings",
+                            "resource_value": {
+                                "endpoints": [],
+                                "scheme": "http",
+                                "username": "admin",
+                                "password": "admin",
+                                "base_url": "/restconf/v2/data",
+                                "timeout": 120,
+                                "verify": false
+                            }
+                        }
+                    }
+                ]
+            },
+            "device_endpoints": []
+        },
+        {
+            "device_id": {
+                "device_uuid": {
+                    "uuid": "172.16.182.25"
+                }
+            },
+            "name": "172.16.182.25",
+            "device_type": "emu-packet-router",
+            "controller_id": {
+                "device_uuid": {
+                    "uuid": "ip-net-controller"
+                }
+            },
+            "device_operational_status": 1,
+            "device_drivers": [
+                0
+            ],
+            "device_config": {
+                "config_rules": [
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/address",
+                            "resource_value": "127.0.0.1"
+                        }
+                    },
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/port",
+                            "resource_value": "0"
+                        }
+                    },
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/settings",
+                            "resource_value": {
+                                "endpoints": [
+                                    {
+                                        "uuid": "mgmt",
+                                        "name": "mgmt",
+                                        "type": "mgmt"
+                                    },
+                                    {
+                                        "uuid": "200",
+                                        "name": "200",
+                                        "type": "optical",
+                                        "ce-ip": "128.32.33.2",
+                                        "address_ip": "128.32.33.254",
+                                        "address_prefix": "24",
+                                        "site_location": "access",
+                                        "mtu": "1500",
+                                        "ipv4_lan_prefixes": [
+                                            {
+                                                "lan": "128.32.10.0/24",
+                                                "lan_tag": "10"
+                                            },
+                                            {
+                                                "lan": "128.32.20.0/24",
+                                                "lan_tag": "20"
+                                            }
+                                        ]
+                                    },
+                                    {
+                                        "uuid": "500",
+                                        "name": "500",
+                                        "type": "optical"
+                                    },
+                                    {
+                                        "uuid": "501",
+                                        "name": "501",
+                                        "type": "optical"
+                                    }
+                                ]
+                            }
+                        }
+                    }
+                ]
+            }
+        },
+        {
+            "device_id": {
+                "device_uuid": {
+                    "uuid": "172.16.185.31"
+                }
+            },
+            "name": "172.16.185.31",
+            "device_type": "emu-packet-router",
+            "controller_id": {
+                "device_uuid": {
+                    "uuid": "ip-net-controller"
+                }
+            },
+            "device_operational_status": 1,
+            "device_drivers": [
+                0
+            ],
+            "device_config": {
+                "config_rules": [
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/address",
+                            "resource_value": "127.0.0.1"
+                        }
+                    },
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/port",
+                            "resource_value": "0"
+                        }
+                    },
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/settings",
+                            "resource_value": {
+                                "endpoints": [
+                                    {
+                                        "uuid": "mgmt",
+                                        "name": "mgmt",
+                                        "type": "mgmt"
+                                    },
+                                    {
+                                        "uuid": "500",
+                                        "name": "500",
+                                        "type": "optical"
+                                    },
+                                    {
+                                        "uuid": "501",
+                                        "name": "501",
+                                        "type": "optical"
+                                    }
+                                ]
+                            }
+                        }
+                    }
+                ]
+            }
+        },
+        {
+            "device_id": {
+                "device_uuid": {
+                    "uuid": "172.16.185.33"
+                }
+            },
+            "name": "172.16.185.33",
+            "device_type": "emu-packet-router",
+            "controller_id": {
+                "device_uuid": {
+                    "uuid": "ip-net-controller"
+                }
+            },
+            "device_operational_status": 1,
+            "device_drivers": [
+                0
+            ],
+            "device_config": {
+                "config_rules": [
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/address",
+                            "resource_value": "127.0.0.1"
+                        }
+                    },
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/port",
+                            "resource_value": "0"
+                        }
+                    },
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/settings",
+                            "resource_value": {
+                                "endpoints": [
+                                    {
+                                        "uuid": "mgmt",
+                                        "name": "mgmt",
+                                        "type": "mgmt"
+                                    },
+                                    {
+                                        "uuid": "500",
+                                        "name": "500",
+                                        "type": "optical"
+                                    },
+                                    {
+                                        "uuid": "501",
+                                        "name": "501",
+                                        "type": "optical"
+                                    }
+                                ]
+                            }
+                        }
+                    }
+                ]
+            }
+        },
+        {
+            "device_id": {
+                "device_uuid": {
+                    "uuid": "172.16.185.32"
+                }
+            },
+            "name": "172.16.185.32",
+            "device_type": "emu-packet-router",
+            "controller_id": {
+                "device_uuid": {
+                    "uuid": "ip-net-controller"
+                }
+            },
+            "device_operational_status": 1,
+            "device_drivers": [
+                0
+            ],
+            "device_config": {
+                "config_rules": [
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/address",
+                            "resource_value": "127.0.0.1"
+                        }
+                    },
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/port",
+                            "resource_value": "0"
+                        }
+                    },
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/settings",
+                            "resource_value": {
+                                "endpoints": [
+                                    {
+                                        "uuid": "mgmt",
+                                        "name": "mgmt",
+                                        "type": "mgmt"
+                                    },
+                                    {
+                                        "uuid": "200",
+                                        "name": "200",
+                                        "type": "optical",
+                                        "ce-ip": "172.10.33.2",
+                                        "address_ip": "172.10.33.254",
+                                        "address_prefix": "24",
+                                        "site_location": "cloud",
+                                        "mtu": "1500",
+                                        "ipv4_lan_prefixes": [
+                                            {
+                                                "lan": "172.1.101.0/24",
+                                                "lan_tag": "101"
+                                            }
+                                        ]
+                                    },
+                                    {
+                                        "uuid": "500",
+                                        "name": "500",
+                                        "type": "optical"
+                                    },
+                                    {
+                                        "uuid": "501",
+                                        "name": "501",
+                                        "type": "optical"
+                                    }
+                                ]
+                            }
+                        }
+                    }
+                ]
+            }
+        }
+    ],
+    "links": [
+        {
+            "link_id": {
+                "link_uuid": {
+                    "uuid": "172.16.182.25-500"
+                }
+            },
+            "name": "172.16.182.25-500",
+            "attributes": {
+                "total_capacity_gbps": 10,
+                "used_capacity_gbps": 0
+            },
+            "link_endpoint_ids": [
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.182.25"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "500"
+                    }
+                },
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.33"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "500"
+                    }
+                }
+            ]
+        },
+        {
+            "link_id": {
+                "link_uuid": {
+                    "uuid": "172.16.185.33-500"
+                }
+            },
+            "name": "172.16.185.33-500",
+            "attributes": {
+                "total_capacity_gbps": 10,
+                "used_capacity_gbps": 0
+            },
+            "link_endpoint_ids": [
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.33"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "500"
+                    }
+                },
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.182.25"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "500"
+                    }
+                }
+            ]
+        },
+        {
+            "link_id": {
+                "link_uuid": {
+                    "uuid": "172.16.182.25-501"
+                }
+            },
+            "name": "172.16.182.25-501",
+            "attributes": {
+                "total_capacity_gbps": 10,
+                "used_capacity_gbps": 0
+            },
+            "link_endpoint_ids": [
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.182.25"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "501"
+                    }
+                },
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.31"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "501"
+                    }
+                }
+            ]
+        },
+        {
+            "link_id": {
+                "link_uuid": {
+                    "uuid": "172.16.185.31-501"
+                }
+            },
+            "name": "172.16.185.31-501",
+            "attributes": {
+                "total_capacity_gbps": 10,
+                "used_capacity_gbps": 0
+            },
+            "link_endpoint_ids": [
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.31"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "501"
+                    }
+                },
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.182.25"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "501"
+                    }
+                }
+            ]
+        },
+        {
+            "link_id": {
+                "link_uuid": {
+                    "uuid": "172.16.185.31-500"
+                }
+            },
+            "name": "172.16.185.31-500",
+            "attributes": {
+                "total_capacity_gbps": 10,
+                "used_capacity_gbps": 0
+            },
+            "link_endpoint_ids": [
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.31"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "500"
+                    }
+                },
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.32"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "500"
+                    }
+                }
+            ]
+        },
+        {
+            "link_id": {
+                "link_uuid": {
+                    "uuid": "172.16.185.32-500"
+                }
+            },
+            "name": "172.16.185.32-500",
+            "attributes": {
+                "total_capacity_gbps": 10,
+                "used_capacity_gbps": 0
+            },
+            "link_endpoint_ids": [
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.32"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "500"
+                    }
+                },
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.31"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "500"
+                    }
+                }
+            ]
+        },
+        {
+            "link_id": {
+                "link_uuid": {
+                    "uuid": "172.16.185.33-501"
+                }
+            },
+            "name": "172.16.185.33-501",
+            "attributes": {
+                "total_capacity_gbps": 10,
+                "used_capacity_gbps": 0
+            },
+            "link_endpoint_ids": [
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.33"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "501"
+                    }
+                },
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.32"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "501"
+                    }
+                }
+            ]
+        },
+        {
+            "link_id": {
+                "link_uuid": {
+                    "uuid": "172.16.185.32-501"
+                }
+            },
+            "name": "172.16.185.32-501",
+            "attributes": {
+                "total_capacity_gbps": 10,
+                "used_capacity_gbps": 0
+            },
+            "link_endpoint_ids": [
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.32"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "501"
+                    }
+                },
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.33"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "501"
+                    }
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/src/nbi/tests/data/ip-net-descriptor.json b/src/nbi/tests/data/ip-net-descriptor.json
new file mode 100644
index 0000000000000000000000000000000000000000..dafeeb5bc22dd457fa7b924a30feac660b38f491
--- /dev/null
+++ b/src/nbi/tests/data/ip-net-descriptor.json
@@ -0,0 +1,535 @@
+{
+    "contexts": [
+        {
+            "context_id": {
+                "context_uuid": {
+                    "uuid": "admin"
+                }
+            }
+        }
+    ],
+    "topologies": [
+        {
+            "topology_id": {
+                "context_id": {
+                    "context_uuid": {
+                        "uuid": "admin"
+                    }
+                },
+                "topology_uuid": {
+                    "uuid": "admin"
+                }
+            }
+        }
+    ],
+    "devices": [
+        {
+            "device_id": {
+                "device_uuid": {
+                    "uuid": "172.16.182.25"
+                }
+            },
+            "name": "172.16.182.25",
+            "device_type": "emu-packet-router",
+            "device_operational_status": 1,
+            "device_drivers": [
+                0
+            ],
+            "device_config": {
+                "config_rules": [
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/address",
+                            "resource_value": "127.0.0.1"
+                        }
+                    },
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/port",
+                            "resource_value": "0"
+                        }
+                    },
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/settings",
+                            "resource_value": {
+                                "endpoints": [
+                                    {
+                                        "uuid": "mgmt",
+                                        "name": "mgmt",
+                                        "type": "mgmt"
+                                    },
+                                    {
+                                        "uuid": "200",
+                                        "name": "200",
+                                        "type": "optical"
+                                    },
+                                    {
+                                        "uuid": "500",
+                                        "name": "500",
+                                        "type": "optical"
+                                    },
+                                    {
+                                        "uuid": "501",
+                                        "name": "501",
+                                        "type": "optical"
+                                    }
+                                ]
+                            }
+                        }
+                    }
+                ]
+            }
+        },
+        {
+            "device_id": {
+                "device_uuid": {
+                    "uuid": "172.16.185.31"
+                }
+            },
+            "name": "172.16.185.31",
+            "device_type": "emu-packet-router",
+            "device_operational_status": 1,
+            "device_drivers": [
+                0
+            ],
+            "device_config": {
+                "config_rules": [
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/address",
+                            "resource_value": "127.0.0.1"
+                        }
+                    },
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/port",
+                            "resource_value": "0"
+                        }
+                    },
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/settings",
+                            "resource_value": {
+                                "endpoints": [
+                                    {
+                                        "uuid": "mgmt",
+                                        "name": "mgmt",
+                                        "type": "mgmt"
+                                    },
+                                    {
+                                        "uuid": "500",
+                                        "name": "500",
+                                        "type": "optical"
+                                    },
+                                    {
+                                        "uuid": "501",
+                                        "name": "501",
+                                        "type": "optical"
+                                    }
+                                ]
+                            }
+                        }
+                    }
+                ]
+            }
+        },
+        {
+            "device_id": {
+                "device_uuid": {
+                    "uuid": "172.16.185.33"
+                }
+            },
+            "name": "172.16.185.33",
+            "device_type": "emu-packet-router",
+            "device_operational_status": 1,
+            "device_drivers": [
+                0
+            ],
+            "device_config": {
+                "config_rules": [
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/address",
+                            "resource_value": "127.0.0.1"
+                        }
+                    },
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/port",
+                            "resource_value": "0"
+                        }
+                    },
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/settings",
+                            "resource_value": {
+                                "endpoints": [
+                                    {
+                                        "uuid": "mgmt",
+                                        "name": "mgmt",
+                                        "type": "mgmt"
+                                    },
+                                    {
+                                        "uuid": "500",
+                                        "name": "500",
+                                        "type": "optical"
+                                    },
+                                    {
+                                        "uuid": "501",
+                                        "name": "501",
+                                        "type": "optical"
+                                    }
+                                ]
+                            }
+                        }
+                    }
+                ]
+            }
+        },
+        {
+            "device_id": {
+                "device_uuid": {
+                    "uuid": "172.16.185.32"
+                }
+            },
+            "name": "172.16.185.32",
+            "device_type": "emu-packet-router",
+            "device_operational_status": 1,
+            "device_drivers": [
+                0
+            ],
+            "device_config": {
+                "config_rules": [
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/address",
+                            "resource_value": "127.0.0.1"
+                        }
+                    },
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/port",
+                            "resource_value": "0"
+                        }
+                    },
+                    {
+                        "action": 1,
+                        "custom": {
+                            "resource_key": "_connect/settings",
+                            "resource_value": {
+                                "endpoints": [
+                                    {
+                                        "uuid": "mgmt",
+                                        "name": "mgmt",
+                                        "type": "mgmt"
+                                    },
+                                    {
+                                        "uuid": "200",
+                                        "name": "200",
+                                        "type": "optical"
+                                    },
+                                    {
+                                        "uuid": "500",
+                                        "name": "500",
+                                        "type": "optical"
+                                    },
+                                    {
+                                        "uuid": "501",
+                                        "name": "501",
+                                        "type": "optical"
+                                    }
+                                ]
+                            }
+                        }
+                    }
+                ]
+            }
+        }
+    ],
+    "links": [
+        {
+            "link_id": {
+                "link_uuid": {
+                    "uuid": "172.16.182.25-500"
+                }
+            },
+            "name": "172.16.182.25-500",
+            "attributes": {
+                "total_capacity_gbps": 10,
+                "used_capacity_gbps": 0
+            },
+            "link_endpoint_ids": [
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.182.25"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "500"
+                    }
+                },
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.33"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "500"
+                    }
+                }
+            ]
+        },
+        {
+            "link_id": {
+                "link_uuid": {
+                    "uuid": "172.16.185.33-500"
+                }
+            },
+            "name": "172.16.185.33-500",
+            "attributes": {
+                "total_capacity_gbps": 10,
+                "used_capacity_gbps": 0
+            },
+            "link_endpoint_ids": [
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.33"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "500"
+                    }
+                },
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.182.25"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "500"
+                    }
+                }
+            ]
+        },
+        {
+            "link_id": {
+                "link_uuid": {
+                    "uuid": "172.16.182.25-501"
+                }
+            },
+            "name": "172.16.182.25-501",
+            "attributes": {
+                "total_capacity_gbps": 10,
+                "used_capacity_gbps": 0
+            },
+            "link_endpoint_ids": [
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.182.25"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "501"
+                    }
+                },
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.31"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "501"
+                    }
+                }
+            ]
+        },
+        {
+            "link_id": {
+                "link_uuid": {
+                    "uuid": "172.16.185.31-501"
+                }
+            },
+            "name": "172.16.185.31-501",
+            "attributes": {
+                "total_capacity_gbps": 10,
+                "used_capacity_gbps": 0
+            },
+            "link_endpoint_ids": [
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.31"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "501"
+                    }
+                },
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.182.25"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "501"
+                    }
+                }
+            ]
+        },
+        {
+            "link_id": {
+                "link_uuid": {
+                    "uuid": "172.16.185.31-500"
+                }
+            },
+            "name": "172.16.185.31-500",
+            "attributes": {
+                "total_capacity_gbps": 10,
+                "used_capacity_gbps": 0
+            },
+            "link_endpoint_ids": [
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.31"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "500"
+                    }
+                },
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.32"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "500"
+                    }
+                }
+            ]
+        },
+        {
+            "link_id": {
+                "link_uuid": {
+                    "uuid": "172.16.185.32-500"
+                }
+            },
+            "name": "172.16.185.32-500",
+            "attributes": {
+                "total_capacity_gbps": 10,
+                "used_capacity_gbps": 0
+            },
+            "link_endpoint_ids": [
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.32"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "500"
+                    }
+                },
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.31"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "500"
+                    }
+                }
+            ]
+        },
+        {
+            "link_id": {
+                "link_uuid": {
+                    "uuid": "172.16.185.33-501"
+                }
+            },
+            "name": "172.16.185.33-501",
+            "attributes": {
+                "total_capacity_gbps": 10,
+                "used_capacity_gbps": 0
+            },
+            "link_endpoint_ids": [
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.33"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "501"
+                    }
+                },
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.32"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "501"
+                    }
+                }
+            ]
+        },
+        {
+            "link_id": {
+                "link_uuid": {
+                    "uuid": "172.16.185.32-501"
+                }
+            },
+            "name": "172.16.185.32-501",
+            "attributes": {
+                "total_capacity_gbps": 10,
+                "used_capacity_gbps": 0
+            },
+            "link_endpoint_ids": [
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.32"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "501"
+                    }
+                },
+                {
+                    "device_id": {
+                        "device_uuid": {
+                            "uuid": "172.16.185.33"
+                        }
+                    },
+                    "endpoint_uuid": {
+                        "uuid": "501"
+                    }
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/src/service/requirements.in b/src/service/requirements.in
index 3f8d2a35453691420a9469dfffd0a0d2648c6397..d0dd6dcfe13610fc315a50437fb5f3e094b4ee5e 100644
--- a/src/service/requirements.in
+++ b/src/service/requirements.in
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 
+deepdiff==6.7.*
 anytree==2.8.0
 geopy==2.3.0
 netaddr==0.9.0
diff --git a/src/service/service/service_handler_api/FilterFields.py b/src/service/service/service_handler_api/FilterFields.py
index 78f084605bcd759825975cb7f11abc659506755b..bb96f28351d6c29de43d7dbb8a42db2a095795c3 100644
--- a/src/service/service/service_handler_api/FilterFields.py
+++ b/src/service/service/service_handler_api/FilterFields.py
@@ -44,6 +44,7 @@ DEVICE_DRIVER_VALUES = {
     DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN,
     DeviceDriverEnum.DEVICEDRIVER_OC,
     DeviceDriverEnum.DEVICEDRIVER_QKD,
+    DeviceDriverEnum.DEVICEDRIVER_IETF_L3VPN,
 }
 
 # Map allowed filter fields to allowed values per Filter field. If no restriction (free text) None is specified
diff --git a/src/service/service/service_handlers/__init__.py b/src/service/service/service_handlers/__init__.py
index f93cf011fe02139ae350b91eab52eb71ded0574d..b4d9f39ded994e629eed9847b94b359bbc9b027b 100644
--- a/src/service/service/service_handlers/__init__.py
+++ b/src/service/service/service_handlers/__init__.py
@@ -16,6 +16,7 @@ from common.proto.context_pb2 import DeviceDriverEnum, ServiceTypeEnum
 from ..service_handler_api.FilterFields import FilterFieldEnum
 from .l2nm_emulated.L2NMEmulatedServiceHandler import L2NMEmulatedServiceHandler
 from .l2nm_ietfl2vpn.L2NM_IETFL2VPN_ServiceHandler import L2NM_IETFL2VPN_ServiceHandler
+from .l3nm_ietfl3vpn.L3NM_IETFL3VPN_ServiceHandler import L3NM_IETFL3VPN_ServiceHandler
 from .l2nm_openconfig.L2NMOpenConfigServiceHandler import L2NMOpenConfigServiceHandler
 from .l3nm_emulated.L3NMEmulatedServiceHandler import L3NMEmulatedServiceHandler
 from .l3nm_openconfig.L3NMOpenConfigServiceHandler import L3NMOpenConfigServiceHandler
@@ -66,6 +67,12 @@ SERVICE_HANDLERS = [
             FilterFieldEnum.DEVICE_DRIVER : DeviceDriverEnum.DEVICEDRIVER_IETF_ACTN,
         }
     ]),
+    (L3NM_IETFL3VPN_ServiceHandler, [
+        {
+            FilterFieldEnum.SERVICE_TYPE  : ServiceTypeEnum.SERVICETYPE_L3NM,
+            FilterFieldEnum.DEVICE_DRIVER : DeviceDriverEnum.DEVICEDRIVER_IETF_L3VPN,
+        }
+    ]),
     (TapiServiceHandler, [
         {
             FilterFieldEnum.SERVICE_TYPE  : ServiceTypeEnum.SERVICETYPE_TAPI_CONNECTIVITY_SERVICE,
diff --git a/src/service/service/service_handlers/l3nm_ietfl3vpn/ConfigRules.py b/src/service/service/service_handlers/l3nm_ietfl3vpn/ConfigRules.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5638fc104c253be20ef1bbeb6c69a4392095ad2
--- /dev/null
+++ b/src/service/service/service_handlers/l3nm_ietfl3vpn/ConfigRules.py
@@ -0,0 +1,316 @@
+# 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.
+
+from typing import Dict, List, Tuple, TypedDict
+
+from common.proto.context_pb2 import Link
+from common.tools.object_factory.ConfigRule import (
+    json_config_rule_delete,
+    json_config_rule_set,
+)
+from context.client.ContextClient import ContextClient
+
+
+class LANPrefixesDict(TypedDict):
+    lan: str
+    lan_tag: str
+
+
+SITE_NETWORK_ACCESS_TYPE = "ietf-l3vpn-svc:multipoint"
+
+
+def create_site_dict(
+    site_id: str,
+    site_location: str,
+    device_uuid: str,
+    endpoint_uuid: str,
+    service_uuid: str,
+    role: str,
+    management_type: str,
+    ce_address: str,
+    pe_address: str,
+    ce_pe_network_prefix: int,
+    mtu: int,
+    input_bw: int,
+    output_bw: int,
+    qos_profile_id: str,
+    qos_profile_direction: str,
+    qos_profile_latency: int,
+    qos_profile_bw_guarantee: int,
+    lan_prefixes: List[LANPrefixesDict],
+) -> Dict:
+    """
+    Helper function that creates a dictionary representing a single 'site'
+    entry (including management, locations, devices, routing-protocols, and
+    site-network-accesses).
+    """
+    site_lan_prefixes = [
+        {
+            "lan": lp["lan"],
+            "lan-tag": lp["lan_tag"],
+            "next-hop": ce_address,
+        }
+        for lp in lan_prefixes
+    ]
+
+    return {
+        "site-id": site_id,
+        "management": {"type": management_type},
+        "locations": {"location": [{"location-id": site_location}]},
+        "devices": {
+            "device": [
+                {
+                    "device-id": device_uuid,
+                    "location": site_location,
+                }
+            ]
+        },
+        "routing-protocols": {
+            "routing-protocol": [
+                {
+                    "type": "ietf-l3vpn-svc:static",
+                    "static": {
+                        "cascaded-lan-prefixes": {
+                            "ipv4-lan-prefixes": site_lan_prefixes
+                        }
+                    },
+                }
+            ]
+        },
+        "site-network-accesses": {
+            "site-network-access": [
+                {
+                    "site-network-access-id": endpoint_uuid,
+                    "site-network-access-type": SITE_NETWORK_ACCESS_TYPE,
+                    "device-reference": device_uuid,
+                    "vpn-attachment": {
+                        "vpn-id": service_uuid,
+                        "site-role": role,
+                    },
+                    "ip-connection": {
+                        "ipv4": {
+                            "address-allocation-type": "ietf-l3vpn-svc:static-address",
+                            "addresses": {
+                                "provider-address": pe_address,
+                                "customer-address": ce_address,
+                                "prefix-length": ce_pe_network_prefix,
+                            },
+                        }
+                    },
+                    "service": {
+                        "svc-mtu": mtu,
+                        "svc-input-bandwidth": input_bw,
+                        "svc-output-bandwidth": output_bw,
+                        "qos": {
+                            "qos-profile": {
+                                "classes": {
+                                    "class": [
+                                        {
+                                            "class-id": qos_profile_id,
+                                            "direction": qos_profile_direction,
+                                            "latency": {
+                                                "latency-boundary": qos_profile_latency
+                                            },
+                                            "bandwidth": {
+                                                "guaranteed-bw-percent": qos_profile_bw_guarantee
+                                            },
+                                        }
+                                    ]
+                                }
+                            }
+                        },
+                    },
+                }
+            ]
+        },
+    }
+
+
+def setup_config_rules(
+    service_uuid: str, json_settings: Dict, operation_type: str
+) -> List[Dict]:
+    # --- Extract common or required fields for the source site ---
+    src_device_uuid: str = json_settings["src_device_name"]
+    src_endpoint_uuid: str = json_settings["src_endpoint_name"]
+    src_site_location: str = json_settings["src_site_location"]
+    src_ipv4_lan_prefixes: list[LANPrefixesDict] = json_settings.get(
+        "src_ipv4_lan_prefixes"
+    )
+    src_site_id: str = json_settings.get("src_site_id", f"site_{src_site_location}")
+    src_management_type: str = json_settings.get(
+        "src_management_type", "ietf-l3vpn-svc:provider-managed"
+    )
+    if src_management_type != "ietf-l3vpn-svc:provider-managed":
+        raise Exception("management type %s not supported", src_management_type)
+
+    src_role: str = "ietf-l3vpn-svc:hub-role"
+    src_ce_address: str = json_settings["src_ce_address"]
+    src_pe_address: str = json_settings["src_pe_address"]
+    src_ce_pe_network_prefix: int = json_settings["src_ce_pe_network_prefix"]
+    src_mtu: int = json_settings["src_mtu"]
+    src_input_bw: int = json_settings["src_input_bw"]
+    src_output_bw: int = json_settings["src_output_bw"]
+    src_qos_profile_id = "qos-realtime"
+    src_qos_profile_direction = "ietf-l3vpn-svc:both"
+    src_qos_profile_latency: int = json_settings["src_qos_profile_latency"]
+    src_qos_profile_bw_guarantee: int = json_settings.get(
+        "src_qos_profile_bw_guarantee", 100
+    )
+
+    # --- Extract common or required fields for the destination site ---
+    dst_device_uuid = json_settings["dst_device_name"]
+    dst_endpoint_uuid = json_settings["dst_endpoint_name"]
+    dst_site_location: str = json_settings["dst_site_location"]
+    dst_ipv4_lan_prefixes: list[LANPrefixesDict] = json_settings[
+        "dst_ipv4_lan_prefixes"
+    ]
+    dst_site_id: str = json_settings.get("dst_site_id", f"site_{dst_site_location}")
+    dst_management_type: str = json_settings.get(
+        "dst_management_type", "ietf-l3vpn-svc:provider-managed"
+    )
+    if dst_management_type != "ietf-l3vpn-svc:provider-managed":
+        raise Exception("management type %s not supported", dst_management_type)
+
+    dst_role: str = "ietf-l3vpn-svc:spoke-role"
+    dst_ce_address: str = json_settings["dst_ce_address"]
+    dst_pe_address: str = json_settings["dst_pe_address"]
+    dst_ce_pe_network_prefix: int = json_settings["dst_ce_pe_network_prefix"]
+    dst_mtu: int = json_settings["dst_mtu"]
+    dst_input_bw: int = json_settings["dst_input_bw"]
+    dst_output_bw: int = json_settings["dst_output_bw"]
+    dst_qos_profile_id = "qos-realtime"
+    dst_qos_profile_direction = "ietf-l3vpn-svc:both"
+    dst_qos_profile_latency: int = json_settings["dst_qos_profile_latency"]
+    dst_qos_profile_bw_guarantee: int = json_settings.get(
+        "dst_qos_profile_bw_guarantee", 100
+    )
+
+    # --- Build site dictionaries using the helper function ---
+    src_site_dict = create_site_dict(
+        site_id=src_site_id,
+        site_location=src_site_location,
+        device_uuid=src_device_uuid,
+        endpoint_uuid=src_endpoint_uuid,
+        service_uuid=service_uuid,
+        role=src_role,
+        management_type=src_management_type,
+        ce_address=src_ce_address,
+        pe_address=src_pe_address,
+        ce_pe_network_prefix=src_ce_pe_network_prefix,
+        mtu=src_mtu,
+        input_bw=src_input_bw,
+        output_bw=src_output_bw,
+        qos_profile_id=src_qos_profile_id,
+        qos_profile_direction=src_qos_profile_direction,
+        qos_profile_latency=src_qos_profile_latency,
+        qos_profile_bw_guarantee=src_qos_profile_bw_guarantee,
+        lan_prefixes=src_ipv4_lan_prefixes,
+    )
+
+    dst_site_dict = create_site_dict(
+        site_id=dst_site_id,
+        site_location=dst_site_location,
+        device_uuid=dst_device_uuid,
+        endpoint_uuid=dst_endpoint_uuid,
+        service_uuid=service_uuid,
+        role=dst_role,
+        management_type=dst_management_type,
+        ce_address=dst_ce_address,
+        pe_address=dst_pe_address,
+        ce_pe_network_prefix=dst_ce_pe_network_prefix,
+        mtu=dst_mtu,
+        input_bw=dst_input_bw,
+        output_bw=dst_output_bw,
+        qos_profile_id=dst_qos_profile_id,
+        qos_profile_direction=dst_qos_profile_direction,
+        qos_profile_latency=dst_qos_profile_latency,
+        qos_profile_bw_guarantee=dst_qos_profile_bw_guarantee,
+        lan_prefixes=dst_ipv4_lan_prefixes,
+    )
+
+    # --- Combine both sites into one structure ---
+    sites = {
+        "site": [
+            src_site_dict,
+            dst_site_dict,
+        ]
+    }
+
+    l3_vpn_data_model = {
+        "ietf-l3vpn-svc:l3vpn-svc": {
+            "vpn-services": {"vpn-service": [{"vpn-id": service_uuid}]},
+            "sites": sites,
+        }
+    }
+
+    json_config_rules = [
+        json_config_rule_set(
+            "/service[{:s}]/IETFL3VPN".format(service_uuid),
+            l3_vpn_data_model,
+        ),
+        json_config_rule_set(
+            "/service[{:s}]/IETFL3VPN/operation".format(service_uuid),
+            {"type": operation_type},
+        ),
+    ]
+
+    return json_config_rules
+
+
+def teardown_config_rules(service_uuid: str) -> List[Dict]:
+    json_config_rules = [
+        json_config_rule_delete(
+            "/service[{:s}]/IETFL3VPN".format(service_uuid),
+            {"id": service_uuid},
+        ),
+        json_config_rule_delete(
+            "/service[{:s}]/IETFL3VPN/operation".format(service_uuid),
+            {},
+        ),
+    ]
+    return json_config_rules
+
+
+def get_link_ep_device_names(
+    link: Link, context_client: ContextClient
+) -> Tuple[str, str, str, str]:
+    ep_ids = link.link_endpoint_ids
+    ep_device_id_1 = ep_ids[0].device_id
+    ep_uuid_1 = ep_ids[0].endpoint_uuid.uuid
+    device_obj_1 = context_client.GetDevice(ep_device_id_1)
+    for d_ep in device_obj_1.device_endpoints:
+        if d_ep.endpoint_id.endpoint_uuid.uuid == ep_uuid_1:
+            ep_name_1 = d_ep.name
+            break
+    else:
+        raise Exception("endpoint not found")
+    device_obj_name_1 = device_obj_1.name
+    ep_device_id_2 = ep_ids[1].device_id
+    ep_uuid_2 = ep_ids[1].endpoint_uuid.uuid
+    device_obj_2 = context_client.GetDevice(ep_device_id_2)
+    for d_ep in device_obj_2.device_endpoints:
+        if d_ep.endpoint_id.endpoint_uuid.uuid == ep_uuid_2:
+            ep_name_2 = d_ep.name
+            break
+    else:
+        raise Exception("endpoint not found")
+    device_obj_name_2 = device_obj_2.name
+    return (
+        device_obj_name_1,
+        ep_name_1,
+        device_obj_1,
+        device_obj_name_2,
+        ep_name_2,
+        device_obj_2,
+    )
diff --git a/src/service/service/service_handlers/l3nm_ietfl3vpn/L3NM_IETFL3VPN_ServiceHandler.py b/src/service/service/service_handlers/l3nm_ietfl3vpn/L3NM_IETFL3VPN_ServiceHandler.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa650c8096b9443b85114166ad1d0bc5b6f2fc55
--- /dev/null
+++ b/src/service/service/service_handlers/l3nm_ietfl3vpn/L3NM_IETFL3VPN_ServiceHandler.py
@@ -0,0 +1,546 @@
+# Copyright 2022-2025 ETSI OSG/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.
+
+import json
+import logging
+from typing import Any, List, Optional, Tuple, TypedDict, Union
+
+from deepdiff import DeepDiff
+
+from common.DeviceTypes import DeviceTypeEnum
+from common.method_wrappers.Decorator import MetricsPool, metered_subclass_method
+from common.proto.context_pb2 import (
+    ConfigRule,
+    Device,
+    DeviceId,
+    Service,
+    ServiceConfig,
+)
+from common.tools.object_factory.Device import json_device_id
+from common.type_checkers.Checkers import chk_type
+from service.service.service_handler_api._ServiceHandler import _ServiceHandler
+from service.service.service_handler_api.SettingsHandler import SettingsHandler
+from service.service.service_handler_api.Tools import (
+    get_device_endpoint_uuids,
+    get_endpoint_matching,
+)
+from service.service.task_scheduler.TaskExecutor import TaskExecutor
+
+from .ConfigRules import setup_config_rules, teardown_config_rules
+
+RUNNING_RESOURCE_KEY = "running_ietf_slice"
+CANDIDATE_RESOURCE_KEY = "candidate_ietf_slice"
+MTU = 1500
+
+LOGGER = logging.getLogger(__name__)
+
+METRICS_POOL = MetricsPool("Service", "Handler", labels={"handler": "l3nm_ietf_l3vpn"})
+
+
+class LANPrefixesDict(TypedDict):
+    lan: str
+    lan_tag: str
+
+
+class Ipv4Info(TypedDict):
+    src_lan: str
+    dst_lan: str
+    src_port: str
+    dst_port: str
+    vlan: str
+
+
+class QoSInfo(TypedDict):
+    src_qos_profile_latency: int
+    src_input_bw: int
+    src_output_bw: int
+    dst_qos_profile_latency: int
+    dst_input_bw: int
+    dst_output_bw: int
+
+
+def get_custom_config_rule(
+    service_config: ServiceConfig, resource_key: str
+) -> Optional[ConfigRule]:
+    """
+    Return the custom ConfigRule from the ServiceConfig matching the given resource_key,
+    or None if not found.
+    """
+    for cr in service_config.config_rules:
+        if (
+            cr.WhichOneof("config_rule") == "custom"
+            and cr.custom.resource_key == resource_key
+        ):
+            return cr
+    return None
+
+
+def load_json_rule_data(service_config: ServiceConfig) -> Tuple[dict, dict]:
+    """
+    Loads the running/candidate JSON data from the service_config for IETF slice data.
+    Raises an exception if either is missing.
+    """
+    running_cr = get_custom_config_rule(service_config, RUNNING_RESOURCE_KEY)
+    candidate_cr = get_custom_config_rule(service_config, CANDIDATE_RESOURCE_KEY)
+
+    if not running_cr or not candidate_cr:
+        raise ValueError("Missing running/candidate IETF slice config rules.")
+
+    running_data = json.loads(running_cr.custom.resource_value)
+    candidate_data = json.loads(candidate_cr.custom.resource_value)
+    return running_data, candidate_data
+
+
+def extract_match_criterion_ipv4_info(match_criterion: dict) -> Ipv4Info:
+    """
+    Extracts IPv4 match criteria data (src/dst IP, ports, VLAN) from a match_criterion dict.
+    """
+    src_lan = dst_lan = src_port = dst_port = vlan = ""
+    for type_value in match_criterion["match-type"]:
+        value = type_value["value"][0]
+        if type_value["type"] == "ietf-network-slice-service:source-ip-prefix":
+            src_lan = value
+        elif type_value["type"] == "ietf-network-slice-service:destination-ip-prefix":
+            dst_lan = value
+        elif type_value["type"] == "ietf-network-slice-service:source-tcp-port":
+            src_port = value
+        elif type_value["type"] == "ietf-network-slice-service:destination-tcp-port":
+            dst_port = value
+        elif type_value["type"] == "ietf-network-slice-service:vlan":
+            vlan = value
+
+    return Ipv4Info(
+        src_lan=src_lan,
+        dst_lan=dst_lan,
+        src_port=src_port,
+        dst_port=dst_port,
+        vlan=vlan,
+    )
+
+
+def extract_qos_info_from_connection_group(
+    src_sdp_id: str, dst_sdp_id: str, connectivity_constructs: list
+) -> QoSInfo:
+    """
+    Given a pair of SDP ids and a list of connectivity constructs, extract QoS info
+    such as latency and bandwidth (for both directions).
+    """
+
+    def _extract_qos_fields(cc: dict) -> Tuple[int, int]:
+        max_delay = 0
+        bandwidth = 0
+        metric_bounds = cc["service-slo-sle-policy"]["slo-policy"]["metric-bound"]
+        for metric_bound in metric_bounds:
+            if (
+                metric_bound["metric-type"]
+                == "ietf-network-slice-service:one-way-delay-maximum"
+                and metric_bound["metric-unit"] == "milliseconds"
+            ):
+                max_delay = int(metric_bound["bound"])
+            elif (
+                metric_bound["metric-type"]
+                == "ietf-network-slice-service:one-way-bandwidth"
+                and metric_bound["metric-unit"] == "Mbps"
+            ):
+                # Convert from Mbps to bps
+                bandwidth = int(metric_bound["bound"]) * 1000000
+        return max_delay, bandwidth
+
+    src_cc = next(
+        cc
+        for cc in connectivity_constructs
+        if cc["p2p-sender-sdp"] == src_sdp_id and cc["p2p-receiver-sdp"] == dst_sdp_id
+    )
+    dst_cc = next(
+        cc
+        for cc in connectivity_constructs
+        if cc["p2p-sender-sdp"] == dst_sdp_id and cc["p2p-receiver-sdp"] == src_sdp_id
+    )
+    src_max_delay, src_bandwidth = _extract_qos_fields(src_cc)
+    dst_max_delay, dst_bandwidth = _extract_qos_fields(dst_cc)
+
+    return QoSInfo(
+        src_qos_profile_latency=src_max_delay,
+        src_input_bw=src_bandwidth,
+        src_output_bw=dst_bandwidth,
+        dst_qos_profile_latency=dst_max_delay,
+        dst_input_bw=dst_bandwidth,
+        dst_output_bw=src_bandwidth,
+    )
+
+
+def get_endpoint_settings(device_obj: Device, endpoint_name: str) -> dict:
+    """
+    Helper to retrieve endpoint settings from a device's config rules given an endpoint name.
+    Raises an exception if not found.
+    """
+    for rule in device_obj.device_config.config_rules:
+        if (
+            rule.WhichOneof("config_rule") == "custom"
+            and rule.custom.resource_key == f"/endpoints/endpoint[{endpoint_name}]"
+        ):
+            return json.loads(rule.custom.resource_value)
+    raise ValueError(f"Endpoint settings not found for endpoint {endpoint_name}")
+
+
+class L3NM_IETFL3VPN_ServiceHandler(_ServiceHandler):
+    def __init__(  # pylint: disable=super-init-not-called
+        self, service: Service, task_executor: TaskExecutor, **settings
+    ) -> None:
+        self.__service = service
+        self.__task_executor = task_executor
+        self.__settings_handler = SettingsHandler(service.service_config, **settings)
+
+    def __find_IP_transport_edge_endpoints(
+        self, endpoints
+    ) -> Tuple[str, str, str, str, Device]:
+        """
+        Searches for two endpoints whose device controllers are IP_SDN_CONTROLLER.
+        Returns (src_device_uuid, src_endpoint_uuid, dst_device_uuid, dst_endpoint_uuid, controller_device).
+        Raises an exception if not found or if the two IP devices differ.
+        """
+
+        # Find the first IP transport edge endpoint from the head of endpoints
+        for ep in endpoints:
+            device_uuid, endpoint_uuid = get_device_endpoint_uuids(ep)
+            device_obj = self.__task_executor.get_device(
+                DeviceId(**json_device_id(device_uuid))
+            )
+            device_controller = self.__task_executor.get_device_controller(device_obj)
+            if device_controller.device_type == DeviceTypeEnum.IP_SDN_CONTROLLER.value:
+                src_device_uuid, src_endpoint_uuid = device_uuid, endpoint_uuid
+                src_device_controller = device_controller
+                break
+        else:
+            raise Exception("No IP transport edge endpoints found")
+
+        # Find the second IP transport edge endpoint from the tail of endpoints
+        for ep in reversed(endpoints):
+            device_uuid, endpoint_uuid = get_device_endpoint_uuids(ep)
+            device_obj = self.__task_executor.get_device(
+                DeviceId(**json_device_id(device_uuid))
+            )
+            device_controller = self.__task_executor.get_device_controller(device_obj)
+            if device_controller.device_type == DeviceTypeEnum.IP_SDN_CONTROLLER.value:
+                dst_device_uuid, dst_endpoint_uuid = device_uuid, endpoint_uuid
+                dst_device_controller = device_controller
+                break
+        else:
+            raise Exception("No IP transport edge endpoints found")
+
+        if src_device_controller != dst_device_controller:
+            raise Exception("Different Src-Dst devices not supported by now")
+
+        return (
+            src_device_uuid,
+            src_endpoint_uuid,
+            dst_device_uuid,
+            dst_endpoint_uuid,
+            src_device_controller,
+        )
+
+    def __build_resource_value_dict(
+        self,
+        service_id: str,
+        src_device_obj: Device,
+        dst_device_obj: Device,
+        src_endpoint_name: str,
+        dst_endpoint_name: str,
+        qos_info: QoSInfo,
+        src_endpoint_settings: dict,
+        dst_endpoint_settings: dict,
+        src_match_criterion_ipv4_info: Ipv4Info,
+        dst_match_criterion_ipv4_info: Ipv4Info,
+    ) -> dict:
+        """
+        Builds the final resource-value dict to be used when calling setup_config_rules().
+        """
+        # Prepare data for source
+        src_device_name = src_device_obj.name
+        src_ce_ip = src_endpoint_settings["address_ip"]
+        src_ce_prefix = src_endpoint_settings["address_prefix"]
+        src_lan_prefixes = [
+            LANPrefixesDict(
+                lan=src_match_criterion_ipv4_info["dst_lan"],
+                lan_tag=src_match_criterion_ipv4_info["vlan"],
+            )
+        ]
+
+        # Prepare data for destination
+        dst_device_name = dst_device_obj.name
+        dst_ce_ip = dst_endpoint_settings["address_ip"]
+        dst_ce_prefix = dst_endpoint_settings["address_prefix"]
+        dst_lan_prefixes = [
+            LANPrefixesDict(
+                lan=dst_match_criterion_ipv4_info["dst_lan"],
+                lan_tag=dst_match_criterion_ipv4_info["vlan"],
+            )
+        ]
+
+        return {
+            "uuid": service_id,
+            "src_device_name": src_device_name,
+            "src_endpoint_name": src_endpoint_name,
+            "src_site_location": src_endpoint_settings["site_location"],
+            "src_ipv4_lan_prefixes": src_lan_prefixes,
+            "src_ce_address": src_ce_ip,
+            "src_pe_address": src_ce_ip,
+            "src_ce_pe_network_prefix": src_ce_prefix,
+            "src_mtu": MTU,
+            "src_qos_profile_latency": qos_info["src_qos_profile_latency"],
+            "src_input_bw": qos_info["src_input_bw"],
+            "src_output_bw": qos_info["src_output_bw"],
+            "dst_device_name": dst_device_name,
+            "dst_endpoint_name": dst_endpoint_name,
+            "dst_site_location": dst_endpoint_settings["site_location"],
+            "dst_ipv4_lan_prefixes": dst_lan_prefixes,
+            "dst_ce_address": dst_ce_ip,
+            "dst_pe_address": dst_ce_ip,
+            "dst_ce_pe_network_prefix": dst_ce_prefix,
+            "dst_mtu": MTU,
+            "dst_qos_profile_latency": qos_info["dst_qos_profile_latency"],
+            "dst_input_bw": qos_info["dst_input_bw"],
+            "dst_output_bw": qos_info["dst_output_bw"],
+        }
+
+    @metered_subclass_method(METRICS_POOL)
+    def SetEndpoint(
+        self,
+        endpoints: List[Tuple[str, str, Optional[str]]],
+        connection_uuid: Optional[str] = None,
+    ) -> List[Union[bool, Exception]]:
+        chk_type("endpoints", endpoints, list)
+        if len(endpoints) < 2:
+            return []
+
+        results = []
+        service_config = self.__service.service_config
+
+        try:
+            # Identify IP transport edge endpoints
+            (
+                src_device_uuid,
+                src_endpoint_uuid,
+                dst_device_uuid,
+                dst_endpoint_uuid,
+                controller,
+            ) = self.__find_IP_transport_edge_endpoints(endpoints)
+
+            # Retrieve device objects
+            src_device_obj = self.__task_executor.get_device(
+                DeviceId(**json_device_id(src_device_uuid))
+            )
+            src_endpoint_obj = get_endpoint_matching(src_device_obj, src_endpoint_uuid)
+
+            dst_device_obj = self.__task_executor.get_device(
+                DeviceId(**json_device_id(dst_device_uuid))
+            )
+            dst_endpoint_obj = get_endpoint_matching(dst_device_obj, dst_endpoint_uuid)
+
+            # Obtain endpoint settings
+            src_endpoint_settings = get_endpoint_settings(
+                src_device_obj, src_endpoint_obj.name
+            )
+            dst_endpoint_settings = get_endpoint_settings(
+                dst_device_obj, dst_endpoint_obj.name
+            )
+
+            # Load running & candidate data, compute diff
+            running_data, candidate_data = load_json_rule_data(service_config)
+            running_candidate_diff = DeepDiff(running_data, candidate_data)
+
+            # Determine service_id and operation_type
+            slice_service = candidate_data["network-slice-services"]["slice-service"][0]
+            service_id = slice_service["id"]
+            if not running_candidate_diff:
+                operation_type = "create"
+            elif "values_changed" in running_candidate_diff:
+                operation_type = "update"
+
+            # Parse relevant connectivity data
+            sdps = slice_service["sdps"]["sdp"]
+            connection_group = slice_service["connection-groups"]["connection-group"][0]
+            connecitivity_constructs = connection_group["connectivity-construct"]
+
+            # The code below assumes a single connectivity construct or
+            # that the relevant one is the first in the list:
+            connecitivity_construct = connecitivity_constructs[0]
+            src_sdp_idx = connecitivity_construct["p2p-sender-sdp"]
+            dst_sdp_idx = connecitivity_construct["p2p-receiver-sdp"]
+
+            # QoS
+            qos_info = extract_qos_info_from_connection_group(
+                src_sdp_idx, dst_sdp_idx, connecitivity_constructs
+            )
+
+            # Retrieve match-criterion info
+            src_sdp = next(sdp for sdp in sdps if sdp["id"] == src_sdp_idx)
+            dst_sdp = next(sdp for sdp in sdps if sdp["id"] == dst_sdp_idx)
+
+            src_match_criterion = src_sdp["service-match-criteria"]["match-criterion"][
+                0
+            ]
+            dst_match_criterion = dst_sdp["service-match-criteria"]["match-criterion"][
+                0
+            ]
+            src_match_criterion_ipv4_info = extract_match_criterion_ipv4_info(
+                src_match_criterion
+            )
+            dst_match_criterion_ipv4_info = extract_match_criterion_ipv4_info(
+                dst_match_criterion
+            )
+
+            # Build resource dict & config rules
+            resource_value_dict = self.__build_resource_value_dict(
+                service_id=service_id,
+                src_device_obj=src_device_obj,
+                dst_device_obj=dst_device_obj,
+                src_endpoint_name=src_endpoint_obj.name,
+                dst_endpoint_name=dst_endpoint_obj.name,
+                qos_info=qos_info,
+                src_endpoint_settings=src_endpoint_settings,
+                dst_endpoint_settings=dst_endpoint_settings,
+                src_match_criterion_ipv4_info=src_match_criterion_ipv4_info,
+                dst_match_criterion_ipv4_info=dst_match_criterion_ipv4_info,
+            )
+            json_config_rules = setup_config_rules(
+                service_id, resource_value_dict, operation_type
+            )
+
+            # Configure device
+            del controller.device_config.config_rules[:]
+            for jcr in json_config_rules:
+                controller.device_config.config_rules.append(ConfigRule(**jcr))
+            self.__task_executor.configure_device(controller)
+        except Exception as e:  # pylint: disable=broad-except
+            LOGGER.exception(
+                "Unable to SetEndpoint for Service({:s})".format(str(service_id))
+            )
+            results.append(e)
+
+        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]]:
+        chk_type("endpoints", endpoints, list)
+        if len(endpoints) < 2:
+            return []
+        service_config = self.__service.service_config
+        ietf_slice_candidate_cr = get_custom_config_rule(
+            service_config, CANDIDATE_RESOURCE_KEY
+        )
+        candidate_resource_value_dict = json.loads(
+            ietf_slice_candidate_cr.custom.resource_value
+        )
+        service_id = candidate_resource_value_dict["network-slice-services"][
+            "slice-service"
+        ][0]["id"]
+        results = []
+        try:
+            src_device_uuid, _ = get_device_endpoint_uuids(endpoints[0])
+            src_device = self.__task_executor.get_device(
+                DeviceId(**json_device_id(src_device_uuid))
+            )
+            src_controller = self.__task_executor.get_device_controller(src_device)
+
+            dst_device_uuid, _ = get_device_endpoint_uuids(endpoints[1])
+            dst_device = self.__task_executor.get_device(
+                DeviceId(**json_device_id(dst_device_uuid))
+            )
+            dst_controller = self.__task_executor.get_device_controller(dst_device)
+            if (
+                src_controller.device_id.device_uuid.uuid
+                != dst_controller.device_id.device_uuid.uuid
+            ):
+                raise Exception("Different Src-Dst devices not supported by now")
+            controller = src_controller
+            json_config_rules = teardown_config_rules(service_id)
+            del controller.device_config.config_rules[:]
+            for jcr in json_config_rules:
+                controller.device_config.config_rules.append(ConfigRule(**jcr))
+            self.__task_executor.configure_device(controller)
+            results.append(True)
+        except Exception as e:  # pylint: disable=broad-except
+            LOGGER.exception(
+                "Unable to DeleteEndpoint for Service({:s})".format(str(service_id))
+            )
+            results.append(e)
+
+        return results
+
+    @metered_subclass_method(METRICS_POOL)
+    def SetConstraint(
+        self, constraints: List[Tuple[str, Any]]
+    ) -> List[Union[bool, Exception]]:
+        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]]:
+        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]]:
+        chk_type("resources", resources, list)
+        if len(resources) == 0:
+            return []
+
+        results = []
+        for resource in resources:
+            try:
+                resource_value = json.loads(resource[1])
+                self.__settings_handler.set(resource[0], resource_value)
+                results.append(True)
+            except Exception as e:  # pylint: disable=broad-except
+                LOGGER.exception("Unable to SetConfig({:s})".format(str(resource)))
+                results.append(e)
+
+        return results
+
+    @metered_subclass_method(METRICS_POOL)
+    def DeleteConfig(
+        self, resources: List[Tuple[str, Any]]
+    ) -> List[Union[bool, Exception]]:
+        chk_type("resources", resources, list)
+        if len(resources) == 0:
+            return []
+
+        results = []
+        for resource in resources:
+            try:
+                self.__settings_handler.delete(resource[0])
+            except Exception as e:  # pylint: disable=broad-except
+                LOGGER.exception("Unable to DeleteConfig({:s})".format(str(resource)))
+                results.append(e)
+
+        return results
diff --git a/src/service/service/service_handlers/l3nm_ietfl3vpn/__init__.py b/src/service/service/service_handlers/l3nm_ietfl3vpn/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..906dd19f3c948b03263251f60addb49e2fb522dc
--- /dev/null
+++ b/src/service/service/service_handlers/l3nm_ietfl3vpn/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2022-2025 ETSI OSG/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.
+