diff --git a/gso/migrations/versions/2023-12-18_bacd55c26106_add_ibgp_mesh_workflow.py b/gso/migrations/versions/2023-12-18_bacd55c26106_add_ibgp_mesh_workflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..27610f3160c006f17269072623fab4667869a3ba
--- /dev/null
+++ b/gso/migrations/versions/2023-12-18_bacd55c26106_add_ibgp_mesh_workflow.py
@@ -0,0 +1,39 @@
+"""Add iBGP mesh workflow.
+
+Revision ID: bacd55c26106
+Revises: 815033570ad7
+Create Date: 2023-12-18 17:58:29.581963
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = 'bacd55c26106'
+down_revision = '815033570ad7'
+branch_labels = None
+depends_on = None
+
+
+from orchestrator.migrations.helpers import create_workflow, delete_workflow
+
+new_workflows = [
+    {
+        "name": "update_ibgp_mesh",
+        "target": "MODIFY",
+        "description": "Update iBGP mesh",
+        "product_type": "Router"
+    }
+]
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    for workflow in new_workflows:
+        create_workflow(conn, workflow)
+
+
+def downgrade() -> None:
+    conn = op.get_bind()
+    for workflow in new_workflows:
+        delete_workflow(conn, workflow["name"])
diff --git a/gso/translations/en-GB.json b/gso/translations/en-GB.json
index ef849f987e5670543adea2eb5b41f7a0ba9d0c29..c8f8d2410f4b94719e127739b2a6e711e01e2efe 100644
--- a/gso/translations/en-GB.json
+++ b/gso/translations/en-GB.json
@@ -41,6 +41,7 @@
         "migrate_iptrunk": "Migrate IP Trunk",
         "modify_isis_metric": "Modify the ISIS metric",
         "modify_trunk_interface": "Modify IP Trunk interface",
-        "redeploy_base_config": "Redeploy base config"
+        "redeploy_base_config": "Redeploy base config",
+        "update_ibgp_mesh": "Update iBGP mesh"
     }
 }
diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py
index 28ba2b51525cee346a26401e7f17c89e18229845..56f8a191946a79a786bc8764f70c03c77d9abd1b 100644
--- a/gso/workflows/__init__.py
+++ b/gso/workflows/__init__.py
@@ -11,6 +11,7 @@ LazyWorkflowInstance("gso.workflows.iptrunk.terminate_iptrunk", "terminate_iptru
 LazyWorkflowInstance("gso.workflows.router.create_router", "create_router")
 LazyWorkflowInstance("gso.workflows.router.redeploy_base_config", "redeploy_base_config")
 LazyWorkflowInstance("gso.workflows.router.terminate_router", "terminate_router")
+LazyWorkflowInstance("gso.workflows.router.update_ibgp_mesh", "update_ibgp_mesh")
 LazyWorkflowInstance("gso.workflows.site.create_site", "create_site")
 LazyWorkflowInstance("gso.workflows.site.modify_site", "modify_site")
 LazyWorkflowInstance("gso.workflows.site.terminate_site", "terminate_site")
diff --git a/gso/workflows/router/update_ibgp_mesh.py b/gso/workflows/router/update_ibgp_mesh.py
new file mode 100644
index 0000000000000000000000000000000000000000..c6bd0e81193960f17ea8c1695523e05d438535d4
--- /dev/null
+++ b/gso/workflows/router/update_ibgp_mesh.py
@@ -0,0 +1,224 @@
+from typing import Any
+
+from orchestrator.forms import FormPage
+from orchestrator.targets import Target
+from orchestrator.types import FormGenerator, State, UUIDstr
+from orchestrator.workflow import StepList, done, init, step, workflow
+from orchestrator.workflows.steps import resync, store_process_subscription, unsync
+from orchestrator.workflows.utils import wrap_modify_initial_input_form
+from pydantic import root_validator
+
+from gso.products.product_blocks.router import RouterRole
+from gso.products.product_types.router import Router
+from gso.services import provisioning_proxy, subscriptions
+from gso.services.provisioning_proxy import pp_interaction, indifferent_pp_interaction
+from gso.services.subscriptions import get_active_trunks_that_terminate_on_router
+
+
+def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
+    subscription = Router.from_subscription(subscription_id)
+
+    class AddBGPSessionForm(FormPage):
+        class Config:
+            title = f"Add {subscription.router.router_fqdn} to the iBGP mesh?"
+
+        @root_validator
+        def router_has_a_trunk(cls, values: dict[str, Any]) -> dict[str, Any]:
+            if len(get_active_trunks_that_terminate_on_router(subscription_id)) == 0:
+                msg = "Selected router does not terminate any active IP trunks."
+                raise ValueError(msg)
+
+            return values
+
+    yield AddBGPSessionForm
+
+    return {"subscription": subscription}
+
+
+@step("Calculate list of all active PE routers")
+def calculate_pe_router_list() -> State:
+    all_routers = [Router.from_subscription(r["subscription_id"]) for r in
+                   subscriptions.get_active_router_subscriptions()]
+    all_pe_routers = [router for router in all_routers if router.router.router_role == RouterRole.PE]
+
+    return {"pe_router_list": all_pe_routers}
+
+
+def _generate_pe_inventory(pe_router_list: list[Router]) -> dict[str, Any]:
+    return {
+        "_meta": {
+            "vars": {
+                router.router.router_fqdn: {
+                    "lo4": router.router.router_lo_ipv4_address,
+                    "lo6": router.router.router_lo_ipv6_address,
+                    "vendor": router.router.vendor,
+                }
+            } for router in pe_router_list
+        },
+        "all": {
+            "hosts": {
+                router.router.router_fqdn: None for router in pe_router_list
+            }
+        }
+    }
+
+
+@step("[DRY RUN] Add P router to iBGP mesh")
+def add_p_to_mesh_dry(subscription: Router, callback_route: str, pe_router_list: list[Router]) -> State:
+    extra_vars = {
+        "dry_run": True,
+        "subscription": subscription
+    }
+
+    provisioning_proxy.execute_playbook(
+        playbook_name="update_ibgp_mesh.yaml",
+        callback_route=callback_route,
+        inventory=_generate_pe_inventory(pe_router_list),
+        extra_vars=extra_vars,
+    )
+
+    return {"subscription": subscription}
+
+
+@step("[FOR REAL] Add P router to iBGP mesh")
+def add_p_to_mesh_real(subscription: Router, callback_route: str, pe_router_list: list[Router]) -> State:
+    extra_vars = {
+        "dry_run": False,
+        "subscription": subscription
+    }
+
+    provisioning_proxy.execute_playbook(
+        playbook_name="update_ibgp_mesh.yaml",
+        callback_route=callback_route,
+        inventory=_generate_pe_inventory(pe_router_list),
+        extra_vars=extra_vars,
+    )
+
+    return {"subscription": subscription}
+
+
+@step("[DRY RUN] Add all PE routers to P router iBGP table")
+def add_all_pe_to_p_dry(subscription: Router, pe_router_list: list[Router], callback_route: str) -> State:
+    extra_vars = {
+        "dry_run": True,
+        "pe_router_list": {
+            router.router.router_fqdn: {
+                "lo4": router.router.router_lo_ipv4_address,
+                "lo6": router.router.router_lo_ipv6_address,
+                "vendor": router.router.vendor,
+            } for router in pe_router_list
+        },
+    }
+
+    inventory = {
+        "all": {
+            "hosts": {
+                router.router.router_fqdn: None
+            } for router in pe_router_list
+        }
+    }
+
+    provisioning_proxy.execute_playbook(
+        playbook_name="update_ibgp_mesh.yaml",
+        callback_route=callback_route,
+        inventory=inventory,
+        extra_vars=extra_vars,
+    )
+
+    return {"subscription": subscription}
+
+
+@step("[FOR REAL] Add all PE routers to P router iBGP table")
+def add_all_pe_to_p_real(subscription: Router, pe_router_list: list[Router], callback_route: str) -> State:
+    extra_vars = {
+        "dry_run": False,
+        "pe_router_list": {
+            router.router.router_fqdn: {
+                "lo4": router.router.router_lo_ipv4_address,
+                "lo6": router.router.router_lo_ipv6_address,
+                "vendor": router.router.vendor,
+            } for router in pe_router_list
+        },
+    }
+
+    inventory = {
+        "all": {
+            "hosts": {
+                router.router.router_fqdn: None
+            } for router in pe_router_list
+        }
+    }
+
+    provisioning_proxy.execute_playbook(
+        playbook_name="update_ibgp_mesh.yaml",
+        callback_route=callback_route,
+        inventory=inventory,
+        extra_vars=extra_vars,
+    )
+
+    return {"subscription": subscription}
+
+
+@step("Verify iBGP session health")
+def check_ibgp_session(subscription: Router, callback_route: str) -> State:
+    inventory = {
+        "all": {
+            "hosts": {
+                subscription.router.router_fqdn: None
+            }
+        }
+    }
+
+    provisioning_proxy.execute_playbook(
+        playbook_name="check_ibgp.yaml",
+        callback_route=callback_route,
+        inventory=inventory,
+        extra_vars={},
+    )
+
+    return {"subscription": subscription}
+
+
+@step("Add the router to LibreNMS")
+def add_device_to_librenms(subscription: Router) -> State:
+    librenms_client.add_device(subscription)
+
+    return {"subscription": subscription}
+
+
+@step("Update subscription model")
+def update_subscription_model(subscription: Router) -> State:
+    subscription.router.router_access_via_ts = False
+
+    return {"subscription": subscription}
+
+
+@workflow(
+    "Update iBGP mesh",
+    initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator),
+    target=Target.MODIFY,
+)
+def update_ibgp_mesh() -> StepList:
+    """Update the iBGP mesh with a new P router
+
+    * Add the new P-router to all other PE-routers in the network, including a dry run.
+    * Add all PE-routers to the P-router, including a dry run.
+    * Verify that the iBGP session is up.
+    * Add the new P-router to LibreNMS.
+    * Update the subscription model.
+    """
+    return (
+            init
+            >> store_process_subscription(Target.MODIFY)
+            >> unsync
+            >> calculate_pe_router_list
+            >> pp_interaction(add_p_to_mesh_dry)
+            >> pp_interaction(add_p_to_mesh_real)
+            >> pp_interaction(add_all_pe_to_p_dry)
+            >> pp_interaction(add_all_pe_to_p_real)
+            >> indifferent_pp_interaction(check_ibgp_session)
+            >> add_device_to_librenms
+            >> update_subscription_model
+            >> resync
+            >> done
+    )