{"id":2151183,"url":"http://patchwork.ozlabs.org/api/patches/2151183/","web_url":"http://patchwork.ozlabs.org/project/ovn/patch/20251016113549.2997884-5-moloings@redhat.com/","project":{"id":68,"url":"http://patchwork.ozlabs.org/api/projects/68/","name":"Open Virtual Network development","link_name":"ovn","list_id":"ovs-dev.openvswitch.org","list_email":"ovs-dev@openvswitch.org","web_url":"http://openvswitch.org/","scm_url":"","webscm_url":"","list_archive_url":"","list_archive_url_format":"","commit_url_format":""},"msgid":"<20251016113549.2997884-5-moloings@redhat.com>","list_archive_url":null,"date":"2025-10-16T11:35:49","name":"[ovs-dev,v4,4/4] ic: Add Transit Router port support.","commit_ref":null,"pull_url":null,"state":"superseded","archived":false,"hash":"ff9cbf73fe717cb2e87a17cfbd7d3dfc23a935a5","submitter":{"id":91032,"url":"http://patchwork.ozlabs.org/api/people/91032/","name":"Mairtin O'Loingsigh","email":"moloings@redhat.com"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/ovn/patch/20251016113549.2997884-5-moloings@redhat.com/mbox/","series":[{"id":478039,"url":"http://patchwork.ozlabs.org/api/series/478039/","web_url":"http://patchwork.ozlabs.org/project/ovn/list/?series=478039","date":"2025-10-16T11:35:45","name":"Add support for transit routers.","version":4,"mbox":"http://patchwork.ozlabs.org/series/478039/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2151183/comments/","check":"fail","checks":"http://patchwork.ozlabs.org/api/patches/2151183/checks/","tags":{},"related":[],"headers":{"Return-Path":"<ovs-dev-bounces@openvswitch.org>","X-Original-To":["incoming@patchwork.ozlabs.org","dev@openvswitch.org"],"Delivered-To":["patchwork-incoming@legolas.ozlabs.org","ovs-dev@lists.linuxfoundation.org"],"Authentication-Results":["legolas.ozlabs.org;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256\n header.s=mimecast20190719 header.b=fm3Xt5xQ;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org\n (client-ip=2605:bc80:3010::138; helo=smtp1.osuosl.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org)","smtp1.osuosl.org;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key)\n header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256\n header.s=mimecast20190719 header.b=fm3Xt5xQ","smtp3.osuosl.org; dmarc=pass (p=quarantine dis=none)\n header.from=redhat.com","smtp3.osuosl.org;\n dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com\n header.a=rsa-sha256 header.s=mimecast20190719 header.b=fm3Xt5xQ"],"Received":["from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4cnQrg0J8pz1yHZ\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 16 Oct 2025 22:36:18 +1100 (AEDT)","from localhost (localhost [127.0.0.1])\n\tby smtp1.osuosl.org (Postfix) with ESMTP id 5E8AC84103;\n\tThu, 16 Oct 2025 11:36:17 +0000 (UTC)","from smtp1.osuosl.org ([127.0.0.1])\n by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id h8JHHgffaPZ7; Thu, 16 Oct 2025 11:36:09 +0000 (UTC)","from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56])\n\tby smtp1.osuosl.org (Postfix) with ESMTPS id D05AB83FEE;\n\tThu, 16 Oct 2025 11:36:05 +0000 (UTC)","from lf-lists.osuosl.org (localhost [127.0.0.1])\n\tby lists.linuxfoundation.org (Postfix) with ESMTP id AD954C058E;\n\tThu, 16 Oct 2025 11:36:05 +0000 (UTC)","from smtp3.osuosl.org (smtp3.osuosl.org [140.211.166.136])\n by lists.linuxfoundation.org (Postfix) with ESMTP id 0EB32C058E\n for <dev@openvswitch.org>; Thu, 16 Oct 2025 11:36:04 +0000 (UTC)","from localhost (localhost [127.0.0.1])\n by smtp3.osuosl.org (Postfix) with ESMTP id DF57161B47\n for <dev@openvswitch.org>; Thu, 16 Oct 2025 11:36:03 +0000 (UTC)","from smtp3.osuosl.org ([127.0.0.1])\n by localhost (smtp3.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id 0OnKJG5-oMMG for <dev@openvswitch.org>;\n Thu, 16 Oct 2025 11:35:59 +0000 (UTC)","from us-smtp-delivery-124.mimecast.com\n (us-smtp-delivery-124.mimecast.com [170.10.129.124])\n by smtp3.osuosl.org (Postfix) with ESMTPS id 22720608E9\n for <dev@openvswitch.org>; Thu, 16 Oct 2025 11:35:58 +0000 (UTC)","from mail-wm1-f72.google.com (mail-wm1-f72.google.com\n [209.85.128.72]) by relay.mimecast.com with ESMTP with STARTTLS\n (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n us-mta-593-5tg7muwWMky9Ecmc-rqJLw-1; Thu, 16 Oct 2025 07:35:56 -0400","by mail-wm1-f72.google.com with SMTP id\n 5b1f17b1804b1-46e4cb3e4deso6449455e9.1\n for <dev@openvswitch.org>; Thu, 16 Oct 2025 04:35:56 -0700 (PDT)","from moloings-thinkpadp1gen7.rmtie.csb\n ([2001:bb6:2be4:f100:a13c:8a56:eb6f:ed3f])\n by smtp.gmail.com with ESMTPSA id\n 5b1f17b1804b1-4711442dbaesm22501605e9.8.2025.10.16.04.35.53\n for <dev@openvswitch.org>\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Thu, 16 Oct 2025 04:35:53 -0700 (PDT)"],"X-Virus-Scanned":["amavis at osuosl.org","amavis at osuosl.org"],"X-Comment":"SPF check N/A for local connections - client-ip=140.211.9.56;\n helo=lists.linuxfoundation.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=<UNKNOWN> ","DKIM-Filter":["OpenDKIM Filter v2.11.0 smtp1.osuosl.org D05AB83FEE","OpenDKIM Filter v2.11.0 smtp3.osuosl.org 22720608E9"],"Received-SPF":"Pass (mailfrom) identity=mailfrom; client-ip=170.10.129.124;\n helo=us-smtp-delivery-124.mimecast.com; envelope-from=moloings@redhat.com;\n receiver=<UNKNOWN>","DMARC-Filter":"OpenDMARC Filter v1.4.2 smtp3.osuosl.org 22720608E9","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n s=mimecast20190719; t=1760614558;\n h=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n to:to:cc:mime-version:mime-version:content-type:content-type:\n content-transfer-encoding:content-transfer-encoding:\n in-reply-to:in-reply-to:references:references;\n bh=xBjvjBVDvuNDJl7Ztrz7Sv/zSZc4VZ2ui6iWRZ3GnZs=;\n b=fm3Xt5xQPEGZP4La8S2mPECeJl+8oUVf+rBWKC3XVTxiu9JELFbiY7OzGC3q1mDo4xtPTg\n TD7R2LM72yuVpAGyRduYZSDQJzuKEG6wepy6IVdG+9uoFGDdIlqHt7dIvBjD9x/4ZbewqN\n 1Ay4j12LPPmHrWmY84CfpZBIOSDWHP8=","X-MC-Unique":"5tg7muwWMky9Ecmc-rqJLw-1","X-Mimecast-MFC-AGG-ID":"5tg7muwWMky9Ecmc-rqJLw_1760614556","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20230601; t=1760614555; x=1761219355;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:to:from:x-gm-message-state:from:to:cc\n :subject:date:message-id:reply-to;\n bh=xBjvjBVDvuNDJl7Ztrz7Sv/zSZc4VZ2ui6iWRZ3GnZs=;\n b=GhLyeSbyUSxj2rmp8KjBpbLQ/XE5E9+rUG5m8mKHMR4dvmfYcRoJ+1OVsRmWbIT45K\n KBINO4pt9q7lLLlWCel/pG1++fpd3Qa4Op5csxwvndhnt7Nl41Z/RKTqpPA3YKas0d71\n 4nSCwR8TOzS+g+4ddP8Hg5KIKdDP0HgfY+eO3G9xYkpA0vhbAxhWhkYmAz1RGXc48+3C\n 46T/Xxu4bUiJWwseS6VFM2aJBfbCYgcwec/4qEohXFH5UUyRSXvJvu120x/+ioFjQl6V\n MUQFdmPtnMvHOC2r/7ExXA4ICDz63ocGehiSL6V1/DaTrH41gC2FTWFVWEfjMFCt06Ol\n GAxQ==","X-Gm-Message-State":"AOJu0Yz50ML1qy/eV8LeNFVJ7M5XjWiHAZVeK3GKuI2HIc83E3libUc4\n qQbCnhRJMU/z7IIGexnDNkb6eFRKt+mszL2Dr/Vn2qqi2L6Nu9bTHdSLo09+tGP1wt6iIad+zlk\n bcDbNIX5N4dGmmm6ZhsavNVrRsQxcuzDjifud0MntlkcXYM97uuSlL+qW8s2mZ3uIppCq+2KWtE\n WDeUf7i7BFlfHwuS5cNo5yoDVjg53rebBC2GYA+w==","X-Gm-Gg":"ASbGncuVTb8O7KvvAt65FsQLCDxEIM5Tq5hmQ3idl1Bs5NUtk2cTnOKxRtvPVDhUEdV\n h4g9o5hq7EIPX4DUgzsqm9m6TiNsz0kTe9hPqk01YPyzJ5wg/AbUIYtdvj979hKnq6VbmET7OQi\n j9kvEHwUCU0/TUfD2b9h+uOrE0qLzBYASgDYk5tfP/IxWPYe5yyIXEavIrZd6LviTzsBW61miB9\n Vi3JcArzuLHoHb2S4lvGdTHElJIdLJzg/RfuHz6iLicccu0DbToZRDZ2/UXjVoEdNdZWf192bJM\n N0doE0LMlf1vv5RVzMkPbOPtP9dZNOzGmQuQDS/QhloErUecAxkTAbfk2lHKlNdkUOb9duCPTN7\n sged//V7gekHnmeQ7kGzOT+CYcCwVAgg=","X-Received":["by 2002:a05:6000:2f83:b0:427:151:3da6 with SMTP id\n ffacd0b85a97d-42701514163mr1190801f8f.29.1760614554914;\n Thu, 16 Oct 2025 04:35:54 -0700 (PDT)","by 2002:a05:6000:2f83:b0:427:151:3da6 with SMTP id\n ffacd0b85a97d-42701514163mr1190756f8f.29.1760614553968;\n Thu, 16 Oct 2025 04:35:53 -0700 (PDT)"],"X-Google-Smtp-Source":"\n AGHT+IGkBas53mMn10hMZGCsKTNC5d+PG18bftnjLSPAO0hFYHPteBH565uYNbYegawbZN+olHGRUA==","To":"dev@openvswitch.org","Date":"Thu, 16 Oct 2025 12:35:49 +0100","Message-ID":"<20251016113549.2997884-5-moloings@redhat.com>","X-Mailer":"git-send-email 2.51.0","In-Reply-To":"<20251016113549.2997884-1-moloings@redhat.com>","References":"<20251016113549.2997884-1-moloings@redhat.com>","MIME-Version":"1.0","X-Mimecast-Spam-Score":"0","X-Mimecast-MFC-PROC-ID":"pq0od2w6IDSXPzHlA76w2waoHDdQIhC16-1-O2DOOSk_1760614556","X-Mimecast-Originator":"redhat.com","Subject":"[ovs-dev] [PATCH ovn v4 4/4] ic: Add Transit Router port support.","X-BeenThere":"ovs-dev@openvswitch.org","X-Mailman-Version":"2.1.30","Precedence":"list","List-Id":"<ovs-dev.openvswitch.org>","List-Unsubscribe":"<https://mail.openvswitch.org/mailman/options/ovs-dev>,\n <mailto:ovs-dev-request@openvswitch.org?subject=unsubscribe>","List-Archive":"<http://mail.openvswitch.org/pipermail/ovs-dev/>","List-Post":"<mailto:ovs-dev@openvswitch.org>","List-Help":"<mailto:ovs-dev-request@openvswitch.org?subject=help>","List-Subscribe":"<https://mail.openvswitch.org/mailman/listinfo/ovs-dev>,\n <mailto:ovs-dev-request@openvswitch.org?subject=subscribe>","From":"Mairtin O'Loingsigh via dev <ovs-dev@openvswitch.org>","Reply-To":"Mairtin O'Loingsigh <moloings@redhat.com>","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Errors-To":"ovs-dev-bounces@openvswitch.org","Sender":"\"dev\" <ovs-dev-bounces@openvswitch.org>"},"content":"This patch adds support for managing transit routers ports.\nPorts create on a transit router are remote for all availability zones\nexcept the zone containing the chassis.\nThis series also add support for commands to manage transit router\nports.\n\nCommands to add and delete a transit router port.\n$ ovn-ic-nbctl trp-add tr0 tr0-p0 00:00:00:11:22:00 192.168.10.10/24\n192.168.10.20/24 chassis=ovn-chassis-1\n$ ovn-ic-nbctl trp-del tr0-p0\n\nReported-at: https://issues.redhat.com/browse/FDP-1477\nReported-at: https://issues.redhat.com/browse/FDP-1478\n\nSigned-off-by: Mairtin O'Loingsigh <moloings@redhat.com>\n---\n NEWS                     |   3 +\n ic/ovn-ic.c              | 354 ++++++++++++++++++++++++++++++++-------\n tests/ovn-ic-nbctl.at    |  29 ++++\n tests/ovn-ic.at          | 197 ++++++++++++++++++++++\n utilities/ovn-ic-nbctl.c | 194 +++++++++++++++++++++\n 5 files changed, 714 insertions(+), 63 deletions(-)","diff":"diff --git a/NEWS b/NEWS\nindex 593af397d..4648d9d87 100644\n--- a/NEWS\n+++ b/NEWS\n@@ -49,6 +49,9 @@ Post v25.09.0\n     * Support the creation of Transit Routers.\n     * Added new ovn-ic-nbctl 'tr-add','tr-del','tr-list' commands to manage\n         Transit Router.\n+    * Support the creation of Transit Router Ports.\n+    * Added new ovn-ic-nbctl 'trp-add' and 'tpr-del' commands to manage\n+        Transit Router Ports.\n OVN v25.09.0 - xxx xx xxxx\n --------------------------\n    - STT tunnels are no longer supported in ovn-encap-type.\ndiff --git a/ic/ovn-ic.c b/ic/ovn-ic.c\nindex 28b0b81f3..0e5f09d0e 100644\n--- a/ic/ovn-ic.c\n+++ b/ic/ovn-ic.c\n@@ -66,6 +66,7 @@ struct ic_context {\n     struct ovsdb_idl_txn *ovnisb_txn;\n     const struct icsbrec_availability_zone *runned_az;\n     struct ovsdb_idl_index *nbrec_ls_by_name;\n+    struct ovsdb_idl_index *nbrec_lr_by_name;\n     struct ovsdb_idl_index *nbrec_lrp_by_name;\n     struct ovsdb_idl_index *nbrec_port_by_name;\n     struct ovsdb_idl_index *sbrec_chassis_by_name;\n@@ -77,6 +78,8 @@ struct ic_context {\n     struct ovsdb_idl_index *icsbrec_port_binding_by_az;\n     struct ovsdb_idl_index *icsbrec_port_binding_by_ts;\n     struct ovsdb_idl_index *icsbrec_port_binding_by_ts_az;\n+    struct ovsdb_idl_index *icsbrec_port_binding_by_nb_ic_uuid;\n+    struct ovsdb_idl_index *icsbrec_port_binding_by_nb_ic_uuid_type;\n     struct ovsdb_idl_index *icsbrec_route_by_az;\n     struct ovsdb_idl_index *icsbrec_route_by_ts;\n     struct ovsdb_idl_index *icsbrec_route_by_ts_az;\n@@ -91,6 +94,7 @@ struct ic_state {\n };\n \n enum ic_datapath_type { IC_SWITCH, IC_ROUTER, IC_DATAPATH_MAX };\n+enum ic_port_binding_type { IC_SWITCH_PORT, IC_ROUTER_PORT, IC_PORT_MAX };\n \n static const char *ovnnb_db;\n static const char *ovnsb_db;\n@@ -215,6 +219,16 @@ ic_dp_get_type(const struct icsbrec_datapath_binding *isb_dp)\n     return IC_SWITCH;\n }\n \n+static enum ic_port_binding_type\n+ic_pb_get_type(const struct icsbrec_port_binding *isb_pb)\n+{\n+    if (isb_pb->type && !strcmp(isb_pb->type, \"transit-router-port\")) {\n+        return IC_ROUTER_PORT;\n+    }\n+\n+    return IC_SWITCH_PORT;\n+}\n+\n static void\n enumerate_datapaths(struct ic_context *ctx, struct hmap *dp_tnlids,\n                     struct shash *isb_ts_dps, struct shash *isb_tr_dps)\n@@ -356,8 +370,8 @@ ts_run(struct ic_context *ctx, struct hmap *dp_tnlids,\n         struct shash_node *node;\n         SHASH_FOR_EACH_SAFE (node, isb_ts_dps) {\n             const struct icsbrec_datapath_binding *isb_dp = node->data;\n-            shash_delete(isb_ts_dps, node);\n             icsbrec_datapath_binding_delete(isb_dp);\n+            shash_delete(isb_ts_dps, node);\n         }\n     }\n }\n@@ -397,7 +411,7 @@ tr_run(struct ic_context *ctx, struct hmap *dp_tnlids,\n         /* Create ISB Datapath_Binding */\n         const struct icnbrec_transit_router *tr;\n         ICNBREC_TRANSIT_ROUTER_FOR_EACH (tr, ctx->ovninb_idl) {\n-            shash_find_and_delete(&nb_tres, tr->name);\n+            lr = shash_find_and_delete(&nb_tres, tr->name);\n             char *uuid_str = uuid_to_string(&tr->header_.uuid);\n             struct icsbrec_datapath_binding *isb_dp =\n                 shash_find_and_delete(isb_tr_dps, uuid_str);\n@@ -416,11 +430,17 @@ tr_run(struct ic_context *ctx, struct hmap *dp_tnlids,\n                 icsbrec_datapath_binding_set_nb_ic_uuid(isb_dp,\n                                                         &tr->header_.uuid, 1);\n                 icsbrec_datapath_binding_set_type(isb_dp, \"transit-router\");\n+                char *tnl_key_str = xasprintf(\"%\"PRId64, isb_dp->tunnel_key);\n+                nbrec_logical_router_update_options_setkey(\n+                    lr, \"requested-tnl-key\", tnl_key_str);\n+                free(tnl_key_str);\n             }\n         }\n \n         struct shash_node *node;\n         SHASH_FOR_EACH_SAFE (node, isb_tr_dps) {\n+            struct icsbrec_datapath_binding *isb_dp = node->data;\n+            ovn_free_tnlid(dp_tnlids, isb_dp->tunnel_key);\n             icsbrec_datapath_binding_delete(node->data);\n             shash_delete(isb_tr_dps, node);\n         }\n@@ -602,6 +622,30 @@ find_ts_in_nb(struct ic_context *ctx, char *ts_name)\n     return NULL;\n }\n \n+static const struct nbrec_logical_router *\n+find_tr_in_nb(struct ic_context *ctx, char *tr_name)\n+{\n+    const struct nbrec_logical_router *key =\n+        nbrec_logical_router_index_init_row(ctx->nbrec_lr_by_name);\n+    nbrec_logical_router_index_set_name(key, tr_name);\n+\n+    const struct nbrec_logical_router *lr;\n+    bool found = false;\n+    NBREC_LOGICAL_ROUTER_FOR_EACH_EQUAL (lr, key, ctx->nbrec_lr_by_name) {\n+        if (smap_get(&lr->options, \"interconn-tr\")) {\n+            found = true;\n+            break;\n+        }\n+    }\n+\n+    nbrec_logical_router_index_destroy_row(key);\n+    if (found) {\n+        return lr;\n+    }\n+\n+    return NULL;\n+}\n+\n static const struct sbrec_port_binding *\n find_sb_pb_by_name(struct ovsdb_idl_index *sbrec_port_binding_by_name,\n                    const char *name)\n@@ -719,6 +763,21 @@ sync_lsp_tnl_key(const struct nbrec_logical_switch_port *lsp,\n \n }\n \n+static void\n+sync_lrp_tnl_key(const struct nbrec_logical_router_port *lrp,\n+                 int64_t isb_tnl_key)\n+{\n+    int64_t tnl_key = smap_get_int(&lrp->options, \"requested-tnl-key\", 0);\n+    if (tnl_key != isb_tnl_key) {\n+        VLOG_DBG(\"Set options:requested-tnl-key %\" PRId64 \" for lrp %s in NB.\",\n+                 isb_tnl_key, lrp->name);\n+        char *tnl_key_str = xasprintf(\"%\" PRId64, isb_tnl_key);\n+        nbrec_logical_router_port_update_options_setkey(\n+            lrp, \"requested-tnl-key\", tnl_key_str);\n+        free(tnl_key_str);\n+    }\n+}\n+\n static bool\n get_router_uuid_by_sb_pb(struct ic_context *ctx,\n                          const struct sbrec_port_binding *sb_pb,\n@@ -856,6 +915,44 @@ sync_remote_port(struct ic_context *ctx,\n     }\n }\n \n+/* For each remote port:\n+ *   - Sync from ISB to NB\n+ */\n+static void\n+sync_remote_router_port(const struct icsbrec_port_binding *isb_pb,\n+                        const struct icnbrec_transit_router_port *trp,\n+                        const struct nbrec_logical_router_port *lrp)\n+{\n+    /* Sync from ICNB to NB */\n+    if (trp->chassis[0]) {\n+        const char *chassis_name =\n+            smap_get(&lrp->options, \"requested-chassis\");\n+        if (!chassis_name || strcmp(trp->chassis, chassis_name)) {\n+            nbrec_logical_router_port_update_options_setkey(\n+                lrp, \"requested-chassis\", trp->chassis);\n+        }\n+    }\n+\n+    bool sync_networks = false;\n+    if (trp->n_networks != lrp->n_networks) {\n+        sync_networks = true;\n+    } else {\n+        for (size_t i = 0; i < trp->n_networks; i++) {\n+            if (strcmp(trp->networks[i], lrp->networks[i])) {\n+                sync_networks |= true;\n+            }\n+        }\n+    }\n+\n+    if (sync_networks) {\n+        nbrec_logical_router_port_set_networks(\n+            lrp, (const char **) trp->networks, trp->n_networks);\n+    }\n+\n+    /* Sync tunnel key from ISB to NB */\n+    sync_lrp_tnl_key(lrp, isb_pb->tunnel_key);\n+}\n+\n static void\n create_nb_lsp(struct ic_context *ctx,\n               const struct icsbrec_port_binding *isb_pb,\n@@ -878,11 +975,10 @@ create_nb_lsp(struct ic_context *ctx,\n }\n \n static void\n-create_isb_pb(struct ic_context *ctx,\n-              const struct sbrec_port_binding *sb_pb,\n-              const struct icsbrec_availability_zone *az,\n-              const char *ts_name,\n-              uint32_t pb_tnl_key)\n+create_isb_pb(struct ic_context *ctx, const struct sbrec_port_binding *sb_pb,\n+              const struct icsbrec_availability_zone *az, const char *ts_name,\n+              const struct uuid *nb_ic_uuid, const char *type, const char *mac,\n+              int64_t pb_tnl_key)\n {\n     const struct icsbrec_port_binding *isb_pb =\n         icsbrec_port_binding_insert(ctx->ovnisb_txn);\n@@ -890,10 +986,17 @@ create_isb_pb(struct ic_context *ctx,\n     icsbrec_port_binding_set_transit_switch(isb_pb, ts_name);\n     icsbrec_port_binding_set_logical_port(isb_pb, sb_pb->logical_port);\n     icsbrec_port_binding_set_tunnel_key(isb_pb, pb_tnl_key);\n+    icsbrec_port_binding_set_nb_ic_uuid(isb_pb, nb_ic_uuid, 1);\n+    icsbrec_port_binding_set_type(isb_pb, type);\n \n-    const char *address = get_lp_address_for_sb_pb(ctx, sb_pb);\n-    if (address) {\n-        icsbrec_port_binding_set_address(isb_pb, address);\n+    if (!strcmp(type, \"transit-switch-port\")) {\n+        const char *address = get_lp_address_for_sb_pb(ctx, sb_pb);\n+        if (address) {\n+            icsbrec_port_binding_set_address(isb_pb, address);\n+        }\n+    } else {\n+        icsbrec_port_binding_set_address(isb_pb, mac);\n+        return;\n     }\n \n     const struct sbrec_port_binding *crp = find_crp_for_sb_pb(ctx, sb_pb);\n@@ -912,10 +1015,9 @@ create_isb_pb(struct ic_context *ctx,\n }\n \n static const struct sbrec_port_binding *\n-find_lsp_in_sb(struct ic_context *ctx,\n-               const struct nbrec_logical_switch_port *lsp)\n+find_lp_in_sb(struct ic_context *ctx, const char *name)\n {\n-    return find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, lsp->name);\n+    return find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, name);\n }\n \n static uint32_t\n@@ -926,6 +1028,55 @@ allocate_port_key(struct hmap *pb_tnlids)\n                               1, (1u << 15) - 1, &hint);\n }\n \n+static const struct nbrec_logical_router_port *\n+get_lrp_by_lrp_name(struct ic_context *ctx, const char *lrp_name)\n+{\n+    const struct nbrec_logical_router_port *lrp;\n+    const struct nbrec_logical_router_port *lrp_key =\n+        nbrec_logical_router_port_index_init_row(ctx->nbrec_lrp_by_name);\n+    nbrec_logical_router_port_index_set_name(lrp_key, lrp_name);\n+    lrp =\n+        nbrec_logical_router_port_index_find(ctx->nbrec_lrp_by_name, lrp_key);\n+    nbrec_logical_router_port_index_destroy_row(lrp_key);\n+\n+    return lrp;\n+}\n+\n+static bool\n+trp_port_is_remote(struct ic_context *ctx, const char *chassis_name)\n+{\n+    if (chassis_name) {\n+        const struct sbrec_chassis *chassis =\n+            find_sb_chassis(ctx, chassis_name);\n+        if (chassis) {\n+            return smap_get_bool(&chassis->other_config, \"is-remote\", false);\n+        }\n+    }\n+\n+    return false;\n+}\n+\n+static struct nbrec_logical_router_port *\n+trp_port_create(struct ic_context *ctx, const struct nbrec_logical_router *lr,\n+                const struct icnbrec_transit_router_port *trp)\n+{\n+    struct nbrec_logical_router_port *lrp =\n+        nbrec_logical_router_port_insert(ctx->ovnnb_txn);\n+    nbrec_logical_router_port_set_name(lrp, trp->name);\n+    nbrec_logical_router_port_set_mac(lrp, trp->mac);\n+    if (strcmp(\"\", trp->chassis)) {\n+        nbrec_logical_router_port_update_options_setkey(\n+            lrp, \"requested-chassis\", trp->chassis);\n+    }\n+\n+    nbrec_logical_router_port_set_networks(lrp, (const char **) trp->networks,\n+                                           trp->n_networks);\n+    nbrec_logical_router_port_update_options_setkey(lrp, \"interconn-tr\",\n+                                                    trp->name);\n+    nbrec_logical_router_update_ports_addvalue(lr, lrp);\n+    return lrp;\n+}\n+\n static void\n port_binding_run(struct ic_context *ctx)\n {\n@@ -933,23 +1084,30 @@ port_binding_run(struct ic_context *ctx)\n         return;\n     }\n \n-    struct shash isb_all_local_pbs = SHASH_INITIALIZER(&isb_all_local_pbs);\n-    struct shash_node *node;\n+    struct shash switch_all_local_pbs =\n+        SHASH_INITIALIZER(&switch_all_local_pbs);\n+    struct shash router_all_local_pbs =\n+        SHASH_INITIALIZER(&router_all_local_pbs);\n+    struct hmap pb_tnlids = HMAP_INITIALIZER(&pb_tnlids);\n \n     const struct icsbrec_port_binding *isb_pb;\n     const struct icsbrec_port_binding *isb_pb_key =\n         icsbrec_port_binding_index_init_row(ctx->icsbrec_port_binding_by_az);\n     icsbrec_port_binding_index_set_availability_zone(isb_pb_key,\n                                                      ctx->runned_az);\n-\n     ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key,\n                                          ctx->icsbrec_port_binding_by_az) {\n-        shash_add(&isb_all_local_pbs, isb_pb->logical_port, isb_pb);\n+        ic_pb_get_type(isb_pb) != IC_ROUTER_PORT\n+            ? shash_add(&switch_all_local_pbs, isb_pb->logical_port, isb_pb)\n+            : shash_add(&router_all_local_pbs, isb_pb->logical_port, isb_pb);\n+\n+        ovn_add_tnlid(&pb_tnlids, isb_pb->tunnel_key);\n     }\n     icsbrec_port_binding_index_destroy_row(isb_pb_key);\n \n     const struct sbrec_port_binding *sb_pb;\n     const struct icnbrec_transit_switch *ts;\n+\n     ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, ctx->ovninb_idl) {\n         const struct nbrec_logical_switch *ls = find_ts_in_nb(ctx, ts->name);\n         if (!ls) {\n@@ -958,21 +1116,23 @@ port_binding_run(struct ic_context *ctx)\n         }\n         struct shash local_pbs = SHASH_INITIALIZER(&local_pbs);\n         struct shash remote_pbs = SHASH_INITIALIZER(&remote_pbs);\n-        struct hmap pb_tnlids = HMAP_INITIALIZER(&pb_tnlids);\n-        isb_pb_key = icsbrec_port_binding_index_init_row(\n-            ctx->icsbrec_port_binding_by_ts);\n-        icsbrec_port_binding_index_set_transit_switch(isb_pb_key, ts->name);\n \n-        ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key,\n-                                             ctx->icsbrec_port_binding_by_ts) {\n+        isb_pb_key = icsbrec_port_binding_index_init_row(\n+            ctx->icsbrec_port_binding_by_nb_ic_uuid_type);\n+        icsbrec_port_binding_index_set_nb_ic_uuid(isb_pb_key,\n+                                                  &ts->header_.uuid, 1);\n+        icsbrec_port_binding_index_set_type(isb_pb_key, \"transit-switch-port\");\n+        ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (\n+            isb_pb, isb_pb_key, ctx->icsbrec_port_binding_by_nb_ic_uuid_type) {\n             if (isb_pb->availability_zone == ctx->runned_az) {\n-                shash_add(&local_pbs, isb_pb->logical_port, isb_pb);\n-                shash_find_and_delete(&isb_all_local_pbs,\n+                shash_find_and_delete(&switch_all_local_pbs,\n                                       isb_pb->logical_port);\n+                shash_add(&local_pbs, isb_pb->logical_port, isb_pb);\n             } else {\n-                shash_add(&remote_pbs, isb_pb->logical_port, isb_pb);\n+                if (!shash_find_data(&remote_pbs, isb_pb->logical_port)) {\n+                    shash_add(&remote_pbs, isb_pb->logical_port, isb_pb);\n+                }\n             }\n-            ovn_add_tnlid(&pb_tnlids, isb_pb->tunnel_key);\n         }\n         icsbrec_port_binding_index_destroy_row(isb_pb_key);\n \n@@ -983,25 +1143,28 @@ port_binding_run(struct ic_context *ctx)\n             if (!strcmp(lsp->type, \"router\")\n                 || !strcmp(lsp->type, \"switch\")) {\n                 /* The port is local. */\n-                sb_pb = find_lsp_in_sb(ctx, lsp);\n+                sb_pb = find_lp_in_sb(ctx, lsp->name);\n                 if (!sb_pb) {\n                     continue;\n                 }\n+\n                 isb_pb = shash_find_and_delete(&local_pbs, lsp->name);\n                 if (!isb_pb) {\n                     uint32_t pb_tnl_key = allocate_port_key(&pb_tnlids);\n-                    create_isb_pb(ctx, sb_pb, ctx->runned_az,\n-                                  ts->name, pb_tnl_key);\n+                    create_isb_pb(ctx, sb_pb, ctx->runned_az, ts->name,\n+                                  &ts->header_.uuid, \"transit-switch-port\",\n+                                  NULL, pb_tnl_key);\n                 } else {\n                     sync_local_port(ctx, isb_pb, sb_pb, lsp);\n                 }\n+\n             } else if (!strcmp(lsp->type, \"remote\")) {\n                 /* The port is remote. */\n                 isb_pb = shash_find_and_delete(&remote_pbs, lsp->name);\n                 if (!isb_pb) {\n                     nbrec_logical_switch_update_ports_delvalue(ls, lsp);\n                 } else {\n-                    sb_pb = find_lsp_in_sb(ctx, lsp);\n+                    sb_pb = find_lp_in_sb(ctx, lsp->name);\n                     if (!sb_pb) {\n                         continue;\n                     }\n@@ -1013,26 +1176,97 @@ port_binding_run(struct ic_context *ctx)\n             }\n         }\n \n-        /* Delete extra port-binding from ISB */\n-        SHASH_FOR_EACH (node, &local_pbs) {\n-            icsbrec_port_binding_delete(node->data);\n+        struct shash_node *node;\n+        SHASH_FOR_EACH_SAFE (node, &local_pbs) {\n+            isb_pb = node->data;\n+            shash_delete(&local_pbs, node);\n+            icsbrec_port_binding_delete(isb_pb);\n         }\n \n         /* Create lsp in NB for remote ports */\n-        SHASH_FOR_EACH (node, &remote_pbs) {\n-            create_nb_lsp(ctx, node->data, ls);\n+        SHASH_FOR_EACH_SAFE (node, &remote_pbs) {\n+            isb_pb = node->data;\n+            shash_delete(&remote_pbs, node);\n+            create_nb_lsp(ctx, isb_pb, ls);\n         }\n \n         shash_destroy(&local_pbs);\n         shash_destroy(&remote_pbs);\n-        ovn_destroy_tnlids(&pb_tnlids);\n     }\n \n-    SHASH_FOR_EACH (node, &isb_all_local_pbs) {\n-        icsbrec_port_binding_delete(node->data);\n+    struct shash_node *node;\n+    SHASH_FOR_EACH_SAFE (node, &switch_all_local_pbs) {\n+        isb_pb = node->data;\n+        icsbrec_port_binding_delete(isb_pb);\n+        shash_delete(&switch_all_local_pbs, node);\n+    }\n+    shash_destroy(&switch_all_local_pbs);\n+\n+    /* Walk list of transit routers*/\n+    const struct icnbrec_transit_router *tr;\n+    ICNBREC_TRANSIT_ROUTER_FOR_EACH (tr, ctx->ovninb_idl) {\n+        struct shash nb_ports = SHASH_INITIALIZER(&nb_ports);\n+        const struct nbrec_logical_router *lr = find_tr_in_nb(ctx, tr->name);\n+        if (!lr) {\n+            VLOG_DBG(\"Transit router %s not found in NB.\", tr->name);\n+            continue;\n+        }\n+\n+        for (size_t i = 0; i < tr->n_ports; i++) {\n+            const struct icnbrec_transit_router_port *trp = tr->ports[i];\n+\n+            const struct nbrec_logical_router_port *lrp =\n+                get_lrp_by_lrp_name(ctx, trp->name);\n+            if (!lrp) {\n+                lrp = trp_port_create(ctx, lr, trp);\n+            }\n+\n+            sb_pb = find_lp_in_sb(ctx, lrp->name);\n+            if (!sb_pb) {\n+                continue;\n+            }\n+\n+            shash_add(&nb_ports, trp->name, lrp);\n+            isb_pb = shash_find_and_delete(&router_all_local_pbs, trp->name);\n+            if (!trp_port_is_remote(ctx, trp->chassis)) {\n+\n+                if (!isb_pb) {\n+                    uint32_t pb_tnl_key = allocate_port_key(&pb_tnlids);\n+                    create_isb_pb(ctx, sb_pb, ctx->runned_az, tr->name,\n+                                  &tr->header_.uuid, \"transit-router-port\",\n+                                  trp->mac, pb_tnl_key);\n+                    continue;\n+                }\n+            }\n+\n+            if (isb_pb) {\n+                sync_remote_router_port(isb_pb, trp, lrp);\n+            }\n+        }\n+\n+        for (size_t i = 0; i < lr->n_ports; i++) {\n+            const struct nbrec_logical_router_port *lrp = lr->ports[i];\n+            if (!shash_find_and_delete(&nb_ports, lrp->name)) {\n+                if (smap_get(&lrp->options, \"interconn-tr\")) {\n+                    nbrec_logical_router_port_delete(lrp);\n+                    nbrec_logical_router_update_ports_delvalue(lr, lrp);\n+                }\n+            }\n+        }\n+\n+        SHASH_FOR_EACH_SAFE (node, &nb_ports) {\n+            shash_delete(&nb_ports, node);\n+        }\n+    }\n+    ovn_destroy_tnlids(&pb_tnlids);\n+\n+    SHASH_FOR_EACH_SAFE (node, &router_all_local_pbs) {\n+        isb_pb = node->data;\n+        icsbrec_port_binding_delete(isb_pb);\n+        shash_delete(&router_all_local_pbs, node);\n     }\n \n-    shash_destroy(&isb_all_local_pbs);\n+    shash_destroy(&router_all_local_pbs);\n }\n \n struct ic_router_info {\n@@ -1713,20 +1947,6 @@ get_lrp_name_by_ts_port_name(struct ic_context *ctx, const char *ts_port_name)\n     return smap_get(&nb_lsp->options, \"router-port\");\n }\n \n-static const struct nbrec_logical_router_port *\n-get_lrp_by_lrp_name(struct ic_context *ctx, const char *lrp_name)\n-{\n-    const struct nbrec_logical_router_port *lrp;\n-    const struct nbrec_logical_router_port *lrp_key =\n-        nbrec_logical_router_port_index_init_row(ctx->nbrec_lrp_by_name);\n-    nbrec_logical_router_port_index_set_name(lrp_key, lrp_name);\n-    lrp = nbrec_logical_router_port_index_find(ctx->nbrec_lrp_by_name,\n-                                               lrp_key);\n-    nbrec_logical_router_port_index_destroy_row(lrp_key);\n-\n-    return lrp;\n-}\n-\n static const struct nbrec_logical_router_port *\n find_lrp_of_nexthop(struct ic_context *ctx,\n                     const struct icsbrec_route *isb_route)\n@@ -2263,6 +2483,9 @@ route_run(struct ic_context *ctx)\n     ICSBREC_PORT_BINDING_FOR_EACH_EQUAL (isb_pb, isb_pb_key,\n                                          ctx->icsbrec_port_binding_by_az)\n     {\n+        if (isb_pb->type && !strcmp(isb_pb->type, \"transit-router-port\")) {\n+            continue;\n+        }\n         const struct nbrec_logical_switch_port *nb_lsp;\n \n         nb_lsp = get_lsp_by_ts_port_name(ctx, isb_pb->logical_port);\n@@ -3241,6 +3464,8 @@ main(int argc, char *argv[])\n     struct ovsdb_idl_index *nbrec_ls_by_name\n         = ovsdb_idl_index_create1(ovnnb_idl_loop.idl,\n                                   &nbrec_logical_switch_col_name);\n+    struct ovsdb_idl_index *nbrec_lr_by_name = ovsdb_idl_index_create1(\n+        ovnnb_idl_loop.idl, &nbrec_logical_router_col_name);\n     struct ovsdb_idl_index *nbrec_port_by_name\n         = ovsdb_idl_index_create1(ovnnb_idl_loop.idl,\n                                   &nbrec_logical_switch_port_col_name);\n@@ -3275,14 +3500,14 @@ main(int argc, char *argv[])\n         = ovsdb_idl_index_create1(ovnisb_idl_loop.idl,\n                                   &icsbrec_port_binding_col_availability_zone);\n \n-    struct ovsdb_idl_index *icsbrec_port_binding_by_ts\n-        = ovsdb_idl_index_create1(ovnisb_idl_loop.idl,\n-                                  &icsbrec_port_binding_col_transit_switch);\n+    struct ovsdb_idl_index *icsbrec_port_binding_by_nb_ic_uuid =\n+        ovsdb_idl_index_create1(ovnisb_idl_loop.idl,\n+                                &icsbrec_port_binding_col_nb_ic_uuid);\n \n-    struct ovsdb_idl_index *icsbrec_port_binding_by_ts_az\n-        = ovsdb_idl_index_create2(ovnisb_idl_loop.idl,\n-                                  &icsbrec_port_binding_col_transit_switch,\n-                                  &icsbrec_port_binding_col_availability_zone);\n+    struct ovsdb_idl_index *icsbrec_port_binding_by_nb_ic_uuid_type =\n+        ovsdb_idl_index_create2(ovnisb_idl_loop.idl,\n+                                &icsbrec_port_binding_col_nb_ic_uuid,\n+                                &icsbrec_port_binding_col_type);\n \n     struct ovsdb_idl_index *icsbrec_route_by_az\n         = ovsdb_idl_index_create1(ovnisb_idl_loop.idl,\n@@ -3357,6 +3582,7 @@ main(int argc, char *argv[])\n                 .ovnisb_idl = ovnisb_idl_loop.idl,\n                 .ovnisb_txn = ovsdb_idl_loop_run(&ovnisb_idl_loop),\n                 .nbrec_ls_by_name = nbrec_ls_by_name,\n+                .nbrec_lr_by_name = nbrec_lr_by_name,\n                 .nbrec_lrp_by_name = nbrec_lrp_by_name,\n                 .nbrec_port_by_name = nbrec_port_by_name,\n                 .sbrec_port_binding_by_name = sbrec_port_binding_by_name,\n@@ -3370,8 +3596,10 @@ main(int argc, char *argv[])\n                 .icnbrec_transit_switch_by_name =\n                     icnbrec_transit_switch_by_name,\n                 .icsbrec_port_binding_by_az = icsbrec_port_binding_by_az,\n-                .icsbrec_port_binding_by_ts = icsbrec_port_binding_by_ts,\n-                .icsbrec_port_binding_by_ts_az = icsbrec_port_binding_by_ts_az,\n+                .icsbrec_port_binding_by_nb_ic_uuid =\n+                    icsbrec_port_binding_by_nb_ic_uuid,\n+                .icsbrec_port_binding_by_nb_ic_uuid_type =\n+                    icsbrec_port_binding_by_nb_ic_uuid_type,\n                 .icsbrec_route_by_az = icsbrec_route_by_az,\n                 .icsbrec_route_by_ts = icsbrec_route_by_ts,\n                 .icsbrec_route_by_ts_az = icsbrec_route_by_ts_az,\ndiff --git a/tests/ovn-ic-nbctl.at b/tests/ovn-ic-nbctl.at\nindex 058d66cb1..0f6290b95 100644\n--- a/tests/ovn-ic-nbctl.at\n+++ b/tests/ovn-ic-nbctl.at\n@@ -102,5 +102,34 @@ AT_CHECK([ovn-ic-nbctl tr-del tr2], [1], [],\n ])\n \n AT_CHECK([ovn-ic-nbctl --if-exists tr-del tr2])\n+AT_CHECK([ovn-ic-nbctl tr-del tr0])\n+\n+AT_CHECK([ovn-ic-nbctl trp-add tr0 tr0-p0 00:11:22:11:22:33 192.168.10.10/24 chassis=chassis], [1], [],\n+  [ovn-ic-nbctl: tr0: router name not found\n+])\n+\n+AT_CHECK([ovn-ic-nbctl tr-add tr0])\n+AT_CHECK([ovn-ic-nbctl trp-add tr0 tr0-p0], [1], [],\n+  [ovn-ic-nbctl: 'trp-add' command requires at least 5 arguments\n+])\n+\n+AT_CHECK([ovn-ic-nbctl trp-add tr0 tr0-p0 00:11:22:11:22:33 192.168.10.10/24 chassis=chassis])\n+AT_CHECK([ovn-ic-nbctl trp-add tr0 tr0-p0 00:11:22:11:22:33 192.168.10.10/24 chassis=chassis], [1], [],\n+  [ovn-ic-nbctl: tr0-p0: a port with this name already exists\n+])\n+\n+AT_CHECK([ovn-ic-nbctl --may-exist trp-add tr0 tr0-p0 00:11:22:11:22:33 192.168.10.10/24 chassis=chassis])\n+\n+AT_CHECK([ovn-ic-nbctl trp-del], [1], [],\n+  [ovn-ic-nbctl: 'trp-del' command requires at least 1 arguments\n+])\n+AT_CHECK([ovn-ic-nbctl trp-del tr0-p0])\n+AT_CHECK([ovn-ic-nbctl trp-del tr0-p0], [1], [],\n+  [ovn-ic-nbctl: tr0-p0: router port name not found\n+])\n+AT_CHECK([ovn-ic-nbctl --if-exists trp-del tr0-p0])\n+\n+AT_CHECK([ovn-ic-nbctl --if-exists tr-del tr0])\n+\n OVN_IC_NBCTL_TEST_STOP\n AT_CLEANUP\ndiff --git a/tests/ovn-ic.at b/tests/ovn-ic.at\nindex a081bd3a7..4357bda4e 100644\n--- a/tests/ovn-ic.at\n+++ b/tests/ovn-ic.at\n@@ -4336,3 +4336,200 @@ check_row_count nb:Logical_Router 0\n OVN_CLEANUP_IC([az1], [az2])\n AT_CLEANUP\n ])\n+\n+OVN_FOR_EACH_NORTHD([\n+AT_SETUP([ovn-ic -- Add transit router remote port])\n+\n+ovn_init_ic_db\n+net_add n1\n+net_add n2\n+ovn_start az1\n+ovn_start az2\n+\n+OVS_WAIT_UNTIL([test 2 = `ovn-ic-sbctl show | wc -l`])\n+\n+check ovn-ic-nbctl --wait=sb sync\n+AT_CHECK([ovn-ic-sbctl show], [0], [dnl\n+availability-zone az1\n+availability-zone az2\n+])\n+\n+sim_add hv1\n+as hv1\n+ovs-vsctl add-br br-phys\n+ovn_az_attach az1 n1 br-phys 192.168.0.1\n+ovs-vsctl set open . external-ids:ovn-is-interconn=true\n+\n+sim_add hv2\n+as hv2\n+ovs-vsctl add-br br-phys\n+ovn_az_attach az2 n2 br-phys 192.168.0.2\n+ovs-vsctl set open . external-ids:ovn-is-interconn=true\n+\n+ovn_as az1\n+check ovn-ic-nbctl tr-add tr0\n+check ovn-ic-nbctl trp-add tr0 tr0-p0 00:00:00:11:22:00 192.168.10.10/24 192.168.10.20/24 chassis=hv2\n+check ovn-ic-nbctl --wait=sb sync\n+AT_CHECK([ovn-sbctl list Port_Binding | grep -E '(tr0-p0|type|mac)'], [0], [dnl\n+logical_port        : tr0-p0\n+mac                 : [[\"00:00:00:11:22:00 192.168.10.10/24 192.168.10.20/24\"]]\n+type                : remote\n+])\n+\n+ovn_as az2\n+AT_CHECK([ovn-sbctl list Port_Binding | grep -E '(tr0-p0|type|mac)'], [0], [dnl\n+logical_port        : tr0-p0\n+mac                 : [[\"00:00:00:11:22:00 192.168.10.10/24 192.168.10.20/24\"]]\n+type                : patch\n+])\n+\n+OVN_CLEANUP_IC([az1], [az2])\n+AT_CLEANUP\n+])\n+\n+OVN_FOR_EACH_NORTHD([\n+AT_SETUP([ovn-ic -- Transit router delete port])\n+\n+ovn_init_ic_db\n+ovn_start az1\n+ovn_start az2\n+\n+check ovn-ic-nbctl --wait=sb sync\n+AT_CHECK([ovn-ic-sbctl show], [0], [dnl\n+availability-zone az1\n+availability-zone az2\n+])\n+\n+ovn_as az1\n+check ovn-ic-nbctl --wait=sb tr-add tr0\n+check ovn-ic-nbctl --wait=sb trp-add tr0 tr0-p0 00:00:00:11:22:00 192.168.10.10/24 192.168.10.20/24 chassis=chassis-az2\n+AT_CHECK([ovn-sbctl list Port_Binding | grep tr0-p0], [0], [dnl\n+logical_port        : tr0-p0\n+])\n+\n+ovn_as az2\n+AT_CHECK([ovn-sbctl list Port_Binding | grep tr0-p0], [0], [dnl\n+logical_port        : tr0-p0\n+])\n+\n+ovn_as az1\n+check ovn-ic-nbctl --wait=sb trp-del tr0-p0\n+check_row_count sb:Port_Binding 0\n+\n+OVN_CLEANUP_IC([az1], [az2])\n+AT_CLEANUP\n+])\n+\n+OVN_FOR_EACH_NORTHD([\n+AT_SETUP([ovn-ic -- Port binding deletion upon transit router deletion])\n+\n+ovn_init_ic_db\n+ovn_start az1\n+ovn_start az2\n+\n+check ovn-ic-nbctl --wait=sb sync\n+AT_CHECK([ovn-ic-sbctl show], [0], [dnl\n+availability-zone az1\n+availability-zone az2\n+])\n+\n+ovn_as az1\n+check ovn-ic-nbctl --wait=sb tr-add tr0\n+check ovn-ic-nbctl --wait=sb trp-add tr0 tr0-p0 00:00:00:11:22:00 192.168.10.10/24 192.168.10.20/24 chassis=chassis-az2\n+AT_CHECK([ovn-sbctl list Port_Binding | grep tr0 | sort], [0], [dnl\n+logical_port        : tr0-p0\n+])\n+\n+ovn_as az2\n+AT_CHECK([ovn-sbctl list Port_Binding | grep tr0 | sort], [0], [dnl\n+logical_port        : tr0-p0\n+])\n+\n+ovn_as az1\n+check ovn-ic-nbctl tr-del tr0\n+check ovn-ic-nbctl --wait=sb sync\n+OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list Port_Binding | grep tr0| wc -l`])\n+AT_CHECK([ovn-sbctl list Port_Binding], [0], [dnl\n+])\n+\n+AT_CHECK([ovn-ic-nbctl list Transit_Router], [0], [dnl\n+])\n+AT_CHECK([ovn-ic-nbctl list Transit_Router_Port], [0], [dnl\n+])\n+\n+OVN_CLEANUP_IC([az1], [az2])\n+AT_CLEANUP\n+])\n+\n+OVN_FOR_EACH_NORTHD([\n+AT_SETUP([ovn-ic -- Duplicate port addresses])\n+\n+ovn_init_ic_db\n+ovn_start az1\n+ovn_start az2\n+\n+OVS_WAIT_UNTIL([test 2 = `ovn-ic-sbctl show | wc -l`])\n+\n+check ovn-ic-nbctl --wait=sb sync\n+AT_CHECK([ovn-ic-sbctl show], [0], [dnl\n+availability-zone az1\n+availability-zone az2\n+])\n+\n+ovn_as az1\n+check ovn-ic-nbctl --wait=sb tr-add tr0\n+check ovn-ic-nbctl --wait=sb trp-add tr0 tr0-p0 00:00:00:11:22:00 \\\n+    192.168.10.10/24 192.168.10.20/24 192.168.10.10/24 192.168.10.20/24 \\\n+    chassis=chassis-az2\n+check_row_count nb:Logical_Router_Port 1 networks=\"192.168.10.10/24 192.168.10.20/24\"\n+\n+ovn_as az2\n+check_row_count nb:Logical_Router_Port 1 networks=\"192.168.10.10/24 192.168.10.20/24\"\n+\n+ovn_as az1\n+check ovn-ic-nbctl --wait=sb trp-del tr0-p0\n+check_row_count sb:Port_Binding 0\n+\n+OVN_CLEANUP_IC([az1], [az2])\n+AT_CLEANUP\n+])\n+\n+OVN_FOR_EACH_NORTHD([\n+AT_SETUP([ovn-ic -- Port binding deletion upon transit router deletion])\n+\n+ovn_init_ic_db\n+ovn_start az1\n+ovn_start az2\n+\n+check ovn-ic-nbctl --wait=sb sync\n+AT_CHECK([ovn-ic-sbctl show], [0], [dnl\n+availability-zone az1\n+availability-zone az2\n+])\n+\n+ovn_as az1\n+check ovn-ic-nbctl --wait=sb tr-add tr0\n+check ovn-ic-nbctl --wait=sb trp-add tr0 tr0-p0 00:00:00:11:22:00 192.168.10.10/24 192.168.10.20/24 chassis=chassis-az2\n+AT_CHECK([ovn-sbctl list Port_Binding | grep tr0 | sort], [0], [dnl\n+logical_port        : tr0-p0\n+])\n+\n+ovn_as az2\n+AT_CHECK([ovn-sbctl list Port_Binding | grep tr0 | sort], [0], [dnl\n+logical_port        : tr0-p0\n+])\n+\n+ovn_as az1\n+check ovn-ic-nbctl --wait=sb tr-del tr0\n+OVS_WAIT_UNTIL([test 0 = `ovn-sbctl list Port_Binding | grep tr0| wc -l`])\n+AT_CHECK([ovn-sbctl list Port_Binding], [0], [dnl\n+])\n+\n+AT_CHECK([ovn-ic-nbctl list Transit_Router], [0], [dnl\n+])\n+AT_CHECK([ovn-ic-nbctl list Transit_Router_Port], [0], [dnl\n+])\n+\n+OVN_CLEANUP_IC([az1], [az2])\n+AT_CLEANUP\n+])\ndiff --git a/utilities/ovn-ic-nbctl.c b/utilities/ovn-ic-nbctl.c\nindex bc480017c..20e86e28c 100644\n--- a/utilities/ovn-ic-nbctl.c\n+++ b/utilities/ovn-ic-nbctl.c\n@@ -341,6 +341,9 @@ Transit router commands:\\n\\\n   tr-add ROUTER              create a transit router named ROUTER\\n\\\n   tr-del ROUTER              delete ROUTER\\n\\\n   tr-list                    print all transit routers\\n\\\n+  trp-add ROUTER PORT MAC [NETWORK]...[chassis=CHASSIS]\\n\\\n+                             add a transit router PORT\\n\\\n+  trp-del PORT               delete a transit router PORT\\n\\\n \\n\\\n Connection commands:\\n\\\n   get-connection             print the connections\\n\\\n@@ -582,6 +585,38 @@ ic_nbctl_tr_add(struct ctl_context *ctx)\n     icnbrec_transit_router_set_name(tr, tr_name);\n }\n \n+static char *\n+trp_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist,\n+                    const struct icnbrec_transit_router_port **trp_p)\n+{\n+    const struct icnbrec_transit_router_port *trp = NULL;\n+    *trp_p = NULL;\n+    struct uuid trp_uuid;\n+    bool is_uuid = uuid_from_string(&trp_uuid, id);\n+    if (is_uuid) {\n+        trp = icnbrec_transit_router_port_get_for_uuid(ctx->idl, &trp_uuid);\n+    }\n+\n+    if (!trp) {\n+        const struct icnbrec_transit_router_port *iter;\n+\n+        ICNBREC_TRANSIT_ROUTER_PORT_FOR_EACH (iter, ctx->idl) {\n+            if (!strcmp(iter->name, id)) {\n+                trp = iter;\n+                break;\n+            }\n+        }\n+    }\n+\n+    if (!trp && must_exist) {\n+        return xasprintf(\"%s: router port %s not found\", id,\n+                         is_uuid ? \"UUID\" : \"name\");\n+    }\n+\n+    *trp_p = trp;\n+    return NULL;\n+}\n+\n static void\n ic_nbctl_tr_del(struct ctl_context *ctx)\n {\n@@ -602,6 +637,40 @@ ic_nbctl_tr_del(struct ctl_context *ctx)\n     icnbrec_transit_router_delete(tr);\n }\n \n+static void\n+ic_nbctl_trp_del(struct ctl_context *ctx)\n+{\n+    bool must_exist = !shash_find(&ctx->options, \"--if-exists\");\n+    const char *trp_name = ctx->argv[1];\n+    const struct icnbrec_transit_router_port *trp = NULL;\n+\n+    char *error = trp_by_name_or_uuid(ctx, trp_name, must_exist, &trp);\n+    if (error) {\n+        ctx->error = error;\n+        return;\n+    }\n+\n+    if (!trp) {\n+        return;\n+    }\n+\n+    const struct icnbrec_transit_router *tr = NULL;\n+    char *tr_uuid = uuid_to_string(&trp->tr_uuid);\n+    error = tr_by_name_or_uuid(ctx, tr_uuid, true, &tr);\n+    free(tr_uuid);\n+    if (error) {\n+        ctx->error = error;\n+        return;\n+    }\n+\n+    if (!trp) {\n+        return;\n+    }\n+\n+    icnbrec_transit_router_update_ports_delvalue(tr, trp);\n+    icnbrec_transit_router_port_delete(trp);\n+}\n+\n static void\n ic_nbctl_tr_list(struct ctl_context *ctx)\n {\n@@ -623,6 +692,126 @@ ic_nbctl_tr_list(struct ctl_context *ctx)\n     smap_destroy(&routers);\n     free(nodes);\n }\n+\n+static void\n+ic_nbctl_trp_add(struct ctl_context *ctx)\n+{\n+    bool may_exist = shash_find(&ctx->options, \"--may-exist\") != NULL;\n+    const char *tr_name = ctx->argv[1];\n+    const char *trp_name = ctx->argv[2];\n+    const char *mac = ctx->argv[3];\n+    const char **networks = (const char **) &ctx->argv[4];\n+    const struct icnbrec_transit_router *tr;\n+\n+    char *error = tr_by_name_or_uuid(ctx, tr_name, true, &tr);\n+    if (error) {\n+        ctx->error = error;\n+        return;\n+    }\n+\n+    const struct icnbrec_transit_router_port *trp;\n+    error = trp_by_name_or_uuid(ctx, trp_name, false, &trp);\n+    if (error) {\n+        ctx->error = error;\n+        return;\n+    }\n+\n+    /* Parse networks*/\n+    int n_networks = ctx->argc - 4;\n+    for (int i = 4; i < ctx->argc; i++) {\n+        if (strchr(ctx->argv[i], '=')) {\n+            n_networks = i - 4;\n+            break;\n+        }\n+    }\n+\n+    char **settings = (char **) &ctx->argv[n_networks + 4];\n+    int n_settings = ctx->argc - 4 - n_networks;\n+    struct eth_addr ea;\n+    if (!eth_addr_from_string(mac, &ea)) {\n+        ctl_error(ctx, \"%s: invalid mac address %s\", trp_name, mac);\n+        return;\n+    }\n+\n+    if (trp) {\n+        if (!may_exist) {\n+            ctl_error(ctx, \"%s: a port with this name already exists\",\n+                      trp_name);\n+            return;\n+        }\n+\n+        struct eth_addr lrp_ea;\n+        eth_addr_from_string(trp->mac, &lrp_ea);\n+        if (!eth_addr_equals(ea, lrp_ea)) {\n+            ctl_error(ctx, \"%s: port already exists with mac %s\", trp_name,\n+                      trp->mac);\n+            return;\n+        }\n+\n+        struct sset *new_networks = lrp_network_sset(networks, n_networks);\n+        if (!new_networks) {\n+            ctl_error(ctx, \"%s: Invalid networks configured\", trp_name);\n+            return;\n+        }\n+\n+        struct sset *orig_networks =\n+            lrp_network_sset((const char **) trp->networks, trp->n_networks);\n+        if (!orig_networks) {\n+            ctl_error(ctx, \"%s: Existing port has invalid networks configured\",\n+                      trp_name);\n+            sset_destroy(new_networks);\n+            free(new_networks);\n+            return;\n+        }\n+\n+        bool same_networks = sset_equals(orig_networks, new_networks);\n+        sset_destroy(orig_networks);\n+        free(orig_networks);\n+        sset_destroy(new_networks);\n+        free(new_networks);\n+        if (!same_networks) {\n+            ctl_error(ctx, \"%s: port already exists with different network\",\n+                      trp_name);\n+            return;\n+        }\n+\n+        return;\n+    }\n+\n+    for (int i = 0; i < n_networks; i++) {\n+        ovs_be32 ipv4;\n+        unsigned int plen;\n+        error = ip_parse_cidr(networks[i], &ipv4, &plen);\n+        if (error) {\n+            free(error);\n+            struct in6_addr ipv6;\n+            error = ipv6_parse_cidr(networks[i], &ipv6, &plen);\n+            if (error) {\n+                free(error);\n+                ctl_error(ctx, \"%s: invalid network address: %s\", trp_name,\n+                          networks[i]);\n+                return;\n+            }\n+        }\n+    }\n+\n+    trp = icnbrec_transit_router_port_insert(ctx->txn);\n+    icnbrec_transit_router_port_set_name(trp, trp_name);\n+    icnbrec_transit_router_port_set_mac(trp, mac);\n+    icnbrec_transit_router_port_set_tr_uuid(trp, tr->header_.uuid);\n+    icnbrec_transit_router_port_set_networks(trp, networks, n_networks);\n+    for (int i = 0; i < n_settings; i++) {\n+        error = ctl_set_column(\"Transit_Router_Port\", &trp->header_,\n+                               settings[i], ctx->symtab);\n+        if (error) {\n+            ctx->error = error;\n+            return;\n+        }\n+    }\n+\n+    icnbrec_transit_router_update_ports_addvalue(tr, trp);\n+}\n+\n static void\n verify_connections(struct ctl_context *ctx)\n {\n@@ -1145,6 +1334,11 @@ static const struct ctl_command_syntax ic_nbctl_commands[] = {\n     { \"tr-del\", 1, 1, \"ROUTER\", NULL, ic_nbctl_tr_del, NULL, \"--if-exists\",\n         RW },\n     { \"tr-list\", 0, 0, \"\", NULL, ic_nbctl_tr_list, NULL, \"\", RO },\n+    { \"trp-add\", 5, INT_MAX,\n+        \"ROUTER PORT MAC [NETWORK]...[COLUMN[:KEY]=VALUE]...\",\n+        NULL, ic_nbctl_trp_add, NULL, \"--may-exist\", RW },\n+    { \"trp-del\", 1, 1, \"PORT\", NULL, ic_nbctl_trp_del, NULL, \"--if-exists\",\n+        RW },\n \n     /* Connection commands. */\n     {\"get-connection\", 0, 0, \"\", pre_connection, cmd_get_connection, NULL, \"\",\n","prefixes":["ovs-dev","v4","4/4"]}