get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/1.2/patches/2235128/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 2235128,
    "url": "http://patchwork.ozlabs.org/api/1.2/patches/2235128/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/ovn/patch/20260508154222.749992-2-moloings@redhat.com/",
    "project": {
        "id": 68,
        "url": "http://patchwork.ozlabs.org/api/1.2/projects/68/?format=api",
        "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": "<20260508154222.749992-2-moloings@redhat.com>",
    "list_archive_url": null,
    "date": "2026-05-08T15:42:22",
    "name": "[ovs-dev,v2,1/1] ic: Add transit switch port and schema.",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "d55b6cb2e3d2853881e527ae0be2228fb81f7240",
    "submitter": {
        "id": 91032,
        "url": "http://patchwork.ozlabs.org/api/1.2/people/91032/?format=api",
        "name": "Mairtin O'Loingsigh",
        "email": "moloings@redhat.com"
    },
    "delegate": null,
    "mbox": "http://patchwork.ozlabs.org/project/ovn/patch/20260508154222.749992-2-moloings@redhat.com/mbox/",
    "series": [
        {
            "id": 503411,
            "url": "http://patchwork.ozlabs.org/api/1.2/series/503411/?format=api",
            "web_url": "http://patchwork.ozlabs.org/project/ovn/list/?series=503411",
            "date": "2026-05-08T15:42:22",
            "name": "Add support for Transit switch ports.",
            "version": 2,
            "mbox": "http://patchwork.ozlabs.org/series/503411/mbox/"
        }
    ],
    "comments": "http://patchwork.ozlabs.org/api/patches/2235128/comments/",
    "check": "warning",
    "checks": "http://patchwork.ozlabs.org/api/patches/2235128/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=fzUfAJx+;\n\tdkim-atps=neutral",
            "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org\n (client-ip=2605:bc80:3010::136; helo=smtp3.osuosl.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org)",
            "smtp3.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=fzUfAJx+",
            "smtp1.osuosl.org; dmarc=pass (p=quarantine dis=none)\n header.from=redhat.com",
            "smtp1.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=fzUfAJx+"
        ],
        "Received": [
            "from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136])\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 4gBtfs3k7Rz1yJq\n\tfor <incoming@patchwork.ozlabs.org>; Sat, 09 May 2026 01:42:45 +1000 (AEST)",
            "from localhost (localhost [127.0.0.1])\n\tby smtp3.osuosl.org (Postfix) with ESMTP id 986C261681;\n\tFri,  8 May 2026 15:42:43 +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 ET0ZJYcDVfai; Fri,  8 May 2026 15:42:41 +0000 (UTC)",
            "from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56])\n\tby smtp3.osuosl.org (Postfix) with ESMTPS id ECD8A61294;\n\tFri,  8 May 2026 15:42:40 +0000 (UTC)",
            "from lf-lists.osuosl.org (localhost [127.0.0.1])\n\tby lists.linuxfoundation.org (Postfix) with ESMTP id B68BAC04EB;\n\tFri,  8 May 2026 15:42:40 +0000 (UTC)",
            "from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138])\n by lists.linuxfoundation.org (Postfix) with ESMTP id DEC32C04E7\n for <dev@openvswitch.org>; Fri,  8 May 2026 15:42:39 +0000 (UTC)",
            "from localhost (localhost [127.0.0.1])\n by smtp1.osuosl.org (Postfix) with ESMTP id DCA8B81387\n for <dev@openvswitch.org>; Fri,  8 May 2026 15:42:39 +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 zxYkbP8Wi6nv for <dev@openvswitch.org>;\n Fri,  8 May 2026 15:42:38 +0000 (UTC)",
            "from us-smtp-delivery-124.mimecast.com\n (us-smtp-delivery-124.mimecast.com [170.10.129.124])\n by smtp1.osuosl.org (Postfix) with ESMTPS id A0EBD812AC\n for <dev@openvswitch.org>; Fri,  8 May 2026 15:42:36 +0000 (UTC)",
            "from mail-qk1-f199.google.com (mail-qk1-f199.google.com\n [209.85.222.199]) by relay.mimecast.com with ESMTP with STARTTLS\n (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n us-mta-573-rOhk2LG-PPmfqEO2fhKqxg-1; Fri, 08 May 2026 11:42:34 -0400",
            "by mail-qk1-f199.google.com with SMTP id\n af79cd13be357-8e8950b3ee1so194435885a.3\n for <dev@openvswitch.org>; Fri, 08 May 2026 08:42:34 -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 af79cd13be357-907b9c542cdsm230358385a.0.2026.05.08.08.42.29\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Fri, 08 May 2026 08:42:30 -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 smtp3.osuosl.org ECD8A61294",
            "OpenDKIM Filter v2.11.0 smtp1.osuosl.org A0EBD812AC"
        ],
        "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 smtp1.osuosl.org A0EBD812AC",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n s=mimecast20190719; t=1778254955;\n h=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n to:to:cc: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=agt9qHh37pRMnvFKUcOnTit9BsQZo564/eM0p0/5QnM=;\n b=fzUfAJx+9YhInKNDTxk1WEbxbOStPO03iDRuqGc/SD7wqUSYiyjHUw6KAxPukLpg/Bn1Pz\n +Uorkcbivqw9wGjLNRlgMmzgWUhMKUBpdavKAmMlSCOEGa5u+vNUpEGGZCjkmkUpV9m+xV\n ekeLqh/pGnln2o9XNYOFlqGUEKCoKCk=",
        "X-MC-Unique": "rOhk2LG-PPmfqEO2fhKqxg-1",
        "X-Mimecast-MFC-AGG-ID": "rOhk2LG-PPmfqEO2fhKqxg_1778254953",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1778254953; x=1778859753;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n :to:cc:subject:date:message-id:reply-to;\n bh=agt9qHh37pRMnvFKUcOnTit9BsQZo564/eM0p0/5QnM=;\n b=iUdyaWlvfOs7IBLT2EqvJ8coS3vO4ETV35LlKP7w2PiNXOWKvebRFAQSu4a7TsQWHT\n loaehrIGHbhx5zNQ0RoQ3cPcqlsmmIdbYF6RK8gnMwf01Wj2zS8D9SFNMukijgo1QRG5\n 8+Q8Va5vTU/k5/6E3LFCHKepQsrwOL65kujWM53BlfU4LeYL6JbPYJEPVCH2JZ87AT5a\n xG0OFBmc+ToIvYHzGq6k19V3z/UDFzsRrpr8e3zAs+iD2M8X/TyK6+7qtGgs7BwMPwsO\n aV2E5qTGtxWrEyopLYokTqLVtfl31iJhk1XeZPmNS/q4Aj7OL1b8pKL6Jx3z+Y2ChHJX\n cdqQ==",
        "X-Gm-Message-State": "AOJu0YwToMR7Bd95VXH3y8VW/MQ6G2Icx+tU4O6oUUrCvZr+/yvBY3S4\n NwFHKa8HNVJ6o6nqTSkXhV7CucjMIou6GqSVTowZcNP1BzLIwdub6JIZblJZG8purxjm0kafsX7\n ErkE9Ajm1czVPVOXyBKouI9g8GaRdAG7j3/fuco+WMZkFadF8eKcOsF4Co48MRzmaQWhetxvzvC\n kGiL4fQinZ/CXvy0hp7fGgaTKSr8zTt2/ziH9fpg==",
        "X-Gm-Gg": "AeBDieuYW/jCJitlz2QLkIXRoiixunOg5MIXbfFKkhI66LUd4v6FHd5OwWShYzVGmDd\n XdMYKn5cjQt9pVRapfATZNJ2oYdPeuWMqPWGiMMA+bmwkrN8WmvtqwPlXmZ/9AS4SVnDZaH1Eo4\n NH2jwAihxfEWxroMTNymswUNfmIHRQ/vJBA/X4YojfTLfq8YMeFB/UqPJBmc7+nXKR9OX8wZZgS\n AV8IBI7voXUPC04/ZCqkL2MNTCaboOceoS7Aj+wZWVMFLbkZQU2UTO9hg3adTrK/lJCCI19iXCw\n Qzz1gOGJnUA97j2AhHI52nIPZHzTkDSnK81xmkeBX0f0WvDXBqttuGJqgby4dJ5KiDRBL2nvU9G\n CLcaWQgljVPfYSxpDNm4ZdQzsTGmpj2ripxUIIqoCVw8k3COxcwQg+MCPjr6CSXEDM1RVjPpwFR\n WlMsOUmyCB",
        "X-Received": [
            "by 2002:a05:620a:1986:b0:8ed:c5ef:119e with SMTP id\n af79cd13be357-904d4d52fbamr1980297485a.22.1778254952157;\n Fri, 08 May 2026 08:42:32 -0700 (PDT)",
            "by 2002:a05:620a:1986:b0:8ed:c5ef:119e with SMTP id\n af79cd13be357-904d4d52fbamr1980287585a.22.1778254951188;\n Fri, 08 May 2026 08:42:31 -0700 (PDT)"
        ],
        "To": "dev@openvswitch.org",
        "Cc": "dceara@redhat.com",
        "Date": "Fri,  8 May 2026 16:42:22 +0100",
        "Message-ID": "<20260508154222.749992-2-moloings@redhat.com>",
        "X-Mailer": "git-send-email 2.52.0",
        "In-Reply-To": "<20260508154222.749992-1-moloings@redhat.com>",
        "References": "<20260508154222.749992-1-moloings@redhat.com>",
        "MIME-Version": "1.0",
        "X-Mimecast-Spam-Score": "0",
        "X-Mimecast-MFC-PROC-ID": "IxswQqCJkaFlktkOKxrreZ50RwV56WRsBhdNQeIi_BA_1778254953",
        "X-Mimecast-Originator": "redhat.com",
        "Subject": "[ovs-dev] [PATCH ovn v2 1/1] ic: Add transit switch port and schema.",
        "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=\"utf-8\"",
        "Content-Transfer-Encoding": "base64",
        "Errors-To": "ovs-dev-bounces@openvswitch.org",
        "Sender": "\"dev\" <ovs-dev-bounces@openvswitch.org>"
    },
    "content": "Enable the creation and management of transit switch ports, when\ncreated, these ports are available across multiple AZs and may share\nport binding depending on configuration. This patch also includes tests\nand a multinode test.\n\nCommands to add, set address and delete port\n    ovn-ic-nbctl tsp-add ts0 ts0-p0 chassis=chassis\n    ovn-ic-nbctl tsp-set-addr ts0-p0 \"00:11:22:11:22:34\n        192.168.10.11/24\"\n    ovn-ic-nbctl tsp-del ts0-p0\n\nReported-at: https://issues.redhat.com/browse/FDP-2878\nSigned-off-by: Mairtin O'Loingsigh <moloings@redhat.com>\n---\n NEWS                     |   4 +\n ic/ovn-ic.c              | 227 ++++++++++++++++++++++++++++++++----\n lib/ovn-util.c           |  44 +++++++\n lib/ovn-util.h           |   4 +\n ovn-ic-nb.ovsschema      |  23 +++-\n ovn-ic-nb.xml            |  37 ++++++\n tests/multinode.at       | 217 ++++++++++++++++++++++++++++++++++-\n tests/ovn-ic-nbctl.at    |  25 ++++\n tests/ovn-ic.at          |  50 ++++++++\n utilities/ovn-ic-nbctl.c | 241 ++++++++++++++++++++++++++++++++++++++-\n utilities/ovn-nbctl.c    |  52 +--------\n 11 files changed, 852 insertions(+), 72 deletions(-)",
    "diff": "diff --git a/NEWS b/NEWS\nindex 68cdbffea..73d91a8db 100644\n--- a/NEWS\n+++ b/NEWS\n@@ -9,6 +9,10 @@ Post v26.03.0\n \n OVN v26.03.0 - xxx xx xxxx\n --------------------------\n+   - Added Transit Switch port support:\n+    * Support the creation of Transit Switch Ports.\n+    * Added new ovn-ic-nbctl 'tsp-add' and 'tsp-set-addr' commands to manage\n+        Transit Switch Ports.\n    - Added LSP/LRP option \"requested-encap-ip\" to let CMS request a specific\n      SB Port_Binding encap IP (e.g., for transit switch ports in ovn-k8s\n      interconnect mode).\ndiff --git a/ic/ovn-ic.c b/ic/ovn-ic.c\nindex ba9490658..b41b78e3e 100644\n--- a/ic/ovn-ic.c\n+++ b/ic/ovn-ic.c\n@@ -631,6 +631,23 @@ find_tr_in_nb(struct ic_context *ctx, char *tr_name)\n     return NULL;\n }\n \n+static const struct nbrec_logical_router *\n+find_router_by_port(struct ic_context *ctx, const char *port_name)\n+{\n+    const struct nbrec_logical_router *lr;\n+    const struct nbrec_logical_router_port *lrp;\n+    NBREC_LOGICAL_ROUTER_FOR_EACH (lr, ctx->ovnnb_idl) {\n+        for (size_t i = 0; i < lr->n_ports; i++) {\n+            lrp = lr->ports[i];\n+            if (!strcmp(lrp->name, port_name)) {\n+                return lr;\n+            }\n+        }\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@@ -733,6 +750,28 @@ get_lp_address_for_sb_pb(struct ic_context *ctx,\n     return peer->n_mac ? *peer->mac : NULL;\n }\n \n+static const char *\n+get_lp_address_for_tr_pb(struct ic_context *ctx,\n+                         struct icnbrec_transit_switch_port *tsp)\n+{\n+    const struct nbrec_logical_switch_port *nb_lsp;\n+\n+    nb_lsp = get_lsp_by_ts_port_name(ctx, tsp->name);\n+    if (nb_lsp->type && !strcmp(nb_lsp->type, \"switch\")) {\n+        /* Switches always have implicit \"unknown\" address, and IC-SB port\n+         * binding can only have one address specified. */\n+        return \"unknown\";\n+    }\n+\n+    const struct sbrec_port_binding *peer =\n+        find_sb_pb_by_name(ctx->sbrec_port_binding_by_name, tsp->peer);\n+    if (peer && peer->n_mac) {\n+        return *peer->mac;\n+    } else {\n+        return NULL;\n+    }\n+}\n+\n static const struct sbrec_chassis *\n find_sb_chassis(struct ic_context *ctx, const char *name)\n {\n@@ -865,6 +904,53 @@ sync_local_port(struct ic_context *ctx,\n     sync_lsp_tnl_key(lsp, isb_pb->tunnel_key);\n }\n \n+/* For each local port:\n+ *   - Sync from ISB to NB.\n+ *   - Sync from ISB to SB.\n+ */\n+static void\n+sync_switch_port(struct ic_context *ctx,\n+                 struct icnbrec_transit_switch_port *tsp,\n+                 const struct icsbrec_port_binding *isb_pb,\n+                 const struct nbrec_logical_switch_port *lsp,\n+                 const struct sbrec_port_binding *sb_pb)\n+{\n+    const char *address = get_lp_address_for_tr_pb(ctx, tsp);\n+    if (!address) {\n+        VLOG_DBG(\"Can't get router/switch port address for logical \"\n+                 \"switch port %s\", lsp->name);\n+        if (isb_pb->address[0]) {\n+            icsbrec_port_binding_set_address(isb_pb, \"\");\n+        }\n+    } else {\n+        if (strcmp(address, isb_pb->address)) {\n+            icsbrec_port_binding_set_address(isb_pb, address);\n+        }\n+    }\n+\n+    /* Sync gateway from SB to ISB */\n+    const struct sbrec_port_binding *crp = find_crp_for_sb_pb(ctx, sb_pb);\n+    if (crp && crp->chassis) {\n+        if (strcmp(crp->chassis->name, isb_pb->gateway)) {\n+            icsbrec_port_binding_set_gateway(isb_pb, crp->chassis->name);\n+        }\n+    } else if (!strcmp(tsp->type, \"switch\") && sb_pb->chassis) {\n+        if (strcmp(sb_pb->chassis->name, isb_pb->gateway)) {\n+            icsbrec_port_binding_set_gateway(isb_pb, sb_pb->chassis->name);\n+        }\n+    } else {\n+        if (isb_pb->gateway[0]) {\n+            icsbrec_port_binding_set_gateway(isb_pb, \"\");\n+        }\n+    }\n+\n+    /* Sync external_ids:router-id to ISB */\n+    update_isb_pb_external_ids(ctx, sb_pb, isb_pb);\n+\n+    /* Sync back tunnel key from ISB to NB */\n+    sync_lsp_tnl_key(lsp, isb_pb->tunnel_key);\n+}\n+\n /* For each remote port:\n  *   - Sync from ISB to NB\n  *   - Sync gateway from ISB to SB\n@@ -991,6 +1077,21 @@ allocate_port_key(struct hmap *pb_tnlids)\n                               1, (1u << 15) - 1, &hint);\n }\n \n+static void\n+set_isb_pb_peer(struct ic_context *ctx,\n+                const struct icnbrec_transit_switch_port *tsp,\n+                const struct icsbrec_port_binding *isb_pb)\n+{\n+    const struct nbrec_logical_router *lr;\n+    lr = find_router_by_port(ctx, tsp->peer);\n+    if (lr) {\n+        char *uuid_s = xasprintf(UUID_FMT, UUID_ARGS(&lr->header_.uuid));\n+        icsbrec_port_binding_update_external_ids_setkey(isb_pb, \"router-id\",\n+                                                        uuid_s);\n+        free(uuid_s);\n+    }\n+}\n+\n static const struct icsbrec_port_binding *\n create_isb_pb(struct ic_context *ctx, const char *logical_port,\n               const struct icsbrec_availability_zone *az, const char *ts_name,\n@@ -1010,6 +1111,7 @@ create_isb_pb(struct ic_context *ctx, const char *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     return isb_pb;\n }\n \n@@ -1028,7 +1130,7 @@ get_lrp_by_lrp_name(struct ic_context *ctx, const char *lrp_name)\n }\n \n static bool\n-trp_is_remote(struct ic_context *ctx, const char *chassis_name)\n+chassis_is_remote(struct ic_context *ctx, const char *chassis_name)\n {\n     if (chassis_name) {\n         const struct sbrec_chassis *chassis =\n@@ -1057,11 +1159,40 @@ lrp_create(struct ic_context *ctx, const struct nbrec_logical_router *lr,\n     return lrp;\n }\n \n+static struct nbrec_logical_switch_port *\n+lsp_create(struct ic_context *ctx, const struct nbrec_logical_switch *ls,\n+           const struct icnbrec_transit_switch_port *tsp)\n+{\n+    bool router_port = !strcmp(tsp->type, \"router\");\n+    const char *type = router_port ? \"router\" : \"\";\n+\n+    struct nbrec_logical_switch_port *lsp =\n+        nbrec_logical_switch_port_insert(ctx->ovnnb_txn);\n+    nbrec_logical_switch_port_set_name(lsp, tsp->name);\n+\n+    nbrec_logical_switch_port_update_options_setkey(lsp, \"interconn-ts\",\n+                                                    tsp->name);\n+    nbrec_logical_switch_port_set_type(lsp, type);\n+    nbrec_logical_switch_port_set_peer(lsp, tsp->peer);\n+\n+    if (router_port) {\n+        nbrec_logical_switch_port_update_options_setkey(\n+            lsp, \"router-port\", tsp->peer);\n+    }\n+\n+    const char *addresses[] = { router_port ? \"router\": \"unknown\" };\n+    nbrec_logical_switch_port_set_addresses(lsp, addresses, 1);\n+\n+    nbrec_logical_switch_update_ports_addvalue(ls, lsp);\n+    return lsp;\n+}\n+\n static void\n sync_ts_isb_pb(struct ic_context *ctx, const struct sbrec_port_binding *sb_pb,\n                const struct icsbrec_port_binding *isb_pb)\n {\n     const char *address = get_lp_address_for_sb_pb(ctx, sb_pb);\n+\n     if (address) {\n         icsbrec_port_binding_set_address(isb_pb, address);\n     }\n@@ -1118,7 +1249,6 @@ port_binding_run(struct ic_context *ctx)\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     ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, ctx->ovninb_idl) {\n         const struct nbrec_logical_switch *ls = find_ts_in_nb(ctx, ts->name);\n@@ -1126,9 +1256,20 @@ port_binding_run(struct ic_context *ctx)\n             VLOG_DBG(\"Transit switch %s not found in NB.\", ts->name);\n             continue;\n         }\n+        struct shash nb_ports = SHASH_INITIALIZER(&nb_ports);\n+        struct shash old_nb_ports = SHASH_INITIALIZER(&old_nb_ports);\n         struct shash local_pbs = SHASH_INITIALIZER(&local_pbs);\n         struct shash remote_pbs = SHASH_INITIALIZER(&remote_pbs);\n \n+        for (size_t i = 0; i < ls->n_ports; i++) {\n+            const struct nbrec_logical_switch_port *lsp = ls->ports[i];\n+            if (smap_get(&lsp->options, \"interconn-ts\")) {\n+                shash_add(&nb_ports, lsp->name, lsp);\n+            } else {\n+                shash_add(&old_nb_ports, lsp->name, lsp);\n+            }\n+        }\n+\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@@ -1145,9 +1286,56 @@ port_binding_run(struct ic_context *ctx)\n         }\n         icsbrec_port_binding_index_destroy_row(isb_pb_key);\n \n+        for (size_t i = 0; i < ts->n_ports; i++) {\n+            struct icnbrec_transit_switch_port *tsp = ts->ports[i];\n+            if (!chassis_is_remote(ctx, tsp->chassis) ||\n+                !strcmp(tsp->chassis, \"\")) {\n+                isb_pb = shash_find_and_delete(&local_pbs, tsp->name);\n+                if (!isb_pb) {\n+                    isb_pb = create_isb_pb(ctx, tsp->name, ctx->runned_az,\n+                                           ts->name, &ts->header_.uuid,\n+                                           \"transit-switch-port\", &pb_tnlids);\n+                    if (!isb_pb) {\n+                        continue;\n+                    }\n+                    set_isb_pb_peer(ctx, tsp, isb_pb);\n+                }\n+\n+                const struct nbrec_logical_switch_port *lsp =\n+                    shash_find_and_delete(&nb_ports, tsp->name);\n+                if (!lsp) {\n+                    lsp = lsp_create(ctx, ls, tsp);\n+                }\n+\n+                const struct sbrec_port_binding *sb_pb;\n+                sb_pb = find_lsp_in_sb(ctx, lsp);\n+                if (sb_pb) {\n+                    sync_switch_port(ctx, tsp, isb_pb, lsp, sb_pb);\n+                }\n+            } else {\n+                /* Remote port sync */\n+                isb_pb = shash_find_and_delete(&remote_pbs, tsp->name);\n+                if (isb_pb) {\n+                    const struct nbrec_logical_switch_port *lsp =\n+                        shash_find_and_delete(&nb_ports, tsp->name);\n+                    if (!lsp) {\n+                        lsp = lsp_create(ctx, ls, tsp);\n+                    }\n+\n+                    const struct sbrec_port_binding *sb_pb;\n+                    sb_pb = find_lsp_in_sb(ctx, lsp);\n+                    if (sb_pb) {\n+                        sync_remote_port(ctx, isb_pb, lsp, sb_pb);\n+                    }\n+                }\n+            }\n+        }\n+\n+        /* Support legacy way of adding transit switch ports. */\n+        const struct sbrec_port_binding *sb_pb;\n         const struct nbrec_logical_switch_port *lsp;\n-        for (int i = 0; i < ls->n_ports; i++) {\n-            lsp = ls->ports[i];\n+        SHASH_FOR_EACH (node, &old_nb_ports) {\n+            lsp = node->data;\n \n             if (!strcmp(lsp->type, \"router\")\n                 || !strcmp(lsp->type, \"switch\")) {\n@@ -1165,16 +1353,6 @@ port_binding_run(struct ic_context *ctx)\n                 } else {\n                     sync_local_port(ctx, isb_pb, sb_pb, lsp);\n                 }\n-\n-                if (isb_pb->type) {\n-                    icsbrec_port_binding_set_type(isb_pb,\n-                                                  \"transit-switch-port\");\n-                }\n-\n-                if (isb_pb->nb_ic_uuid) {\n-                    icsbrec_port_binding_set_nb_ic_uuid(isb_pb,\n-                                                        &ts->header_.uuid, 1);\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@@ -1193,6 +1371,11 @@ port_binding_run(struct ic_context *ctx)\n             }\n         }\n \n+        SHASH_FOR_EACH (node, &nb_ports) {\n+            nbrec_logical_switch_port_delete(node->data);\n+            nbrec_logical_switch_update_ports_delvalue(ls, node->data);\n+        }\n+\n         /* Delete extra port-binding from ISB */\n         SHASH_FOR_EACH (node, &local_pbs) {\n             icsbrec_port_binding_delete(node->data);\n@@ -1203,8 +1386,10 @@ port_binding_run(struct ic_context *ctx)\n             create_nb_lsp(ctx, node->data, ls);\n         }\n \n+        shash_destroy(&nb_ports);\n         shash_destroy(&local_pbs);\n         shash_destroy(&remote_pbs);\n+        shash_destroy(&old_nb_ports);\n     }\n \n     SHASH_FOR_EACH (node, &switch_all_local_pbs) {\n@@ -1250,7 +1435,7 @@ port_binding_run(struct ic_context *ctx)\n         for (size_t i = 0; i < tr->n_ports; i++) {\n             const struct icnbrec_transit_router_port *trp = tr->ports[i];\n \n-            if (trp_is_remote(ctx, trp->chassis)) {\n+            if (chassis_is_remote(ctx, trp->chassis)) {\n                 isb_pb = shash_find_and_delete(&remote_pbs, trp->name);\n             } else {\n                 isb_pb = shash_find_and_delete(&local_pbs, trp->name);\n@@ -1275,7 +1460,7 @@ port_binding_run(struct ic_context *ctx)\n             }\n         }\n \n-        SHASH_FOR_EACH(node, &nb_ports) {\n+        SHASH_FOR_EACH (node, &nb_ports) {\n             nbrec_logical_router_port_delete(node->data);\n             nbrec_logical_router_update_ports_delvalue(lr, node->data);\n         }\n@@ -2580,10 +2765,12 @@ route_run(struct ic_context *ctx)\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-        if (!strcmp(nb_lsp->type, \"switch\")) {\n-            VLOG_DBG(\"IC-SB Port_Binding '%s' on ts '%s' corresponds to a \"\n-                     \"switch port, not considering for route collection.\",\n-                     isb_pb->logical_port, isb_pb->transit_switch);\n+        if (!strcmp(nb_lsp->type, \"switch\") || !strcmp(nb_lsp->type, \"\")) {\n+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);\n+            VLOG_DBG_RL(&rl,\n+                        \"IC-SB Port_Binding '%s' on ts '%s' corresponds to a \"\n+                        \"switch port, not considering for route collection.\",\n+                        isb_pb->logical_port, isb_pb->transit_switch);\n             continue;\n         }\n \ndiff --git a/lib/ovn-util.c b/lib/ovn-util.c\nindex fb02825ac..d41ab366f 100644\n--- a/lib/ovn-util.c\n+++ b/lib/ovn-util.c\n@@ -1869,3 +1869,47 @@ eth_addr_parse_masked(const char *s, struct eth_addr *ea, unsigned int *plen)\n     *ea = eth_addr_zero;\n     return false;\n }\n+\n+bool\n+sp_contains_duplicate_ip(struct lport_addresses *laddrs1,\n+                          struct lport_addresses *laddrs2,\n+                          char *port_name,\n+                          char **error_str)\n+{\n+    for (size_t i = 0; i < laddrs1->n_ipv4_addrs; i++) {\n+        for (size_t j = 0; j < laddrs2->n_ipv4_addrs; j++) {\n+            if (laddrs1->ipv4_addrs[i].addr == laddrs2->ipv4_addrs[j].addr) {\n+                if (error_str) {\n+                    *error_str = xasprintf(\"duplicate IPv4 address '%s' \"\n+                                           \"found on logical switch \"\n+                                           \"port '%s'\",\n+                                           laddrs1->ipv4_addrs[i].addr_s,\n+                                           port_name);\n+                }\n+                return true;\n+            }\n+        }\n+    }\n+\n+    for (size_t i = 0; i < laddrs1->n_ipv6_addrs; i++) {\n+        for (size_t j = 0; j < laddrs2->n_ipv6_addrs; j++) {\n+            if (IN6_ARE_ADDR_EQUAL(&laddrs1->ipv6_addrs[i].addr,\n+                                   &laddrs2->ipv6_addrs[j].addr)) {\n+                if (error_str) {\n+                    *error_str = xasprintf(\"duplicate IPv6 address \"\n+                                           \"'%s' found on logical \"\n+                                           \"switch port '%s'\",\n+                                           laddrs1->ipv6_addrs[i].addr_s,\n+                                           port_name);\n+                }\n+                return true;\n+            }\n+        }\n+    }\n+\n+    if (error_str) {\n+        *error_str = NULL;\n+    }\n+\n+    return false;\n+}\ndiff --git a/lib/ovn-util.h b/lib/ovn-util.h\nindex b44c9c770..001af3c3e 100644\n--- a/lib/ovn-util.h\n+++ b/lib/ovn-util.h\n@@ -790,6 +790,10 @@ char *normalize_ipv6_addr_str(const char *orig_addr);\n \n char *normalize_addr_str(const char *orig_addr);\n \n+bool sp_contains_duplicate_ip(struct lport_addresses *laddrs1,\n+                              struct lport_addresses *laddrs2, char *name,\n+                              char **error_str);\n+\n #define NEIGH_REDISTRIBUTE_MODES    \\\n     NEIGH_REDISTRIBUTE_MODE(FDB, 0) \\\n     NEIGH_REDISTRIBUTE_MODE(IP, 1)\ndiff --git a/ovn-ic-nb.ovsschema b/ovn-ic-nb.ovsschema\nindex ca67a2fa9..ae7f38377 100644\n--- a/ovn-ic-nb.ovsschema\n+++ b/ovn-ic-nb.ovsschema\n@@ -1,7 +1,7 @@\n {\n     \"name\": \"OVN_IC_Northbound\",\n-    \"version\": \"1.3.0\",\n-    \"cksum\": \"1918565391 5082\",\n+    \"version\": \"1.4.0\",\n+    \"cksum\": \"1916436818 6015\",\n     \"tables\": {\n         \"IC_NB_Global\": {\n             \"columns\": {\n@@ -24,9 +24,28 @@\n                              \"min\": 0, \"max\": \"unlimited\"}}},\n             \"maxRows\": 1,\n             \"isRoot\": true},\n+        \"Transit_Switch_Port\": {\n+            \"columns\": {\n+                \"name\": {\"type\": \"string\"},\n+                \"other_config\": {\n+                    \"type\": {\"key\": \"string\", \"value\": \"string\",\n+                             \"min\": 0, \"max\": \"unlimited\"}},\n+                \"type\": {\"type\": \"string\"},\n+                \"chassis\": {\"type\": \"string\"},\n+                \"peer\": {\"type\": \"string\"},\n+                \"addresses\": {\"type\": {\"key\": \"string\",\n+                                       \"min\": 0,\n+                                       \"max\": \"unlimited\"}}},\n+            \"isRoot\": false,\n+            \"indexes\": [[\"name\"]]},\n         \"Transit_Switch\": {\n             \"columns\": {\n                 \"name\": {\"type\": \"string\"},\n+                \"ports\": {\"type\": {\"key\": {\"type\": \"uuid\",\n+                                           \"refTable\": \"Transit_Switch_Port\",\n+                                           \"refType\": \"strong\"},\n+                                   \"min\": 0,\n+                                   \"max\": \"unlimited\"}},\n                 \"other_config\": {\n                     \"type\": {\"key\": \"string\", \"value\": \"string\",\n                              \"min\": 0, \"max\": \"unlimited\"}},\ndiff --git a/ovn-ic-nb.xml b/ovn-ic-nb.xml\nindex a3a35baf2..b660f10ab 100644\n--- a/ovn-ic-nb.xml\n+++ b/ovn-ic-nb.xml\n@@ -112,6 +112,10 @@\n       </column>\n     </group>\n \n+    <column name=\"ports\">\n+      The switch's ports.\n+    </column>\n+\n     <group title=\"Common Columns\">\n       <column name=\"other_config\"/>\n       <column name=\"external_ids\">\n@@ -190,6 +194,39 @@\n     </group>\n   </table>\n \n+  <table name=\"Transit_Switch_Port\" title=\"Transit logical switch port\">\n+    <p>\n+      Each row represents one transit logical switch port for interconnection\n+      between different OVN deployments (availability zones).\n+    </p>\n+\n+    <group title=\"Naming\">\n+      <column name=\"name\">\n+        A name that uniquely identifies the transit logical switch port.\n+      </column>\n+    </group>\n+\n+    <column name=\"type\">\n+      Specify if the port if of type router or vif.\n+    </column>\n+\n+    <column name=\"chassis\">\n+      The chassis this switch port should be bound to.\n+    </column>\n+\n+    <column name=\"peer\">\n+      Name of peer port.\n+    </column>\n+\n+    <column name=\"addresses\">\n+      Addresses to assign to port.\n+    </column>\n+\n+    <group title=\"Common Columns\">\n+      <column name=\"other_config\"/>\n+    </group>\n+  </table>\n+\n   <table name=\"SSL\">\n     SSL/TLS configuration for ovn-nb database access.\n \ndiff --git a/tests/multinode.at b/tests/multinode.at\nindex d07660797..30015eb82 100644\n--- a/tests/multinode.at\n+++ b/tests/multinode.at\n@@ -2453,7 +2453,222 @@ for i in 1 2; do\n     check m_as $chassis ovn-nbctl lsp-add-router-port ts ts-tr tr-ts\n \n     check m_as $chassis ovn-nbctl lrp-add tr tr-ts 00:00:00:00:10:00 10.100.200.1/24 10:200::1/64\n-    check m_as $chassis ovn-nbctl set logical_router tr options:requested-tnl-key=20\n+    check m_as $chassis ovn-nbctl lrp-set-gateway-chassis tr-ts $chassis\n+\n+    # Add TS pods, with the same tunnel keys on both sides\n+    check m_as $chassis ovn-nbctl lsp-add ts pod10\n+    check m_as $chassis ovn-nbctl lsp-set-addresses pod10 \"00:00:00:00:10:10 10.100.200.10 10:200::10\"\n+    check m_as $chassis ovn-nbctl set logical_switch_port pod10 options:requested-tnl-key=10\n+\n+    check m_as $chassis ovn-nbctl lsp-add ts pod20\n+    check m_as $chassis ovn-nbctl lsp-set-addresses pod20 \"00:00:00:00:10:20 10.100.200.20 10:200::20\"\n+    check m_as $chassis ovn-nbctl set logical_switch_port pod20 options:requested-tnl-key=20\n+\n+    # Add mgmt pod\n+    check m_as $chassis ovn-nbctl lsp-add ts mgmt\n+    check m_as $chassis ovn-nbctl lsp-set-addresses mgmt \"00:00:00:00:10:11 10.100.200.11 10:200::11\"\n+    check m_as $chassis ovn-nbctl set logical_switch_port mgmt options:requested-tnl-key=11\n+done\n+\n+# Add SNAT for the GW router that corresponds to \"gw-tr\" LRP IP\n+check m_as ovn-chassis-1 ovn-nbctl lr-nat-add gw snat 100.65.0.1 192.168.100.0/24\n+check m_as ovn-chassis-1 ovn-nbctl lr-nat-add gw snat 100:65::1 1000::/64\n+check m_as ovn-chassis-2 ovn-nbctl lr-nat-add gw snat 100.65.0.5 192.168.100.0/24\n+check m_as ovn-chassis-2 ovn-nbctl lr-nat-add gw snat 100:65::5 1000::/64\n+\n+# Add peer ports between GW and TR\n+check m_as ovn-chassis-1 ovn-nbctl lrp-add gw gw-tr 00:00:00:00:30:01 100.65.0.1/30 100:65::1/126 peer=tr-gw1\n+check m_as ovn-chassis-1 ovn-nbctl set logical_router_port tr-gw1 peer=\"gw-tr\"\n+\n+check m_as ovn-chassis-2 ovn-nbctl lrp-add gw gw-tr 00:00:00:00:30:05 100.65.0.5/30 100:65::5/126 peer=tr-gw2\n+check m_as ovn-chassis-2 ovn-nbctl set logical_router_port tr-gw2 peer=\"gw-tr\"\n+\n+# Add routes for the TS subnet\n+check m_as ovn-chassis-1 ovn-nbctl lr-route-add gw 10.100.200.0/24 100.65.0.2\n+check m_as ovn-chassis-1 ovn-nbctl lr-route-add gw 10:200::/64 100:65::2\n+check m_as ovn-chassis-2 ovn-nbctl lr-route-add gw 10.100.200.0/24 100.65.0.6\n+check m_as ovn-chassis-2 ovn-nbctl lr-route-add gw 10:200::/64 100:65::6\n+\n+# Add LB on TS and condition NAT\n+check m_as ovn-chassis-1 ovn-nbctl lb-add lb0 172.16.0.5:5656 10.100.200.10:2324 tcp\n+check m_as ovn-chassis-1 ovn-nbctl ls-lb-add ts lb0\n+check m_as ovn-chassis-1 ovn-nbctl --match=\"eth.dst == 00:00:00:00:10:11\" lr-nat-add tr snat 172.16.0.2 10.100.200.0/24\n+check m_as ovn-chassis-1 ovn-nbctl set logical_router tr options:ct-commit-all=\"true\"\n+\n+check m_as ovn-chassis-1 ovn-nbctl lsp-add public external\n+check m_as ovn-chassis-1 ovn-nbctl lsp-set-addresses external \"00:00:00:00:20:10 192.168.100.10 1000::10\"\n+\n+# Configure ports on the transit switch as remotes\n+check m_as ovn-chassis-1 ovn-nbctl lsp-set-type pod20 remote\n+check m_as ovn-chassis-1 ovn-nbctl lsp-set-options pod10 requested-chassis=ovn-chassis-1\n+check m_as ovn-chassis-1 ovn-nbctl lsp-set-options mgmt requested-chassis=ovn-chassis-1\n+check m_as ovn-chassis-1 ovn-nbctl lsp-set-options pod20 requested-chassis=ovn-chassis-2\n+\n+check m_as ovn-chassis-2 ovn-nbctl lsp-set-type pod10 remote\n+check m_as ovn-chassis-2 ovn-nbctl lsp-set-type mgmt remote\n+check m_as ovn-chassis-2 ovn-nbctl lsp-set-options pod10 requested-chassis=ovn-chassis-1\n+check m_as ovn-chassis-2 ovn-nbctl lsp-set-options mgmt requested-chassis=ovn-chassis-1\n+check m_as ovn-chassis-2 ovn-nbctl lsp-set-options pod20 requested-chassis=ovn-chassis-2\n+\n+m_as ovn-chassis-1 /data/create_fake_vm.sh external external 00:00:00:00:20:10 1500 192.168.100.10 24 192.168.100.1 1000::10/64 1000::1\n+m_as ovn-chassis-1 /data/create_fake_vm.sh pod10 pod10 00:00:00:00:10:10 1500 10.100.200.10 24 10.100.200.1 10:200::10/64 10:200::1\n+m_as ovn-chassis-1 /data/create_fake_vm.sh mgmt mgmt 00:00:00:00:10:11 1500 10.100.200.11 24 10.100.200.1 10:200::11/64 10:200::1\n+m_as ovn-chassis-2 /data/create_fake_vm.sh pod20 pod20 00:00:00:00:10:20 1500 10.100.200.20 24 10.100.200.1 10:200::20/64 10:200::1\n+\n+# We cannot use any of the helpers as they assume that there is only single ovn-northd instance running\n+check m_as ovn-chassis-1 ovn-nbctl --wait=hv sync\n+OVS_WAIT_UNTIL([test -n \"$(m_as ovn-chassis-1 ovn-sbctl --bare --columns _uuid find Port_Binding logical_port=external up=true)\"])\n+OVS_WAIT_UNTIL([test -n \"$(m_as ovn-chassis-1 ovn-sbctl --bare --columns _uuid find Port_Binding logical_port=pod10 up=true)\"])\n+check m_as ovn-chassis-2 ovn-nbctl --wait=hv sync\n+OVS_WAIT_UNTIL([test -n \"$(m_as ovn-chassis-2 ovn-sbctl --bare --columns _uuid find Port_Binding logical_port=pod20 up=true)\"])\n+\n+M_NS_CHECK_EXEC([ovn-chassis-1], [external], [ping -q -c 5 -i 0.3 -w 2 10.100.200.20 | FORMAT_PING], \\\n+[0], [dnl\n+5 packets transmitted, 5 received, 0% packet loss, time 0ms\n+])\n+\n+M_NS_CHECK_EXEC([ovn-chassis-1], [external], [ping -q -c 5 -i 0.3 -w 2 10:200::20 | FORMAT_PING], \\\n+[0], [dnl\n+5 packets transmitted, 5 received, 0% packet loss, time 0ms\n+])\n+\n+M_NS_CHECK_EXEC([ovn-chassis-1], [mgmt], [ip a a 172.16.100.2/24 dev mgmt])\n+M_NS_DAEMONIZE([ovn-chassis-1], [pod10], [nc -e /bin/cat -v -l -o server.log 10.100.200.10 2324], [pod10.pid])\n+M_START_TCPDUMP([ovn-chassis-1], [-neei pod10-p ip], [pod10])\n+M_START_TCPDUMP([ovn-chassis-1], [-neei mgmt-p ip], [mgmt])\n+\n+m_as ovn-chassis-1 sh -c 'echo -e \"Hello\\nHello\" > msg.expected'\n+check m_as ovn-chassis-1 ovn-nbctl --policy=\"src-ip\" lr-route-add tr 10.100.200.0/24 10.100.200.11\n+\n+check test $(m_as ovn-chassis-1 grep -c \"skipping output to input port\" \\\n+    /var/log/openvswitch/ovs-vswitchd.log) -eq 0\n+check test $(m_as ovn-chassis-2 grep -c \"skipping output to input port\" \\\n+    /var/log/openvswitch/ovs-vswitchd.log) -eq 0\n+\n+M_NS_CHECK_EXEC([ovn-chassis-1], [mgmt], [sh -c '(echo \"Hello\"; sleep 3) | nc -s 172.16.100.2 -o client.log 172.16.0.5 5656'], [0], [ignore], [ignore])\n+check m_as ovn-chassis-1 cmp server.log msg.expected\n+check m_as ovn-chassis-1 cmp client.log msg.expected\n+\n+echo \"Chassis1\"\n+m_as ovn-chassis-1 ovn-sbctl show\n+m_as ovn-chassis-1 ovn-nbctl show\n+m_as ovn-chassis-1 ovs-vsctl show\n+\n+echo \"Chassis2\"\n+m_as ovn-chassis-2 ovn-sbctl show\n+m_as ovn-chassis-2 ovn-nbctl show\n+m_as ovn-chassis-2 ovs-vsctl show\n+\n+# Connect the chassis back to the original northd and remove northd per chassis.\n+for i in 1 2; do\n+    chassis=\"ovn-chassis-$i\"\n+    ip=$(m_as $chassis ip -4 addr show eth1 | grep inet | awk '{print $2}' | cut -d'/' -f1)\n+\n+    multinode_cleanup_ic $chassis\n+    multinode_setup_controller $chassis $chassis $ip \"170.168.0.2\"\n+    multinode_cleanup_northd $chassis\n+done\n+\n+m_as ovn-chassis-1 killall tcpdump\n+\n+AT_CLEANUP\n+\n+AT_SETUP([ovn multinode - Transit Router + Transit Switch])\n+\n+# Check that ovn-fake-multinode setup is up and running\n+check_fake_multinode_setup\n+\n+# Delete the multinode NB and OVS resources before starting the test.\n+cleanup_multinode_resources\n+\n+# Network topology\n+#    ┌─────────────────────────────────┐     ┌────────────────────────────────┐\n+#    │                                 │     │                                │\n+#    │    ┌───────────────────┐   AZ1  │     │  AZ2   ┌───────────────────┐   │\n+#    │    │     external      │        │     │        │                   │   │\n+#    │    │                   │        │     │        │                   │   │\n+#    │    │ 192.168.100.10/24 │        │     │        │ ................. │   │\n+#    │    │    1000::10/64    │        │     │        │                   │   │\n+#    │    └─────────┬─────────┘        │     │        └─────────┬─────────┘   │\n+#    │              │                  │     │                  │             │\n+#    │              │                  │     │                  │             │\n+#    │    ┌─────────┴─────────┐        │     │        ┌─────────┴─────────┐   │\n+#    │    │ 192.168.100.1/24  │        │     │        │ 192.168.100.2/24  │   │\n+#    │    │    1000::1/64     │        │     │        │    1000::2/64     │   │\n+#    │    │                   │        │     │        │                   │   │\n+#    │    │        GW         │        │     │        │        GW         │   │\n+#    │    │                   │        │     │        │                   │   │\n+#    │    │   100.65.0.1/30   │        │     │        │   100.65.0.5/30   │   │\n+#    │    │   100:65::1/126   │        │     │        │   100:65::5/126   │   │\n+#    │    └─────────┬─────────┘        │     │        └───────────────────┘   │\n+#    │              │                  │     │                  │             │\n+#    │              │  Peer ports      │     │                  │  Peer ports │\n+#    │              │                  │     │                  │             │\n+#    │    ┌─────────┴──────────────────│─────│──────────────────┴─────────┐   │\n+#    │    │   100.65.0.2/30            │     │            100.65.0.6/30   │   │\n+#    │    │   100:65::2/126            │     │            100:65::6/126   │   │\n+#    │    │                            │     │                            │   │\n+#    │    │                            │  TR │                            │   │\n+#    │    │                            │     │                            │   │\n+#    │    │  10.100.200.1/24           │     │           10.100.200.1/24  │   │\n+#    │    │   10:200::1/64             │     │            10:200::1/64    │   │\n+#    │    └─────────┬──────────────────│─────│────────────────────────────┘   │\n+#    │              │                  │     │                  │             │\n+#    │              │                  │     │                  │             │\n+#    │              │                  │     │                  │             │\n+#    │    ┌─────────┴──────────────────│─────│────────────────────────────┐   │\n+#    │    │                            │  TS │                            │   │\n+#    │    └─────────┬──────────────────│─────│────────────────────────────┘   │\n+#    │              │                  │     │                  │             │\n+#    │              │                  │     │                  │             │\n+#    │              │                  │     │                  │             │\n+#    │    ┌─────────┴─────────┐        │     │        ┌─────────┴─────────┐   │\n+#    │    │       pod10       │        │     │        │       pod20       │   │\n+#    │    │                   │        │     │        │                   │   │\n+#    │    │  10.100.200.10/24 │        │     │        │  10.100.200.20/24 │   │\n+#    │    │   10:200::10/64   │        │     │        │   10:200::20/64   │   │\n+#    │    └───────────────────┘        │     │        └───────────────────┘   │\n+#    └─────────────────────────────────┘     └────────────────────────────────┘\n+\n+for i in 1 2; do\n+    chassis=\"ovn-chassis-$i\"\n+    ip=$(m_as $chassis ip -4 addr show eth1 | grep inet | awk '{print $2}' | cut -d'/' -f1)\n+    central_ip=$(m_central_as ip -4 addr show eth1 | grep inet | awk '{print $2}' | cut -d'/' -f1)\n+\n+    multinode_setup_northd $chassis\n+    multinode_setup_controller $chassis $chassis $ip $ip\n+    multinode_setup_ic $chassis $central_ip\n+\n+    check m_as $chassis ovs-vsctl set open . external_ids:ovn-monitor-all=true\n+    check m_as $chassis ovs-vsctl set open . external_ids:ovn-is-interconn=true\n+done\n+\n+# Add TR and TS\n+check m_central_as ovn-ic-nbctl tr-add tr\n+check m_central_as ovn-ic-nbctl trp-add tr tr-gw1 \\\n+    00:00:00:00:30:02 100.65.0.2/30 100:65::2/126 \\\n+    chassis=ovn-chassis-1\n+check m_central_as ovn-ic-nbctl trp-add tr tr-gw2 \\\n+    00:00:00:00:30:06 100.65.0.6/30 100:65::6/126 \\\n+    chassis=ovn-chassis-2\n+check m_central_as ovn-ic-nbctl --wait=sb sync\n+\n+check m_central_as ovn-ic-nbctl ts-add ts\n+check m_central_as ovn-ic-nbctl tsp-add ts ts-tr type=router peer=tr-ts\n+\n+for i in 1 2; do\n+    chassis=\"ovn-chassis-$i\"\n+\n+    check m_as $chassis ovn-nbctl ls-add public\n+\n+    check m_as $chassis ovn-nbctl lsp-add-router-port public public-gw gw-public\n+\n+    check m_as $chassis ovn-nbctl lr-add gw\n+    check m_as $chassis ovn-nbctl lrp-add gw gw-public 00:00:00:00:20:00 192.168.100.$i/24 1000::$i/64\n+\n+    check m_as $chassis ovn-nbctl set logical_router gw options:chassis=$chassis\n+\n+    check m_as $chassis ovn-nbctl lrp-add tr tr-ts 00:00:00:00:10:00 10.100.200.1/24 10:200::1/64\n     check m_as $chassis ovn-nbctl lrp-set-gateway-chassis tr-ts $chassis\n \n     # Add TS pods, with the same tunnel keys on both sides\ndiff --git a/tests/ovn-ic-nbctl.at b/tests/ovn-ic-nbctl.at\nindex 4c5269784..576828428 100644\n--- a/tests/ovn-ic-nbctl.at\n+++ b/tests/ovn-ic-nbctl.at\n@@ -61,6 +61,31 @@ AT_CHECK([ovn-ic-nbctl ts-del ts2], [1], [],\n \n AT_CHECK([ovn-ic-nbctl --if-exists ts-del ts2])\n \n+AT_CHECK([ovn-ic-nbctl --may-exist ts-add ts0])\n+AT_CHECK([ovn-ic-nbctl ts-list | uuidfilt], [0], [dnl\n+<0> (ts0)\n+])\n+\n+AT_CHECK([ovn-ic-nbctl tsp-add], [1], [],\n+  [ovn-ic-nbctl: 'tsp-add' command requires at least 2 arguments\n+])\n+\n+AT_CHECK([ovn-ic-nbctl tsp-add ts0], [1], [],\n+  [ovn-ic-nbctl: 'tsp-add' command requires at least 2 arguments\n+])\n+\n+AT_CHECK([ovn-ic-nbctl tsp-add ts0 ts0-p0 chassis=chassis])\n+\n+AT_CHECK([ovn-ic-nbctl --may-exist tsp-add ts0 ts0-p0 chassis=chassis])\n+AT_CHECK([ovn-ic-nbctl tsp-set-addr ts0-p0 \"00:11:22:11:22:33 192.168.10.10/24\"])\n+AT_CHECK([ovn-ic-nbctl tsp-set-addr ts0-p1 \"00:11:22:11:22:34 192.168.10.11/24\"], [1], [],\n+    [ovn-ic-nbctl: ts0-p1: switch port name not found\n+])\n+\n+AT_CHECK([ovn-ic-nbctl tsp-del], [1], [],\n+  [ovn-ic-nbctl: 'tsp-del' command requires at least 1 arguments\n+])\n+\n OVN_IC_NBCTL_TEST_STOP\n AT_CLEANUP\n \ndiff --git a/tests/ovn-ic.at b/tests/ovn-ic.at\nindex 0fa7c4f29..6c3ab4618 100644\n--- a/tests/ovn-ic.at\n+++ b/tests/ovn-ic.at\n@@ -164,6 +164,56 @@ AT_CHECK([ovn-ic-sbctl show | grep -A2 lsp1], [0], [dnl\n             address: [[\"00:00:00:00:00:01 10.0.0.1/24\"]]\n ])\n \n+# remove transit switch and check if port_binding is deleted\n+check ovn-ic-nbctl --wait=sb ts-del ts1\n+check_row_count ic-sb:Port_Binding 0 logical_port=lsp1\n+for i in 1 2; do\n+    az=az$i\n+    ovn_as $az\n+    OVN_CLEANUP_SBOX(gw-$az)\n+    OVN_CLEANUP_AZ([$az])\n+done\n+OVN_CLEANUP_IC\n+AT_CLEANUP\n+])\n+OVN_FOR_EACH_NORTHD([\n+AT_SETUP([ovn-ic -- transit port-bindings deletion upon TS deletion])\n+\n+ovn_init_ic_db\n+net_add n1\n+\n+# 1 GW per AZ\n+for i in 1 2; do\n+    az=az$i\n+    ovn_start $az\n+    sim_add gw-$az\n+    as gw-$az\n+    check ovs-vsctl add-br br-phys\n+    ovn_az_attach $az n1 br-phys 192.168.1.$i\n+    check ovs-vsctl set open . external-ids:ovn-is-interconn=true\n+done\n+\n+ovn_as az1\n+\n+# create transit switch and connect to LR\n+check ovn-ic-nbctl --wait=sb ts-add ts1\n+check ovn-nbctl lr-add lr1\n+check ovn-nbctl lrp-add lr1 lrp1 00:00:00:00:00:01 10.0.0.1/24\n+check ovn-nbctl lrp-set-gateway-chassis lrp1 gw-az1\n+sleep 6000\n+check ovn-ic-nbctl tsp-add ts1 lsp1 type=router chassis=gw-az1 peer=lrp1\n+\n+wait_row_count Datapath_Binding 1 external_ids:interconn-ts=ts1\n+\n+# Sync ic-sb DB to see the TS changes.\n+check ovn-ic-nbctl --wait=sb sync\n+\n+AT_CHECK([ovn-ic-sbctl show | grep -A2 lsp1], [0], [dnl\n+        port lsp1\n+            transit switch: ts1\n+            address: [[\"00:00:00:00:00:01 10.0.0.1/24\"]]\n+])\n+\n # remove transit switch and check if port_binding is deleted\n check ovn-ic-nbctl --wait=sb ts-del ts1\n check_row_count ic-sb:Port_Binding 0 logical_port=lsp1\ndiff --git a/utilities/ovn-ic-nbctl.c b/utilities/ovn-ic-nbctl.c\nindex 50e975283..9c1635d81 100644\n--- a/utilities/ovn-ic-nbctl.c\n+++ b/utilities/ovn-ic-nbctl.c\n@@ -336,6 +336,9 @@ Transit switch commands:\\n\\\n   ts-add SWITCH              create a transit switch named SWITCH\\n\\\n   ts-del SWITCH              delete SWITCH\\n\\\n   ts-list                    print all transit switches\\n\\\n+  tsp-add SWITCH PORT        add a transit switch PORT\\n\\\n+  tsp-set-addr PORT NETWORK  add a transit switch PORT\\n\\\n+  tsp-del PORT               delete a transit switch PORT\\n\\\n \\n\\\n Transit router commands:\\n\\\n   tr-add ROUTER              create a transit router named ROUTER\\n\\\n@@ -400,6 +403,7 @@ struct ic_nbctl_context {\n      * ic_nbctl_context_invalidate_cache() or manually update the cache to\n      * maintain its correctness. */\n     bool cache_valid;\n+    struct shash tsp_to_ts_map;\n };\n \n static struct cmd_show_table cmd_show_tables[] = {\n@@ -617,6 +621,38 @@ trp_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist,\n     return NULL;\n }\n \n+static char *\n+tsp_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist,\n+                    const struct icnbrec_transit_switch_port **tsp_p)\n+{\n+    const struct icnbrec_transit_switch_port *tsp = NULL;\n+    *tsp_p = NULL;\n+    struct uuid tsp_uuid;\n+    bool is_uuid = uuid_from_string(&tsp_uuid, id);\n+    if (is_uuid) {\n+        tsp = icnbrec_transit_switch_port_get_for_uuid(ctx->idl, &tsp_uuid);\n+    }\n+\n+    if (!tsp) {\n+        const struct icnbrec_transit_switch_port *iter;\n+\n+        ICNBREC_TRANSIT_SWITCH_PORT_FOR_EACH (iter, ctx->idl) {\n+            if (!strcmp(iter->name, id)) {\n+                tsp = iter;\n+                break;\n+            }\n+        }\n+    }\n+\n+    if (!tsp && must_exist) {\n+        return xasprintf(\"%s: switch port %s not found\", id,\n+                         is_uuid ? \"UUID\" : \"name\");\n+    }\n+\n+    *tsp_p = tsp;\n+    return NULL;\n+}\n+\n static void\n ic_nbctl_tr_del(struct ctl_context *ctx)\n {\n@@ -664,6 +700,68 @@ ic_nbctl_trp_del(struct ctl_context *ctx)\n     icnbrec_transit_router_port_delete(trp);\n }\n \n+static struct ic_nbctl_context *\n+ic_nbctl_context_get(struct ctl_context *base)\n+{\n+    struct ic_nbctl_context *icnbctx\n+        = CONTAINER_OF(base, struct ic_nbctl_context, base);\n+    if (icnbctx->cache_valid) {\n+        return icnbctx;\n+    }\n+\n+    const struct icnbrec_transit_switch *ts;\n+    ICNBREC_TRANSIT_SWITCH_FOR_EACH (ts, base->idl) {\n+        for (size_t i = 0; i < ts->n_ports; i++) {\n+            shash_add_once(&icnbctx->tsp_to_ts_map, ts->ports[i]->name, ts);\n+        }\n+    }\n+\n+    icnbctx->cache_valid = true;\n+    return icnbctx;\n+}\n+\n+/* Returns the logical switch that contains 'lsp'. */\n+static char * OVS_WARN_UNUSED_RESULT\n+tsp_to_ts(struct ctl_context *ctx,\n+          const struct icnbrec_transit_switch_port *tsp,\n+          const struct icnbrec_transit_switch **ts_p)\n+{\n+    struct ic_nbctl_context *icnbctx = ic_nbctl_context_get(ctx);\n+    const struct icnbrec_transit_switch *ts;\n+    *ts_p = NULL;\n+\n+    ts = shash_find_data(&icnbctx->tsp_to_ts_map, tsp->name);\n+    if (ts) {\n+        *ts_p = ts;\n+        return NULL;\n+    }\n+    /* Can't happen because of the database schema */\n+    return xasprintf(\"transit port %s is not part of any transit switch\",\n+                     tsp->name);\n+}\n+\n+static void\n+ic_nbctl_tsp_del(struct ctl_context *ctx)\n+{\n+    bool must_exist = !shash_find(&ctx->options, \"--if-exists\");\n+    const char *tsp_name = ctx->argv[1];\n+    const struct icnbrec_transit_switch_port *tsp = NULL;\n+\n+    ctx->error = tsp_by_name_or_uuid(ctx, tsp_name, must_exist, &tsp);\n+    if (ctx->error) {\n+        return;\n+    }\n+\n+    const struct icnbrec_transit_switch *ts = NULL;\n+    char *error = tsp_to_ts(ctx, tsp, &ts);\n+    if (error) {\n+        return;\n+    }\n+\n+    icnbrec_transit_switch_update_ports_delvalue(ts, tsp);\n+    icnbrec_transit_switch_port_delete(tsp);\n+}\n+\n static void\n ic_nbctl_tr_list(struct ctl_context *ctx)\n {\n@@ -802,6 +900,140 @@ ic_nbctl_trp_add(struct ctl_context *ctx)\n     icnbrec_transit_router_update_ports_addvalue(tr, trp);\n }\n \n+static void\n+ic_nbctl_tsp_add(struct ctl_context *ctx)\n+{\n+    bool may_exist = shash_find(&ctx->options, \"--may-exist\") != NULL;\n+    const char *ts_name = ctx->argv[1];\n+    const char *tsp_name = ctx->argv[2];\n+    const struct icnbrec_transit_switch *ts;\n+\n+    ctx->error = ts_by_name_or_uuid(ctx, ts_name, true, &ts);\n+    if (ctx->error) {\n+        return;\n+    }\n+\n+    const struct icnbrec_transit_switch_port *tsp;\n+    ctx->error = tsp_by_name_or_uuid(ctx, tsp_name, false, &tsp);\n+    if (ctx->error) {\n+        return;\n+    }\n+\n+    if (tsp) {\n+        if (!may_exist) {\n+            ctl_error(ctx, \"%s: a port with this name already exists\",\n+                      tsp_name);\n+        }\n+        return;\n+    }\n+\n+    tsp = icnbrec_transit_switch_port_insert(ctx->txn);\n+    icnbrec_transit_switch_port_set_name(tsp, tsp_name);\n+\n+    int n_settings = ctx->argc - 3;\n+    char **settings = (char **) &ctx->argv[3];\n+    for (size_t i = 0; i < n_settings; i++) {\n+        ctx->error = ctl_set_column(\"Transit_Switch_Port\", &tsp->header_,\n+                                    settings[i], ctx->symtab);\n+        if (ctx->error) {\n+            return;\n+        }\n+    }\n+\n+    icnbrec_transit_switch_update_ports_addvalue(ts, tsp);\n+}\n+\n+static char *\n+tsp_contains_duplicates(const struct icnbrec_transit_switch *ts,\n+                        const struct icnbrec_transit_switch_port *tsp,\n+                        const char *address)\n+{\n+    char *sub_error = NULL;\n+    struct lport_addresses laddrs;\n+    if (!extract_lsp_addresses(address, &laddrs)) {\n+        return NULL;\n+    }\n+\n+    for (size_t i = 0; i < ts->n_ports; i++) {\n+        struct icnbrec_transit_switch_port *tsp_test = ts->ports[i];\n+        if (tsp_test == tsp) {\n+            continue;\n+        }\n+\n+        for (size_t j = 0; j < tsp_test->n_addresses; j++) {\n+            struct lport_addresses laddrs_test;\n+            char *addr = tsp_test->addresses[j];\n+            if (extract_lsp_addresses(addr, &laddrs_test)) {\n+                bool has_duplicate =\n+                    sp_contains_duplicate_ip(&laddrs, &laddrs_test,\n+                                             tsp_test->name, &sub_error);\n+                destroy_lport_addresses(&laddrs_test);\n+                if (has_duplicate) {\n+                    goto err_out;\n+                }\n+            }\n+        }\n+    }\n+\n+err_out: ;\n+    char *error = NULL;\n+    if (sub_error) {\n+        error = xasprintf(\"Error on switch %s: %s\", ts->name, sub_error);\n+        free(sub_error);\n+    }\n+    destroy_lport_addresses(&laddrs);\n+    return error;\n+}\n+\n+static void\n+ic_nbctl_tsp_set_addr(struct ctl_context *ctx)\n+{\n+    const char *tsp_name = ctx->argv[1];\n+\n+    const struct icnbrec_transit_switch_port *tsp;\n+    ctx->error = tsp_by_name_or_uuid(ctx, tsp_name, true, &tsp);\n+    if (ctx->error) {\n+        return;\n+    }\n+\n+    const struct icnbrec_transit_switch *ts = NULL;\n+    char *error = tsp_to_ts(ctx, tsp, &ts);\n+    if (error) {\n+        ctx->error = error;\n+        return;\n+    }\n+\n+    int i;\n+    for (i = 2; i < ctx->argc; i++) {\n+        char ipv6_s[IPV6_SCAN_LEN + 1];\n+        struct eth_addr ea;\n+        ovs_be32 ip;\n+\n+        if (strcmp(ctx->argv[i], \"unknown\") && strcmp(ctx->argv[i], \"dynamic\")\n+            && strcmp(ctx->argv[i], \"router\")\n+            && !ovs_scan(ctx->argv[i], ETH_ADDR_SCAN_FMT,\n+                         ETH_ADDR_SCAN_ARGS(ea))\n+            && !ovs_scan(ctx->argv[i], \"dynamic \"IPV6_SCAN_FMT, ipv6_s)\n+            && !ovs_scan(ctx->argv[i], \"dynamic \"IP_SCAN_FMT,\n+                         IP_SCAN_ARGS(&ip))) {\n+            ctl_error(ctx, \"%s: Invalid address format. See ovn-nb(5). \"\n+                      \"Hint: An Ethernet address must be \"\n+                      \"listed before an IP address, together as a single \"\n+                      \"argument.\", ctx->argv[i]);\n+            return;\n+        }\n+\n+        ctx->error = tsp_contains_duplicates(ts, tsp, ctx->argv[i]);\n+        if (ctx->error) {\n+            return;\n+        }\n+    }\n+\n+    icnbrec_transit_switch_port_set_addresses(tsp,\n+                                              (const char **) ctx->argv + 2,\n+                                              ctx->argc - 2);\n+}\n+\n static void\n verify_connections(struct ctl_context *ctx)\n {\n@@ -1036,6 +1268,7 @@ ic_nbctl_context_init(struct ic_nbctl_context *ic_nbctl_ctx,\n     ctl_context_init(&ic_nbctl_ctx->base, command, idl, txn, symtab,\n                      NULL);\n     ic_nbctl_ctx->cache_valid = false;\n+    shash_init(&ic_nbctl_ctx->tsp_to_ts_map);\n }\n \n static void\n@@ -1077,6 +1310,7 @@ ic_nbctl_context_done(struct ic_nbctl_context *ic_nbctl_ctx,\n                    struct ctl_command *command)\n {\n     ctl_context_done(&ic_nbctl_ctx->base, command);\n+    shash_destroy(&ic_nbctl_ctx->tsp_to_ts_map);\n }\n \n static void\n@@ -1317,7 +1551,13 @@ static const struct ctl_command_syntax ic_nbctl_commands[] = {\n     { \"ts-add\", 1, 1, \"SWITCH\", NULL, ic_nbctl_ts_add, NULL, \"--may-exist\", RW },\n     { \"ts-del\", 1, 1, \"SWITCH\", NULL, ic_nbctl_ts_del, NULL, \"--if-exists\", RW },\n     { \"ts-list\", 0, 0, \"\", NULL, ic_nbctl_ts_list, NULL, \"\", RO },\n+    { \"tsp-add\", 2, INT_MAX, \"SWITCH PORT COLUMN[:KEY]=VALUE]...\",\n+        NULL, ic_nbctl_tsp_add, NULL, \"--may-exist\", RW },\n+    { \"tsp-set-addr\", 2, INT_MAX, \"PORT [ADDRESS]...\",\n+        NULL, ic_nbctl_tsp_set_addr, NULL, \"\", RW },\n \n+    { \"tsp-del\", 1, 1, \"PORT\", NULL, ic_nbctl_tsp_del, NULL, \"--if-exists\",\n+        RW },\n     /* transit router commands. */\n     { \"tr-add\", 1, 1, \"ROUTER\", NULL, ic_nbctl_tr_add, NULL, \"--may-exist\",\n         RW },\n@@ -1329,7 +1569,6 @@ static const struct ctl_command_syntax ic_nbctl_commands[] = {\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         RO},\ndiff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c\nindex 0ef207272..56cfdf5b4 100644\n--- a/utilities/ovn-nbctl.c\n+++ b/utilities/ovn-nbctl.c\n@@ -1482,50 +1482,6 @@ nbctl_pre_lsp_set_addresses(struct ctl_context *ctx)\n                          &nbrec_logical_switch_port_col_dynamic_addresses);\n }\n \n-static bool\n-lsp_contains_duplicate_ip(struct lport_addresses *laddrs1,\n-                          struct lport_addresses *laddrs2,\n-                          const struct nbrec_logical_switch_port *lsp_test,\n-                          char **error_str)\n-{\n-    for (size_t i = 0; i < laddrs1->n_ipv4_addrs; i++) {\n-        for (size_t j = 0; j < laddrs2->n_ipv4_addrs; j++) {\n-            if (laddrs1->ipv4_addrs[i].addr == laddrs2->ipv4_addrs[j].addr) {\n-                if (error_str) {\n-                    *error_str = xasprintf(\"duplicate IPv4 address '%s' \"\n-                                           \"found on logical switch \"\n-                                           \"port '%s'\",\n-                                           laddrs1->ipv4_addrs[i].addr_s,\n-                                           lsp_test->name);\n-                }\n-                return true;\n-            }\n-        }\n-    }\n-\n-    for (size_t i = 0; i < laddrs1->n_ipv6_addrs; i++) {\n-        for (size_t j = 0; j < laddrs2->n_ipv6_addrs; j++) {\n-            if (IN6_ARE_ADDR_EQUAL(&laddrs1->ipv6_addrs[i].addr,\n-                                   &laddrs2->ipv6_addrs[j].addr)) {\n-                if (error_str) {\n-                    *error_str = xasprintf(\"duplicate IPv6 address \"\n-                                           \"'%s' found on logical \"\n-                                           \"switch port '%s'\",\n-                                           laddrs1->ipv6_addrs[i].addr_s,\n-                                           lsp_test->name);\n-                }\n-                return true;\n-            }\n-        }\n-    }\n-\n-    if (error_str) {\n-        *error_str = NULL;\n-    }\n-\n-    return false;\n-}\n-\n static char *\n lsp_contains_duplicates(const struct nbrec_logical_switch *ls,\n                         const struct nbrec_logical_switch_port *lsp,\n@@ -1550,8 +1506,8 @@ lsp_contains_duplicates(const struct nbrec_logical_switch *ls,\n             }\n             if (extract_lsp_addresses(addr, &laddrs_test)) {\n                 bool has_duplicate =\n-                    lsp_contains_duplicate_ip(&laddrs, &laddrs_test,\n-                                              lsp_test, &sub_error);\n+                    sp_contains_duplicate_ip(&laddrs, &laddrs_test,\n+                                             lsp_test->name, &sub_error);\n                 destroy_lport_addresses(&laddrs_test);\n                 if (has_duplicate) {\n                     goto err_out;\n@@ -8835,8 +8791,8 @@ lsp_health_check_parse_target_address(\n             goto cleanup;\n         }\n \n-        if (lsp_contains_duplicate_ip(&target_address,\n-                                      &lsp_address, lsp, NULL)) {\n+        if (sp_contains_duplicate_ip(&target_address,\n+                                      &lsp_address, lsp->name, NULL)) {\n             ip_found_on_port = true;\n         }\n \n",
    "prefixes": [
        "ovs-dev",
        "v2",
        "1/1"
    ]
}