Blob Blame History Raw
diff --git a/.ci/ci.sh b/.ci/ci.sh
index 10f11939c..3f1b41ead 100755
--- a/.ci/ci.sh
+++ b/.ci/ci.sh
@@ -23,7 +23,7 @@ CONTAINER_WORKDIR="/workspace/ovn-tmp"
 IMAGE_NAME=${IMAGE_NAME:-"ovn-org/ovn-tests"}
 
 # Test variables
-ARCH=${ARCH:-$(uname -i)}
+ARCH=${ARCH:-$(uname -m)}
 CC=${CC:-gcc}
 
 
@@ -105,6 +105,16 @@ function run_tests() {
     "
 }
 
+function check_clang_version_ge() {
+    lower=$1
+    version=$(clang --version | head -n1 | cut -d' ' -f3)
+    if ! echo -e "$lower\n$version" | sort -CV; then
+      return 1
+    fi
+
+    return 0
+}
+
 options=$(getopt --options "" \
     --long help,shell,archive-logs,jobs:,ovn-path:,ovs-path:,image-name:\
     -- "${@}")
@@ -149,7 +159,7 @@ while true; do
 done
 
 # Workaround for https://bugzilla.redhat.com/2153359
-if [ "$ARCH" = "aarch64" ]; then
+if [ "$ARCH" = "aarch64" ] && ! check_clang_version_ge "16.0.0"; then
     ASAN_OPTIONS="detect_leaks=0"
 fi
 
diff --git a/.ci/linux-build.sh b/.ci/linux-build.sh
index 5a79a52da..7270b9e60 100755
--- a/.ci/linux-build.sh
+++ b/.ci/linux-build.sh
@@ -80,7 +80,7 @@ function configure_gcc()
             # We should install gcc-multilib for x86 build, we cannot
             # do it directly because gcc-multilib is not available
             # for arm64
-            sudo apt install -y gcc-multilib
+            sudo apt update && sudo apt install -y gcc-multilib
         fi
     fi
 }
diff --git a/.ci/linux-util.sh b/.ci/linux-util.sh
new file mode 100755
index 000000000..920345123
--- /dev/null
+++ b/.ci/linux-util.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+function free_up_disk_space_ubuntu()
+{
+    local pkgs='azure-cli aspnetcore-* dotnet-* ghc-* firefox*
+                google-chrome-stable google-cloud-cli libmono-* llvm-*
+                microsoft-edge-stable mono-* msbuild mysql-server-core-*
+                php-* php7* powershell* temurin-* zulu-*'
+
+    # Use apt patterns to only select real packages that match the names
+    # in the list above.
+    local pkgs=$(echo $pkgs | sed 's/[^ ]* */~n&/g')
+
+    sudo apt update && sudo apt-get --auto-remove -y purge $pkgs
+
+    local paths='/usr/local/lib/android/ /usr/share/dotnet/ /opt/ghc/
+                 /usr/local/share/boost/'
+    sudo rm -rf $paths
+}
diff --git a/.ci/ovn-kubernetes/Dockerfile b/.ci/ovn-kubernetes/Dockerfile
index 12f819017..eda8b6d02 100644
--- a/.ci/ovn-kubernetes/Dockerfile
+++ b/.ci/ovn-kubernetes/Dockerfile
@@ -81,6 +81,7 @@ RUN dnf install -y *.rpm && rm -f *.rpm
 # install ovn-kubernetes binaries built in previous stage
 RUN mkdir -p /usr/libexec/cni/
 COPY --from=ovnkubebuilder /root/ovn-kubernetes/go-controller/_output/go/bin/ovnkube /usr/bin/
+COPY --from=ovnkubebuilder /root/ovn-kubernetes/go-controller/_output/go/bin/ovnkube-identity /usr/bin/
 COPY --from=ovnkubebuilder /root/ovn-kubernetes/go-controller/_output/go/bin/ovn-kube-util /usr/bin/
 COPY --from=ovnkubebuilder /root/ovn-kubernetes/go-controller/_output/go/bin/ovndbchecker /usr/bin/
 COPY --from=ovnkubebuilder /root/ovn-kubernetes/go-controller/_output/go/bin/ovn-k8s-cni-overlay /usr/libexec/cni/ovn-k8s-cni-overlay
diff --git a/.cirrus.yml b/.cirrus.yml
index bd4cd08aa..3b53c45ae 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -1,14 +1,33 @@
-arm_unit_tests_task:
+compute_engine_instance:
+  image_project: ubuntu-os-cloud
+  image: family/ubuntu-2310-arm64
+  architecture: arm64
+  platform: linux
+  memory: 4G
+
+# Run separate task for the image build, so it's running only once outside
+# the test matrix.
+build_image_task:
+  install_dependencies_script:
+    - sudo apt update
+    - sudo apt install -y podman make
+
+  build_container_script:
+    - cd utilities/containers
+    - make ubuntu
+    - podman save -o /tmp/image.tar ovn-org/ovn-tests:ubuntu
 
-  arm_container:
-    image: ghcr.io/ovn-org/ovn-tests:fedora
-    memory: 4G
-    cpu: 2
+  upload_image_script:
+    - curl -s -X POST -T /tmp/image.tar http://$CIRRUS_HTTP_CACHE_HOST/${CIRRUS_CHANGE_IN_REPO}
+
+arm_unit_tests_task:
+  depends_on:
+    - build_image
 
   env:
-    ARCH: aarch64
     CIRRUS_CLONE_SUBMODULES: true
     PATH: ${HOME}/bin:${HOME}/.local/bin:${PATH}
+    IMAGE_NAME: ovn-org/ovn-tests:ubuntu
     matrix:
       - CC: gcc
         TESTSUITE: test
@@ -31,5 +50,22 @@ arm_unit_tests_task:
 
   name: ARM64 ${CC} ${TESTSUITE} ${TEST_RANGE}
 
+  install_dependencies_script:
+    - sudo apt update
+    - sudo apt install -y podman
+
+  #  XXX This should be removed when native crun >=1.9.1
+  update_crun_script:
+    - crun --version
+    - curl -L "https://github.com/containers/crun/releases/download/1.14.1/crun-1.14.1-linux-arm64" -o /usr/bin/crun
+    - chmod +x /usr/bin/crun
+
+  download_cache_script:
+    - curl http://$CIRRUS_HTTP_CACHE_HOST/${CIRRUS_CHANGE_IN_REPO} -o /tmp/image.tar
+
+  load_image_script:
+    - podman load -i /tmp/image.tar
+    - rm -rf /tmp/image.tar
+
   build_script:
-    - ./.ci/linux-build.sh
+    - ./.ci/ci.sh --archive-logs
diff --git a/.github/workflows/containers.yml b/.github/workflows/containers.yml
index 57e815ed8..87e28d645 100644
--- a/.github/workflows/containers.yml
+++ b/.github/workflows/containers.yml
@@ -15,12 +15,12 @@ env:
 
 jobs:
   container:
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
     strategy:
       matrix:
         distro: [ fedora, ubuntu ]
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
 
       - name: Update APT cache
         run: sudo apt update
diff --git a/.github/workflows/ovn-fake-multinode-tests.yml b/.github/workflows/ovn-fake-multinode-tests.yml
index 75c5ca818..79b6c4253 100644
--- a/.github/workflows/ovn-fake-multinode-tests.yml
+++ b/.github/workflows/ovn-fake-multinode-tests.yml
@@ -13,7 +13,7 @@ concurrency:
 jobs:
   build:
     name: Build ovn-fake-multinode image
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
     strategy:
       matrix:
         cfg:
@@ -26,7 +26,7 @@ jobs:
       XDG_RUNTIME_DIR: ''
     steps:
     - name: Check out ovn-fake-multi-node
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
       with:
         repository: 'ovn-org/ovn-fake-multinode'
         path: 'ovn-fake-multinode'
@@ -36,14 +36,14 @@ jobs:
     # ovn-fake-multinode builds and installs ovs from ovn-fake-multinode/ovs
     # and it builds and installs ovn from ovn-fake-multinode/ovn. It uses the ovs submodule for ovn compilation.
     - name: Check out ovs master
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
       with:
         path: 'ovn-fake-multinode/ovs'
         repository: 'openvswitch/ovs'
         ref: 'master'
 
     - name: Check out ovn ${{ matrix.cfg.branch }}
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
       with:
         path: 'ovn-fake-multinode/ovn'
         submodules: recursive
@@ -63,21 +63,21 @@ jobs:
         sudo podman save ovn/ovn-multi-node:${{ matrix.cfg.branch }} > /tmp/_output/ovn_${{ matrix.cfg.branch }}_image.tar
       working-directory: ovn-fake-multinode
 
-    - uses: actions/upload-artifact@v3
+    - uses: actions/upload-artifact@v4
       with:
         name: test-${{ matrix.cfg.branch }}-image
         path: /tmp/_output/ovn_${{ matrix.cfg.branch }}_image.tar
 
   multinode-tests:
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
     timeout-minutes: 15
     needs: [build]
     strategy:
       fail-fast: false
       matrix:
         cfg:
-        - { branch: "main" }
-        - { branch: "branch-22.03" }
+        - { branch: "main", testsuiteflags: ""}
+        - { branch: "branch-22.03", testsuiteflags: "-k 'basic test'" }
     name: multinode tests ${{ join(matrix.cfg.*, ' ') }}
     env:
       RUNC_CMD: podman
@@ -99,19 +99,24 @@ jobs:
       XDG_RUNTIME_DIR: ''
 
     steps:
+    - name: Check out ovn
+      uses: actions/checkout@v4
+
     - name: install required dependencies
       run:  |
         sudo apt update || true
         sudo apt install -y ${{ env.dependencies }}
 
     - name: Free up disk space
-      run: sudo eatmydata apt-get remove --auto-remove -y aspnetcore-* dotnet-* libmono-* mono-* msbuild php-* php7* ghc-* zulu-*
+      run: |
+        . .ci/linux-util.sh
+        free_up_disk_space_ubuntu
 
-    - uses: actions/download-artifact@v3
+    - uses: actions/download-artifact@v4
       with:
         name: test-main-image
 
-    - uses: actions/download-artifact@v3
+    - uses: actions/download-artifact@v4
       with:
         name: test-branch-22.03-image
 
@@ -121,7 +126,7 @@ jobs:
         sudo podman load --input ovn_branch-22.03_image.tar
 
     - name: Check out ovn-fake-multi-node
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
       with:
         repository: 'ovn-org/ovn-fake-multinode'
         path: 'ovn-fake-multinode'
@@ -132,6 +137,14 @@ jobs:
         sudo systemctl start openvswitch-switch
         sudo ovs-vsctl show
 
+    # XXX This should be removed when native crun >=1.9.1
+    - name: update crun script
+      run: |
+        crun --version
+        sudo curl -L "https://github.com/containers/crun/releases/download/1.14.1/crun-1.14.1-linux-amd64" -o /usr/bin/crun
+        sudo chmod +x /usr/bin/crun
+        echo "New crun version: "$(crun --version)
+
     - name: Start basic cluster
       run: |
         sudo -E ./ovn_cluster.sh start
@@ -151,12 +164,12 @@ jobs:
         echo "$HOME/.local/bin" >> $GITHUB_PATH
 
     - name: set up python
-      uses: actions/setup-python@v4
+      uses: actions/setup-python@v5
       with:
-        python-version: '3.x'
+        python-version: '3.12'
 
     - name: Check out ovn
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
       with:
         path: 'ovn'
         submodules: recursive
@@ -171,7 +184,7 @@ jobs:
 
     - name: Run fake-multinode system tests
       run: |
-        if ! sudo make check-multinode; then
+        if ! sudo make check-multinode TESTSUITEFLAGS="${{ matrix.cfg.testsuiteflags }}"; then
           sudo podman exec -it ovn-central ovn-nbctl show || :
           sudo podman exec -it ovn-central ovn-sbctl show || :
           sudo podman exec -it ovn-chassis-1 ovs-vsctl show || :
@@ -185,9 +198,9 @@ jobs:
     - name: copy logs on failure
       if: failure() || cancelled()
       run: |
-        # upload-artifact@v3 throws exceptions if it tries to upload socket
+        # upload-artifact throws exceptions if it tries to upload socket
         # files and we could have some socket files in testsuite.dir.
-        # Also, upload-artifact@v3 doesn't work well enough with wildcards.
+        # Also, upload-artifact doesn't work well enough with wildcards.
         # So, we're just archiving everything here to avoid any issues.
         mkdir logs
         cp ovn/config.log ./logs/
@@ -198,7 +211,7 @@ jobs:
 
     - name: upload logs on failure
       if: failure() || cancelled()
-      uses: actions/upload-artifact@v3
+      uses: actions/upload-artifact@v4
       with:
         name: logs-linux-${{ join(matrix.cfg.*, '-') }}
         path: logs.tgz
diff --git a/.github/workflows/ovn-kubernetes.yml b/.github/workflows/ovn-kubernetes.yml
index 69ab0566d..0f2b30497 100644
--- a/.github/workflows/ovn-kubernetes.yml
+++ b/.github/workflows/ovn-kubernetes.yml
@@ -24,7 +24,7 @@ env:
 jobs:
   build:
     name: Build
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
     steps:
     - name: Enable Docker experimental features
       run: |
@@ -32,12 +32,12 @@ jobs:
         sudo service docker restart
 
     - name: Check out ovn
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
       with:
         submodules: recursive
 
     - name: Check out ovn-kubernetes
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
       with:
           path: src/github.com/ovn-org/ovn-kubernetes
           repository: ovn-org/ovn-kubernetes
@@ -54,7 +54,7 @@ jobs:
         mkdir /tmp/_output
         docker save ovn-daemonset-f:dev > /tmp/_output/image.tar
 
-    - uses: actions/upload-artifact@v3
+    - uses: actions/upload-artifact@v4
       with:
         name: test-image
         path: /tmp/_output/image.tar
@@ -62,7 +62,7 @@ jobs:
   e2e:
     name: e2e
     if: github.event_name != 'schedule'
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
     timeout-minutes: 220
     strategy:
       fail-fast: false
@@ -95,20 +95,16 @@ jobs:
       KIND_IPV6_SUPPORT: "${{ matrix.ipfamily == 'IPv6' || matrix.ipfamily == 'dualstack' }}"
     steps:
 
+    - name: Check out ovn
+      uses: actions/checkout@v4
+
     - name: Free up disk space
       run: |
-        sudo eatmydata apt-get purge --auto-remove -y \
-          azure-cli aspnetcore-* dotnet-* ghc-* firefox \
-          google-chrome-stable google-cloud-sdk \
-          llvm-* microsoft-edge-stable mono-* \
-          msbuild mysql-server-core-* php-* php7* \
-          powershell temurin-* zulu-*
-
-    - name: Check out ovn
-      uses: actions/checkout@v3
+        . .ci/linux-util.sh
+        free_up_disk_space_ubuntu
 
     - name: Check out ovn-kubernetes
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
       with:
           path: src/github.com/ovn-org/ovn-kubernetes
           repository: ovn-org/ovn-kubernetes
@@ -118,9 +114,10 @@ jobs:
         .ci/ovn-kubernetes/prepare.sh src/github.com/ovn-org/ovn-kubernetes $GITHUB_ENV
 
     - name: Set up Go
-      uses: actions/setup-go@v3
+      uses: actions/setup-go@v5
       with:
         go-version: ${{ env.GO_VERSION }}
+        cache-dependency-path: "**/*.sum"
       id: go
 
     - name: Set up GOPATH
@@ -135,7 +132,7 @@ jobs:
       run: |
         sudo ufw disable
 
-    - uses: actions/download-artifact@v3
+    - uses: actions/download-artifact@v4
       with:
         name: test-image
 
@@ -159,7 +156,7 @@ jobs:
 
     - name: Upload Junit Reports
       if: always()
-      uses: actions/upload-artifact@v3
+      uses: actions/upload-artifact@v4
       with:
         name: kind-junit-${{ env.JOB_NAME }}-${{ github.run_id }}
         path: 'src/github.com/ovn-org/ovn-kubernetes/test/_artifacts/*.xml'
@@ -173,7 +170,7 @@ jobs:
 
     - name: Upload logs
       if: always()
-      uses: actions/upload-artifact@v3
+      uses: actions/upload-artifact@v4
       with:
         name: kind-logs-${{ env.JOB_NAME }}-${{ github.run_id }}
         path: /tmp/kind/logs
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index fe2a14c40..85916548a 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -26,7 +26,7 @@ jobs:
 
     steps:
     - name: checkout
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
 
     - name: update PATH
       run: |
@@ -54,14 +54,14 @@ jobs:
 
     - name: cache
       id: dpdk_cache
-      uses: actions/cache@v3
+      uses: actions/cache@v4
       with:
         path: dpdk-dir
         key: ${{ steps.gen_dpdk_key.outputs.key }}
 
     - name: set up python
       if: steps.dpdk_cache.outputs.cache-hit != 'true'
-      uses: actions/setup-python@v4
+      uses: actions/setup-python@v5
       with:
         python-version: '3.9'
 
@@ -80,10 +80,62 @@ jobs:
       if: steps.dpdk_cache.outputs.cache-hit != 'true'
       run: ./.ci/dpdk-build.sh
 
+  prepare-container:
+    # This job has the following matrix, x: Job trigger, y: Branch
+    # (scheduled jobs run only on main):
+    # +-------+-------------------+-------------------+
+    # |       | Push/Pull request |     Scheduled     |
+    # +-------+-------------------+-------------------+
+    # |  main |  ghcr.io - Ubuntu | ghcr.io - Fedora  |
+    # +-------+-------------------+-------------------+
+    # | !main |  Builds - Ubuntu  | xxxxxxxxxxxxxxxxx |
+    # +-------+-------------------+-------------------+
+    env:
+      DEPENDENCIES: podman
+    name: Prepare container
+    runs-on: ubuntu-22.04
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Update APT cache
+        run: sudo apt update
+
+      - name: Install dependencies
+        run: sudo apt install -y ${{ env.DEPENDENCIES }}
+
+      - name: Choose image distro
+        if: github.event_name == 'push' || github.event_name == 'pull_request'
+        run: |
+          echo "IMAGE_DISTRO=ubuntu" >> $GITHUB_ENV
+
+      - name: Choose image distro
+        if: github.event_name == 'schedule'
+        run: |
+          echo "IMAGE_DISTRO=fedora" >> $GITHUB_ENV
+
+      - name: Build container
+        if: github.ref_name != 'main'
+        run: make ${{ env.IMAGE_DISTRO }}
+        working-directory: utilities/containers
+
+      - name: Download container
+        if: github.ref_name == 'main'
+        run: podman pull ghcr.io/ovn-org/ovn-tests:${{ env.IMAGE_DISTRO }}
+
+      - name: Export image
+        run: podman save -o /tmp/image.tar ovn-org/ovn-tests
+
+      - name: Cache image
+        id: image_cache
+        uses: actions/cache@v4
+        with:
+          path: /tmp/image.tar
+          key: ${{ github.sha }}
+
   build-linux:
-    needs: build-dpdk
+    needs: [build-dpdk, prepare-container]
     env:
-      IMAGE_NAME:  ghcr.io/ovn-org/ovn-tests:ubuntu
       ARCH:        ${{ matrix.cfg.arch }}
       CC:          ${{ matrix.cfg.compiler }}
       DPDK:        ${{ matrix.cfg.dpdk }}
@@ -94,7 +146,7 @@ jobs:
       SANITIZERS:  ${{ matrix.cfg.sanitizers }}
 
     name: linux ${{ join(matrix.cfg.*, ' ') }}
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
 
     strategy:
       fail-fast: false
@@ -130,20 +182,20 @@ jobs:
     steps:
     - name: checkout
       if: github.event_name == 'push' || github.event_name == 'pull_request'
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
       with:
         submodules: recursive
 
     # For weekly runs, don't update submodules
     - name: checkout without submodule
       if: github.event_name == 'schedule'
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
 
     # Weekly runs test using the tip of the most recent stable OVS branch
     # instead of the submodule.
     - name: checkout OVS
       if: github.event_name == 'schedule'
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
       with:
         repository: 'openvswitch/ovs'
         fetch-depth: 0
@@ -157,13 +209,33 @@ jobs:
             sort -V | tail -1)
       working-directory: ovs
 
-    - name: cache
+    - name: cache dpdk
       if: matrix.cfg.dpdk != ''
-      uses: actions/cache@v3
+      uses: actions/cache@v4
       with:
         path: dpdk-dir
         key: ${{ needs.build-dpdk.outputs.dpdk_key }}
 
+    - name: image cache
+      uses: actions/cache@v4
+      with:
+        path: /tmp/image.tar
+        key: ${{ github.sha }}
+
+    # XXX This should be removed when native crun >=1.9.1
+    - name: update crun script
+      run: |
+        crun --version
+        sudo curl -L "https://github.com/containers/crun/releases/download/1.14.1/crun-1.14.1-linux-amd64" -o /usr/bin/crun
+        sudo chmod +x /usr/bin/crun
+        echo "New crun version: "$(crun --version)
+
+    - name: load image
+      run: |
+        sudo podman load -i /tmp/image.tar
+        podman load -i /tmp/image.tar
+        rm -rf /tmp/image.tar
+
     - name: build
       if: ${{ startsWith(matrix.cfg.testsuite, 'system-test') }}
       run: sudo -E ./.ci/ci.sh --archive-logs
@@ -174,7 +246,7 @@ jobs:
 
     - name: upload logs on failure
       if: failure() || cancelled()
-      uses: actions/upload-artifact@v3
+      uses: actions/upload-artifact@v4
       with:
         name: logs-linux-${{ join(matrix.cfg.*, '-') }}
         path: logs.tgz
@@ -193,18 +265,18 @@ jobs:
     steps:
     - name: checkout
       if: github.event_name == 'push' || github.event_name == 'pull_request'
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
       with:
         submodules: recursive
     # For weekly runs, don't update submodules
     - name: checkout without submodule
       if: github.event_name == 'schedule'
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
     # Weekly runs test using the tip of the most recent stable OVS branch
     # instead of the submodule.
     - name: checkout OVS
       if: github.event_name == 'schedule'
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
       with:
         repository: 'openvswitch/ovs'
         fetch-depth: 0
@@ -223,24 +295,24 @@ jobs:
         echo "$HOME/bin"        >> $GITHUB_PATH
         echo "$HOME/.local/bin" >> $GITHUB_PATH
     - name: set up python
-      uses: actions/setup-python@v4
+      uses: actions/setup-python@v5
       with:
-        python-version: '3.x'
+        python-version: '3.12'
     - name: prepare
       run:  ./.ci/osx-prepare.sh
     - name: build
       run:  ./.ci/osx-build.sh
     - name: upload logs on failure
       if: failure()
-      uses: actions/upload-artifact@v3
+      uses: actions/upload-artifact@v4
       with:
         name: logs-osx-clang---disable-ssl
         path: config.log
 
   build-linux-rpm:
     name: linux rpm fedora
-    runs-on: ubuntu-latest
-    container: fedora:latest
+    runs-on: ubuntu-22.04
+    container: fedora:38
     timeout-minutes: 30
 
     strategy:
@@ -251,7 +323,7 @@ jobs:
         run: dnf install -y dnf-plugins-core git rpm-build
 
       - name: checkout
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           submodules: recursive
 
@@ -279,7 +351,7 @@ jobs:
         run:  make rpm-fedora
 
       - name: upload rpm packages
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: rpm-packages
           path: |
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 000000000..8c451663a
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,26 @@
+# .readthedocs.yaml
+# Read the Docs configuration file.
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details.
+
+# Required.
+version: 2
+
+# Set the OS, Python version, etc.
+build:
+  os: ubuntu-22.04
+  tools:
+    python: "3.12"
+
+# Build documentation in the "Documentation/" directory with Sphinx.
+sphinx:
+  configuration: Documentation/conf.py
+  # Default HTML builder.
+  builder: "html"
+
+# Build all formats: HTML, PDF, ePub.
+formats: all
+
+# Declare the Python requirements.
+python:
+  install:
+  - requirements: Documentation/requirements.txt
diff --git a/Documentation/conf.py b/Documentation/conf.py
index f7eceaec8..f8fc0125f 100644
--- a/Documentation/conf.py
+++ b/Documentation/conf.py
@@ -12,16 +12,14 @@
 # All configuration values have a default; values that are commented out
 # serve to show the default.
 
+import importlib
 import string
 import sys
 
-try:
-    import ovs_sphinx_theme
-    use_ovs_theme = True
-except ImportError:
-    print("Cannot find 'ovs-sphinx-theme' package. "
+use_rtd_theme = importlib.util.find_spec('sphinx_rtd_theme') is not None
+if not use_rtd_theme:
+    print("Cannot find 'sphinx_rtd_theme' package. "
           "Falling back to default theme.")
-    use_ovs_theme = False
 
 # -- General configuration ------------------------------------------------
 
@@ -48,7 +46,7 @@ master_doc = 'contents'
 
 # General information about the project.
 project = u'Open Virtual Network (OVN)'
-copyright = u'2020, The Open Virtual Network (OVN) Development Community'
+copyright = u'2020-2023, The Open Virtual Network (OVN) Development Community'
 author = u'The Open Virtual Network (OVN) Development Community'
 
 # The version info for the project you're documenting, acts as replacement for
@@ -89,14 +87,8 @@ linkcheck_anchors = False
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
 #
-if use_ovs_theme:
-    html_theme = 'ovs'
-
-# Add any paths that contain custom themes here, relative to this directory.
-if use_ovs_theme:
-    html_theme_path = [ovs_sphinx_theme.get_theme_dir()]
-else:
-    html_theme_path = []
+if use_rtd_theme:
+    html_theme = 'sphinx_rtd_theme'
 
 # The name of an image file (relative to this directory) to place at the top
 # of the sidebar.
diff --git a/Documentation/internals/documentation.rst b/Documentation/internals/documentation.rst
index 72a120bef..59c18b3a2 100644
--- a/Documentation/internals/documentation.rst
+++ b/Documentation/internals/documentation.rst
@@ -41,17 +41,13 @@ variety of other output formats but also allows for things like
 cross-referencing and indexing. for more information on the two, refer to the
 :doc:`contributing/documentation-style`.
 
-ovs-sphinx-theme
+sphinx_rtd_theme
 ----------------
 
-The documentation uses its own theme, `ovs-sphinx-theme`, which can be found on
-GitHub__ and is published on pypi__. This is shared by Open vSwitch and OVN.
-It is packaged separately to ensure all documentation gets the latest version
-of the theme (assuming there are no major version bumps in that package). If
-building locally and the package is installed, it will be used. If the package
-is not installed, Sphinx will fallback to the default theme.
-
-The package is currently maintained by Stephen Finucane and Russell Bryant.
+The documentation uses `sphinx_rtd_theme`, which can be found on GitHub__ and
+is published on pypi__.  It is also packaged in major distributions.
+If building locally and the package is installed, it will be used.  If the
+package is not installed, Sphinx will fallback to the default theme.
 
 Read the Docs
 -------------
@@ -72,6 +68,6 @@ modifications to this site, refer to the `GitHub project`__.
 
 __ http://docutils.sourceforge.net/rst.html
 __ http://www.sphinx-doc.org/
-__ https://github.com/openvswitch/ovs-sphinx-theme
-__ https://pypi.python.org/pypi/ovs-sphinx-theme
+__ https://github.com/readthedocs/sphinx_rtd_theme
+__ https://pypi.python.org/pypi/sphinx_rtd_theme
 __ https://github.com/ovn-org/ovn-org.github.io
diff --git a/Documentation/internals/release-process.rst b/Documentation/internals/release-process.rst
index ec79fe48c..26d3f8d4d 100644
--- a/Documentation/internals/release-process.rst
+++ b/Documentation/internals/release-process.rst
@@ -64,6 +64,10 @@ Scheduling`_ for the timing of each stage:
    branch.  Features to be added to release branches should be limited in scope
    and risk and discussed on ovs-dev before creating the branch.
 
+   In order to keep the CI stable on the new release branch, the Ubuntu
+   container should be pinned to the current LTS version in the Dockerfile
+   e.g. registry.hub.docker.com/library/ubuntu:22.04.
+
 3. When committers come to rough consensus that the release is ready, they
    release the .0 release on its branch, e.g. 25.09.0 for branch-25.09.  To
    make the actual release, a committer pushes a signed tag named, e.g.
diff --git a/Documentation/intro/install/general.rst b/Documentation/intro/install/general.rst
index 589518846..dd8bf5c2c 100644
--- a/Documentation/intro/install/general.rst
+++ b/Documentation/intro/install/general.rst
@@ -134,10 +134,7 @@ following to obtain better warnings:
 
 - clang, version 3.4 or later
 
-- flake8 along with the hacking flake8 plugin (for Python code). The automatic
-  flake8 check that runs against Python code has some warnings enabled that
-  come from the "hacking" flake8 plugin. If it's not installed, the warnings
-  just won't occur until it's run on a system with "hacking" installed.
+- flake8 (for Python code)
 
 You may find the ovs-dev script found in ``ovs/utilities/ovs-dev.py`` useful.
 
diff --git a/Documentation/requirements.txt b/Documentation/requirements.txt
index 77130c6e0..63a7997f7 100644
--- a/Documentation/requirements.txt
+++ b/Documentation/requirements.txt
@@ -1,2 +1,2 @@
-sphinx>=1.1,<2.0
-ovs_sphinx_theme>=1.0,<1.1
+sphinx>=1.1
+sphinx_rtd_theme>=1.0,<2.0
diff --git a/Documentation/tutorials/ovn-sandbox.rst b/Documentation/tutorials/ovn-sandbox.rst
index 2b574c02f..decc8abb3 100644
--- a/Documentation/tutorials/ovn-sandbox.rst
+++ b/Documentation/tutorials/ovn-sandbox.rst
@@ -162,16 +162,16 @@ to OpenFlow flows programmed by ``ovn-controller``.  See the `ovn-trace(8)`_
 man page for more detail.
 
 
-.. _ovn-architecture: http://openvswitch.org/support/dist-docs/ovn-architecture.7.html
-.. _ovn-nb(5): http://openvswitch.org/support/dist-docs/ovn-nb.5.html
-.. _ovn-sb(5): http://openvswitch.org/support/dist-docs/ovn-sb.5.html
+.. _ovn-architecture: http://www.ovn.org/support/dist-docs/ovn-architecture.7.html
+.. _ovn-nb(5): http://www.ovn.org/support/dist-docs/ovn-nb.5.html
+.. _ovn-sb(5): http://www.ovn.org/support/dist-docs/ovn-sb.5.html
 .. _vtep(5): http://openvswitch.org/support/dist-docs/vtep.5.html
-.. _ovn-northd(8): http://openvswitch.org/support/dist-docs/ovn-northd.8.html
-.. _ovn-controller(8): http://openvswitch.org/support/dist-docs/ovn-controller.8.html
-.. _ovn-controller-vtep(8): http://openvswitch.org/support/dist-docs/ovn-controller-vtep.8.html
+.. _ovn-northd(8): http://www.ovn.org/support/dist-docs/ovn-northd.8.html
+.. _ovn-controller(8): http://www.ovn.org/support/dist-docs/ovn-controller.8.html
+.. _ovn-controller-vtep(8): http://www.ovn.org/support/dist-docs/ovn-controller-vtep.8.html
 .. _vtep-ctl(8): http://openvswitch.org/support/dist-docs/vtep-ctl.8.html
-.. _ovn-nbctl(8): http://openvswitch.org/support/dist-docs/ovn-nbctl.8.html
-.. _ovn-sbctl(8): http://openvswitch.org/support/dist-docs/ovn-sbctl.8.html
-.. _ovn-trace(8): http://openvswitch.org/support/dist-docs/ovn-trace.8.html
+.. _ovn-nbctl(8): http://www.ovn.org/support/dist-docs/ovn-nbctl.8.html
+.. _ovn-sbctl(8): http://www.ovn.org/support/dist-docs/ovn-sbctl.8.html
+.. _ovn-trace(8): http://www.ovn.org/support/dist-docs/ovn-trace.8.html
 .. _ovs-advanced: https://github.com/openvswitch/ovs/blob/master/Documentation/tutorials/ovs-advanced.rst
 
diff --git a/Makefile.am b/Makefile.am
index b58d4a501..bfc9565e8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -90,6 +90,7 @@ EXTRA_DIST = \
 	.ci/dpdk-build.sh \
 	.ci/dpdk-prepare.sh \
 	.ci/linux-build.sh \
+	.ci/linux-util.sh \
 	.ci/osx-build.sh \
 	.ci/osx-prepare.sh \
 	.ci/ovn-kubernetes/Dockerfile \
@@ -99,6 +100,7 @@ EXTRA_DIST = \
 	.github/workflows/test.yml \
 	.github/workflows/ovn-kubernetes.yml \
 	.github/workflows/ovn-fake-multinode-tests.yml \
+	.readthedocs.yaml \
 	boot.sh \
 	$(MAN_FRAGMENTS) \
 	$(MAN_ROOTS) \
@@ -414,16 +416,10 @@ ALL_LOCAL += flake8-check
 #   F811 redefinition of unused <name> from line <N> (only from flake8 v2.0)
 # D*** -- warnings from flake8-docstrings plugin
 # H*** -- warnings from flake8 hacking plugin (custom style checks beyond PEP8)
-#   H231 Python 3.x incompatible 'except x,y:' construct
-#   H232 Python 3.x incompatible octal 077 should be written as 0o77
-#   H233 Python 3.x incompatible use of print operator
-#   H238 old style class declaration, use new style (inherit from `object`)
-FLAKE8_SELECT = H231,H232,H233,H238
 FLAKE8_IGNORE = E121,E123,E125,E126,E127,E128,E129,E131,E722,W503,W504,F811,D,H,I
 flake8-check: $(FLAKE8_PYFILES)
 	$(FLAKE8_WERROR)$(AM_V_GEN) \
 	  src='$^' && \
-	  flake8 $$src --select=$(FLAKE8_SELECT) $(FLAKE8_FLAGS) && \
 	  flake8 $$src --ignore=$(FLAKE8_IGNORE) $(FLAKE8_FLAGS) && \
 	  touch $@
 endif
diff --git a/NEWS b/NEWS
index 75e046ab3..58b1c9066 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,15 @@
+OVN v23.09.3 - xx xxx xxxx
+--------------------------
+
+OVN v23.09.2 - 01 Mar 2024
+--------------------------
+  - Bug fixes
+  - Enable PMTU discovery on geneve/vxlan tunnels for E/W traffic.
+
+OVN v23.09.1 - 01 Dec 2023
+--------------------------
+  - Bug fixes
+
 OVN v23.09.0 - 15 Sep 2023
 ----------------------------
   - Added FDB aging mechanism, that is disabled by default.
diff --git a/configure.ac b/configure.ac
index ab404b959..090a29a15 100644
--- a/configure.ac
+++ b/configure.ac
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 AC_PREREQ(2.63)
-AC_INIT(ovn, 23.09.0, bugs@openvswitch.org)
+AC_INIT(ovn, 23.09.3, bugs@openvswitch.org)
 AC_CONFIG_MACRO_DIR([m4])
 AC_CONFIG_AUX_DIR([build-aux])
 AC_CONFIG_HEADERS([config.h])
diff --git a/controller-vtep/ovn-controller-vtep.c b/controller-vtep/ovn-controller-vtep.c
index 23b368179..4472e5285 100644
--- a/controller-vtep/ovn-controller-vtep.c
+++ b/controller-vtep/ovn-controller-vtep.c
@@ -306,10 +306,12 @@ parse_options(int argc, char *argv[])
 
         switch (c) {
         case 'd':
+            free(ovnsb_remote);
             ovnsb_remote = xstrdup(optarg);
             break;
 
         case 'D':
+            free(vtep_remote);
             vtep_remote = xstrdup(optarg);
             break;
 
diff --git a/controller/bfd.c b/controller/bfd.c
index cf011e382..f47333191 100644
--- a/controller/bfd.c
+++ b/controller/bfd.c
@@ -235,6 +235,9 @@ bfd_run(const struct ovsrec_interface_table *interface_table,
         if (mult) {
             smap_add(&bfd, "mult", mult);
         }
+        /* `check_tnl_key` must always be set to "true" to avoid processing of
+         * BFD control messages originating from a logical port. */
+        smap_add(&bfd, "check_tnl_key", "true");
     }
 
     /* Enable or disable bfd */
diff --git a/controller/binding.c b/controller/binding.c
index a521f2828..2afc5d48a 100644
--- a/controller/binding.c
+++ b/controller/binding.c
@@ -55,8 +55,13 @@ struct claimed_port {
     long long int last_claimed;
 };
 
+struct qos_port {
+    bool added;
+};
+
 static struct shash _claimed_ports = SHASH_INITIALIZER(&_claimed_ports);
 static struct sset _postponed_ports = SSET_INITIALIZER(&_postponed_ports);
+static struct shash _qos_ports = SHASH_INITIALIZER(&_qos_ports);
 
 static void
 remove_additional_chassis(const struct sbrec_port_binding *pb,
@@ -218,6 +223,17 @@ get_qos_egress_port_interface(struct shash *bridge_mappings,
     return NULL;
 }
 
+static void
+add_or_del_qos_port(const char *ovn_port, bool add)
+{
+    struct qos_port *qos_port = shash_find_data(&_qos_ports, ovn_port);
+    if (!qos_port) {
+        qos_port = xzalloc(sizeof *qos_port);
+        shash_add(&_qos_ports, ovn_port, qos_port);
+    }
+    qos_port->added = add;
+}
+
 /* 34359738360 == (2^32 - 1) * 8.  netdev_set_qos() doesn't support
  * 64-bit rate netlink attributes, so the maximum value is 2^32 - 1
  * bytes. The 'max-rate' config option is in bits, so multiplying by 8.
@@ -225,7 +241,7 @@ get_qos_egress_port_interface(struct shash *bridge_mappings,
  * can be unrecognized for certain NICs or reported too low for virtual
  * interfaces. */
 #define OVN_QOS_MAX_RATE    34359738360ULL
-static void
+static bool
 add_ovs_qos_table_entry(struct ovsdb_idl_txn *ovs_idl_txn,
                         const struct ovsrec_port *port,
                         unsigned long long min_rate,
@@ -239,7 +255,7 @@ add_ovs_qos_table_entry(struct ovsdb_idl_txn *ovs_idl_txn,
     const struct ovsrec_qos *qos = port->qos;
     if (qos && !smap_get_bool(&qos->external_ids, "ovn_qos", false)) {
         /* External configured QoS, do not overwrite it. */
-        return;
+        return false;
     }
 
     if (!qos) {
@@ -282,22 +298,18 @@ add_ovs_qos_table_entry(struct ovsdb_idl_txn *ovs_idl_txn,
     ovsrec_queue_verify_external_ids(queue);
     ovsrec_queue_set_external_ids(queue, &external_ids);
     smap_destroy(&external_ids);
+    return true;
 }
 
 static void
-remove_stale_qos_entry(struct ovsdb_idl_txn *ovs_idl_txn,
-                       const struct sbrec_port_binding *pb,
+remove_stale_qos_entry( const char *logical_port,
                        struct ovsdb_idl_index *ovsrec_port_by_qos,
                        const struct ovsrec_qos_table *qos_table,
                        struct hmap *queue_map)
 {
-    if (!ovs_idl_txn) {
-        return;
-    }
-
     struct qos_queue *q = find_qos_queue(
-            queue_map, hash_string(pb->logical_port, 0),
-            pb->logical_port);
+            queue_map, hash_string(logical_port, 0),
+            logical_port);
     if (!q) {
         return;
     }
@@ -338,8 +350,12 @@ remove_stale_qos_entry(struct ovsdb_idl_txn *ovs_idl_txn,
 
 static void
 configure_qos(const struct sbrec_port_binding *pb,
-              struct binding_ctx_in *b_ctx_in,
-              struct binding_ctx_out *b_ctx_out)
+              struct ovsdb_idl_txn *ovs_idl_txn,
+              struct ovsdb_idl_index *ovsrec_port_by_qos,
+              const struct ovsrec_qos_table *qos_table,
+              struct hmap *qos_map,
+              const struct ovsrec_open_vswitch_table *ovs_table,
+              const struct ovsrec_bridge_table *bridge_table)
 {
     unsigned long long min_rate = smap_get_ullong(
             &pb->options, "qos_min_rate", 0);
@@ -351,20 +367,20 @@ configure_qos(const struct sbrec_port_binding *pb,
 
     if ((!min_rate && !max_rate && !burst) || !queue_id) {
         /* Qos is not configured for this port. */
-        remove_stale_qos_entry(b_ctx_in->ovs_idl_txn, pb,
-                               b_ctx_in->ovsrec_port_by_qos,
-                               b_ctx_in->qos_table, b_ctx_out->qos_map);
+        remove_stale_qos_entry(pb->logical_port,
+                               ovsrec_port_by_qos,
+                               qos_table, qos_map);
         return;
     }
 
     const char *network = smap_get(&pb->options, "qos_physical_network");
     uint32_t hash = hash_string(pb->logical_port, 0);
-    struct qos_queue *q = find_qos_queue(b_ctx_out->qos_map, hash,
+    struct qos_queue *q = find_qos_queue(qos_map, hash,
                                          pb->logical_port);
     if (!q || q->min_rate != min_rate || q->max_rate != max_rate ||
         q->burst != burst || (network && strcmp(network, q->network))) {
         struct shash bridge_mappings = SHASH_INITIALIZER(&bridge_mappings);
-        add_ovs_bridge_mappings(b_ctx_in->ovs_table, b_ctx_in->bridge_table,
+        add_ovs_bridge_mappings(ovs_table, bridge_table,
                                 &bridge_mappings);
 
         const struct ovsrec_port *port = NULL;
@@ -375,25 +391,58 @@ configure_qos(const struct sbrec_port_binding *pb,
         }
         if (iface) {
             /* Add new QoS entries. */
-            add_ovs_qos_table_entry(b_ctx_in->ovs_idl_txn, port, min_rate,
+            if (add_ovs_qos_table_entry(ovs_idl_txn, port, min_rate,
                                     max_rate, burst, queue_id,
-                                    pb->logical_port);
-            if (!q) {
-                q = xzalloc(sizeof *q);
-                hmap_insert(b_ctx_out->qos_map, &q->node, hash);
-                q->port = xstrdup(pb->logical_port);
-                q->queue_id = queue_id;
+                                    pb->logical_port)) {
+                if (!q) {
+                    q = xzalloc(sizeof *q);
+                    hmap_insert(qos_map, &q->node, hash);
+                    q->port = xstrdup(pb->logical_port);
+                    q->queue_id = queue_id;
+                }
+                free(q->network);
+                q->network = network ? xstrdup(network) : NULL;
+                q->min_rate = min_rate;
+                q->max_rate = max_rate;
+                q->burst = burst;
             }
-            free(q->network);
-            q->network = network ? xstrdup(network) : NULL;
-            q->min_rate = min_rate;
-            q->max_rate = max_rate;
-            q->burst = burst;
         }
         shash_destroy(&bridge_mappings);
     }
 }
 
+void
+update_qos(struct ovsdb_idl_index *sbrec_port_binding_by_name,
+           struct ovsdb_idl_txn *ovs_idl_txn,
+           struct ovsdb_idl_index *ovsrec_port_by_qos,
+           const struct ovsrec_qos_table *qos_table,
+           struct hmap *qos_map,
+           const struct ovsrec_open_vswitch_table *ovs_table,
+           const struct ovsrec_bridge_table *bridge_table)
+{
+    /* Remove qos for any ports for which we could not do it before */
+    const struct sbrec_port_binding *pb;
+
+    struct shash_node *node;
+    SHASH_FOR_EACH_SAFE (node, &_qos_ports) {
+        struct qos_port *qos_port = (struct qos_port *) node->data;
+        if (qos_port->added) {
+            pb = lport_lookup_by_name(sbrec_port_binding_by_name,
+                                      node->name);
+            if (pb) {
+                configure_qos(pb, ovs_idl_txn, ovsrec_port_by_qos, qos_table,
+                              qos_map, ovs_table, bridge_table);
+            }
+        } else {
+            remove_stale_qos_entry(node->name,
+                                   ovsrec_port_by_qos,
+                                   qos_table, qos_map);
+        }
+        free(qos_port);
+        shash_delete(&_qos_ports, node);
+    }
+}
+
 static const struct ovsrec_queue *
 find_qos_queue_by_external_ids(const struct smap *external_ids,
     struct ovsdb_idl_index *ovsrec_queue_by_external_ids)
@@ -1144,33 +1193,22 @@ local_binding_set_pb(struct shash *local_bindings, const char *pb_name,
  * - set the 'pb.up' field to true if 'parent_pb.up' is 'true' (e.g., for
  *   container and virtual ports).
  *
- * Returns false if lport is not claimed due to 'sb_readonly'.
- * Returns true otherwise.
- *
  * Note:
  *   Updates the 'pb->up' field only if it's explicitly set to 'false'.
  *   This is to ensure compatibility with older versions of ovn-northd.
  */
-static bool
+void
 claimed_lport_set_up(const struct sbrec_port_binding *pb,
-                     const struct sbrec_port_binding *parent_pb,
-                     bool sb_readonly)
+                     const struct sbrec_port_binding *parent_pb)
 {
-    /* When notify_up is false in claim_port(), no state is created
-     * by if_status_mgr. In such cases, return false (i.e. trigger recompute)
-     * if we can't update sb (because it is readonly).
-     */
     bool up = true;
     if (!parent_pb || (parent_pb->n_up && parent_pb->up[0])) {
-        if (!sb_readonly) {
-            if (pb->n_up) {
-                sbrec_port_binding_set_up(pb, &up, 1);
-            }
-        } else if (pb->n_up && !pb->up[0]) {
-            return false;
+        if (pb->n_up) {
+            VLOG_INFO("Setting lport %s up in Southbound",
+                      pb->logical_port);
+            sbrec_port_binding_set_up(pb, &up, 1);
         }
     }
-    return true;
 }
 
 typedef void (*set_func)(const struct sbrec_port_binding *pb,
@@ -1250,7 +1288,7 @@ remove_additional_chassis(const struct sbrec_port_binding *pb,
     remove_additional_encap_for_chassis(pb, chassis_rec);
 }
 
-static bool
+bool
 lport_maybe_postpone(const char *port_name, long long int now,
                      struct sset *postponed_ports)
 {
@@ -1273,7 +1311,7 @@ claim_lport(const struct sbrec_port_binding *pb,
             const struct sbrec_port_binding *parent_pb,
             const struct sbrec_chassis *chassis_rec,
             const struct ovsrec_interface *iface_rec,
-            bool sb_readonly, bool notify_up,
+            bool sb_readonly, bool is_vif,
             struct hmap *tracked_datapaths,
             struct if_status_mgr *if_mgr,
             struct sset *postponed_ports)
@@ -1298,39 +1336,27 @@ claim_lport(const struct sbrec_port_binding *pb,
             }
             update_tracked = true;
 
-            if (!notify_up) {
-                if (!claimed_lport_set_up(pb, parent_pb, sb_readonly)) {
-                    return false;
-                }
-                if (sb_readonly) {
-                    return false;
-                }
-                set_pb_chassis_in_sbrec(pb, chassis_rec, true);
-            } else {
-                if_status_mgr_claim_iface(if_mgr, pb, chassis_rec, iface_rec,
-                                          sb_readonly, can_bind);
-            }
+            if_status_mgr_claim_iface(if_mgr, pb, chassis_rec, iface_rec,
+                                      sb_readonly, can_bind, is_vif,
+                                      parent_pb);
             register_claim_timestamp(pb->logical_port, now);
             sset_find_and_delete(postponed_ports, pb->logical_port);
         } else {
-            if (!notify_up) {
-                if (!claimed_lport_set_up(pb, parent_pb, sb_readonly)) {
-                    return false;
-                }
-            } else {
-                if ((pb->n_up && !pb->up[0]) ||
-                    !smap_get_bool(&iface_rec->external_ids,
-                                   OVN_INSTALLED_EXT_ID, false)) {
-                    if_status_mgr_claim_iface(if_mgr, pb, chassis_rec,
-                                              iface_rec, sb_readonly,
-                                              can_bind);
-                }
+            update_tracked = true;
+            if ((pb->n_up && !pb->up[0]) ||
+                (is_vif && !smap_get_bool(&iface_rec->external_ids,
+                                          OVN_INSTALLED_EXT_ID, false))) {
+                if_status_mgr_claim_iface(if_mgr, pb, chassis_rec,
+                                          iface_rec, sb_readonly,
+                                          can_bind, is_vif,
+                                          parent_pb);
             }
         }
     } else if (can_bind == CAN_BIND_AS_ADDITIONAL) {
         if (!is_additional_chassis(pb, chassis_rec)) {
             if_status_mgr_claim_iface(if_mgr, pb, chassis_rec, iface_rec,
-                                      sb_readonly, can_bind);
+                                      sb_readonly, can_bind, is_vif,
+                                      parent_pb);
             update_tracked = true;
         }
     }
@@ -1524,8 +1550,8 @@ consider_vif_lport_(const struct sbrec_port_binding *pb,
                 tracked_datapath_lport_add(pb, TRACKED_RESOURCE_UPDATED,
                                            b_ctx_out->tracked_dp_bindings);
             }
-            if (b_lport->lbinding->iface && b_ctx_in->ovs_idl_txn) {
-                configure_qos(pb, b_ctx_in, b_ctx_out);
+            if (b_lport->lbinding->iface) {
+                add_or_del_qos_port(pb->logical_port, true);
             }
         } else {
             /* We could, but can't claim the lport. */
@@ -1814,8 +1840,10 @@ consider_nonvif_lport_(const struct sbrec_port_binding *pb,
                            b_ctx_out->postponed_ports);
     }
 
-    if (pb->chassis == b_ctx_in->chassis_rec ||
-            is_additional_chassis(pb, b_ctx_in->chassis_rec)) {
+    if (pb->chassis == b_ctx_in->chassis_rec
+            || is_additional_chassis(pb, b_ctx_in->chassis_rec)
+            || if_status_is_port_claimed(b_ctx_out->if_mgr,
+                                         pb->logical_port)) {
         return release_lport(pb, b_ctx_in->chassis_rec,
                              !b_ctx_in->ovnsb_idl_txn,
                              b_ctx_out->tracked_dp_bindings,
@@ -1851,7 +1879,6 @@ consider_l3gw_lport(const struct sbrec_port_binding *pb,
 
 static void
 consider_localnet_lport(const struct sbrec_port_binding *pb,
-                        struct binding_ctx_in *b_ctx_in,
                         struct binding_ctx_out *b_ctx_out)
 {
     struct local_datapath *ld
@@ -1860,14 +1887,23 @@ consider_localnet_lport(const struct sbrec_port_binding *pb,
     if (!ld) {
         return;
     }
+
+    bool pb_localnet_learn_fdb = smap_get_bool(&pb->options,
+                                               "localnet_learn_fdb", false);
+    if (pb_localnet_learn_fdb != b_ctx_out->localnet_learn_fdb) {
+        b_ctx_out->localnet_learn_fdb = pb_localnet_learn_fdb;
+        if (b_ctx_out->tracked_dp_bindings) {
+            b_ctx_out->localnet_learn_fdb_changed = true;
+            tracked_datapath_lport_add(pb, TRACKED_RESOURCE_UPDATED,
+                                       b_ctx_out->tracked_dp_bindings);
+        }
+    }
+
     /* Add all localnet ports to local_ifaces so that we allocate ct zones
      * for them. */
     update_local_lports(pb->logical_port, b_ctx_out);
 
-    if (b_ctx_in->ovs_idl_txn) {
-        configure_qos(pb, b_ctx_in, b_ctx_out);
-    }
-
+    add_or_del_qos_port(pb->logical_port, true);
     update_related_lport(pb, b_ctx_out);
 }
 
@@ -1988,6 +2024,7 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out)
         return;
     }
 
+    shash_clear_free_data(&_qos_ports);
     struct shash bridge_mappings = SHASH_INITIALIZER(&bridge_mappings);
 
     if (b_ctx_in->br_int) {
@@ -2103,7 +2140,7 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out)
      * accordingly. */
     struct lport *lnet_lport;
     LIST_FOR_EACH_POP (lnet_lport, list_node, &localnet_lports) {
-        consider_localnet_lport(lnet_lport->pb, b_ctx_in, b_ctx_out);
+        consider_localnet_lport(lnet_lport->pb, b_ctx_out);
         update_ld_localnet_port(lnet_lport->pb, &bridge_mappings,
                                 b_ctx_out->local_datapaths);
         free(lnet_lport);
@@ -2345,7 +2382,7 @@ consider_iface_release(const struct ovsrec_interface *iface_rec,
 
     lbinding = local_binding_find(local_bindings, iface_id);
 
-   if (lbinding) {
+    if (lbinding) {
         if (lbinding->multiple_bindings) {
             VLOG_INFO("Multiple bindings for %s: force recompute to clean up",
                       iface_id);
@@ -2365,54 +2402,50 @@ consider_iface_release(const struct ovsrec_interface *iface_rec,
                 return true;
             }
         }
-    }
 
-    struct binding_lport *b_lport =
-        local_binding_get_primary_or_localport_lport(lbinding);
-    if (is_binding_lport_this_chassis(b_lport, b_ctx_in->chassis_rec)) {
-        struct local_datapath *ld =
-            get_local_datapath(b_ctx_out->local_datapaths,
-                               b_lport->pb->datapath->tunnel_key);
-        if (ld) {
-            remove_pb_from_local_datapath(b_lport->pb,
-                                          b_ctx_out, ld);
-        }
-
-        remove_stale_qos_entry(b_ctx_in->ovs_idl_txn, b_lport->pb,
-                               b_ctx_in->ovsrec_port_by_qos,
-                               b_ctx_in->qos_table, b_ctx_out->qos_map);
+        struct binding_lport *b_lport =
+            local_binding_get_primary_or_localport_lport(lbinding);
 
-        /* Release the primary binding lport and other children lports if
-         * any. */
-        LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) {
-            if (!release_binding_lport(b_ctx_in->chassis_rec, b_lport,
-                                       !b_ctx_in->ovnsb_idl_txn,
-                                       b_ctx_out)) {
-                return false;
+        if (is_binding_lport_this_chassis(b_lport, b_ctx_in->chassis_rec)) {
+            struct local_datapath *ld =
+                get_local_datapath(b_ctx_out->local_datapaths,
+                                   b_lport->pb->datapath->tunnel_key);
+            if (ld) {
+                remove_pb_from_local_datapath(b_lport->pb,
+                                              b_ctx_out, ld);
             }
-        }
-        if (lbinding->iface && lbinding->iface->name) {
-            if_status_mgr_remove_ovn_installed(b_ctx_out->if_mgr,
-                                               lbinding->iface->name,
-                                               &lbinding->iface->header_.uuid);
-        }
+            add_or_del_qos_port(b_lport->pb->logical_port, false);
 
-    } else if (lbinding && b_lport && b_lport->type == LP_LOCALPORT) {
-        /* lbinding is associated with a localport.  Remove it from the
-         * related lports. */
-        remove_related_lport(b_lport->pb, b_ctx_out);
-    }
+            /* Release the primary binding lport and other children lports if
+             * any. */
+            LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) {
+                if (!release_binding_lport(b_ctx_in->chassis_rec, b_lport,
+                                           !b_ctx_in->ovnsb_idl_txn,
+                                           b_ctx_out)) {
+                    return false;
+                }
+            }
+            if (lbinding->iface && lbinding->iface->name) {
+                if_status_mgr_remove_ovn_installed(b_ctx_out->if_mgr,
+                                           lbinding->iface->name,
+                                           &lbinding->iface->header_.uuid);
+            }
 
-    if (lbinding) {
-        /* Clear the iface of the local binding. */
-        lbinding->iface = NULL;
-    }
+        } else if (b_lport && b_lport->type == LP_LOCALPORT) {
+            /* lbinding is associated with a localport.  Remove it from the
+             * related lports. */
+            remove_related_lport(b_lport->pb, b_ctx_out);
+        }
 
-    /* Check if the lbinding has children of type PB_CONTAINER.
-     * If so, don't delete the local_binding. */
-    if (lbinding && !is_lbinding_container_parent(lbinding)) {
-        local_binding_delete(lbinding, local_bindings, binding_lports,
-                             b_ctx_out->if_mgr);
+        /* Check if the lbinding has children of type PB_CONTAINER.
+         * If so, don't delete the local_binding. */
+        if (!is_lbinding_container_parent(lbinding)) {
+            local_binding_delete(lbinding, local_bindings, binding_lports,
+                                 b_ctx_out->if_mgr);
+        } else {
+            /* Clear the iface of the local binding. */
+            lbinding->iface = NULL;
+        }
     }
 
     remove_local_lports(iface_id, b_ctx_out);
@@ -2938,7 +2971,7 @@ handle_updated_port(struct binding_ctx_in *b_ctx_in,
         break;
 
     case LP_LOCALNET: {
-        consider_localnet_lport(pb, b_ctx_in, b_ctx_out);
+        consider_localnet_lport(pb, b_ctx_out);
 
         struct shash bridge_mappings =
             SHASH_INITIALIZER(&bridge_mappings);
@@ -3026,9 +3059,7 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in,
             shash_add(&deleted_other_pbs, pb->logical_port, pb);
         }
 
-        remove_stale_qos_entry(b_ctx_in->ovs_idl_txn, pb,
-                               b_ctx_in->ovsrec_port_by_qos,
-                               b_ctx_in->qos_table, b_ctx_out->qos_map);
+        add_or_del_qos_port(pb->logical_port, false);
     }
 
     struct shash_node *node;
@@ -3140,7 +3171,7 @@ delete_done:
                 b_ctx_in->sbrec_port_binding_by_datapath) {
                 enum en_lport_type lport_type = get_lport_type(pb);
                 if (lport_type == LP_LOCALNET) {
-                    consider_localnet_lport(pb, b_ctx_in, b_ctx_out);
+                    consider_localnet_lport(pb, b_ctx_out);
                     update_ld_localnet_port(pb, &bridge_mappings,
                                             b_ctx_out->local_datapaths);
                 } else if (lport_type == LP_EXTERNAL) {
@@ -3207,7 +3238,7 @@ local_binding_delete(struct local_binding *lbinding,
                      struct if_status_mgr *if_mgr)
 {
     shash_find_and_delete(local_bindings, lbinding->name);
-    if_status_mgr_delete_iface(if_mgr, lbinding->name);
+    if_status_mgr_delete_iface(if_mgr, lbinding->name, lbinding->iface);
     local_binding_destroy(lbinding, binding_lports);
 }
 
@@ -3424,6 +3455,80 @@ port_binding_set_down(const struct sbrec_chassis *chassis_rec,
         }
 }
 
+void
+port_binding_set_up(const struct sbrec_chassis *chassis_rec,
+                    const struct sbrec_port_binding_table *pb_table,
+                    const char *iface_id,
+                    const struct uuid *pb_uuid)
+{
+    const struct sbrec_port_binding *pb =
+        sbrec_port_binding_table_get_for_uuid(pb_table, pb_uuid);
+    if (!pb) {
+        VLOG_DBG("port_binding already deleted for %s", iface_id);
+        return;
+    }
+    if (pb->n_up && !pb->up[0]) {
+        bool up = true;
+        sbrec_port_binding_set_up(pb, &up, 1);
+        VLOG_INFO("Setting lport %s up in Southbound", pb->logical_port);
+        set_pb_chassis_in_sbrec(pb, chassis_rec, true);
+    }
+}
+
+void
+port_binding_set_pb(const struct sbrec_chassis *chassis_rec,
+                    const struct sbrec_port_binding_table *pb_table,
+                    const char *iface_id,
+                    const struct uuid *pb_uuid,
+                    enum can_bind bind_type)
+{
+    const struct sbrec_port_binding *pb =
+        sbrec_port_binding_table_get_for_uuid(pb_table, pb_uuid);
+    if (!pb) {
+        VLOG_DBG("port_binding already deleted for %s", iface_id);
+        return;
+    }
+    if (bind_type == CAN_BIND_AS_MAIN) {
+        set_pb_chassis_in_sbrec(pb, chassis_rec, true);
+    } else  if (bind_type == CAN_BIND_AS_ADDITIONAL) {
+        set_pb_additional_chassis_in_sbrec(pb, chassis_rec, true);
+    }
+}
+
+bool
+port_binding_pb_chassis_is_set(
+    const struct sbrec_chassis *chassis_rec,
+    const struct sbrec_port_binding_table *pb_table,
+    const struct uuid *pb_uuid)
+{
+    const struct sbrec_port_binding *pb =
+        sbrec_port_binding_table_get_for_uuid(pb_table, pb_uuid);
+
+    if (pb && ((pb->chassis == chassis_rec) ||
+        is_additional_chassis(pb, chassis_rec))) {
+        return true;
+    }
+    return false;
+}
+
+bool
+port_binding_is_up(const struct sbrec_chassis *chassis_rec,
+                   const struct sbrec_port_binding_table *pb_table,
+                   const struct uuid *pb_uuid)
+{
+    const struct sbrec_port_binding *pb =
+        sbrec_port_binding_table_get_for_uuid(pb_table, pb_uuid);
+
+    if (!pb || pb->chassis != chassis_rec) {
+        return false;
+    }
+
+    if (pb->n_up && !pb->up[0]) {
+        return false;
+    }
+    return true;
+}
+
 static void
 binding_lport_set_up(struct binding_lport *b_lport, bool sb_readonly)
 {
@@ -3614,5 +3719,6 @@ void
 binding_destroy(void)
 {
     shash_destroy_free_data(&_claimed_ports);
+    shash_destroy_free_data(&_qos_ports);
     sset_clear(&_postponed_ports);
 }
diff --git a/controller/binding.h b/controller/binding.h
index 24bc84079..75e7a2679 100644
--- a/controller/binding.h
+++ b/controller/binding.h
@@ -108,6 +108,9 @@ struct binding_ctx_out {
     struct if_status_mgr *if_mgr;
 
     struct sset *postponed_ports;
+
+    bool localnet_learn_fdb;
+    bool localnet_learn_fdb_changed;
 };
 
 /* Local bindings. binding.c module binds the logical port (represented by
@@ -217,6 +220,18 @@ void port_binding_set_down(const struct sbrec_chassis *chassis_rec,
                            const struct sbrec_port_binding_table *pb_table,
                            const char *iface_id,
                            const struct uuid *pb_uuid);
+void port_binding_set_up(const struct sbrec_chassis *chassis_rec,
+                         const struct sbrec_port_binding_table *,
+                         const char *iface_id,
+                         const struct uuid *pb_uuid);
+void port_binding_set_pb(const struct sbrec_chassis *chassis_rec,
+                         const struct sbrec_port_binding_table *,
+                         const char *iface_id,
+                         const struct uuid *pb_uuid,
+                         enum can_bind bind_type);
+bool port_binding_pb_chassis_is_set(const struct sbrec_chassis *chassis_rec,
+                                    const struct sbrec_port_binding_table *,
+                                    const struct uuid *pb_uuid);
 
 /* This structure represents a logical port (or port binding)
  * which is associated with 'struct local_binding'.
@@ -250,4 +265,20 @@ void binding_destroy(void);
 
 void destroy_qos_map(struct hmap *);
 
+void update_qos(struct ovsdb_idl_index * sbrec_port_binding_by_name,
+                struct ovsdb_idl_txn *ovs_idl_txn,
+                struct ovsdb_idl_index *ovsrec_port_by_qos,
+                const struct ovsrec_qos_table *qos_table,
+                struct hmap *qos_map,
+                const struct ovsrec_open_vswitch_table *ovs_table,
+                const struct ovsrec_bridge_table *bridge_table);
+
+bool lport_maybe_postpone(const char *port_name, long long int now,
+                          struct sset *postponed_ports);
+
+void claimed_lport_set_up(const struct sbrec_port_binding *pb,
+                          const struct sbrec_port_binding *parent_pb);
+bool port_binding_is_up(const struct sbrec_chassis *chassis_rec,
+                        const struct sbrec_port_binding_table *,
+                        const struct uuid *pb_uuid);
 #endif /* controller/binding.h */
diff --git a/controller/chassis.c b/controller/chassis.c
index 031bd4463..ba2e57238 100644
--- a/controller/chassis.c
+++ b/controller/chassis.c
@@ -369,6 +369,8 @@ chassis_build_other_config(const struct ovs_chassis_cfg *ovs_cfg,
     smap_replace(config, OVN_FEATURE_MAC_BINDING_TIMESTAMP, "true");
     smap_replace(config, OVN_FEATURE_CT_LB_RELATED, "true");
     smap_replace(config, OVN_FEATURE_FDB_TIMESTAMP, "true");
+    smap_replace(config, OVN_FEATURE_LS_DPG_COLUMN, "true");
+    smap_replace(config, OVN_FEATURE_CT_COMMIT_NAT_V2, "true");
 }
 
 /*
@@ -502,6 +504,18 @@ chassis_other_config_changed(const struct ovs_chassis_cfg *ovs_cfg,
         return true;
     }
 
+    if (!smap_get_bool(&chassis_rec->other_config,
+                       OVN_FEATURE_LS_DPG_COLUMN,
+                       false)) {
+        return true;
+    }
+
+    if (!smap_get_bool(&chassis_rec->other_config,
+                       OVN_FEATURE_CT_COMMIT_NAT_V2,
+                       false)) {
+        return true;
+    }
+
     return false;
 }
 
@@ -632,6 +646,8 @@ update_supported_sset(struct sset *supported)
     sset_add(supported, OVN_FEATURE_MAC_BINDING_TIMESTAMP);
     sset_add(supported, OVN_FEATURE_CT_LB_RELATED);
     sset_add(supported, OVN_FEATURE_FDB_TIMESTAMP);
+    sset_add(supported, OVN_FEATURE_LS_DPG_COLUMN);
+    sset_add(supported, OVN_FEATURE_CT_COMMIT_NAT_V2);
 }
 
 static void
diff --git a/controller/if-status.c b/controller/if-status.c
index 6c5afc866..6e8aa1f7e 100644
--- a/controller/if-status.c
+++ b/controller/if-status.c
@@ -177,7 +177,9 @@ static const char *if_state_names[] = {
 
 struct ovs_iface {
     char *id;               /* Extracted from OVS external_ids.iface_id. */
+    char *name;             /* OVS iface name. */
     struct uuid pb_uuid;    /* Port_binding uuid */
+    struct uuid parent_pb_uuid; /* Parent port_binding uuid */
     enum if_state state;    /* State of the interface in the state machine. */
     uint32_t install_seqno; /* Seqno at which this interface is expected to
                              * be fully programmed in OVS.  Only used in state
@@ -185,6 +187,7 @@ struct ovs_iface {
                              */
     uint16_t mtu;           /* Extracted from OVS interface.mtu field. */
     enum can_bind bind_type;/* CAN_BIND_AS_MAIN or CAN_BIND_AS_ADDITIONAL */
+    bool is_vif;            /* Vifs, container or virtual ports */
 };
 
 static uint64_t ifaces_usage;
@@ -224,6 +227,7 @@ static void if_status_mgr_update_bindings(
     struct if_status_mgr *mgr, struct local_binding_data *binding_data,
     const struct sbrec_chassis *,
     const struct ovsrec_interface_table *iface_table,
+    const struct sbrec_port_binding_table *pb_table,
     bool sb_readonly, bool ovs_readonly);
 
 static void ovn_uninstall_hash_account_mem(const char *name, bool erase);
@@ -273,12 +277,23 @@ if_status_mgr_destroy(struct if_status_mgr *mgr)
     free(mgr);
 }
 
+/* is_vif controls whether we wait for flows to be updated
+ * before setting the interface up. It is true for VIF, CONTAINER
+ * and VIRTUAL ports.
+ * Non-VIF ports are reported up as soon as they are claimed
+ * to maintain compatibility with older versions.
+ * See aae25e6 binding: Correctly set Port_Binding.up for container/virtual
+ * ports.
+ */
+
 void
 if_status_mgr_claim_iface(struct if_status_mgr *mgr,
                           const struct sbrec_port_binding *pb,
                           const struct sbrec_chassis *chassis_rec,
                           const struct ovsrec_interface *iface_rec,
-                          bool sb_readonly, enum can_bind bind_type)
+                          bool sb_readonly, enum can_bind bind_type,
+                          bool is_vif,
+                          const struct sbrec_port_binding *parent_pb)
 {
     const char *iface_id = pb->logical_port;
     struct ovs_iface *iface = shash_find_data(&mgr->ifaces, iface_id);
@@ -287,8 +302,13 @@ if_status_mgr_claim_iface(struct if_status_mgr *mgr,
         iface = ovs_iface_create(mgr, iface_id, iface_rec, OIF_CLAIMED);
     }
     iface->bind_type = bind_type;
+    iface->is_vif = is_vif;
 
     memcpy(&iface->pb_uuid, &pb->header_.uuid, sizeof(iface->pb_uuid));
+    if (parent_pb) {
+        memcpy(&iface->parent_pb_uuid, &parent_pb->header_.uuid,
+               sizeof(iface->pb_uuid));
+    }
     if (!sb_readonly) {
         if (bind_type == CAN_BIND_AS_MAIN) {
             set_pb_chassis_in_sbrec(pb, chassis_rec, true);
@@ -357,7 +377,8 @@ if_status_mgr_release_iface(struct if_status_mgr *mgr, const char *iface_id)
 }
 
 void
-if_status_mgr_delete_iface(struct if_status_mgr *mgr, const char *iface_id)
+if_status_mgr_delete_iface(struct if_status_mgr *mgr, const char *iface_id,
+                           const struct ovsrec_interface *iface_rec)
 {
     struct ovs_iface *iface = shash_find_data(&mgr->ifaces, iface_id);
 
@@ -365,6 +386,12 @@ if_status_mgr_delete_iface(struct if_status_mgr *mgr, const char *iface_id)
         return;
     }
 
+    if (iface_rec && strcmp(iface->name, iface_rec->name)) {
+        VLOG_DBG("Interface %s not deleted as port %s bound to %s",
+                 iface_rec->name, iface_id, iface->name);
+        return;
+    }
+
     switch (iface->state) {
     case OIF_CLAIMED:
     case OIF_INSTALL_FLOWS:
@@ -394,6 +421,7 @@ if_status_handle_claims(struct if_status_mgr *mgr,
                         struct local_binding_data *binding_data,
                         const struct sbrec_chassis *chassis_rec,
                         struct hmap *tracked_datapath,
+                        const struct sbrec_port_binding_table *pb_table,
                         bool sb_readonly)
 {
     if (!binding_data || sb_readonly) {
@@ -406,9 +434,15 @@ if_status_handle_claims(struct if_status_mgr *mgr,
     bool rc = false;
     HMAPX_FOR_EACH (node, &mgr->ifaces_per_state[OIF_CLAIMED]) {
         struct ovs_iface *iface = node->data;
-        VLOG_INFO("if_status_handle_claims for %s", iface->id);
-        local_binding_set_pb(bindings, iface->id, chassis_rec,
-                             tracked_datapath, true, iface->bind_type);
+        VLOG_DBG("if_status_handle_claims for %s, is_vif = %d", iface->id,
+                  iface->is_vif);
+        if (iface->is_vif) {
+            local_binding_set_pb(bindings, iface->id, chassis_rec,
+                                 tracked_datapath, true, iface->bind_type);
+        } else {
+            port_binding_set_pb(chassis_rec, pb_table, iface->id,
+                                &iface->pb_uuid, iface->bind_type);
+        }
         rc = true;
     }
     return rc;
@@ -470,18 +504,37 @@ if_status_mgr_update(struct if_status_mgr *mgr,
      */
     HMAPX_FOR_EACH_SAFE (node, &mgr->ifaces_per_state[OIF_MARK_UP]) {
         struct ovs_iface *iface = node->data;
-
-        if (!local_bindings_pb_chassis_is_set(bindings, iface->id,
-            chassis_rec)) {
-            if (!sb_readonly) {
-                local_binding_set_pb(bindings, iface->id, chassis_rec,
-                                     NULL, true, iface->bind_type);
-            } else {
-                continue;
+        if (iface->is_vif) {
+            if (!local_bindings_pb_chassis_is_set(bindings, iface->id,
+                chassis_rec)) {
+                if (!sb_readonly) {
+                    long long int now = time_msec();
+                    if (lport_maybe_postpone(iface->id, now,
+                                             get_postponed_ports())) {
+                        continue;
+                    }
+                    local_binding_set_pb(bindings, iface->id, chassis_rec,
+                                         NULL, true, iface->bind_type);
+                } else {
+                    continue;
+                }
+            }
+            if (local_binding_is_up(bindings, iface->id, chassis_rec)) {
+                ovs_iface_set_state(mgr, iface, OIF_INSTALLED);
+            }
+        } else {
+            if (!port_binding_pb_chassis_is_set(chassis_rec, pb_table,
+                                                &iface->pb_uuid)) {
+                if (!sb_readonly) {
+                    port_binding_set_pb(chassis_rec, pb_table, iface->id,
+                                        &iface->pb_uuid, iface->bind_type);
+                } else {
+                    continue;
+                }
+            }
+            if (port_binding_is_up(chassis_rec, pb_table, &iface->pb_uuid)) {
+                ovs_iface_set_state(mgr, iface, OIF_INSTALLED);
             }
-        }
-        if (local_binding_is_up(bindings, iface->id, chassis_rec)) {
-            ovs_iface_set_state(mgr, iface, OIF_INSTALLED);
         }
     }
 
@@ -510,9 +563,13 @@ if_status_mgr_update(struct if_status_mgr *mgr,
     if (!sb_readonly) {
         HMAPX_FOR_EACH_SAFE (node, &mgr->ifaces_per_state[OIF_INSTALL_FLOWS]) {
             struct ovs_iface *iface = node->data;
-
             if (!local_bindings_pb_chassis_is_set(bindings, iface->id,
                 chassis_rec)) {
+                long long int now = time_msec();
+                if (lport_maybe_postpone(iface->id, now,
+                                         get_postponed_ports())) {
+                    continue;
+                }
                 local_binding_set_pb(bindings, iface->id, chassis_rec,
                                      NULL, true, iface->bind_type);
             }
@@ -528,9 +585,13 @@ if_status_mgr_update(struct if_status_mgr *mgr,
             /* No need to to update pb->chassis as already done
              * in if_status_handle_claims or if_status_mgr_claim_iface
              */
-            ovs_iface_set_state(mgr, iface, OIF_INSTALL_FLOWS);
-            iface->install_seqno = mgr->iface_seqno + 1;
-            new_ifaces = true;
+            if (iface->is_vif) {
+                ovs_iface_set_state(mgr, iface, OIF_INSTALL_FLOWS);
+                iface->install_seqno = mgr->iface_seqno + 1;
+                new_ifaces = true;
+            } else {
+                ovs_iface_set_state(mgr, iface, OIF_MARK_UP);
+            }
         }
     } else {
         HMAPX_FOR_EACH_SAFE (node, &mgr->ifaces_per_state[OIF_CLAIMED]) {
@@ -587,6 +648,7 @@ if_status_mgr_run(struct if_status_mgr *mgr,
                   struct local_binding_data *binding_data,
                   const struct sbrec_chassis *chassis_rec,
                   const struct ovsrec_interface_table *iface_table,
+                  const struct sbrec_port_binding_table *pb_table,
                   bool sb_readonly, bool ovs_readonly)
 {
     struct ofctrl_acked_seqnos *acked_seqnos =
@@ -622,15 +684,18 @@ if_status_mgr_run(struct if_status_mgr *mgr,
 
     /* Update binding states. */
     if_status_mgr_update_bindings(mgr, binding_data, chassis_rec,
-                                  iface_table,
+                                  iface_table, pb_table,
                                   sb_readonly, ovs_readonly);
 }
 
 static void
-ovs_iface_account_mem(const char *iface_id, bool erase)
+ovs_iface_account_mem(const char *iface_id, char *iface_name, bool erase)
 {
     uint32_t size = (strlen(iface_id) + sizeof(struct ovs_iface) +
                      sizeof(struct shash_node));
+    if (iface_name) {
+        size += strlen(iface_name);
+    }
     if (erase) {
         ifaces_usage -= size;
     } else {
@@ -682,12 +747,18 @@ ovs_iface_create(struct if_status_mgr *mgr, const char *iface_id,
 {
     struct ovs_iface *iface = xzalloc(sizeof *iface);
 
-    VLOG_DBG("Interface %s create.", iface_id);
+    VLOG_DBG("Interface %s create for iface %s.", iface_id,
+             iface_rec ? iface_rec->name : "");
     iface->id = xstrdup(iface_id);
     shash_add_nocopy(&mgr->ifaces, iface->id, iface);
     ovs_iface_set_state(mgr, iface, state);
-    ovs_iface_account_mem(iface_id, false);
-    if_status_mgr_iface_update(mgr, iface_rec);
+    if (iface_rec) {
+        ovs_iface_account_mem(iface_id, iface_rec->name, false);
+        if_status_mgr_iface_update(mgr, iface_rec);
+        iface->name = xstrdup(iface_rec->name);
+    } else {
+        ovs_iface_account_mem(iface_id, NULL, false);
+    }
     return iface;
 }
 
@@ -711,7 +782,8 @@ ovs_iface_destroy(struct if_status_mgr *mgr, struct ovs_iface *iface)
     if (node) {
         shash_steal(&mgr->ifaces, node);
     }
-    ovs_iface_account_mem(iface->id, true);
+    ovs_iface_account_mem(iface->id, iface->name, true);
+    free(iface->name);
     free(iface->id);
     free(iface);
 }
@@ -752,6 +824,7 @@ if_status_mgr_update_bindings(struct if_status_mgr *mgr,
                               struct local_binding_data *binding_data,
                               const struct sbrec_chassis *chassis_rec,
                               const struct ovsrec_interface_table *iface_table,
+                              const struct sbrec_port_binding_table *pb_table,
                               bool sb_readonly, bool ovs_readonly)
 {
     if (!binding_data) {
@@ -788,9 +861,20 @@ if_status_mgr_update_bindings(struct if_status_mgr *mgr,
     char *ts_now_str = xasprintf("%lld", time_wall_msec());
     HMAPX_FOR_EACH (node, &mgr->ifaces_per_state[OIF_MARK_UP]) {
         struct ovs_iface *iface = node->data;
-
-        local_binding_set_up(bindings, iface->id, chassis_rec, ts_now_str,
-                             sb_readonly, ovs_readonly);
+        if (iface->is_vif) {
+            local_binding_set_up(bindings, iface->id, chassis_rec, ts_now_str,
+                                 sb_readonly, ovs_readonly);
+        } else if (!sb_readonly) {
+            const struct sbrec_port_binding *pb =
+                sbrec_port_binding_table_get_for_uuid(pb_table,
+                                                      &iface->pb_uuid);
+            if (pb) {
+                const struct sbrec_port_binding *parent_pb =
+                    sbrec_port_binding_table_get_for_uuid(pb_table,
+                                                  &iface->parent_pb_uuid);
+                claimed_lport_set_up(pb, parent_pb);
+            }
+        }
     }
     free(ts_now_str);
 
diff --git a/controller/if-status.h b/controller/if-status.h
index 9714f6d8d..4ae5ad481 100644
--- a/controller/if-status.h
+++ b/controller/if-status.h
@@ -32,9 +32,12 @@ void if_status_mgr_claim_iface(struct if_status_mgr *,
                                const struct sbrec_port_binding *pb,
                                const struct sbrec_chassis *chassis_rec,
                                const struct ovsrec_interface *iface_rec,
-                               bool sb_readonly, enum can_bind bind_type);
+                               bool sb_readonly, enum can_bind bind_type,
+                               bool notify_up,
+                               const struct sbrec_port_binding *parent_pb);
 void if_status_mgr_release_iface(struct if_status_mgr *, const char *iface_id);
-void if_status_mgr_delete_iface(struct if_status_mgr *, const char *iface_id);
+void if_status_mgr_delete_iface(struct if_status_mgr *, const char *iface_id,
+                                const struct ovsrec_interface *iface_rec);
 
 void if_status_mgr_update(struct if_status_mgr *, struct local_binding_data *,
                           const struct sbrec_chassis *chassis,
@@ -45,6 +48,7 @@ void if_status_mgr_update(struct if_status_mgr *, struct local_binding_data *,
 void if_status_mgr_run(struct if_status_mgr *mgr, struct local_binding_data *,
                        const struct sbrec_chassis *,
                        const struct ovsrec_interface_table *iface_table,
+                       const struct sbrec_port_binding_table *pb_table,
                        bool sb_readonly, bool ovs_readonly);
 void if_status_mgr_get_memory_usage(struct if_status_mgr *mgr,
                                     struct simap *usage);
@@ -54,6 +58,7 @@ bool if_status_handle_claims(struct if_status_mgr *mgr,
                              struct local_binding_data *binding_data,
                              const struct sbrec_chassis *chassis_rec,
                              struct hmap *tracked_datapath,
+                             const struct sbrec_port_binding_table *pb_table,
                              bool sb_readonly);
 void if_status_mgr_remove_ovn_installed(struct if_status_mgr *mgr,
                                         const char *name,
diff --git a/controller/lflow.c b/controller/lflow.c
index f70080e8e..b0cf4253c 100644
--- a/controller/lflow.c
+++ b/controller/lflow.c
@@ -1017,6 +1017,7 @@ convert_match_to_expr(const struct sbrec_logical_flow *lflow,
         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
         VLOG_WARN_RL(&rl, "error parsing match \"%s\": %s",
                     lflow->match, error);
+        expr_destroy(e);
         free(error);
         return NULL;
     }
@@ -1892,6 +1893,7 @@ add_lb_ct_snat_hairpin_vip_flow(const struct ovn_controller_lb *lb,
                                           local_datapaths, &match,
                                           &ofpacts, flow_table);
         }
+        /* datapath_group column is deprecated. */
         if (lb->slb->datapath_group) {
             for (size_t i = 0; i < lb->slb->datapath_group->n_datapaths; i++) {
                 add_lb_ct_snat_hairpin_for_dp(
@@ -1900,6 +1902,15 @@ add_lb_ct_snat_hairpin_vip_flow(const struct ovn_controller_lb *lb,
                     local_datapaths, &match, &ofpacts, flow_table);
             }
         }
+        if (lb->slb->ls_datapath_group) {
+            for (size_t i = 0;
+                 i < lb->slb->ls_datapath_group->n_datapaths; i++) {
+                add_lb_ct_snat_hairpin_for_dp(
+                    lb, !!lb_vip->vip_port,
+                    lb->slb->ls_datapath_group->datapaths[i],
+                    local_datapaths, &match, &ofpacts, flow_table);
+            }
+        }
     }
 
     ofpbuf_uninit(&ofpacts);
@@ -2055,11 +2066,17 @@ lflow_handle_changed_static_mac_bindings(
 static void
 consider_fdb_flows(const struct sbrec_fdb *fdb,
                    const struct hmap *local_datapaths,
-                   struct ovn_desired_flow_table *flow_table)
+                   struct ovn_desired_flow_table *flow_table,
+                   struct ovsdb_idl_index *sbrec_port_binding_by_key,
+                   bool localnet_learn_fdb)
 {
-    if (!get_local_datapath(local_datapaths, fdb->dp_key)) {
+    struct local_datapath *ld = get_local_datapath(local_datapaths,
+                                                   fdb->dp_key);
+    if (!ld) {
         return;
     }
+    const struct sbrec_port_binding *pb = lport_lookup_by_key_with_dp(
+        sbrec_port_binding_by_key, ld->datapath, fdb->port_key);
 
     struct eth_addr mac;
     if (!eth_addr_from_string(fdb->mac, &mac)) {
@@ -2081,6 +2098,7 @@ consider_fdb_flows(const struct sbrec_fdb *fdb,
     ofpbuf_clear(&ofpacts);
 
     uint8_t value = 1;
+    uint8_t is_vif =  pb ? !strcmp(pb->type, "") : 0;
     put_load(&value, sizeof value, MFF_LOG_FLAGS,
              MLF_LOOKUP_FDB_BIT, 1, &ofpacts);
 
@@ -2091,6 +2109,18 @@ consider_fdb_flows(const struct sbrec_fdb *fdb,
     ofctrl_add_flow(flow_table, OFTABLE_LOOKUP_FDB, 100,
                     fdb->header_.uuid.parts[0], &lookup_match, &ofpacts,
                     &fdb->header_.uuid);
+
+    if (is_vif && localnet_learn_fdb) {
+        struct match lookup_match_vif = MATCH_CATCHALL_INITIALIZER;
+        match_set_metadata(&lookup_match_vif, htonll(fdb->dp_key));
+        match_set_dl_src(&lookup_match_vif, mac);
+        match_set_reg_masked(&lookup_match_vif, MFF_LOG_FLAGS - MFF_REG0,
+                             MLF_LOCALNET, MLF_LOCALNET);
+
+        ofctrl_add_flow(flow_table, OFTABLE_LOOKUP_FDB, 100,
+                        fdb->header_.uuid.parts[0], &lookup_match_vif,
+                        &ofpacts, &fdb->header_.uuid);
+    }
     ofpbuf_uninit(&ofpacts);
 }
 
@@ -2099,11 +2129,14 @@ consider_fdb_flows(const struct sbrec_fdb *fdb,
 static void
 add_fdb_flows(const struct sbrec_fdb_table *fdb_table,
               const struct hmap *local_datapaths,
-              struct ovn_desired_flow_table *flow_table)
+              struct ovn_desired_flow_table *flow_table,
+              struct ovsdb_idl_index *sbrec_port_binding_by_key,
+              bool localnet_learn_fdb)
 {
     const struct sbrec_fdb *fdb;
     SBREC_FDB_TABLE_FOR_EACH (fdb, fdb_table) {
-        consider_fdb_flows(fdb, local_datapaths, flow_table);
+        consider_fdb_flows(fdb, local_datapaths, flow_table,
+                           sbrec_port_binding_by_key, localnet_learn_fdb);
     }
 }
 
@@ -2126,7 +2159,9 @@ lflow_run(struct lflow_ctx_in *l_ctx_in, struct lflow_ctx_out *l_ctx_out)
                          l_ctx_in->lb_hairpin_use_ct_mark,
                          l_ctx_out->flow_table);
     add_fdb_flows(l_ctx_in->fdb_table, l_ctx_in->local_datapaths,
-                  l_ctx_out->flow_table);
+                  l_ctx_out->flow_table,
+                  l_ctx_in->sbrec_port_binding_by_key,
+                  l_ctx_in->localnet_learn_fdb);
     add_port_sec_flows(l_ctx_in->binding_lports, l_ctx_in->chassis,
                        l_ctx_out->flow_table);
 }
@@ -2216,7 +2251,9 @@ lflow_add_flows_for_datapath(const struct sbrec_datapath_binding *dp,
     SBREC_FDB_FOR_EACH_EQUAL (fdb_row, fdb_index_row,
                               l_ctx_in->sbrec_fdb_by_dp_key) {
         consider_fdb_flows(fdb_row, l_ctx_in->local_datapaths,
-                           l_ctx_out->flow_table);
+                           l_ctx_out->flow_table,
+                           l_ctx_in->sbrec_port_binding_by_key,
+                           l_ctx_in->localnet_learn_fdb);
     }
     sbrec_fdb_index_destroy_row(fdb_index_row);
 
@@ -2280,6 +2317,15 @@ lflow_handle_flows_for_lport(const struct sbrec_port_binding *pb,
                                           pb->logical_port)) {
         consider_port_sec_flows(pb, l_ctx_out->flow_table);
     }
+    if (l_ctx_in->localnet_learn_fdb_changed && l_ctx_in->localnet_learn_fdb) {
+        const struct sbrec_fdb *fdb;
+        SBREC_FDB_TABLE_FOR_EACH (fdb, l_ctx_in->fdb_table) {
+            consider_fdb_flows(fdb, l_ctx_in->local_datapaths,
+                               l_ctx_out->flow_table,
+                               l_ctx_in->sbrec_port_binding_by_key,
+                               l_ctx_in->localnet_learn_fdb);
+        }
+    }
     return true;
 }
 
@@ -2409,7 +2455,9 @@ lflow_handle_changed_fdbs(struct lflow_ctx_in *l_ctx_in,
         VLOG_DBG("Add fdb flows for fdb "UUID_FMT,
                  UUID_ARGS(&fdb->header_.uuid));
         consider_fdb_flows(fdb, l_ctx_in->local_datapaths,
-                           l_ctx_out->flow_table);
+                           l_ctx_out->flow_table,
+                           l_ctx_in->sbrec_port_binding_by_key,
+                           l_ctx_in->localnet_learn_fdb);
     }
 
     return true;
diff --git a/controller/lflow.h b/controller/lflow.h
index 5da4385e4..9b7ffa19c 100644
--- a/controller/lflow.h
+++ b/controller/lflow.h
@@ -100,6 +100,7 @@ struct lflow_ctx_in {
     struct ovsdb_idl_index *sbrec_logical_flow_by_logical_datapath;
     struct ovsdb_idl_index *sbrec_logical_flow_by_logical_dp_group;
     struct ovsdb_idl_index *sbrec_port_binding_by_name;
+    struct ovsdb_idl_index *sbrec_port_binding_by_key;
     struct ovsdb_idl_index *sbrec_fdb_by_dp_key;
     struct ovsdb_idl_index *sbrec_mac_binding_by_datapath;
     struct ovsdb_idl_index *sbrec_static_mac_binding_by_datapath;
@@ -127,6 +128,8 @@ struct lflow_ctx_in {
     const struct flow_collector_ids *collector_ids;
     const struct hmap *local_lbs;
     bool lb_hairpin_use_ct_mark;
+    bool localnet_learn_fdb;
+    bool localnet_learn_fdb_changed;
 };
 
 struct lflow_ctx_out {
diff --git a/controller/local_data.c b/controller/local_data.c
index cf0b21bb1..ab18386c7 100644
--- a/controller/local_data.c
+++ b/controller/local_data.c
@@ -56,6 +56,20 @@ static bool datapath_is_transit_switch(const struct sbrec_datapath_binding *);
 
 static uint64_t local_datapath_usage;
 
+/* To be used when hmap_node.hash might be wrong e.g. tunnel_key got updated */
+struct local_datapath *
+get_local_datapath_no_hash(const struct hmap *local_datapaths,
+                           uint32_t tunnel_key)
+{
+    struct local_datapath *ld;
+    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
+        if (ld->datapath->tunnel_key == tunnel_key) {
+            return ld;
+        }
+    }
+    return NULL;
+}
+
 struct local_datapath *
 get_local_datapath(const struct hmap *local_datapaths, uint32_t tunnel_key)
 {
@@ -661,8 +675,24 @@ lb_is_local(const struct sbrec_load_balancer *sbrec_lb,
         }
     }
 
+    /* datapath_group column is deprecated. */
     struct sbrec_logical_dp_group *dp_group = sbrec_lb->datapath_group;
+    for (size_t i = 0; dp_group && i < dp_group->n_datapaths; i++) {
+        if (get_local_datapath(local_datapaths,
+                               dp_group->datapaths[i]->tunnel_key)) {
+            return true;
+        }
+    }
+
+    dp_group = sbrec_lb->ls_datapath_group;
+    for (size_t i = 0; dp_group && i < dp_group->n_datapaths; i++) {
+        if (get_local_datapath(local_datapaths,
+                               dp_group->datapaths[i]->tunnel_key)) {
+            return true;
+        }
+    }
 
+    dp_group = sbrec_lb->lr_datapath_group;
     for (size_t i = 0; dp_group && i < dp_group->n_datapaths; i++) {
         if (get_local_datapath(local_datapaths,
                                dp_group->datapaths[i]->tunnel_key)) {
diff --git a/controller/local_data.h b/controller/local_data.h
index f6d8f725f..2a1a3c0f9 100644
--- a/controller/local_data.h
+++ b/controller/local_data.h
@@ -69,6 +69,10 @@ struct local_datapath *local_datapath_alloc(
     const struct sbrec_datapath_binding *);
 struct local_datapath *get_local_datapath(const struct hmap *,
                                           uint32_t tunnel_key);
+struct local_datapath *get_local_datapath_no_hash(
+    const struct hmap *local_datapaths,
+    uint32_t tunnel_key);
+
 bool
 need_add_peer_to_local(
     struct ovsdb_idl_index *sbrec_port_binding_by_name,
diff --git a/controller/lport.c b/controller/lport.c
index add7e91aa..b3721024b 100644
--- a/controller/lport.c
+++ b/controller/lport.c
@@ -44,13 +44,10 @@ lport_lookup_by_name(struct ovsdb_idl_index *sbrec_port_binding_by_name,
 }
 
 const struct sbrec_port_binding *
-lport_lookup_by_key(struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-                    struct ovsdb_idl_index *sbrec_port_binding_by_key,
-                    uint64_t dp_key, uint64_t port_key)
+lport_lookup_by_key_with_dp(struct ovsdb_idl_index *sbrec_port_binding_by_key,
+                            const struct sbrec_datapath_binding *db,
+                            uint64_t port_key)
 {
-    /* Lookup datapath corresponding to dp_key. */
-    const struct sbrec_datapath_binding *db = datapath_lookup_by_key(
-        sbrec_datapath_binding_by_key, dp_key);
     if (!db) {
         return NULL;
     }
@@ -69,6 +66,19 @@ lport_lookup_by_key(struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
     return retval;
 }
 
+const struct sbrec_port_binding *
+lport_lookup_by_key(struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+                    struct ovsdb_idl_index *sbrec_port_binding_by_key,
+                    uint64_t dp_key, uint64_t port_key)
+{
+    /* Lookup datapath corresponding to dp_key. */
+    const struct sbrec_datapath_binding *db = datapath_lookup_by_key(
+        sbrec_datapath_binding_by_key, dp_key);
+
+    return lport_lookup_by_key_with_dp(sbrec_port_binding_by_key, db,
+                                       port_key);
+}
+
 bool
 lport_is_chassis_resident(struct ovsdb_idl_index *sbrec_port_binding_by_name,
                           const struct sbrec_chassis *chassis,
diff --git a/controller/lport.h b/controller/lport.h
index 644c67255..2f72aef5e 100644
--- a/controller/lport.h
+++ b/controller/lport.h
@@ -43,6 +43,10 @@ const struct sbrec_port_binding *lport_lookup_by_key(
     struct ovsdb_idl_index *sbrec_port_binding_by_key,
     uint64_t dp_key, uint64_t port_key);
 
+const struct sbrec_port_binding *lport_lookup_by_key_with_dp(
+    struct ovsdb_idl_index *sbrec_port_binding_by_key,
+    const struct sbrec_datapath_binding *dp, uint64_t port_key);
+
 enum can_bind {
     CANNOT_BIND = 0,
     CAN_BIND_AS_MAIN,
diff --git a/controller/ofctrl.c b/controller/ofctrl.c
index a1676a788..1f06f4e48 100644
--- a/controller/ofctrl.c
+++ b/controller/ofctrl.c
@@ -2259,18 +2259,29 @@ ofctrl_meter_bands_erase(struct ovn_extend_table_info *entry,
     }
 }
 
+static const struct sbrec_meter *
+sb_meter_lookup_by_name(struct ovsdb_idl_index *sbrec_meter_by_name,
+                        const char *name)
+{
+    const struct sbrec_meter *sb_meter;
+    struct sbrec_meter *index_row;
+
+    index_row = sbrec_meter_index_init_row(sbrec_meter_by_name);
+    sbrec_meter_index_set_name(index_row, name);
+    sb_meter = sbrec_meter_index_find(sbrec_meter_by_name, index_row);
+    sbrec_meter_index_destroy_row(index_row);
+
+    return sb_meter;
+}
+
 static void
 ofctrl_meter_bands_sync(struct ovn_extend_table_info *m_existing,
-                        const struct sbrec_meter_table *meter_table,
+                        struct ovsdb_idl_index *sbrec_meter_by_name,
                         struct ovs_list *msgs)
 {
     const struct sbrec_meter *sb_meter;
-    SBREC_METER_TABLE_FOR_EACH (sb_meter, meter_table) {
-        if (!strcmp(m_existing->name, sb_meter->name)) {
-            break;
-        }
-    }
 
+    sb_meter = sb_meter_lookup_by_name(sbrec_meter_by_name, m_existing->name);
     if (sb_meter) {
         /* OFPMC13_ADD or OFPMC13_MODIFY */
         ofctrl_meter_bands_update(sb_meter, m_existing, msgs);
@@ -2282,16 +2293,12 @@ ofctrl_meter_bands_sync(struct ovn_extend_table_info *m_existing,
 
 static void
 add_meter(struct ovn_extend_table_info *m_desired,
-          const struct sbrec_meter_table *meter_table,
+          struct ovsdb_idl_index *sbrec_meter_by_name,
           struct ovs_list *msgs)
 {
     const struct sbrec_meter *sb_meter;
-    SBREC_METER_TABLE_FOR_EACH (sb_meter, meter_table) {
-        if (!strcmp(m_desired->name, sb_meter->name)) {
-            break;
-        }
-    }
 
+    sb_meter = sb_meter_lookup_by_name(sbrec_meter_by_name, m_desired->name);
     if (!sb_meter) {
         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
         VLOG_ERR_RL(&rl, "could not find meter named \"%s\"", m_desired->name);
@@ -2658,7 +2665,7 @@ ofctrl_put(struct ovn_desired_flow_table *lflow_table,
            struct ovn_desired_flow_table *pflow_table,
            struct shash *pending_ct_zones,
            struct hmap *pending_lb_tuples,
-           const struct sbrec_meter_table *meter_table,
+           struct ovsdb_idl_index *sbrec_meter_by_name,
            uint64_t req_cfg,
            bool lflows_changed,
            bool pflows_changed)
@@ -2735,10 +2742,10 @@ ofctrl_put(struct ovn_desired_flow_table *lflow_table,
                  * describes the meter itself. */
                 add_meter_string(m_desired, &msgs);
             } else {
-                add_meter(m_desired, meter_table, &msgs);
+                add_meter(m_desired, sbrec_meter_by_name, &msgs);
             }
         } else {
-            ofctrl_meter_bands_sync(m_existing, meter_table, &msgs);
+            ofctrl_meter_bands_sync(m_existing, sbrec_meter_by_name, &msgs);
         }
     }
 
diff --git a/controller/ofctrl.h b/controller/ofctrl.h
index 105f9370b..bb7891440 100644
--- a/controller/ofctrl.h
+++ b/controller/ofctrl.h
@@ -59,7 +59,7 @@ void ofctrl_put(struct ovn_desired_flow_table *lflow_table,
                 struct ovn_desired_flow_table *pflow_table,
                 struct shash *pending_ct_zones,
                 struct hmap *pending_lb_tuples,
-                const struct sbrec_meter_table *,
+                struct ovsdb_idl_index *sbrec_meter_by_name,
                 uint64_t nb_cfg,
                 bool lflow_changed,
                 bool pflow_changed);
diff --git a/controller/ovn-controller.8.xml b/controller/ovn-controller.8.xml
index 7b4100592..0b9641045 100644
--- a/controller/ovn-controller.8.xml
+++ b/controller/ovn-controller.8.xml
@@ -363,7 +363,10 @@
         The boolean flag indicates if <code>ovn-controller</code> when create
         tunnel ports should set <code>local_ip</code> parameter.  Can be
         heplful to pin source outer IP for the tunnel when multiple interfaces
-        are used on the host for overlay traffic.
+        are used on the host for overlay traffic. This is also useful when
+        running multiple <code>ovn-controller</code> instances on the same
+        chassis, in which case this setting will guarantee that their tunnel
+        ports have unique configuration and can exist in parallel.
       </dd>
       <dt><code>external_ids:garp-max-timeout-sec</code></dt>
       <dd>
@@ -398,9 +401,11 @@
         names on the same host using the same <code>vswitchd</code> instance.
         This may be useful when running a hybrid setup with more than one CMS
         managing ports on the host, or to use different datapath types on the
-        same host. Note that this ability is highly experimental and has known
-        limitations (for example, stateful ACLs are not supported). Use at your
-        own risk.
+        same host. Make sure you also set
+        <code>external_ids:ovn-set-local-ip</code> when using such
+        configuration. Also note that this ability is highly experimental and
+        has known limitations (for example, stateful ACLs are not supported).
+        Use at your own risk.
     </p>
 
     <p>
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index b3e4e0da8..003490a06 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -88,7 +88,6 @@
 
 VLOG_DEFINE_THIS_MODULE(main);
 
-static unixctl_cb_func ovn_controller_exit;
 static unixctl_cb_func ct_zone_list;
 static unixctl_cb_func extend_table_list;
 static unixctl_cb_func inject_pkt;
@@ -211,8 +210,8 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
 {
     /* Monitor Port_Bindings rows for local interfaces and local datapaths.
      *
-     * Monitor Logical_Flow, MAC_Binding, Multicast_Group, and DNS tables for
-     * local datapaths.
+     * Monitor Logical_Flow, MAC_Binding, FDB, Multicast_Group, and DNS tables
+     * for local datapaths.
      *
      * Monitor Controller_Event rows for local chassis.
      *
@@ -230,6 +229,7 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
     struct ovsdb_idl_condition lf = OVSDB_IDL_CONDITION_INIT(&lf);
     struct ovsdb_idl_condition ldpg = OVSDB_IDL_CONDITION_INIT(&ldpg);
     struct ovsdb_idl_condition mb = OVSDB_IDL_CONDITION_INIT(&mb);
+    struct ovsdb_idl_condition fdb = OVSDB_IDL_CONDITION_INIT(&fdb);
     struct ovsdb_idl_condition mg = OVSDB_IDL_CONDITION_INIT(&mg);
     struct ovsdb_idl_condition dns = OVSDB_IDL_CONDITION_INIT(&dns);
     struct ovsdb_idl_condition ce =  OVSDB_IDL_CONDITION_INIT(&ce);
@@ -248,6 +248,7 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
         ovsdb_idl_condition_add_clause_true(&pb);
         ovsdb_idl_condition_add_clause_true(&lf);
         ovsdb_idl_condition_add_clause_true(&mb);
+        ovsdb_idl_condition_add_clause_true(&fdb);
         ovsdb_idl_condition_add_clause_true(&mg);
         ovsdb_idl_condition_add_clause_true(&dns);
         ovsdb_idl_condition_add_clause_true(&ce);
@@ -337,6 +338,8 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
             sbrec_logical_flow_add_clause_logical_datapath(&lf, OVSDB_F_EQ,
                                                            uuid);
             sbrec_mac_binding_add_clause_datapath(&mb, OVSDB_F_EQ, uuid);
+            sbrec_fdb_add_clause_dp_key(&fdb, OVSDB_F_EQ,
+                                        ld->datapath->tunnel_key);
             sbrec_multicast_group_add_clause_datapath(&mg, OVSDB_F_EQ, uuid);
             sbrec_dns_add_clause_datapaths(&dns, OVSDB_F_INCLUDES, &uuid, 1);
             sbrec_ip_multicast_add_clause_datapath(&ip_mcast, OVSDB_F_EQ,
@@ -360,6 +363,7 @@ out:;
         sb_table_set_req_mon_condition(ovnsb_idl, logical_flow, &lf),
         sb_table_set_req_mon_condition(ovnsb_idl, logical_dp_group, &ldpg),
         sb_table_set_req_mon_condition(ovnsb_idl, mac_binding, &mb),
+        sb_table_set_req_mon_condition(ovnsb_idl, fdb, &fdb),
         sb_table_set_req_mon_condition(ovnsb_idl, multicast_group, &mg),
         sb_table_set_req_mon_condition(ovnsb_idl, dns, &dns),
         sb_table_set_req_mon_condition(ovnsb_idl, controller_event, &ce),
@@ -378,6 +382,7 @@ out:;
     ovsdb_idl_condition_destroy(&lf);
     ovsdb_idl_condition_destroy(&ldpg);
     ovsdb_idl_condition_destroy(&mb);
+    ovsdb_idl_condition_destroy(&fdb);
     ovsdb_idl_condition_destroy(&mg);
     ovsdb_idl_condition_destroy(&dns);
     ovsdb_idl_condition_destroy(&ce);
@@ -1499,6 +1504,8 @@ struct ed_type_runtime_data {
     /* Tracked data. See below for more details and comments. */
     bool tracked;
     bool local_lports_changed;
+    bool localnet_learn_fdb;
+    bool localnet_learn_fdb_changed;
     struct hmap tracked_dp_bindings;
 
     struct shash local_active_ports_ipv6_pd;
@@ -1709,6 +1716,8 @@ init_binding_ctx(struct engine_node *node,
     b_ctx_out->postponed_ports = rt_data->postponed_ports;
     b_ctx_out->tracked_dp_bindings = NULL;
     b_ctx_out->if_mgr = ctrl_ctx->if_mgr;
+    b_ctx_out->localnet_learn_fdb = rt_data->localnet_learn_fdb;
+    b_ctx_out->localnet_learn_fdb_changed = false;
 }
 
 static void
@@ -1766,6 +1775,7 @@ en_runtime_data_run(struct engine_node *node, void *data)
     }
 
     binding_run(&b_ctx_in, &b_ctx_out);
+    rt_data->localnet_learn_fdb = b_ctx_out.localnet_learn_fdb;
 
     engine_set_node_state(node, EN_UPDATED);
 }
@@ -1815,6 +1825,8 @@ runtime_data_sb_ro_handler(struct engine_node *node, void *data)
         engine_ovsdb_node_get_index(
                 engine_get_input("SB_chassis", node),
                 "name");
+    const struct sbrec_port_binding_table *pb_table =
+        EN_OVSDB_GET(engine_get_input("SB_port_binding", node));
 
     if (chassis_id) {
         chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
@@ -1829,7 +1841,7 @@ runtime_data_sb_ro_handler(struct engine_node *node, void *data)
                                     &rt_data->lbinding_data,
                                     chassis,
                                     &rt_data->tracked_dp_bindings,
-                                    sb_readonly)) {
+                                    pb_table, sb_readonly)) {
             engine_set_node_state(node, EN_UPDATED);
             rt_data->tracked = true;
         }
@@ -1878,9 +1890,12 @@ runtime_data_sb_port_binding_handler(struct engine_node *node, void *data)
     }
 
     rt_data->local_lports_changed = b_ctx_out.local_lports_changed;
+    rt_data->localnet_learn_fdb = b_ctx_out.localnet_learn_fdb;
+    rt_data->localnet_learn_fdb_changed = b_ctx_out.localnet_learn_fdb_changed;
     if (b_ctx_out.related_lports_changed ||
             b_ctx_out.non_vif_ports_changed ||
             b_ctx_out.local_lports_changed ||
+            b_ctx_out.localnet_learn_fdb_changed ||
             !hmap_is_empty(b_ctx_out.tracked_dp_bindings)) {
         engine_set_node_state(node, EN_UPDATED);
     }
@@ -1897,6 +1912,7 @@ runtime_data_sb_datapath_binding_handler(struct engine_node *node OVS_UNUSED,
             engine_get_input("SB_datapath_binding", node));
     const struct sbrec_datapath_binding *dp;
     struct ed_type_runtime_data *rt_data = data;
+    struct local_datapath *ld;
 
     SBREC_DATAPATH_BINDING_TABLE_FOR_EACH_TRACKED (dp, dp_table) {
         if (sbrec_datapath_binding_is_deleted(dp)) {
@@ -1904,6 +1920,28 @@ runtime_data_sb_datapath_binding_handler(struct engine_node *node OVS_UNUSED,
                                    dp->tunnel_key)) {
                 return false;
             }
+            /* If the tunnel key got updated, get_local_datapath will not find
+             * the ld. Use get_local_datapath_no_hash which does not
+             * rely on the hash.
+             */
+            if (sbrec_datapath_binding_is_updated(
+                    dp, SBREC_DATAPATH_BINDING_COL_TUNNEL_KEY)) {
+                if (get_local_datapath_no_hash(&rt_data->local_datapaths,
+                                               dp->tunnel_key)) {
+                    return false;
+                }
+            }
+        } else if (sbrec_datapath_binding_is_updated(
+                        dp, SBREC_DATAPATH_BINDING_COL_TUNNEL_KEY)
+                   && !sbrec_datapath_binding_is_new(dp)) {
+            /* If the tunnel key is updated, remove the entry (with a wrong
+             * hash) from the map. It will be (properly) added back later.
+             */
+            if ((ld = get_local_datapath_no_hash(&rt_data->local_datapaths,
+                                                 dp->tunnel_key))) {
+                hmap_remove(&rt_data->local_datapaths, &ld->hmap_node);
+                local_datapath_destroy(ld);
+            }
         }
     }
 
@@ -2637,9 +2675,10 @@ ct_zones_runtime_data_handler(struct engine_node *node, void *data)
             struct tracked_lport *t_lport = shash_node->data;
             if (strcmp(t_lport->pb->type, "")
                 && strcmp(t_lport->pb->type, "localport")
+                && strcmp(t_lport->pb->type, "l3gateway")
                 && strcmp(t_lport->pb->type, "localnet")) {
-                /* We allocate zone-id's only to VIF, localport, and localnet
-                 * lports. */
+                /* We allocate zone-id's only to VIF, localport, l3gateway,
+                 * and localnet lports. */
                 continue;
             }
 
@@ -2803,12 +2842,25 @@ load_balancers_by_dp_init(const struct hmap *local_datapaths,
             load_balancers_by_dp_add_one(local_datapaths,
                                          lb->datapaths[i], lb, lbs);
         }
+        /* datapath_group column is deprecated. */
         for (size_t i = 0; lb->datapath_group
                            && i < lb->datapath_group->n_datapaths; i++) {
             load_balancers_by_dp_add_one(local_datapaths,
                                          lb->datapath_group->datapaths[i],
                                          lb, lbs);
         }
+        for (size_t i = 0; lb->ls_datapath_group
+                           && i < lb->ls_datapath_group->n_datapaths; i++) {
+            load_balancers_by_dp_add_one(local_datapaths,
+                                         lb->ls_datapath_group->datapaths[i],
+                                         lb, lbs);
+        }
+        for (size_t i = 0; lb->lr_datapath_group
+                           && i < lb->lr_datapath_group->n_datapaths; i++) {
+            load_balancers_by_dp_add_one(local_datapaths,
+                                         lb->lr_datapath_group->datapaths[i],
+                                         lb, lbs);
+        }
     }
     return lbs;
 }
@@ -3788,6 +3840,11 @@ init_lflow_ctx(struct engine_node *node,
                 engine_get_input("SB_port_binding", node),
                 "name");
 
+    struct ovsdb_idl_index *sbrec_port_binding_by_key =
+        engine_ovsdb_node_get_index(
+                engine_get_input("SB_port_binding", node),
+                "key");
+
     struct ovsdb_idl_index *sbrec_logical_flow_by_dp =
         engine_ovsdb_node_get_index(
                 engine_get_input("SB_logical_flow", node),
@@ -3887,6 +3944,7 @@ init_lflow_ctx(struct engine_node *node,
     l_ctx_in->sbrec_logical_flow_by_logical_dp_group =
         sbrec_logical_flow_by_dp_group;
     l_ctx_in->sbrec_port_binding_by_name = sbrec_port_binding_by_name;
+    l_ctx_in->sbrec_port_binding_by_key = sbrec_port_binding_by_key;
     l_ctx_in->sbrec_fdb_by_dp_key = sbrec_fdb_by_dp_key;
     l_ctx_in->sbrec_mac_binding_by_datapath = sbrec_mac_binding_by_datapath;
     l_ctx_in->sbrec_static_mac_binding_by_datapath =
@@ -3905,6 +3963,8 @@ init_lflow_ctx(struct engine_node *node,
     l_ctx_in->active_tunnels = &rt_data->active_tunnels;
     l_ctx_in->related_lport_ids = &rt_data->related_lports.lport_ids;
     l_ctx_in->binding_lports = &rt_data->lbinding_data.lports;
+    l_ctx_in->localnet_learn_fdb = rt_data->localnet_learn_fdb;
+    l_ctx_in->localnet_learn_fdb_changed = rt_data->localnet_learn_fdb_changed;
     l_ctx_in->chassis_tunnels = &non_vif_data->chassis_tunnels;
     l_ctx_in->lb_hairpin_use_ct_mark = n_opts->lb_hairpin_use_ct_mark;
     l_ctx_in->nd_ra_opts = &fo->nd_ra_opts;
@@ -3930,8 +3990,8 @@ en_lflow_output_init(struct engine_node *node OVS_UNUSED,
 {
     struct ed_type_lflow_output *data = xzalloc(sizeof *data);
     ovn_desired_flow_table_init(&data->flow_table);
-    ovn_extend_table_init(&data->group_table);
-    ovn_extend_table_init(&data->meter_table);
+    ovn_extend_table_init(&data->group_table, "group-table", 0);
+    ovn_extend_table_init(&data->meter_table, "meter-table", 0);
     objdep_mgr_init(&data->lflow_deps_mgr);
     lflow_conj_ids_init(&data->conj_ids);
     uuidset_init(&data->objs_processed);
@@ -4895,11 +4955,6 @@ controller_output_mac_cache_handler(struct engine_node *node,
     return true;
 }
 
-struct ovn_controller_exit_args {
-    bool *exiting;
-    bool *restart;
-};
-
 /* Handles sbrec_chassis changes.
  * If a new chassis is added or removed return false, so that
  * flows are recomputed.  For any updates, there is no need for
@@ -4974,9 +5029,7 @@ int
 main(int argc, char *argv[])
 {
     struct unixctl_server *unixctl;
-    bool exiting;
-    bool restart;
-    struct ovn_controller_exit_args exit_args = {&exiting, &restart};
+    struct ovn_exit_args exit_args = {};
     int retval;
 
     /* Read from system-id-override file once on startup. */
@@ -4996,7 +5049,7 @@ main(int argc, char *argv[])
     if (retval) {
         exit(EXIT_FAILURE);
     }
-    unixctl_command_register("exit", "", 0, 1, ovn_controller_exit,
+    unixctl_command_register("exit", "", 0, 1, ovn_exit_command_callback,
                              &exit_args);
 
     daemonize_complete();
@@ -5049,6 +5102,8 @@ main(int argc, char *argv[])
         = chassis_private_index_create(ovnsb_idl_loop.idl);
     struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath
         = mcast_group_index_create(ovnsb_idl_loop.idl);
+    struct ovsdb_idl_index *sbrec_meter_by_name
+        = ovsdb_idl_index_create1(ovnsb_idl_loop.idl, &sbrec_meter_col_name);
     struct ovsdb_idl_index *sbrec_logical_flow_by_logical_datapath
         = ovsdb_idl_index_create1(ovnsb_idl_loop.idl,
                                   &sbrec_logical_flow_col_logical_datapath);
@@ -5508,10 +5563,8 @@ main(int argc, char *argv[])
     VLOG_INFO("OVN internal version is : [%s]", ovn_version);
 
     /* Main loop. */
-    exiting = false;
-    restart = false;
     bool sb_monitor_all = false;
-    while (!exiting) {
+    while (!exit_args.exiting) {
         memory_run();
         if (memory_should_report()) {
             struct simap usage = SIMAP_INITIALIZER(&usage);
@@ -5645,9 +5698,20 @@ main(int argc, char *argv[])
                                            br_int ? br_int->name : NULL)) {
                 VLOG_INFO("OVS feature set changed, force recompute.");
                 engine_set_force_recompute(true);
+                if (ovs_feature_set_discovered()) {
+                    uint32_t max_groups = ovs_feature_max_select_groups_get();
+                    uint32_t max_meters = ovs_feature_max_meters_get();
+                    struct ed_type_lflow_output *lflow_out_data =
+                        engine_get_internal_data(&en_lflow_output);
+
+                    ovn_extend_table_reinit(&lflow_out_data->group_table,
+                                            max_groups);
+                    ovn_extend_table_reinit(&lflow_out_data->meter_table,
+                                            max_meters);
+                }
             }
 
-            if (br_int) {
+            if (br_int && ovs_feature_set_discovered()) {
                 ct_zones_data = engine_get_data(&en_ct_zones);
                 if (ct_zones_data && ofctrl_run(br_int, ovs_table,
                                                 &ct_zones_data->pending)) {
@@ -5809,6 +5873,13 @@ main(int argc, char *argv[])
                                     &runtime_data->local_datapaths,
                                     sb_monitor_all);
                         }
+                        if (ovs_idl_txn) {
+                            update_qos(sbrec_port_binding_by_name, ovs_idl_txn,
+                                       ovsrec_port_by_qos,
+                                       ovsrec_qos_table_get(ovs_idl_loop.idl),
+                                       &runtime_data->qos_map,
+                                       ovs_table, bridge_table);
+                        }
                     }
 
                     if (mac_cache_data) {
@@ -5848,7 +5919,7 @@ main(int argc, char *argv[])
                                    &pflow_output_data->flow_table,
                                    &ct_zones_data->pending,
                                    &lb_data->removed_tuples,
-                                   sbrec_meter_table_get(ovnsb_idl_loop.idl),
+                                   sbrec_meter_by_name,
                                    ofctrl_seqno_get_req_cfg(),
                                    engine_node_changed(&en_lflow_output),
                                    engine_node_changed(&en_pflow_output));
@@ -5864,6 +5935,8 @@ main(int argc, char *argv[])
                     if_status_mgr_run(if_mgr, binding_data, chassis,
                                       ovsrec_interface_table_get(
                                                   ovs_idl_loop.idl),
+                                      sbrec_port_binding_table_get(
+                                                 ovnsb_idl_loop.idl),
                                       !ovnsb_idl_txn, !ovs_idl_txn);
                     stopwatch_stop(IF_STATUS_MGR_RUN_STOPWATCH_NAME,
                                    time_msec());
@@ -5941,7 +6014,7 @@ main(int argc, char *argv[])
         unixctl_server_run(unixctl);
 
         unixctl_server_wait(unixctl);
-        if (exiting || pending_pkt.conn) {
+        if (exit_args.exiting || pending_pkt.conn) {
             poll_immediate_wake();
         }
 
@@ -5992,7 +6065,7 @@ loop_done:
         memory_wait();
         poll_block();
         if (should_service_stop()) {
-            exiting = true;
+            exit_args.exiting = true;
         }
     }
 
@@ -6000,7 +6073,7 @@ loop_done:
     engine_cleanup();
 
     /* It's time to exit.  Clean up the databases if we are not restarting */
-    if (!restart) {
+    if (!exit_args.restart) {
         bool done = !ovsdb_idl_has_ever_connected(ovnsb_idl_loop.idl);
         while (!done) {
             update_sb_db(ovs_idl_loop.idl, ovnsb_idl_loop.idl,
@@ -6052,7 +6125,6 @@ loop_done:
     }
 
     free(ovn_version);
-    unixctl_server_destroy(unixctl);
     lflow_destroy();
     ofctrl_destroy();
     pinctrl_destroy();
@@ -6077,6 +6149,8 @@ loop_done:
     if (cli_system_id) {
         free(cli_system_id);
     }
+    ovn_exit_args_finish(&exit_args);
+    unixctl_server_destroy(unixctl);
     service_stop();
     ovsrcu_exit();
 
@@ -6142,6 +6216,13 @@ parse_options(int argc, char *argv[])
             ssl_ca_cert_file = optarg;
             break;
 
+        case OPT_SSL_PROTOCOLS:
+            stream_ssl_set_protocols(optarg);
+            break;
+
+        case OPT_SSL_CIPHERS:
+            stream_ssl_set_ciphers(optarg);
+            break;
 
         case OPT_PEER_CA_CERT:
             stream_ssl_set_peer_ca_cert_file(optarg);
@@ -6156,6 +6237,7 @@ parse_options(int argc, char *argv[])
             break;
 
         case 'n':
+            free(cli_system_id);
             cli_system_id = xstrdup(optarg);
             break;
 
@@ -6200,16 +6282,6 @@ usage(void)
     exit(EXIT_SUCCESS);
 }
 
-static void
-ovn_controller_exit(struct unixctl_conn *conn, int argc,
-             const char *argv[], void *exit_args_)
-{
-    struct ovn_controller_exit_args *exit_args = exit_args_;
-    *exit_args->exiting = true;
-    *exit_args->restart = argc == 2 && !strcmp(argv[1], "--restart");
-    unixctl_command_reply(conn, NULL);
-}
-
 static void
 ct_zone_list(struct unixctl_conn *conn, int argc OVS_UNUSED,
              const char *argv[] OVS_UNUSED, void *ct_zones_)
diff --git a/controller/physical.c b/controller/physical.c
index 75257bc85..dbce84c4d 100644
--- a/controller/physical.c
+++ b/controller/physical.c
@@ -341,7 +341,7 @@ get_remote_tunnels(const struct sbrec_port_binding *binding,
             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
             VLOG_WARN_RL(
                 &rl, "Failed to locate tunnel to reach main chassis %s "
-                     "for port %s. Cloning packets disabled for the chassis.",
+                     "for port %s.",
                 binding->chassis->name, binding->logical_port);
         } else {
             struct tunnel *tun_elem = xmalloc(sizeof *tun_elem);
@@ -363,7 +363,7 @@ get_remote_tunnels(const struct sbrec_port_binding *binding,
             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
             VLOG_WARN_RL(
                 &rl, "Failed to locate tunnel to reach additional chassis %s "
-                     "for port %s. Cloning packets disabled for the chassis.",
+                     "for port %s.",
                 binding->additional_chassis[i]->name, binding->logical_port);
             continue;
         }
@@ -703,22 +703,33 @@ put_replace_chassis_mac_flows(const struct simap *ct_zones,
     }
 }
 
-#define VLAN_80211AD_ETHTYPE 0x88a8
-#define VLAN_80211Q_ETHTYPE 0x8100
+#define VLAN_8021AD_ETHTYPE 0x88a8
+#define VLAN_8021Q_ETHTYPE 0x8100
 
 static void
 ofpact_put_push_vlan(struct ofpbuf *ofpacts, const struct smap *options, int tag)
 {
     const char *ethtype_opt = options ? smap_get(options, "ethtype") : NULL;
 
-    int ethtype = VLAN_80211Q_ETHTYPE;
+    int ethtype = VLAN_8021Q_ETHTYPE;
     if (ethtype_opt) {
-        if (!strcasecmp(ethtype_opt, "802.11ad")) {
-            ethtype = VLAN_80211AD_ETHTYPE;
-        } else if (strcasecmp(ethtype_opt, "802.11q")) {
+      if (!strcasecmp(ethtype_opt, "802.11ad")
+          || !strcasecmp(ethtype_opt, "802.1ad")) {
+        ethtype = VLAN_8021AD_ETHTYPE;
+      } else if (!strcasecmp(ethtype_opt, "802.11q")
+                 || !strcasecmp(ethtype_opt, "802.1q")) {
+        ethtype = VLAN_8021Q_ETHTYPE;
+      } else {
             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
             VLOG_WARN_RL(&rl, "Unknown port ethtype: %s", ethtype_opt);
-        }
+      }
+      if (!strcasecmp(ethtype_opt, "802.11ad")
+          || !strcasecmp(ethtype_opt, "802.11q")) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+        VLOG_WARN_RL(&rl, "Using incorrect value ethtype: %s for either "
+                          "802.1q or 802.1ad please correct this value",
+                          ethtype_opt);
+      }
     }
 
     struct ofpact_push_vlan *push_vlan;
@@ -2371,9 +2382,37 @@ physical_run(struct physical_ctx *p_ctx,
         }
 
         put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
-
         ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, 0, &match,
                         &ofpacts, hc_uuid);
+
+        /* Set allow rx from tunnel bit. */
+        put_load(1, MFF_LOG_FLAGS, MLF_RX_FROM_TUNNEL_BIT, 1, &ofpacts);
+
+        /* Add specif flows for E/W ICMPv{4,6} packets if tunnelled packets
+         * do not fit path MTU.
+         */
+        put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, &ofpacts);
+
+        /* IPv4 */
+        match_init_catchall(&match);
+        match_set_in_port(&match, tun->ofport);
+        match_set_dl_type(&match, htons(ETH_TYPE_IP));
+        match_set_nw_proto(&match, IPPROTO_ICMP);
+        match_set_icmp_type(&match, 3);
+        match_set_icmp_code(&match, 4);
+
+        ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 120, 0, &match,
+                        &ofpacts, hc_uuid);
+        /* IPv6 */
+        match_init_catchall(&match);
+        match_set_in_port(&match, tun->ofport);
+        match_set_dl_type(&match, htons(ETH_TYPE_IPV6));
+        match_set_nw_proto(&match, IPPROTO_ICMPV6);
+        match_set_icmp_type(&match, 2);
+        match_set_icmp_code(&match, 0);
+
+        ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 120, 0, &match,
+                        &ofpacts, hc_uuid);
     }
 
     /* Add VXLAN specific rules to transform port keys
diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index ff5a3444c..79b00c878 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -373,9 +373,13 @@ static const struct sbrec_fdb *fdb_lookup(
     uint32_t dp_key, const char *mac);
 static void run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn,
                         struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
+            struct ovsdb_idl_index *sbrec_port_binding_by_key,
+            struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
                         const struct fdb_entry *fdb_e)
                         OVS_REQUIRES(pinctrl_mutex);
 static void run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn,
+            struct ovsdb_idl_index *sbrec_port_binding_by_key,
+            struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
                         struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac)
                         OVS_REQUIRES(pinctrl_mutex);
 static void wait_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn);
@@ -1280,11 +1284,25 @@ fill_ipv6_prefix_state(struct ovsdb_idl_txn *ovnsb_idl_txn,
             continue;
         }
 
+        /* To reach this point, the port binding must be a logical router
+         * port. LRPs are configured with a single MAC that is always non-NULL.
+         * Therefore, as long as we are working with a port_binding that was
+         * inserted into the southbound database by northd, we can always
+         * safely extract pb->mac[0] since it will be non-NULL.
+         *
+         * However, if a port_binding was inserted by someone else, then we
+         * need to double-check our assumption first.
+         */
+        if (pb->n_mac != 1) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+            VLOG_ERR_RL(&rl, "Port binding "UUID_FMT" has %"PRIuSIZE" MACs "
+                        "instead of 1", UUID_ARGS(&pb->header_.uuid),
+                        pb->n_mac);
+            continue;
+        }
         struct lport_addresses c_addrs;
-        for (size_t j = 0; j < pb->n_mac; j++) {
-            if (extract_lsp_addresses(pb->mac[j], &c_addrs)) {
-                    break;
-            }
+        if (!extract_lsp_addresses(pb->mac[0], &c_addrs)) {
+            continue;
         }
 
         pfd = shash_find_data(&ipv6_prefixd, pb->logical_port);
@@ -2850,6 +2868,8 @@ dns_build_ptr_answer(
     free(encoded);
 }
 
+#define DNS_QUERY_TYPE_CLASS_LEN (2 * sizeof(ovs_be16))
+
 /* Called with in the pinctrl_handler thread context. */
 static void
 pinctrl_handle_dns_lookup(
@@ -2911,18 +2931,13 @@ pinctrl_handle_dns_lookup(
         goto exit;
     }
 
-    /* Check if there is an additional record present, which is unsupported */
-    if (in_dns_header->arcount) {
-        VLOG_DBG_RL(&rl, "Received DNS query with additional records, which"
-                    " is unsupported");
-        goto exit;
-    }
-
     struct udp_header *in_udp = dp_packet_l4(pkt_in);
     size_t udp_len = ntohs(in_udp->udp_len);
     size_t l4_len = dp_packet_l4_size(pkt_in);
+    uint8_t *l4_start = (uint8_t *) in_udp;
     uint8_t *end = (uint8_t *)in_udp + MIN(udp_len, l4_len);
     uint8_t *in_dns_data = (uint8_t *)(in_dns_header + 1);
+    uint8_t *in_dns_data_start = in_dns_data;
     uint8_t *in_queryname = in_dns_data;
     uint16_t idx = 0;
     struct ds query_name;
@@ -2946,7 +2961,7 @@ pinctrl_handle_dns_lookup(
     in_dns_data += idx;
 
     /* Query should have TYPE and CLASS fields */
-    if (in_dns_data + (2 * sizeof(ovs_be16)) > end) {
+    if (in_dns_data + DNS_QUERY_TYPE_CLASS_LEN > end) {
         ds_destroy(&query_name);
         goto exit;
     }
@@ -2960,6 +2975,10 @@ pinctrl_handle_dns_lookup(
         goto exit;
     }
 
+    uint8_t *rest = in_dns_data + DNS_QUERY_TYPE_CLASS_LEN;
+    uint32_t query_size = rest - in_dns_data_start;
+    uint32_t query_l4_size = rest - l4_start;
+
     uint64_t dp_key = ntohll(pin->flow_metadata.flow.metadata);
     const char *answer_data = NULL;
     struct shash_node *iter;
@@ -3028,7 +3047,7 @@ pinctrl_handle_dns_lookup(
         goto exit;
     }
 
-    uint16_t new_l4_size = ntohs(in_udp->udp_len) +  dns_answer.size;
+    uint16_t new_l4_size = query_l4_size + dns_answer.size;
     size_t new_packet_size = pkt_in->l4_ofs + new_l4_size;
     struct dp_packet pkt_out;
     dp_packet_init(&pkt_out, new_packet_size);
@@ -3061,7 +3080,7 @@ pinctrl_handle_dns_lookup(
     out_dns_header->arcount = 0;
 
     /* Copy the Query section. */
-    dp_packet_put(&pkt_out, dp_packet_data(pkt_in), dp_packet_size(pkt_in));
+    dp_packet_put(&pkt_out, dp_packet_data(pkt_in), query_size);
 
     /* Copy the answer sections. */
     dp_packet_put(&pkt_out, dns_answer.data, dns_answer.size);
@@ -3577,7 +3596,8 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
                       chassis);
     bfd_monitor_run(ovnsb_idl_txn, bfd_table, sbrec_port_binding_by_name,
                     chassis, active_tunnels);
-    run_put_fdbs(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac);
+    run_put_fdbs(ovnsb_idl_txn, sbrec_port_binding_by_key,
+                 sbrec_datapath_binding_by_key, sbrec_fdb_by_dp_key_mac);
     run_activated_ports(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
                         sbrec_port_binding_by_key, chassis);
     ovs_mutex_unlock(&pinctrl_mutex);
@@ -6226,6 +6246,12 @@ pinctrl_handle_put_nd_ra_opts(
 
     /* Set the IPv6 payload length and calculate the ICMPv6 checksum. */
     struct ovs_16aligned_ip6_hdr *nh = dp_packet_l3(&pkt_out);
+
+    /* Set the source to "ff02::1" if the original source is "::". */
+    if (!memcmp(&nh->ip6_src, &in6addr_any, sizeof in6addr_any)) {
+        memcpy(&nh->ip6_src, &in6addr_all_hosts, sizeof in6addr_all_hosts);
+    }
+
     nh->ip6_plen = htons(userdata->size);
     struct ovs_ra_msg *ra = dp_packet_l4(&pkt_out);
     ra->icmph.icmp6_cksum = 0;
@@ -6338,26 +6364,31 @@ pinctrl_handle_empty_lb_backends_opts(struct ofpbuf *userdata)
     char *protocol = NULL;
     char *load_balancer = NULL;
 
+    struct empty_lb_backends_event *event = NULL;
+
     while (userdata->size) {
         userdata_opt = ofpbuf_try_pull(userdata, sizeof opt_hdr);
         if (!userdata_opt) {
-            return false;
+            goto cleanup;
         }
         memcpy(&opt_hdr, userdata_opt, sizeof opt_hdr);
 
         size_t size = ntohs(opt_hdr.size);
         char *userdata_opt_data = ofpbuf_try_pull(userdata, size);
         if (!userdata_opt_data) {
-            return false;
+            goto cleanup;
         }
         switch (ntohs(opt_hdr.opt_code)) {
         case EMPTY_LB_VIP:
+            free(vip);
             vip = xmemdup0(userdata_opt_data, size);
             break;
         case EMPTY_LB_PROTOCOL:
+            free(protocol);
             protocol = xmemdup0(userdata_opt_data, size);
             break;
         case EMPTY_LB_LOAD_BALANCER:
+            free(load_balancer);
             load_balancer = xmemdup0(userdata_opt_data, size);
             break;
         default:
@@ -6368,36 +6399,39 @@ pinctrl_handle_empty_lb_backends_opts(struct ofpbuf *userdata)
     if (!vip || !protocol || !load_balancer) {
         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
         VLOG_WARN_RL(&rl, "missing lb parameters in userdata");
-        free(vip);
-        free(protocol);
-        free(load_balancer);
-        return false;
+        goto cleanup;
     }
 
-    struct empty_lb_backends_event *event;
-
     event = pinctrl_find_empty_lb_backends_event(vip, protocol,
                                                  load_balancer, hash);
-    if (!event) {
-        if (hmap_count(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS]) >= 1000) {
-            COVERAGE_INC(pinctrl_drop_controller_event);
-            return false;
-        }
+    if (event) {
+        goto cleanup;
+    }
 
-        event = xzalloc(sizeof *event);
-        hmap_insert(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS],
-                    &event->hmap_node, hash);
-        event->vip = vip;
-        event->protocol = protocol;
-        event->load_balancer = load_balancer;
-        event->timestamp = time_msec();
-        notify_pinctrl_main();
-    } else {
-        free(vip);
-        free(protocol);
-        free(load_balancer);
+    if (hmap_count(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS]) >= 1000) {
+        COVERAGE_INC(pinctrl_drop_controller_event);
+        goto cleanup;
     }
-    return true;
+
+    event = xzalloc(sizeof *event);
+    hmap_insert(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS],
+                &event->hmap_node, hash);
+    event->vip = vip;
+    event->protocol = protocol;
+    event->load_balancer = load_balancer;
+    event->timestamp = time_msec();
+    notify_pinctrl_main();
+
+    vip = NULL;
+    protocol = NULL;
+    load_balancer = NULL;
+
+cleanup:
+    free(vip);
+    free(protocol);
+    free(load_balancer);
+
+    return event != NULL;
 }
 
 static void
@@ -7755,7 +7789,9 @@ svc_monitors_run(struct rconn *swconn,
             if (svc_mon->n_success >= svc_mon->success_count) {
                 svc_mon->status = SVC_MON_ST_ONLINE;
                 svc_mon->n_success = 0;
+                svc_mon->n_failures = 0;
             }
+
             if (current_time >= svc_mon->next_send_time) {
                 svc_monitor_send_health_check(swconn, svc_mon);
                 next_run_time = svc_mon->wait_time;
@@ -7767,6 +7803,7 @@ svc_monitors_run(struct rconn *swconn,
         case SVC_MON_S_OFFLINE:
             if (svc_mon->n_failures >= svc_mon->failure_count) {
                 svc_mon->status = SVC_MON_ST_OFFLINE;
+                svc_mon->n_success = 0;
                 svc_mon->n_failures = 0;
             }
 
@@ -7812,7 +7849,6 @@ pinctrl_handle_tcp_svc_check(struct rconn *swconn,
         return false;
     }
 
-    uint32_t tcp_seq = ntohl(get_16aligned_be32(&th->tcp_seq));
     uint32_t tcp_ack = ntohl(get_16aligned_be32(&th->tcp_ack));
 
     if (th->tcp_dst != svc_mon->tp_src) {
@@ -7829,10 +7865,10 @@ pinctrl_handle_tcp_svc_check(struct rconn *swconn,
         svc_mon->n_success++;
         svc_mon->state = SVC_MON_S_ONLINE;
 
-        /* Send RST-ACK packet. */
-        svc_monitor_send_tcp_health_check__(swconn, svc_mon, TCP_RST | TCP_ACK,
-                                            htonl(tcp_ack + 1),
-                                            htonl(tcp_seq + 1), th->tcp_dst);
+        /* Send RST packet. */
+        svc_monitor_send_tcp_health_check__(swconn, svc_mon, TCP_RST,
+                                            htonl(tcp_ack),
+                                            htonl(0), th->tcp_dst);
         /* Calculate next_send_time. */
         svc_mon->next_send_time = time_msec() + svc_mon->interval;
         return true;
@@ -8184,6 +8220,8 @@ fdb_lookup(struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac, uint32_t dp_key,
 static void
 run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn,
             struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
+            struct ovsdb_idl_index *sbrec_port_binding_by_key,
+            struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
             const struct fdb_entry *fdb_e)
 {
     /* Convert ethernet argument to string form for database. */
@@ -8192,14 +8230,28 @@ run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn,
              ETH_ADDR_FMT, ETH_ADDR_ARGS(fdb_e->mac));
 
     /* Update or add an FDB entry. */
+    const struct sbrec_port_binding *sb_entry_pb = NULL;
+    const struct sbrec_port_binding *new_entry_pb = NULL;
     const struct sbrec_fdb *sb_fdb =
         fdb_lookup(sbrec_fdb_by_dp_key_mac, fdb_e->dp_key, mac_string);
     if (!sb_fdb) {
         sb_fdb = sbrec_fdb_insert(ovnsb_idl_txn);
         sbrec_fdb_set_dp_key(sb_fdb, fdb_e->dp_key);
         sbrec_fdb_set_mac(sb_fdb, mac_string);
+    } else {
+        /* check whether sb_fdb->port_key is vif or localnet type */
+        sb_entry_pb = lport_lookup_by_key(
+            sbrec_datapath_binding_by_key, sbrec_port_binding_by_key,
+            sb_fdb->dp_key, sb_fdb->port_key);
+        new_entry_pb = lport_lookup_by_key(
+            sbrec_datapath_binding_by_key, sbrec_port_binding_by_key,
+            fdb_e->dp_key, fdb_e->port_key);
+    }
+    /* Do not have localnet overwrite a previous vif entry */
+    if (!sb_entry_pb || !new_entry_pb || strcmp(sb_entry_pb->type, "") ||
+        strcmp(new_entry_pb->type, "localnet")) {
+        sbrec_fdb_set_port_key(sb_fdb, fdb_e->port_key);
     }
-    sbrec_fdb_set_port_key(sb_fdb, fdb_e->port_key);
 
     /* For backward compatibility check if timestamp column is available
      * in SB DB. */
@@ -8210,6 +8262,8 @@ run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn,
 
 static void
 run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn,
+             struct ovsdb_idl_index *sbrec_port_binding_by_key,
+             struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
              struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac)
              OVS_REQUIRES(pinctrl_mutex)
 {
@@ -8219,7 +8273,9 @@ run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn,
 
     const struct fdb_entry *fdb_e;
     HMAP_FOR_EACH (fdb_e, hmap_node, &put_fdbs) {
-        run_put_fdb(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac, fdb_e);
+        run_put_fdb(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac,
+                    sbrec_port_binding_by_key,
+                    sbrec_datapath_binding_by_key, fdb_e);
     }
     ovn_fdbs_flush(&put_fdbs);
 }
diff --git a/controller/statctrl.c b/controller/statctrl.c
index cb1545cbb..8cce97df8 100644
--- a/controller/statctrl.c
+++ b/controller/statctrl.c
@@ -233,7 +233,7 @@ statctrl_thread_handler(void *arg)
     struct statctrl_ctx *ctx = arg;
 
     /* OpenFlow connection to the switch. */
-    struct rconn *swconn = rconn_create(5, 0, DSCP_DEFAULT,
+    struct rconn *swconn = rconn_create(0, 0, DSCP_DEFAULT,
                                         1 << OFP15_VERSION);
 
     while (!latch_is_set(&ctx->exit_latch)) {
diff --git a/debian/changelog b/debian/changelog
index 96d132784..d61c4a6ef 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,21 @@
+OVN (23.09.3-1) unstable; urgency=low
+   [ OVN team ]
+   * New upstream version
+
+ -- OVN team <dev@openvswitch.org>  Fri, 01 Mar 2024 14:06:41 -0500
+
+OVN (23.09.2-1) unstable; urgency=low
+   [ OVN team ]
+   * New upstream version
+
+ -- OVN team <dev@openvswitch.org>  Fri, 01 Mar 2024 14:06:41 -0500
+
+OVN (23.09.1-1) unstable; urgency=low
+   [ OVN team ]
+   * New upstream version
+
+ -- OVN team <dev@openvswitch.org>  Fri, 01 Dec 2023 14:41:31 -0500
+
 ovn (23.09.0-1) unstable; urgency=low
 
    * New upstream version
diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c
index e2023c2ba..6db6ce839 100644
--- a/ic/ovn-ic.c
+++ b/ic/ovn-ic.c
@@ -132,14 +132,18 @@ az_run(struct ic_context *ctx)
         return NULL;
     }
 
-    /* Delete old AZ if name changes.  Note: if name changed when ovn-ic
-     * is not running, one has to manually delete the old AZ with:
+    /* Update old AZ if name changes.  Note: if name changed when ovn-ic
+     * is not running, one has to manually delete/update the old AZ with:
      * "ovn-ic-sbctl destroy avail <az>". */
     static char *az_name;
     const struct icsbrec_availability_zone *az;
     if (az_name && strcmp(az_name, nb_global->name)) {
         ICSBREC_AVAILABILITY_ZONE_FOR_EACH (az, ctx->ovnisb_idl) {
-            if (!strcmp(az->name, az_name)) {
+            /* AZ name update locally need to update az in ISB. */
+            if (nb_global->name[0] && !strcmp(az->name, az_name)) {
+                icsbrec_availability_zone_set_name(az, nb_global->name);
+                break;
+            } else if (!nb_global->name[0] && !strcmp(az->name, az_name)) {
                 icsbrec_availability_zone_delete(az);
                 break;
             }
@@ -1064,12 +1068,15 @@ prefix_is_black_listed(const struct smap *nb_options,
                 continue;
             }
         } else {
-            struct in6_addr mask = ipv6_create_mask(bl_plen);
-            for (int i = 0; i < 16 && mask.s6_addr[i] != 0; i++) {
-                if ((prefix->s6_addr[i] & mask.s6_addr[i])
-                    != (bl_prefix.s6_addr[i] & mask.s6_addr[i])) {
-                    continue;
-                }
+            struct in6_addr mask = ipv6_create_mask(plen);
+            /* First calculate the difference between bl_prefix and prefix, so
+             * use the bl mask to ensure prefixes are correctly validated.
+             * e.g.: 2005:1734:5678::/50 is a subnet of 2005:1234::/21 */
+            struct in6_addr m_prefixes = ipv6_addr_bitand(prefix, &bl_prefix);
+            struct in6_addr m_prefix = ipv6_addr_bitand(&m_prefixes, &mask);
+            struct in6_addr m_bl_prefix = ipv6_addr_bitand(&bl_prefix, &mask);
+            if (!ipv6_addr_equals(&m_prefix, &m_bl_prefix)) {
+                continue;
             }
         }
         matched = true;
@@ -1630,13 +1637,18 @@ collect_lr_routes(struct ic_context *ctx,
     const struct icnbrec_transit_switch *key;
 
     struct hmap *routes_ad;
+    const struct icnbrec_transit_switch *t_sw;
     for (int i = 0; i < ic_lr->n_isb_pbs; i++) {
         isb_pb = ic_lr->isb_pbs[i];
         key = icnbrec_transit_switch_index_init_row(
             ctx->icnbrec_transit_switch_by_name);
         icnbrec_transit_switch_index_set_name(key, isb_pb->transit_switch);
-        ts_name = icnbrec_transit_switch_index_find(
-            ctx->icnbrec_transit_switch_by_name, key)->name;
+        t_sw = icnbrec_transit_switch_index_find(
+             ctx->icnbrec_transit_switch_by_name, key);
+        if (!t_sw) {
+            continue;
+        }
+        ts_name = t_sw->name;
         icnbrec_transit_switch_index_destroy_row(key);
         routes_ad = shash_find_data(routes_ad_by_ts, ts_name);
         if (!routes_ad) {
@@ -1841,6 +1853,14 @@ parse_options(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
             ssl_ca_cert_file = optarg;
             break;
 
+        case OPT_SSL_PROTOCOLS:
+            stream_ssl_set_protocols(optarg);
+            break;
+
+        case OPT_SSL_CIPHERS:
+            stream_ssl_set_ciphers(optarg);
+            break;
+
         case 'd':
             ovnsb_db = optarg;
             break;
@@ -2216,10 +2236,19 @@ main(int argc, char *argv[])
                 ovn_db_run(&ctx);
             }
 
-            ovsdb_idl_loop_commit_and_wait(&ovnnb_idl_loop);
-            ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop);
-            ovsdb_idl_loop_commit_and_wait(&ovninb_idl_loop);
-            ovsdb_idl_loop_commit_and_wait(&ovnisb_idl_loop);
+            int rc1 = ovsdb_idl_loop_commit_and_wait(&ovnnb_idl_loop);
+            int rc2 = ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop);
+            int rc3 = ovsdb_idl_loop_commit_and_wait(&ovninb_idl_loop);
+            int rc4 = ovsdb_idl_loop_commit_and_wait(&ovnisb_idl_loop);
+            if (!rc1 || !rc2 || !rc3 || !rc4) {
+                VLOG_DBG(" a transaction failed in: %s %s %s %s",
+                         !rc1 ? "nb" : "", !rc2 ? "sb" : "",
+                         !rc3 ? "ic_nb" : "", rc4 ? "ic_sb" : "");
+                /* A transaction failed. Wake up immediately to give
+                 * opportunity to send the proper transaction
+                 */
+                poll_immediate_wake();
+            }
         } else {
             /* ovn-ic is paused
              *    - we still want to handle any db updates and update the
diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index 04bb6ffd0..b99c086e3 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -75,7 +75,7 @@ struct collector_set_ids;
     OVNACT(CT_LB_MARK,        ovnact_ct_lb)           \
     OVNACT(SELECT,            ovnact_select)          \
     OVNACT(CT_CLEAR,          ovnact_null)            \
-    OVNACT(CT_COMMIT_NAT,     ovnact_ct_nat)          \
+    OVNACT(CT_COMMIT_NAT,     ovnact_ct_commit_nat)   \
     OVNACT(CLONE,             ovnact_nest)            \
     OVNACT(ARP,               ovnact_nest)            \
     OVNACT(ICMP4,             ovnact_nest)            \
@@ -274,7 +274,7 @@ enum ovnact_ct_nat_type {
     OVNACT_CT_NAT_UNSPEC,
 };
 
-/* OVNACT_CT_DNAT, OVNACT_CT_SNAT, OVNACT_CT_COMMIT_NAT. */
+/* OVNACT_CT_DNAT, OVNACT_CT_SNAT. */
 struct ovnact_ct_nat {
     struct ovnact ovnact;
     int family;
@@ -296,6 +296,14 @@ struct ovnact_ct_nat {
     uint8_t ltable;             /* Logical table ID of next table. */
 };
 
+/* OVNACT_CT_COMMIT_NAT. */
+struct ovnact_ct_commit_nat {
+    struct ovnact ovnact;
+
+    bool dnat_zone;
+    uint8_t ltable;
+};
+
 enum ovnact_ct_lb_flag {
     OVNACT_CT_LB_FLAG_NONE,
     OVNACT_CT_LB_FLAG_SKIP_SNAT,
diff --git a/include/ovn/features.h b/include/ovn/features.h
index 3bf536127..08f1d8288 100644
--- a/include/ovn/features.h
+++ b/include/ovn/features.h
@@ -26,6 +26,8 @@
 #define OVN_FEATURE_MAC_BINDING_TIMESTAMP "mac-binding-timestamp"
 #define OVN_FEATURE_CT_LB_RELATED "ovn-ct-lb-related"
 #define OVN_FEATURE_FDB_TIMESTAMP "fdb-timestamp"
+#define OVN_FEATURE_LS_DPG_COLUMN "ls-dpg-column"
+#define OVN_FEATURE_CT_COMMIT_NAT_V2 "ct-commit-nat-v2"
 
 /* OVS datapath supported features.  Based on availability OVN might generate
  * different types of openflows.
@@ -48,5 +50,8 @@ void ovs_feature_support_destroy(void);
 bool ovs_feature_is_supported(enum ovs_feature_value feature);
 bool ovs_feature_support_run(const struct smap *ovs_capabilities,
                              const char *br_name);
+bool ovs_feature_set_discovered(void);
+uint32_t ovs_feature_max_meters_get(void);
+uint32_t ovs_feature_max_select_groups_get(void);
 
 #endif
diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
index a7b64ef67..f07c4c42e 100644
--- a/include/ovn/logical-fields.h
+++ b/include/ovn/logical-fields.h
@@ -77,6 +77,9 @@ enum mff_log_flags_bits {
     MLF_CHECK_PORT_SEC_BIT = 12,
     MLF_LOOKUP_COMMIT_ECMP_NH_BIT = 13,
     MLF_USE_LB_AFF_SESSION_BIT = 14,
+    MLF_LOCALNET_BIT = 15,
+    MLF_RX_FROM_TUNNEL_BIT = 16,
+    MLF_ICMP_SNAT_BIT = 17,
 };
 
 /* MFF_LOG_FLAGS_REG flag assignments */
@@ -124,6 +127,14 @@ enum mff_log_flags {
     MLF_LOOKUP_COMMIT_ECMP_NH = (1 << MLF_LOOKUP_COMMIT_ECMP_NH_BIT),
 
     MLF_USE_LB_AFF_SESSION = (1 << MLF_USE_LB_AFF_SESSION_BIT),
+
+    /* Indicate that the port is localnet. */
+    MLF_LOCALNET = (1 << MLF_LOCALNET_BIT),
+
+    /* Indicate the packet has been received from the tunnel. */
+    MLF_RX_FROM_TUNNEL = (1 << MLF_RX_FROM_TUNNEL_BIT),
+
+    MLF_ICMP_SNAT = (1 << MLF_ICMP_SNAT_BIT),
 };
 
 /* OVN logical fields
diff --git a/lib/actions.c b/lib/actions.c
index b880927b6..1384672f5 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -1020,16 +1020,29 @@ parse_CT_COMMIT_NAT(struct action_context *ctx)
 
     if (ctx->pp->cur_ltable >= ctx->pp->n_tables) {
         lexer_error(ctx->lexer,
-                    "\"ct_commit_related\" action not allowed in last table.");
+                    "\"ct_commit_nat\" action not allowed in last table.");
         return;
     }
 
-    struct ovnact_ct_nat *cn = ovnact_put_CT_COMMIT_NAT(ctx->ovnacts);
-    cn->commit = true;
+    struct ovnact_ct_commit_nat *cn = ovnact_put_CT_COMMIT_NAT(ctx->ovnacts);
     cn->ltable = ctx->pp->cur_ltable + 1;
-    cn->family = AF_UNSPEC;
-    cn->type = OVNACT_CT_NAT_UNSPEC;
-    cn->port_range.exists = false;
+    cn->dnat_zone = true;
+
+    if (!lexer_match(ctx->lexer, LEX_T_LPAREN)) {
+        return;
+    }
+
+    if (lexer_match_id(ctx->lexer, "dnat")) {
+        cn->dnat_zone = true;
+    } else if (lexer_match_id(ctx->lexer, "snat")) {
+        cn->dnat_zone = false;
+    } else {
+        lexer_error(ctx->lexer, "\"ct_commit_nat\" action accepts"
+                    " only \"dnat\" or \"snat\" parameter.");
+        return;
+    }
+
+    lexer_force_match(ctx->lexer, LEX_T_RPAREN);
 }
 
 static void
@@ -1082,9 +1095,10 @@ format_CT_SNAT_IN_CZONE(const struct ovnact_ct_nat *cn, struct ds *s)
 }
 
 static void
-format_CT_COMMIT_NAT(const struct ovnact_ct_nat *cn OVS_UNUSED, struct ds *s)
+format_CT_COMMIT_NAT(const struct ovnact_ct_commit_nat *cn, struct ds *s)
 {
-    ds_put_cstr(s, "ct_commit_nat;");
+    ds_put_cstr(s, "ct_commit_nat");
+    ds_put_cstr(s, cn->dnat_zone ? "(dnat);" : "(snat);");
 }
 
 static void
@@ -1131,8 +1145,20 @@ encode_ct_nat(const struct ovnact_ct_nat *cn,
     }
 
     if (cn->port_range.exists) {
-       nat->range.proto.min = cn->port_range.port_lo;
-       nat->range.proto.max = cn->port_range.port_hi;
+        nat->range.proto.min = cn->port_range.port_lo;
+        nat->range.proto.max = cn->port_range.port_hi;
+
+        /* Explicitly set the port selection algorithm to "random".  Otherwise
+         * it's up to the datapath to choose how to select the port and that
+         * might create unexpected behavior changes when the datapath defaults
+         * change.
+         *
+         * NOTE: for the userspace datapath the "random" function doesn't
+         * really generate random ports, it uses "hash" under the hood:
+         * https://issues.redhat.com/browse/FDP-269. */
+        if (nat->range.proto.min && nat->range.proto.max) {
+            nat->flags |= NX_NAT_F_PROTO_RANDOM;
+        }
     }
 
     ofpacts->header = ofpbuf_push_uninit(ofpacts, nat_offset);
@@ -1177,20 +1203,45 @@ encode_CT_SNAT_IN_CZONE(const struct ovnact_ct_nat *cn,
 }
 
 static void
-encode_CT_COMMIT_NAT(const struct ovnact_ct_nat *cn,
-                         const struct ovnact_encode_params *ep,
-                         struct ofpbuf *ofpacts)
+encode_CT_COMMIT_NAT(const struct ovnact_ct_commit_nat *cn,
+                     const struct ovnact_encode_params *ep,
+                     struct ofpbuf *ofpacts)
 {
-    enum mf_field_id zone = ep->is_switch
-                            ? MFF_LOG_CT_ZONE
-                            : MFF_LOG_DNAT_ZONE;
-    encode_ct_nat(cn, ep, zone, ofpacts);
+    const size_t ct_offset = ofpacts->size;
+
+    struct ofpact_conntrack *ct = ofpact_put_CT(ofpacts);
+    ct->recirc_table = cn->ltable + first_ptable(ep, ep->pipeline);
+    ct->zone_src.ofs = 0;
+    ct->zone_src.n_bits = 16;
+    ct->flags = NX_CT_F_COMMIT;
+    ct->alg = 0;
+
+    if (ep->is_switch) {
+        ct->zone_src.field = mf_from_id(MFF_LOG_CT_ZONE);
+    } else {
+        ct->zone_src.field = mf_from_id(cn->dnat_zone
+                                        ? MFF_LOG_DNAT_ZONE
+                                        : MFF_LOG_SNAT_ZONE);
+    }
+
+    struct ofpact_nat *nat = ofpact_put_NAT(ofpacts);
+    nat->range_af = AF_UNSPEC;
+    nat->flags = 0;
+
+    ct = ofpbuf_at_assert(ofpacts, ct_offset, sizeof *ct);
+    ofpacts->header = ct;
+    ofpact_finish_CT(ofpacts, &ct);
 }
 
 static void
 ovnact_ct_nat_free(struct ovnact_ct_nat *ct_nat OVS_UNUSED)
 {
 }
+
+static void
+ovnact_ct_commit_nat_free(struct ovnact_ct_commit_nat *cn OVS_UNUSED)
+{
+}
 
 static void
 parse_ct_lb_action(struct action_context *ctx, bool ct_lb_mark)
@@ -5004,6 +5055,7 @@ encode_COMMIT_LB_AFF(const struct ovnact_commit_lb_aff *lb_aff,
     ol->hard_timeout = OFP_FLOW_PERMANENT;
     ol->priority = OFP_DEFAULT_PRIORITY;
     ol->table_id = OFTABLE_CHK_LB_AFFINITY;
+    ol->cookie = htonll(ep->lflow_uuid.parts[0]);
 
     /* Match on metadata of the packet that created the new table. */
     ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
diff --git a/lib/extend-table.c b/lib/extend-table.c
index ebb1a054c..8e79d7177 100644
--- a/lib/extend-table.c
+++ b/lib/extend-table.c
@@ -17,9 +17,9 @@
 #include <config.h>
 #include <string.h>
 
-#include "bitmap.h"
 #include "extend-table.h"
 #include "hash.h"
+#include "id-pool.h"
 #include "lib/uuid.h"
 #include "openvswitch/vlog.h"
 
@@ -30,13 +30,29 @@ ovn_extend_table_delete_desired(struct ovn_extend_table *table,
                                 struct ovn_extend_table_lflow_to_desired *l);
 
 void
-ovn_extend_table_init(struct ovn_extend_table *table)
+ovn_extend_table_init(struct ovn_extend_table *table, const char *table_name,
+                      uint32_t n_ids)
 {
-    table->table_ids = bitmap_allocate(MAX_EXT_TABLE_ID);
-    bitmap_set1(table->table_ids, 0); /* table id 0 is invalid. */
-    hmap_init(&table->desired);
-    hmap_init(&table->lflow_to_desired);
-    hmap_init(&table->existing);
+    *table = (struct ovn_extend_table) {
+        .name = xstrdup(table_name),
+        .n_ids = n_ids,
+        /* Table id 0 is invalid, set id-pool base to 1. */
+        .table_ids = id_pool_create(1, n_ids),
+        .desired = HMAP_INITIALIZER(&table->desired),
+        .lflow_to_desired = HMAP_INITIALIZER(&table->lflow_to_desired),
+        .existing = HMAP_INITIALIZER(&table->existing),
+    };
+}
+
+void
+ovn_extend_table_reinit(struct ovn_extend_table *table, uint32_t n_ids)
+{
+    if (n_ids != table->n_ids) {
+        ovn_extend_table_clear(table, true);
+        id_pool_destroy(table->table_ids);
+        table->table_ids = id_pool_create(1, n_ids);
+        table->n_ids = n_ids;
+    }
 }
 
 static struct ovn_extend_table_info *
@@ -117,13 +133,13 @@ ovn_extend_table_add_desired_to_lflow(struct ovn_extend_table *table,
         ovs_list_init(&l->desired);
         hmap_insert(&table->lflow_to_desired, &l->hmap_node,
                     uuid_hash(lflow_uuid));
-        VLOG_DBG("%s: add new lflow_to_desired entry "UUID_FMT,
-                 __func__, UUID_ARGS(lflow_uuid));
+        VLOG_DBG("%s: table %s: add new lflow_to_desired entry "UUID_FMT,
+                 __func__, table->name, UUID_ARGS(lflow_uuid));
     }
 
     ovs_list_insert(&l->desired, &r->list_node);
-    VLOG_DBG("%s: lflow "UUID_FMT" use new item %s, id %"PRIu32,
-             __func__, UUID_ARGS(lflow_uuid), r->desired->name,
+    VLOG_DBG("%s: table %s: lflow "UUID_FMT" use new item %s, id %"PRIu32,
+             __func__, table->name, UUID_ARGS(lflow_uuid), r->desired->name,
              r->desired->table_id);
 }
 
@@ -160,10 +176,11 @@ ovn_extend_info_add_lflow_ref(struct ovn_extend_table *table,
 }
 
 static void
-ovn_extend_info_del_lflow_ref(struct ovn_extend_table_lflow_ref *r)
+ovn_extend_info_del_lflow_ref(struct ovn_extend_table *table,
+                              struct ovn_extend_table_lflow_ref *r)
 {
-    VLOG_DBG("%s: name %s, lflow "UUID_FMT" n %"PRIuSIZE, __func__,
-             r->desired->name, UUID_ARGS(&r->lflow_uuid),
+    VLOG_DBG("%s: table %s: name %s, lflow "UUID_FMT" n %"PRIuSIZE, __func__,
+             table->name, r->desired->name, UUID_ARGS(&r->lflow_uuid),
              hmap_count(&r->desired->references));
     hmap_remove(&r->desired->references, &r->hmap_node);
     ovs_list_remove(&r->list_node);
@@ -191,8 +208,8 @@ ovn_extend_table_clear(struct ovn_extend_table *table, bool existing)
         if (g->peer) {
             g->peer->peer = NULL;
         } else {
-            /* Unset the bitmap because the peer is deleted already. */
-            bitmap_set0(table->table_ids, g->table_id);
+            /* Unset the id because the peer is deleted already. */
+            id_pool_free_id(table->table_ids, g->table_id);
         }
         ovn_extend_table_info_destroy(g);
     }
@@ -206,7 +223,8 @@ ovn_extend_table_destroy(struct ovn_extend_table *table)
     hmap_destroy(&table->lflow_to_desired);
     ovn_extend_table_clear(table, true);
     hmap_destroy(&table->existing);
-    bitmap_free(table->table_ids);
+    id_pool_destroy(table->table_ids);
+    free(table->name);
 }
 
 /* Remove an entry from existing table */
@@ -221,7 +239,7 @@ ovn_extend_table_remove_existing(struct ovn_extend_table *table,
         existing->peer->peer = NULL;
     } else {
         /* Dealloc the ID. */
-        bitmap_set0(table->table_ids, existing->table_id);
+        id_pool_free_id(table->table_ids, existing->table_id);
     }
     ovn_extend_table_info_destroy(existing);
 }
@@ -234,15 +252,15 @@ ovn_extend_table_delete_desired(struct ovn_extend_table *table,
     struct ovn_extend_table_lflow_ref *r;
     LIST_FOR_EACH_SAFE (r, list_node, &l->desired) {
         struct ovn_extend_table_info *e = r->desired;
-        ovn_extend_info_del_lflow_ref(r);
+        ovn_extend_info_del_lflow_ref(table, r);
         if (hmap_is_empty(&e->references)) {
-            VLOG_DBG("%s: %s, "UUID_FMT, __func__,
-                     e->name, UUID_ARGS(&l->lflow_uuid));
+            VLOG_DBG("%s: table %s: %s, "UUID_FMT, __func__,
+                     table->name, e->name, UUID_ARGS(&l->lflow_uuid));
             hmap_remove(&table->desired, &e->hmap_node);
             if (e->peer) {
                 e->peer->peer = NULL;
             } else {
-                bitmap_set0(table->table_ids, e->table_id);
+                id_pool_free_id(table->table_ids, e->table_id);
             }
             ovn_extend_table_info_destroy(e);
         }
@@ -284,7 +302,7 @@ ovn_extend_table_sync(struct ovn_extend_table *table)
     }
 }
 
-/* Assign a new table ID for the table information from the bitmap.
+/* Assign a new table ID for the table information from the ID pool.
  * If it already exists, return the old ID. */
 uint32_t
 ovn_extend_table_assign_id(struct ovn_extend_table *table, const char *name,
@@ -298,9 +316,9 @@ ovn_extend_table_assign_id(struct ovn_extend_table *table, const char *name,
     /* Check whether we have non installed but allocated group_id. */
     HMAP_FOR_EACH_WITH_HASH (table_info, hmap_node, hash, &table->desired) {
         if (!strcmp(table_info->name, name)) {
-            VLOG_DBG("ovn_externd_table_assign_id: reuse old id %"PRIu32
-                     " for %s, used by lflow "UUID_FMT,
-                     table_info->table_id, table_info->name,
+            VLOG_DBG("ovn_extend_table_assign_id: table %s: "
+                     "reuse old id %"PRIu32" for %s, used by lflow "UUID_FMT,
+                     table->name, table_info->table_id, table_info->name,
                      UUID_ARGS(&lflow_uuid));
             ovn_extend_info_add_lflow_ref(table, table_info, &lflow_uuid);
             return table_info->table_id;
@@ -320,15 +338,13 @@ ovn_extend_table_assign_id(struct ovn_extend_table *table, const char *name,
 
     if (!existing_info) {
         /* Reserve a new id. */
-        table_id = bitmap_scan(table->table_ids, 0, 1, MAX_EXT_TABLE_ID + 1);
-    }
+        if (!id_pool_alloc_id(table->table_ids, &table_id)) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
 
-    if (table_id == MAX_EXT_TABLE_ID + 1) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_ERR_RL(&rl, "%"PRIu32" out of table ids.", table_id);
-        return EXT_TABLE_ID_INVALID;
+            VLOG_ERR_RL(&rl, "table %s: out of table ids.", table->name);
+            return EXT_TABLE_ID_INVALID;
+        }
     }
-    bitmap_set1(table->table_ids, table_id);
 
     table_info = ovn_extend_table_info_alloc(name, table_id, existing_info,
                                              hash);
diff --git a/lib/extend-table.h b/lib/extend-table.h
index b43a146b4..90e6e470d 100644
--- a/lib/extend-table.h
+++ b/lib/extend-table.h
@@ -17,18 +17,21 @@
 #ifndef EXTEND_TABLE_H
 #define EXTEND_TABLE_H 1
 
-#define MAX_EXT_TABLE_ID 65535
 #define EXT_TABLE_ID_INVALID 0
 
 #include "openvswitch/hmap.h"
 #include "openvswitch/list.h"
 #include "openvswitch/uuid.h"
 
+struct id_pool;
+
 /* Used to manage expansion tables associated with Flow table,
  * such as the Group Table or Meter Table. */
 struct ovn_extend_table {
-    unsigned long *table_ids;  /* Used as a bitmap with value set
-                                * for allocated ids in either desired or
+    char *name; /* Used to identify this table in a user friendly way,
+                 * e.g., for logging. */
+    uint32_t n_ids;
+    struct id_pool *table_ids; /* Used to allocate ids in either desired or
                                 * existing (or both).  If the same "name"
                                 * exists in both desired and existing tables,
                                 * they must share the same ID.  The "peer"
@@ -81,7 +84,9 @@ struct ovn_extend_table_lflow_ref {
     struct ovn_extend_table_info *desired;
 };
 
-void ovn_extend_table_init(struct ovn_extend_table *);
+void ovn_extend_table_init(struct ovn_extend_table *, const char *table_name,
+                           uint32_t n_ids);
+void ovn_extend_table_reinit(struct ovn_extend_table *, uint32_t n_ids);
 
 void ovn_extend_table_destroy(struct ovn_extend_table *);
 
diff --git a/lib/features.c b/lib/features.c
index d24e8f6c5..d391930c6 100644
--- a/lib/features.c
+++ b/lib/features.c
@@ -27,6 +27,7 @@
 #include "openvswitch/rconn.h"
 #include "openvswitch/ofp-msgs.h"
 #include "openvswitch/ofp-meter.h"
+#include "openvswitch/ofp-group.h"
 #include "openvswitch/ofp-util.h"
 #include "ovn/features.h"
 
@@ -81,6 +82,18 @@ static struct ovs_feature all_ovs_features[] = {
 /* A bitmap of OVS features that have been detected as 'supported'. */
 static uint32_t supported_ovs_features;
 
+/* Last set of received feature replies. */
+static struct ofputil_meter_features ovs_meter_features_reply;
+static struct ofputil_group_features ovs_group_features_reply;
+
+/* Currently discovered set of features. */
+static struct ofputil_meter_features ovs_meter_features;
+static struct ofputil_group_features ovs_group_features;
+
+/* Number of features replies still expected to receive for the requests
+ * we sent already. */
+static uint32_t n_features_reply_expected;
+
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
 
 /* ovs-vswitchd connection. */
@@ -140,16 +153,26 @@ ovs_feature_get_openflow_cap(const char *br_name)
 
     rconn_run(swconn);
     if (!rconn_is_connected(swconn)) {
+        rconn_run_wait(swconn);
+        rconn_recv_wait(swconn);
         return false;
     }
 
     /* send new requests just after reconnect. */
     if (conn_seq_no != rconn_get_connection_seqno(swconn)) {
-        /* dump datapath meter capabilities. */
+        n_features_reply_expected = 0;
+
+        /* Dump OpenFlow switch meter capabilities. */
         msg = ofpraw_alloc(OFPRAW_OFPST13_METER_FEATURES_REQUEST,
                            rconn_get_version(swconn), 0);
         rconn_send(swconn, msg, NULL);
+        n_features_reply_expected++;
+        /* Dump OpenFlow switch group capabilities. */
+        msg = ofputil_encode_group_features_request(rconn_get_version(swconn));
+        rconn_send(swconn, msg, NULL);
+        n_features_reply_expected++;
     }
+    conn_seq_no = rconn_get_connection_seqno(swconn);
 
     bool ret = false;
     for (int i = 0; i < 50; i++) {
@@ -163,21 +186,13 @@ ovs_feature_get_openflow_cap(const char *br_name)
         ofptype_decode(&type, oh);
 
         if (type == OFPTYPE_METER_FEATURES_STATS_REPLY) {
-            struct ofputil_meter_features mf;
-            ofputil_decode_meter_features(oh, &mf);
-
-            bool old_state = supported_ovs_features & OVS_DP_METER_SUPPORT;
-            bool new_state = mf.max_meters > 0;
-
-            if (old_state != new_state) {
-                ret = true;
-                if (new_state) {
-                    supported_ovs_features |= OVS_DP_METER_SUPPORT;
-                } else {
-                    supported_ovs_features &= ~OVS_DP_METER_SUPPORT;
-                }
-            }
-            conn_seq_no = rconn_get_connection_seqno(swconn);
+            ofputil_decode_meter_features(oh, &ovs_meter_features_reply);
+            ovs_assert(n_features_reply_expected);
+            n_features_reply_expected--;
+        } else if (type == OFPTYPE_GROUP_FEATURES_STATS_REPLY) {
+            ofputil_decode_group_features_reply(oh, &ovs_group_features_reply);
+            ovs_assert(n_features_reply_expected);
+            n_features_reply_expected--;
         } else if (type == OFPTYPE_ECHO_REQUEST) {
             rconn_send(swconn, ofputil_encode_echo_reply(oh), NULL);
         }
@@ -186,6 +201,26 @@ ovs_feature_get_openflow_cap(const char *br_name)
     rconn_run_wait(swconn);
     rconn_recv_wait(swconn);
 
+    /* If all feature replies were received, update the set of supported
+     * features. */
+    if (!n_features_reply_expected) {
+        if (memcmp(&ovs_meter_features, &ovs_meter_features_reply,
+                   sizeof ovs_meter_features_reply)) {
+            ovs_meter_features = ovs_meter_features_reply;
+            if (ovs_meter_features.max_meters) {
+                supported_ovs_features |= OVS_DP_METER_SUPPORT;
+            } else {
+                supported_ovs_features &= ~OVS_DP_METER_SUPPORT;
+            }
+            ret = true;
+        }
+        if (memcmp(&ovs_group_features, &ovs_group_features_reply,
+                   sizeof ovs_group_features_reply)) {
+            ovs_group_features = ovs_group_features_reply;
+            ret = true;
+        }
+    }
+
     return ret;
 }
 
@@ -229,3 +264,26 @@ ovs_feature_support_run(const struct smap *ovs_capabilities,
     }
     return updated;
 }
+
+bool
+ovs_feature_set_discovered(void)
+{
+    /* The supported feature set has been discovered if we're connected
+     * to OVS and it replied to all our feature request messages. */
+    return swconn && rconn_is_connected(swconn) &&
+           n_features_reply_expected == 0;
+}
+
+/* Returns the number of meters the OVS datapath supports. */
+uint32_t
+ovs_feature_max_meters_get(void)
+{
+    return ovs_meter_features.max_meters;
+}
+
+/* Returns the number of select groups the OVS datapath supports. */
+uint32_t
+ovs_feature_max_select_groups_get(void)
+{
+    return ovs_group_features.max_groups[OFPGT11_SELECT];
+}
diff --git a/lib/logical-fields.c b/lib/logical-fields.c
index fd509d9ee..20219a67a 100644
--- a/lib/logical-fields.c
+++ b/lib/logical-fields.c
@@ -129,6 +129,16 @@ ovn_init_symtab(struct shash *symtab)
              MLF_USE_SNAT_ZONE);
     expr_symtab_add_subfield(symtab, "flags.use_snat_zone", NULL,
                              flags_str);
+    snprintf(flags_str, sizeof flags_str, "flags[%d]",
+             MLF_LOCALNET_BIT);
+    expr_symtab_add_subfield(symtab, "flags.localnet", NULL,
+                            flags_str);
+    snprintf(flags_str, sizeof flags_str, "flags[%d]",
+             MLF_ICMP_SNAT_BIT);
+    expr_symtab_add_subfield(symtab, "flags.icmp_snat", NULL,
+                             flags_str);
+    snprintf(flags_str, sizeof flags_str, "flags[%d]", MLF_RX_FROM_TUNNEL_BIT);
+    expr_symtab_add_subfield(symtab, "flags.tunnel_rx", NULL, flags_str);
 
     /* Connection tracking state. */
     expr_symtab_add_field_scoped(symtab, "ct_mark", MFF_CT_MARK, NULL, false,
diff --git a/lib/memory-trim.c b/lib/memory-trim.c
index d8c6b4743..be8983fa7 100644
--- a/lib/memory-trim.c
+++ b/lib/memory-trim.c
@@ -71,13 +71,14 @@ memory_trimmer_can_run(struct memory_trimmer *mt)
     }
 
     long long int now = time_msec();
-    if (now < mt->last_active_ms || now < mt->trim_timeout_ms) {
+    if (now < mt->last_active_ms) {
         VLOG_WARN_RL(&rl, "Detected last active timestamp overflow");
         mt->recently_active = false;
         return true;
     }
 
-    if (now - mt->trim_timeout_ms >= mt->last_active_ms) {
+    if (now > mt->trim_timeout_ms
+        && now - mt->trim_timeout_ms >= mt->last_active_ms) {
         VLOG_INFO_RL(&rl, "Detected inactivity "
                     "(last active %lld ms ago): trimming memory",
                     now - mt->last_active_ms);
diff --git a/lib/ovn-util.c b/lib/ovn-util.c
index ffe295696..33105202f 100644
--- a/lib/ovn-util.c
+++ b/lib/ovn-util.c
@@ -1302,3 +1302,34 @@ sorted_array_apply_diff(const struct sorted_array *a1,
         apply_callback(arg, a2->arr[idx2], false);
     }
 }
+
+/* Call for the unixctl command that will store the connection and
+ * set the appropriate conditions. */
+void
+ovn_exit_command_callback(struct unixctl_conn *conn, int argc,
+                          const char *argv[], void *exit_args_)
+{
+    struct ovn_exit_args *exit_args = exit_args_;
+
+    exit_args->n_conns++;
+    exit_args->conns = xrealloc(exit_args->conns,
+                                exit_args->n_conns * sizeof *exit_args->conns);
+
+    exit_args->exiting = true;
+    exit_args->conns[exit_args->n_conns - 1] = conn;
+
+    if (!exit_args->restart) {
+        exit_args->restart = argc == 2 && !strcmp(argv[1], "--restart");
+    }
+}
+
+/* Reply to all waiting unixctl connections and free the connection array.
+ * This function should be called after the heaviest cleanup has finished. */
+void
+ovn_exit_args_finish(struct ovn_exit_args *exit_args)
+{
+    for (size_t i = 0; i < exit_args->n_conns; i++) {
+        unixctl_command_reply(exit_args->conns[i], NULL);
+    }
+    free(exit_args->conns);
+}
diff --git a/lib/ovn-util.h b/lib/ovn-util.h
index 16f18353d..bff50dbde 100644
--- a/lib/ovn-util.h
+++ b/lib/ovn-util.h
@@ -474,4 +474,17 @@ void sorted_array_apply_diff(const struct sorted_array *a1,
                                                     const char *item,
                                                     bool add),
                              const void *arg);
+
+/* Utilities around properly handling exit command. */
+struct ovn_exit_args {
+    struct unixctl_conn **conns;
+    size_t n_conns;
+    bool exiting;
+    bool restart;
+};
+
+void ovn_exit_command_callback(struct unixctl_conn *conn, int argc,
+                               const char *argv[], void *exit_args_);
+void ovn_exit_args_finish(struct ovn_exit_args *exit_args);
+
 #endif /* OVN_UTIL_H */
diff --git a/northd/en-lb-data.c b/northd/en-lb-data.c
index 250cec848..d06f46a54 100644
--- a/northd/en-lb-data.c
+++ b/northd/en-lb-data.c
@@ -144,6 +144,12 @@ lb_data_load_balancer_handler(struct engine_node *node, void *data)
 
     const struct nbrec_load_balancer *tracked_lb;
     NBREC_LOAD_BALANCER_TABLE_FOR_EACH_TRACKED (tracked_lb, nb_lb_table) {
+        /* "New" + "Deleted" is a no-op. */
+        if (nbrec_load_balancer_is_new(tracked_lb)
+            && nbrec_load_balancer_is_deleted(tracked_lb)) {
+            continue;
+        }
+
         struct ovn_northd_lb *lb;
         if (nbrec_load_balancer_is_new(tracked_lb)) {
             /* New load balancer. */
@@ -153,19 +159,22 @@ lb_data_load_balancer_handler(struct engine_node *node, void *data)
             add_crupdated_lb_to_tracked_data(lb, trk_lb_data,
                                              lb->health_checks);
             trk_lb_data->has_routable_lb |= lb->routable;
-        } else if (nbrec_load_balancer_is_deleted(tracked_lb)) {
-            lb = ovn_northd_lb_find(&lb_data->lbs,
-                                    &tracked_lb->header_.uuid);
-            ovs_assert(lb);
+            continue;
+        }
+
+        /* Protect against "spurious" deletes reported by the IDL. */
+        lb = ovn_northd_lb_find(&lb_data->lbs, &tracked_lb->header_.uuid);
+        if (!lb) {
+            continue;
+        }
+
+        if (nbrec_load_balancer_is_deleted(tracked_lb)) {
             hmap_remove(&lb_data->lbs, &lb->hmap_node);
             add_deleted_lb_to_tracked_data(lb, trk_lb_data,
                                            lb->health_checks);
             trk_lb_data->has_routable_lb |= lb->routable;
         } else {
             /* Load balancer updated. */
-            lb = ovn_northd_lb_find(&lb_data->lbs,
-                                    &tracked_lb->header_.uuid);
-            ovs_assert(lb);
             bool health_checks = lb->health_checks;
             struct sset old_ips_v4 = SSET_INITIALIZER(&old_ips_v4);
             struct sset old_ips_v6 = SSET_INITIALIZER(&old_ips_v6);
@@ -217,6 +226,12 @@ lb_data_load_balancer_group_handler(struct engine_node *node, void *data)
     const struct nbrec_load_balancer_group *tracked_lb_group;
     NBREC_LOAD_BALANCER_GROUP_TABLE_FOR_EACH_TRACKED (tracked_lb_group,
                                                       nb_lbg_table) {
+        /* "New" + "Deleted" is a no-op. */
+        if (nbrec_load_balancer_group_is_new(tracked_lb_group)
+            && nbrec_load_balancer_group_is_deleted(tracked_lb_group)) {
+            continue;
+        }
+
         if (nbrec_load_balancer_group_is_new(tracked_lb_group)) {
             struct ovn_lb_group *lb_group =
                 create_lb_group(tracked_lb_group, &lb_data->lbs,
@@ -228,21 +243,22 @@ lb_data_load_balancer_group_handler(struct engine_node *node, void *data)
             }
 
             trk_lb_data->has_routable_lb |= lb_group->has_routable_lb;
-        } else if (nbrec_load_balancer_group_is_deleted(tracked_lb_group)) {
-            struct ovn_lb_group *lb_group;
-            lb_group = ovn_lb_group_find(&lb_data->lbgrps,
-                                         &tracked_lb_group->header_.uuid);
-            ovs_assert(lb_group);
+            continue;
+        }
+
+        /* Protect against "spurious" deletes reported by the IDL. */
+        struct ovn_lb_group *lb_group;
+        lb_group = ovn_lb_group_find(&lb_data->lbgrps,
+                                     &tracked_lb_group->header_.uuid);
+        if (!lb_group) {
+            continue;
+        }
+
+        if (nbrec_load_balancer_group_is_deleted(tracked_lb_group)) {
             hmap_remove(&lb_data->lbgrps, &lb_group->hmap_node);
             add_deleted_lbgrp_to_tracked_data(lb_group, trk_lb_data);
             trk_lb_data->has_routable_lb |= lb_group->has_routable_lb;
         } else {
-
-            struct ovn_lb_group *lb_group;
-            lb_group = ovn_lb_group_find(&lb_data->lbgrps,
-                                         &tracked_lb_group->header_.uuid);
-            ovs_assert(lb_group);
-
             /* Determine the lbs which are added or deleted for this
              * lb group and add them to tracked data.
              * Eg.  If an lb group lbg1 before the update had [lb1, lb2, lb3]
diff --git a/northd/en-meters.c b/northd/en-meters.c
index aabd002b6..793a46335 100644
--- a/northd/en-meters.c
+++ b/northd/en-meters.c
@@ -203,9 +203,13 @@ sync_acl_fair_meter(struct ovsdb_idl_txn *ovnsb_txn,
                     const struct nbrec_acl *acl, struct shash *sb_meters,
                     struct sset *used_sb_meters)
 {
-    const struct nbrec_meter *nb_meter =
-        fair_meter_lookup_by_name(meter_groups, acl->meter);
+    const struct nbrec_meter *nb_meter;
+
+    if (!acl->log || !acl->meter) {
+        return;
+    }
 
+    nb_meter = fair_meter_lookup_by_name(meter_groups, acl->meter);
     if (!nb_meter) {
         return;
     }
diff --git a/northd/en-sync-sb.c b/northd/en-sync-sb.c
index aae396a43..2ec3bf54f 100644
--- a/northd/en-sync-sb.c
+++ b/northd/en-sync-sb.c
@@ -220,7 +220,8 @@ en_sync_to_sb_lb_run(struct engine_node *node, void *data OVS_UNUSED)
     struct northd_data *northd_data = engine_get_input_data("northd", node);
 
     sync_lbs(eng_ctx->ovnsb_idl_txn, sb_load_balancer_table,
-             &northd_data->ls_datapaths, &northd_data->lb_datapaths_map);
+             &northd_data->ls_datapaths, &northd_data->lr_datapaths,
+             &northd_data->lb_datapaths_map, &northd_data->features);
     engine_set_node_state(node, EN_UPDATED);
 }
 
@@ -248,6 +249,23 @@ sync_to_sb_lb_northd_handler(struct engine_node *node, void *data OVS_UNUSED)
     return true;
 }
 
+bool
+sync_to_sb_lb_sb_load_balancer(struct engine_node *node, void *data OVS_UNUSED)
+{
+    const struct sbrec_load_balancer_table *sb_load_balancer_table =
+        EN_OVSDB_GET(engine_get_input("SB_load_balancer", node));
+
+    /* The only reason to handle SB.Load_Balancer updates is to detect
+     * spurious records being created in clustered databases due to
+     * lack of indexing on the SB.Load_Balancer table.  All other changes
+     * are valid and performed by northd, the only write-client for
+     * this table. */
+    if (check_sb_lb_duplicates(sb_load_balancer_table)) {
+        return false;
+    }
+    return true;
+}
+
 /* sync_to_sb_pb engine node functions.
  * This engine node syncs the SB Port Bindings (partly).
  * en_northd engine create the SB Port binding rows and
diff --git a/northd/en-sync-sb.h b/northd/en-sync-sb.h
index f08565eee..3bcbb8259 100644
--- a/northd/en-sync-sb.h
+++ b/northd/en-sync-sb.h
@@ -21,6 +21,8 @@ void *en_sync_to_sb_lb_init(struct engine_node *, struct engine_arg *);
 void en_sync_to_sb_lb_run(struct engine_node *, void *data);
 void en_sync_to_sb_lb_cleanup(void *data);
 bool sync_to_sb_lb_northd_handler(struct engine_node *, void *data OVS_UNUSED);
+bool sync_to_sb_lb_sb_load_balancer(struct engine_node *,
+                                    void *data OVS_UNUSED);
 
 void *en_sync_to_sb_pb_init(struct engine_node *, struct engine_arg *);
 void en_sync_to_sb_pb_run(struct engine_node *, void *data);
diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
index 8b0817117..04df0b06c 100644
--- a/northd/inc-proc-northd.c
+++ b/northd/inc-proc-northd.c
@@ -230,7 +230,8 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
 
     engine_add_input(&en_sync_to_sb_lb, &en_northd,
                      sync_to_sb_lb_northd_handler);
-    engine_add_input(&en_sync_to_sb_lb, &en_sb_load_balancer, NULL);
+    engine_add_input(&en_sync_to_sb_lb, &en_sb_load_balancer,
+                     sync_to_sb_lb_sb_load_balancer);
 
     engine_add_input(&en_sync_to_sb_pb, &en_northd,
                      sync_to_sb_pb_northd_handler);
diff --git a/northd/northd.c b/northd/northd.c
index 04afe62a8..b1546f7bf 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -491,6 +491,24 @@ build_chassis_features(const struct sbrec_chassis_table *sbrec_chassis_table,
             chassis_features->fdb_timestamp) {
             chassis_features->fdb_timestamp = false;
         }
+
+        bool ls_dpg_column =
+            smap_get_bool(&chassis->other_config,
+                          OVN_FEATURE_LS_DPG_COLUMN,
+                          false);
+        if (!ls_dpg_column &&
+            chassis_features->ls_dpg_column) {
+            chassis_features->ls_dpg_column = false;
+        }
+
+        bool ct_commit_nat_v2 =
+                smap_get_bool(&chassis->other_config,
+                              OVN_FEATURE_CT_COMMIT_NAT_V2,
+                              false);
+        if (!ct_commit_nat_v2 &&
+            chassis_features->ct_commit_nat_v2) {
+            chassis_features->ct_commit_nat_v2 = false;
+        }
     }
 }
 
@@ -4511,6 +4529,7 @@ struct sb_lb {
 
     const struct sbrec_load_balancer *slb;
     struct ovn_dp_group *dpg;
+    struct ovn_dp_group *lr_dpg;
     struct uuid lb_uuid;
 };
 
@@ -4533,10 +4552,13 @@ find_slb_in_sb_lbs(struct hmap *sb_lbs, const struct uuid *lb_uuid)
 void
 sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
          const struct sbrec_load_balancer_table *sbrec_load_balancer_table,
-         struct ovn_datapaths *ls_datapaths, struct hmap *lb_dps_map)
+         struct ovn_datapaths *ls_datapaths,
+         struct ovn_datapaths *lr_datapaths,
+         struct hmap *lb_dps_map,
+         struct chassis_features *chassis_features)
 {
-    struct hmap dp_groups = HMAP_INITIALIZER(&dp_groups);
-    size_t bitmap_len = ods_size(ls_datapaths);
+    struct hmap ls_dp_groups = HMAP_INITIALIZER(&ls_dp_groups);
+    struct hmap lr_dp_groups = HMAP_INITIALIZER(&lr_dp_groups);
     struct ovn_lb_datapaths *lb_dps;
     struct hmap sb_lbs = HMAP_INITIALIZER(&sb_lbs);
 
@@ -4554,7 +4576,8 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
         }
 
         /* Delete any SB load balancer entries that refer to NB load balancers
-         * that don't exist anymore or are not applied to switches anymore.
+         * that don't exist anymore or are not applied to switches/routers
+         * anymore.
          *
          * There is also a special case in which duplicate LBs might be created
          * in the SB, e.g., due to the fact that OVSDB only ensures
@@ -4562,7 +4585,8 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
          * are not indexed in any way.
          */
         lb_dps = ovn_lb_datapaths_find(lb_dps_map, &lb_uuid);
-        if (!lb_dps || !lb_dps->n_nb_ls || !hmapx_add(&existing_lbs, lb_dps)) {
+        if (!lb_dps || (!lb_dps->n_nb_ls && !lb_dps->n_nb_lr) ||
+            !hmapx_add(&existing_lbs, lb_dps)) {
             sbrec_load_balancer_delete(sbrec_lb);
             continue;
         }
@@ -4573,12 +4597,26 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
         hmap_insert(&sb_lbs, &sb_lb->hmap_node, uuid_hash(&lb_uuid));
 
         /* Find or create datapath group for this load balancer. */
-        sb_lb->dpg = ovn_dp_group_get_or_create(ovnsb_txn, &dp_groups,
-                                                sb_lb->slb->datapath_group,
-                                                lb_dps->n_nb_ls,
-                                                lb_dps->nb_ls_map,
-                                                bitmap_len, true,
-                                                ls_datapaths, NULL);
+        if (lb_dps->n_nb_ls) {
+            struct sbrec_logical_dp_group *ls_datapath_group
+                = chassis_features->ls_dpg_column
+                    ? sb_lb->slb->ls_datapath_group
+                    : sb_lb->slb->datapath_group; /* deprecated */
+            sb_lb->dpg = ovn_dp_group_get_or_create(
+                    ovnsb_txn, &ls_dp_groups,
+                    ls_datapath_group,
+                    lb_dps->n_nb_ls, lb_dps->nb_ls_map,
+                    ods_size(ls_datapaths), true,
+                    ls_datapaths, NULL);
+        }
+        if (lb_dps->n_nb_lr) {
+            sb_lb->lr_dpg = ovn_dp_group_get_or_create(
+                    ovnsb_txn, &lr_dp_groups,
+                    sb_lb->slb->lr_datapath_group,
+                    lb_dps->n_nb_lr, lb_dps->nb_lr_map,
+                    ods_size(lr_datapaths), false,
+                    NULL, lr_datapaths);
+        }
     }
     hmapx_destroy(&existing_lbs);
 
@@ -4586,7 +4624,7 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
      * the SB load balancer columns. */
     HMAP_FOR_EACH (lb_dps, hmap_node, lb_dps_map) {
 
-        if (!lb_dps->n_nb_ls) {
+        if (!lb_dps->n_nb_ls && !lb_dps->n_nb_lr) {
             continue;
         }
 
@@ -4599,8 +4637,8 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
 
         struct sb_lb *sb_lb = find_slb_in_sb_lbs(&sb_lbs,
                                             &lb_dps->lb->nlb->header_.uuid);
-        ovs_assert(!sb_lb || (sb_lb->slb && sb_lb->dpg));
-        struct ovn_dp_group *lb_dpg = NULL;
+        ovs_assert(!sb_lb || (sb_lb->slb && (sb_lb->dpg || sb_lb->lr_dpg)));
+        struct ovn_dp_group *lb_dpg = NULL, *lb_lr_dpg = NULL;
         if (!sb_lb) {
             sbrec_lb = sbrec_load_balancer_insert(ovnsb_txn);
             char *lb_id = xasprintf(
@@ -4612,15 +4650,29 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
         } else {
             sbrec_lb = sb_lb->slb;
             lb_dpg = sb_lb->dpg;
+            lb_lr_dpg = sb_lb->lr_dpg;
         }
 
         /* Find or create datapath group for this load balancer. */
-        if (!lb_dpg) {
-            lb_dpg = ovn_dp_group_get_or_create(ovnsb_txn, &dp_groups,
-                                                sbrec_lb->datapath_group,
-                                                lb_dps->n_nb_ls,
-                                                lb_dps->nb_ls_map, bitmap_len,
-                                                true, ls_datapaths, NULL);
+        if (!lb_dpg && lb_dps->n_nb_ls) {
+            struct sbrec_logical_dp_group *ls_datapath_group
+                = chassis_features->ls_dpg_column
+                    ? sbrec_lb->ls_datapath_group
+                    : sbrec_lb->datapath_group; /* deprecated */
+            lb_dpg = ovn_dp_group_get_or_create(
+                    ovnsb_txn, &ls_dp_groups,
+                    ls_datapath_group,
+                    lb_dps->n_nb_ls, lb_dps->nb_ls_map,
+                    ods_size(ls_datapaths), true,
+                    ls_datapaths, NULL);
+        }
+        if (!lb_lr_dpg && lb_dps->n_nb_lr) {
+            lb_lr_dpg = ovn_dp_group_get_or_create(
+                    ovnsb_txn, &lr_dp_groups,
+                    sbrec_lb->lr_datapath_group,
+                    lb_dps->n_nb_lr, lb_dps->nb_lr_map,
+                    ods_size(lr_datapaths), false,
+                    NULL, lr_datapaths);
         }
 
         /* Update columns. */
@@ -4628,7 +4680,23 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
         sbrec_load_balancer_set_vips(sbrec_lb,
                                      ovn_northd_lb_get_vips(lb_dps->lb));
         sbrec_load_balancer_set_protocol(sbrec_lb, lb_dps->lb->nlb->protocol);
-        sbrec_load_balancer_set_datapath_group(sbrec_lb, lb_dpg->dp_group);
+
+        if (chassis_features->ls_dpg_column) {
+            sbrec_load_balancer_set_ls_datapath_group(
+                sbrec_lb, lb_dpg ? lb_dpg->dp_group : NULL
+            );
+            sbrec_load_balancer_set_datapath_group(sbrec_lb, NULL);
+        } else {
+            /* datapath_group column is deprecated. */
+            sbrec_load_balancer_set_ls_datapath_group(sbrec_lb, NULL);
+            sbrec_load_balancer_set_datapath_group(
+                sbrec_lb, lb_dpg ? lb_dpg->dp_group : NULL
+            );
+        }
+
+        sbrec_load_balancer_set_lr_datapath_group(
+            sbrec_lb, lb_lr_dpg ? lb_lr_dpg->dp_group : NULL
+        );
         sbrec_load_balancer_set_options(sbrec_lb, &options);
         /* Clearing 'datapaths' column, since 'dp_group' is in use. */
         sbrec_load_balancer_set_datapaths(sbrec_lb, NULL, 0);
@@ -4636,11 +4704,17 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
     }
 
     struct ovn_dp_group *dpg;
-    HMAP_FOR_EACH_POP (dpg, node, &dp_groups) {
+    HMAP_FOR_EACH_POP (dpg, node, &ls_dp_groups) {
         bitmap_free(dpg->bitmap);
         free(dpg);
     }
-    hmap_destroy(&dp_groups);
+    hmap_destroy(&ls_dp_groups);
+
+    HMAP_FOR_EACH_POP (dpg, node, &lr_dp_groups) {
+        bitmap_free(dpg->bitmap);
+        free(dpg);
+    }
+    hmap_destroy(&lr_dp_groups);
 
     struct sb_lb *sb_lb;
     HMAP_FOR_EACH_POP (sb_lb, hmap_node, &sb_lbs) {
@@ -4655,12 +4729,39 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
     HMAP_FOR_EACH (od, key_node, &ls_datapaths->datapaths) {
         ovs_assert(od->nbs);
 
+        if (od->sb->n_load_balancers) {
+            sbrec_datapath_binding_set_load_balancers(od->sb, NULL, 0);
+        }
+    }
+    HMAP_FOR_EACH (od, key_node, &lr_datapaths->datapaths) {
+        ovs_assert(od->nbr);
+
         if (od->sb->n_load_balancers) {
             sbrec_datapath_binding_set_load_balancers(od->sb, NULL, 0);
         }
     }
 }
 
+bool
+check_sb_lb_duplicates(const struct sbrec_load_balancer_table *table)
+{
+    struct sset existing_nb_lb_uuids =
+        SSET_INITIALIZER(&existing_nb_lb_uuids);
+    const struct sbrec_load_balancer *sbrec_lb;
+    bool duplicates = false;
+
+    SBREC_LOAD_BALANCER_TABLE_FOR_EACH (sbrec_lb, table) {
+        const char *nb_lb_uuid = smap_get(&sbrec_lb->external_ids, "lb_id");
+        if (nb_lb_uuid && !sset_add(&existing_nb_lb_uuids, nb_lb_uuid)) {
+            duplicates = true;
+            break;
+        }
+    }
+
+    sset_destroy(&existing_nb_lb_uuids);
+    return duplicates;
+}
+
 /* Syncs the SB port binding for the ovn_port 'op'.  Caller should make sure
  * that the OVN SB IDL txn is not NULL.  Presently it only syncs the nat
  * column of port binding corresponding to the 'op->nbsp' */
@@ -5089,23 +5190,21 @@ lsp_can_be_inc_processed(const struct nbrec_logical_switch_port *nbsp)
 }
 
 static bool
-ls_port_has_changed(const struct nbrec_logical_switch_port *old,
-                    const struct nbrec_logical_switch_port *new)
+ls_port_has_changed(const struct nbrec_logical_switch_port *new)
 {
-    if (old != new) {
-        return true;
-    }
     /* XXX: Need a better OVSDB IDL interface for this check. */
     return (nbrec_logical_switch_port_row_get_seqno(new,
                                 OVSDB_IDL_CHANGE_MODIFY) > 0);
 }
 
 static struct ovn_port *
-ovn_port_find_in_datapath(struct ovn_datapath *od, const char *name)
+ovn_port_find_in_datapath(struct ovn_datapath *od,
+                          const struct nbrec_logical_switch_port *nbsp)
 {
     struct ovn_port *op;
-    HMAP_FOR_EACH_WITH_HASH (op, dp_node, hash_string(name, 0), &od->ports) {
-        if (!strcmp(op->key, name)) {
+    HMAP_FOR_EACH_WITH_HASH (op, dp_node, hash_string(nbsp->name, 0),
+                             &od->ports) {
+        if (!strcmp(op->key, nbsp->name) && op->nbsp == nbsp) {
             return op;
         }
     }
@@ -5290,7 +5389,7 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
     /* Compare the individual ports in the old and new Logical Switches */
     for (size_t j = 0; j < changed_ls->n_ports; ++j) {
         struct nbrec_logical_switch_port *new_nbsp = changed_ls->ports[j];
-        op = ovn_port_find_in_datapath(od, new_nbsp->name);
+        op = ovn_port_find_in_datapath(od, new_nbsp);
 
         if (!op) {
             if (!lsp_can_be_inc_processed(new_nbsp)) {
@@ -5307,7 +5406,7 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
             }
             ovs_list_push_back(&ls_change->added_ports,
                                 &op->list);
-        } else if (ls_port_has_changed(op->nbsp, new_nbsp)) {
+        } else if (ls_port_has_changed(new_nbsp)) {
             /* Existing port updated */
             bool temp = false;
             if (lsp_is_type_changed(op->sb, new_nbsp, &temp) ||
@@ -5580,9 +5679,9 @@ northd_handle_sb_port_binding_changes(
              * notification of that transaction, and we can ignore in this
              * case. Fallback to recompute otherwise, to avoid dangling
              * sb idl pointers and other unexpected behavior. */
-            if (op) {
-                VLOG_WARN_RL(&rl, "A port-binding for %s is deleted but the "
-                            "LSP still exists.", pb->logical_port);
+            if (op && op->sb == pb) {
+                VLOG_WARN_RL(&rl, "A port-binding for %s is deleted but "
+                            "the LSP still exists.", pb->logical_port);
                 return false;
             }
         } else {
@@ -6954,6 +7053,9 @@ build_lswitch_learn_fdb_op(
         ds_clear(match);
         ds_clear(actions);
         ds_put_format(match, "inport == %s", op->json_key);
+        if (lsp_is_localnet(op->nbsp)) {
+            ds_put_cstr(actions, "flags.localnet = 1; ");
+        }
         ds_put_format(actions, REGBIT_LKUP_FDB
                       " = lookup_fdb(inport, eth.src); next;");
         ovn_lflow_add_with_lport_and_hint(lflows, op->od,
@@ -8291,12 +8393,12 @@ build_lb_rules_pre_stateful(struct hmap *lflows,
  *
  * - load balancing:
  *   table=lr_in_dnat, priority=150
- *      match=(REGBIT_KNOWN_LB_SESSION == 1 && ct.new && ip4
+ *      match=(REGBIT_KNOWN_LB_SESSION == 1 && ct.new && ip4.dst == V
  *             && REG_LB_AFF_BACKEND_IP4 == B1 && REG_LB_AFF_MATCH_PORT == BP1)
  *      action=(REG_NEXT_HOP_IPV4 = V; lb_action;
  *              ct_lb_mark(backends=B1:BP1; ct_flag);)
  *   table=lr_in_dnat, priority=150
- *      match=(REGBIT_KNOWN_LB_SESSION == 1 && ct.new && ip4
+ *      match=(REGBIT_KNOWN_LB_SESSION == 1 && ct.new && ip4.dst == V
  *             && REG_LB_AFF_BACKEND_IP4 == B2 && REG_LB_AFF_MATCH_PORT == BP2)
  *      action=(REG_NEXT_HOP_IPV4 = V; lb_action;
  *              ct_lb_mark(backends=B2:BP2; ct_flag);)
@@ -8395,7 +8497,8 @@ build_lb_affinity_lr_flows(struct hmap *lflows, const struct ovn_northd_lb *lb,
 
     /* Prepare common part of affinity match. */
     ds_put_format(&aff_match, REGBIT_KNOWN_LB_SESSION" == 1 && "
-                  "ct.new && %s && %s == ", ip_match, reg_backend);
+                  "ct.new && %s.dst == %s && %s == ", ip_match,
+                  lb_vip->vip_str, reg_backend);
 
     /* Store the common part length. */
     size_t aff_action_len = aff_action.length;
@@ -8474,13 +8577,13 @@ build_lb_affinity_lr_flows(struct hmap *lflows, const struct ovn_northd_lb *lb,
  *
  * - load balancing:
  *   table=ls_in_lb, priority=150
- *      match=(REGBIT_KNOWN_LB_SESSION == 1 && ct.new && ip4
+ *      match=(REGBIT_KNOWN_LB_SESSION == 1 && ct.new && ip4.dst == V
  *             && REG_LB_AFF_BACKEND_IP4 == B1 && REG_LB_AFF_MATCH_PORT == BP1)
  *      action=(REGBIT_CONNTRACK_COMMIT = 0;
  *              REG_ORIG_DIP_IPV4 = V; REG_ORIG_TP_DPORT = VP;
  *              ct_lb_mark(backends=B1:BP1);)
  *   table=ls_in_lb, priority=150
- *      match=(REGBIT_KNOWN_LB_SESSION == 1 && ct.new && ip4
+ *      match=(REGBIT_KNOWN_LB_SESSION == 1 && ct.new && ip4.dst == V
  *             && REG_LB_AFF_BACKEND_IP4 == B2 && REG_LB_AFF_MATCH_PORT == BP2)
  *      action=(REGBIT_CONNTRACK_COMMIT = 0;
  *              REG_ORIG_DIP_IPV4 = V;
@@ -8583,7 +8686,8 @@ build_lb_affinity_ls_flows(struct hmap *lflows,
 
     /* Prepare common part of affinity match. */
     ds_put_format(&aff_match, REGBIT_KNOWN_LB_SESSION" == 1 && "
-                  "ct.new && %s && %s == ", ip_match, reg_backend);
+                  "ct.new && %s.dst == %s && %s == ", ip_match,
+                  lb_vip->vip_str, reg_backend);
 
     /* Store the common part length. */
     size_t aff_action_len = aff_action.length;
@@ -9307,6 +9411,37 @@ build_lswitch_rport_arp_req_flows(struct ovn_port *op,
         }
     }
 
+    struct shash_node *snat_snode;
+    SHASH_FOR_EACH (snat_snode, &op->od->snat_ips) {
+        struct ovn_snat_ip *snat_ip = snat_snode->data;
+
+        if (ovs_list_is_empty(&snat_ip->snat_entries)) {
+            continue;
+        }
+
+        struct ovn_nat *nat_entry =
+            CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries),
+                         struct ovn_nat, ext_addr_list_node);
+        const struct nbrec_nat *nat = nat_entry->nb;
+
+        /* Check if the ovn port has a network configured on which we could
+         * expect ARP requests/NS for the SNAT external_ip.
+         */
+        if (nat_entry_is_v6(nat_entry)) {
+            if (!sset_contains(&op->od->lb_ips->ips_v6, nat->external_ip)) {
+                build_lswitch_rport_arp_req_flow(
+                    nat->external_ip, AF_INET6, sw_op, sw_od, 80, lflows,
+                    stage_hint);
+            }
+        } else {
+            if (!sset_contains(&op->od->lb_ips->ips_v4, nat->external_ip)) {
+                build_lswitch_rport_arp_req_flow(
+                    nat->external_ip, AF_INET, sw_op, sw_od, 80, lflows,
+                    stage_hint);
+            }
+        }
+    }
+
     for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
         build_lswitch_rport_arp_req_flow(
             op->lrp_networks.ipv4_addrs[i].addr_s, AF_INET, sw_op, sw_od, 80,
@@ -9651,6 +9786,13 @@ build_lswitch_lflows_admission_control(struct ovn_datapath *od,
                                        struct hmap *lflows)
 {
     ovs_assert(od->nbs);
+
+    /* Default action for recirculated ICMP error 'packet too big'. */
+    ovn_lflow_add(lflows, od, S_SWITCH_IN_CHECK_PORT_SEC, 110,
+                  "((ip4 && icmp4.type == 3 && icmp4.code == 4) ||"
+                  " (ip6 && icmp6.type == 2 && icmp6.code == 0)) &&"
+                  " flags.tunnel_rx == 1", debug_drop_action());
+
     /* Logical VLANs not supported. */
     if (!is_vlan_transparent(od)) {
         /* Block logical VLANs. */
@@ -11385,7 +11527,6 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows,
                                struct ds *route_match)
 {
     const struct nbrec_logical_router_static_route *st_route = route->route;
-    struct ds base_match = DS_EMPTY_INITIALIZER;
     struct ds match = DS_EMPTY_INITIALIZER;
     struct ds actions = DS_EMPTY_INITIALIZER;
     struct ds ecmp_reply = DS_EMPTY_INITIALIZER;
@@ -11397,14 +11538,14 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows,
     /* If symmetric ECMP replies are enabled, then packets that arrive over
      * an ECMP route need to go through conntrack.
      */
-    ds_put_format(&base_match, "inport == %s && ip%s.%s == %s",
+    ds_put_format(&match, "inport == %s && ip%s.%s == %s",
                   out_port->json_key,
                   IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "4" : "6",
                   route->is_src_route ? "dst" : "src",
                   cidr);
     free(cidr);
     ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG, 100,
-                             ds_cstr(&base_match), "ct_next;",
+                             ds_cstr(&match), "ct_next;",
                              &st_route->header_);
 
     /* And packets that go out over an ECMP route need conntrack */
@@ -11418,73 +11559,7 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows,
      * NOTE: we purposely are not clearing match before this
      * ds_put_cstr() call. The previous contents are needed.
      */
-    ds_put_format(&match, "%s && (ct.new && !ct.est) && tcp",
-                  ds_cstr(&base_match));
-    ds_put_format(&actions,
-            "ct_commit { ct_label.ecmp_reply_eth = eth.src; "
-            " %s = %" PRId64 ";}; "
-            "next;",
-            ct_ecmp_reply_port_match, out_port->sb->tunnel_key);
-    ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100,
-                            ds_cstr(&match), ds_cstr(&actions),
-                            &st_route->header_);
-    ds_clear(&match);
-    ds_put_format(&match, "%s && (ct.new && !ct.est) && udp",
-                  ds_cstr(&base_match));
-    ds_clear(&actions);
-    ds_put_format(&actions,
-            "ct_commit { ct_label.ecmp_reply_eth = eth.src; "
-            " %s = %" PRId64 ";}; "
-            "next;",
-            ct_ecmp_reply_port_match, out_port->sb->tunnel_key);
-    ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100,
-                            ds_cstr(&match), ds_cstr(&actions),
-                            &st_route->header_);
-    ds_clear(&match);
-    ds_put_format(&match, "%s && (ct.new && !ct.est) && sctp",
-                  ds_cstr(&base_match));
-    ds_clear(&actions);
-    ds_put_format(&actions,
-            "ct_commit { ct_label.ecmp_reply_eth = eth.src; "
-            " %s = %" PRId64 ";}; "
-            "next;",
-            ct_ecmp_reply_port_match, out_port->sb->tunnel_key);
-    ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100,
-                            ds_cstr(&match), ds_cstr(&actions),
-                            &st_route->header_);
-
-    ds_clear(&match);
-    ds_put_format(&match,
-            "%s && (!ct.rpl && ct.est) && tcp",
-            ds_cstr(&base_match));
-    ds_clear(&actions);
-    ds_put_format(&actions,
-            "ct_commit { ct_label.ecmp_reply_eth = eth.src; "
-            " %s = %" PRId64 ";}; "
-            "next;",
-            ct_ecmp_reply_port_match, out_port->sb->tunnel_key);
-    ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100,
-                            ds_cstr(&match), ds_cstr(&actions),
-                            &st_route->header_);
-
-    ds_clear(&match);
-    ds_put_format(&match,
-            "%s && (!ct.rpl && ct.est) && udp",
-            ds_cstr(&base_match));
-    ds_clear(&actions);
-    ds_put_format(&actions,
-            "ct_commit { ct_label.ecmp_reply_eth = eth.src; "
-            " %s = %" PRId64 ";}; "
-            "next;",
-            ct_ecmp_reply_port_match, out_port->sb->tunnel_key);
-    ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100,
-                            ds_cstr(&match), ds_cstr(&actions),
-                            &st_route->header_);
-    ds_clear(&match);
-    ds_put_format(&match,
-            "%s && (!ct.rpl && ct.est) && sctp",
-            ds_cstr(&base_match));
-    ds_clear(&actions);
+    ds_put_cstr(&match, " && !ct.rpl && (ct.new || ct.est)");
     ds_put_format(&actions,
             "ct_commit { ct_label.ecmp_reply_eth = eth.src; "
             " %s = %" PRId64 ";}; "
@@ -11534,7 +11609,6 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows,
                             200, ds_cstr(&ecmp_reply),
                             action, &st_route->header_);
 
-    ds_destroy(&base_match);
     ds_destroy(&match);
     ds_destroy(&actions);
     ds_destroy(&ecmp_reply);
@@ -11950,7 +12024,6 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
     struct ds skip_snat_act = DS_EMPTY_INITIALIZER;
     struct ds force_snat_act = DS_EMPTY_INITIALIZER;
     struct ds undnat_match = DS_EMPTY_INITIALIZER;
-    struct ds unsnat_match = DS_EMPTY_INITIALIZER;
     struct ds gw_redir_action = DS_EMPTY_INITIALIZER;
 
     ds_clear(match);
@@ -11996,13 +12069,6 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
     /* Remove the trailing " || ". */
     ds_truncate(&undnat_match, undnat_match.length - 4);
 
-    ds_put_format(&unsnat_match, "%s && %s.dst == %s && %s",
-                  ip_match, ip_match, lb_vip->vip_str, lb->proto);
-    if (lb_vip->port_str) {
-        ds_put_format(&unsnat_match, " && %s.dst == %s", lb->proto,
-                      lb_vip->port_str);
-    }
-
     struct lrouter_nat_lb_flows_ctx ctx = {
         .lb_vip = lb_vip,
         .lb = lb,
@@ -12054,23 +12120,6 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
         if (lb->affinity_timeout) {
             bitmap_set1(aff_dp_bitmap[type], index);
         }
-
-        if (sset_contains(&od->external_ips, lb_vip->vip_str)) {
-            /* The load balancer vip is also present in the NAT entries.
-             * So add a high priority lflow to advance the the packet
-             * destined to the vip (and the vip port if defined)
-             * in the S_ROUTER_IN_UNSNAT stage.
-             * There seems to be an issue with ovs-vswitchd. When the new
-             * connection packet destined for the lb vip is received,
-             * it is dnat'ed in the S_ROUTER_IN_DNAT stage in the dnat
-             * conntrack zone. For the next packet, if it goes through
-             * unsnat stage, the conntrack flags are not set properly, and
-             * it doesn't hit the established state flows in
-             * S_ROUTER_IN_DNAT stage. */
-            ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, 120,
-                                    ds_cstr(&unsnat_match), "next;",
-                                    &lb->nlb->header_);
-        }
     }
 
     for (size_t type = 0; type < LROUTER_NAT_LB_FLOW_MAX; type++) {
@@ -12081,7 +12130,6 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
                                    lr_datapaths);
     }
 
-    ds_destroy(&unsnat_match);
     ds_destroy(&undnat_match);
     ds_destroy(&skip_snat_act);
     ds_destroy(&force_snat_act);
@@ -12690,6 +12738,72 @@ build_lrouter_force_snat_flows(struct hmap *lflows, struct ovn_datapath *od,
     ds_destroy(&actions);
 }
 
+/* Following flows are used to manage traffic redirected by the kernel
+ * (e.g. ICMP errors packets) that enter the cluster from the geneve ports
+ */
+static void
+build_lrouter_icmp_packet_toobig_admin_flows(
+        struct ovn_port *op, struct hmap *lflows,
+        struct ds *match, struct ds *actions)
+{
+    ovs_assert(op->nbrp);
+
+    if (!is_l3dgw_port(op)) {
+        return;
+    }
+
+    ds_clear(match);
+    ds_put_format(match,
+                  "((ip4 && icmp4.type == 3 && icmp4.code == 4) ||"
+                  " (ip6 && icmp6.type == 2 && icmp6.code == 0)) &&"
+                  " eth.dst == %s && !is_chassis_resident(%s) &&"
+                  " flags.tunnel_rx == 1",
+                  op->nbrp->mac, op->cr_port->json_key);
+    ds_clear(actions);
+    ds_put_format(actions, "outport <-> inport; inport = %s; next;",
+                  op->json_key);
+    ovn_lflow_add(lflows, op->od, S_ROUTER_IN_ADMISSION, 120,
+                  ds_cstr(match), ds_cstr(actions));
+}
+
+static void
+build_lswitch_icmp_packet_toobig_admin_flows(
+        struct ovn_port *op, struct hmap *lflows,
+        struct ds *match, struct ds *actions)
+{
+    ovs_assert(op->nbsp);
+
+    if (!lsp_is_router(op->nbsp)) {
+        return;
+    }
+
+    struct ovn_port *peer = op->peer;
+    if (!peer) {
+        return;
+    }
+
+    ds_clear(match);
+    if (peer->od->is_gw_router) {
+        ds_put_format(match,
+                      "((ip4 && icmp4.type == 3 && icmp4.code == 4) ||"
+                      " (ip6 && icmp6.type == 2 && icmp6.code == 0)) && "
+                      "eth.src == %s && outport == %s && flags.tunnel_rx == 1",
+                      peer->nbrp->mac, op->json_key);
+    } else {
+        ds_put_format(match,
+                      "((ip4 && icmp4.type == 3 && icmp4.code == 4) ||"
+                      " (ip6 && icmp6.type == 2 && icmp6.code == 0)) && "
+                      "eth.dst == %s && flags.tunnel_rx == 1",
+                      peer->nbrp->mac);
+    }
+    ds_clear(actions);
+    ds_put_format(actions,
+                  "outport <-> inport; next(pipeline=ingress,table=%d);",
+                  ovn_stage_get_table(S_SWITCH_IN_L2_LKUP));
+    ovn_lflow_add(lflows, op->od, S_SWITCH_IN_CHECK_PORT_SEC, 120,
+                  ds_cstr(match), ds_cstr(actions));
+}
+
 static void
 build_lrouter_force_snat_flows_op(struct ovn_port *op,
                                   struct hmap *lflows,
@@ -12822,6 +12936,13 @@ build_adm_ctrl_flows_for_lrouter(
         struct ovn_datapath *od, struct hmap *lflows)
 {
     ovs_assert(od->nbr);
+
+    /* Default action for recirculated ICMP error 'packet too big'. */
+    ovn_lflow_add(lflows, od, S_ROUTER_IN_ADMISSION, 110,
+                  "((ip4 && icmp4.type == 3 && icmp4.code == 4) ||"
+                  " (ip6 && icmp6.type == 2 && icmp6.code == 0)) &&"
+                  " flags.tunnel_rx == 1", debug_drop_action());
+
     /* Logical VLANs not supported.
      * Broadcast/multicast source address is invalid. */
     ovn_lflow_add(lflows, od, S_ROUTER_IN_ADMISSION, 100,
@@ -13047,9 +13168,9 @@ build_neigh_learning_flows_for_lrouter(
          *   address, the all-nodes multicast address. */
         ds_clear(actions);
         ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
-                               " = lookup_nd(inport, ip6.src, nd.tll); "
+                               " = lookup_nd(inport, nd.target, nd.tll); "
                                REGBIT_LOOKUP_NEIGHBOR_IP_RESULT
-                               " = lookup_nd_ip(inport, ip6.src); next;");
+                               " = lookup_nd_ip(inport, nd.target); next;");
         ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 110,
                       "nd_na && ip6.src == fe80::/10 && ip6.dst == ff00::/8",
                       ds_cstr(actions));
@@ -13885,82 +14006,103 @@ build_arp_resolve_flows_for_lsp(
     }
 }
 
+#define ICMP4_NEED_FRAG_FORMAT                           \
+    "icmp4_error {"                                      \
+    "%s"                                                 \
+    REGBIT_EGRESS_LOOPBACK" = 1; "                       \
+    REGBIT_PKT_LARGER" = 0; "                            \
+    "eth.dst = %s; "                                     \
+    "ip4.dst = ip4.src; "                                \
+    "ip4.src = %s; "                                     \
+    "ip.ttl = 255; "                                     \
+    "icmp4.type = 3; /* Destination Unreachable. */ "    \
+    "icmp4.code = 4; /* Frag Needed and DF was Set. */ " \
+    "icmp4.frag_mtu = %d; "                              \
+    "next(pipeline=ingress, table=%d); };"               \
+
+#define ICMP6_NEED_FRAG_FORMAT               \
+    "icmp6_error {"                          \
+    "%s"                                     \
+    REGBIT_EGRESS_LOOPBACK" = 1; "           \
+    REGBIT_PKT_LARGER" = 0; "                \
+    "eth.dst = %s; "                         \
+    "ip6.dst = ip6.src; "                    \
+    "ip6.src = %s; "                         \
+    "ip.ttl = 255; "                         \
+    "icmp6.type = 2; /* Packet Too Big. */ " \
+    "icmp6.code = 0; "                       \
+    "icmp6.frag_mtu = %d; "                  \
+    "next(pipeline=ingress, table=%d); };"
+
+static void
+create_icmp_need_frag_lflow(const struct ovn_port *op, int mtu,
+                            struct ds *actions, struct ds *match,
+                            const char *meter, struct hmap *lflows,
+                            enum ovn_stage stage, uint16_t priority,
+                            bool is_ipv6, const char *extra_match,
+                            const char *extra_action)
+{
+    if ((is_ipv6 && !op->lrp_networks.ipv6_addrs) ||
+        (!is_ipv6 && !op->lrp_networks.ipv4_addrs)) {
+        return;
+    }
+
+    const char *ip = is_ipv6
+                     ? op->lrp_networks.ipv6_addrs[0].addr_s
+                     : op->lrp_networks.ipv4_addrs[0].addr_s;
+    size_t match_len = match->length;
+
+    ds_put_format(match, " && ip%c && "REGBIT_PKT_LARGER
+                  " && "REGBIT_EGRESS_LOOPBACK" == 0", is_ipv6 ? '6' : '4');
+
+    if (*extra_match) {
+        ds_put_format(match, " && %s", extra_match);
+    }
+
+    ds_clear(actions);
+    ds_put_format(actions,
+                  is_ipv6 ? ICMP6_NEED_FRAG_FORMAT : ICMP4_NEED_FRAG_FORMAT,
+                  extra_action, op->lrp_networks.ea_s, ip,
+                  mtu, ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
+
+    ovn_lflow_add_with_hint__(lflows, op->od, stage, priority,
+                              ds_cstr(match), ds_cstr(actions),
+                              NULL, meter, &op->nbrp->header_);
+
+    ds_truncate(match, match_len);
+}
+
 static void
 build_icmperr_pkt_big_flows(struct ovn_port *op, int mtu, struct hmap *lflows,
                             const struct shash *meter_groups, struct ds *match,
                             struct ds *actions, enum ovn_stage stage,
                             struct ovn_port *outport)
 {
-    char *outport_match = outport ? xasprintf("outport == %s && ",
-                                              outport->json_key)
-                                  : NULL;
-
-    if (op->lrp_networks.ipv4_addrs) {
-        ds_clear(match);
-        ds_put_format(match, "inport == %s && %sip4 && "REGBIT_PKT_LARGER
-                      " && "REGBIT_EGRESS_LOOPBACK" == 0", op->json_key,
-                      outport ? outport_match : "");
+    const char *ipv4_meter = copp_meter_get(COPP_ICMP4_ERR, op->od->nbr->copp,
+                                            meter_groups);
+    const char *ipv6_meter = copp_meter_get(COPP_ICMP6_ERR, op->od->nbr->copp,
+                                            meter_groups);
 
-        ds_clear(actions);
-        /* Set icmp4.frag_mtu to gw_mtu */
-        ds_put_format(actions,
-            "icmp4_error {"
-            REGBIT_EGRESS_LOOPBACK" = 1; "
-            REGBIT_PKT_LARGER" = 0; "
-            "eth.dst = %s; "
-            "ip4.dst = ip4.src; "
-            "ip4.src = %s; "
-            "ip.ttl = 255; "
-            "icmp4.type = 3; /* Destination Unreachable. */ "
-            "icmp4.code = 4; /* Frag Needed and DF was Set. */ "
-            "icmp4.frag_mtu = %d; "
-            "next(pipeline=ingress, table=%d); };",
-            op->lrp_networks.ea_s,
-            op->lrp_networks.ipv4_addrs[0].addr_s,
-            mtu, ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
-        ovn_lflow_add_with_hint__(lflows, op->od, stage, 150,
-                                  ds_cstr(match), ds_cstr(actions),
-                                  NULL,
-                                  copp_meter_get(
-                                        COPP_ICMP4_ERR,
-                                        op->od->nbr->copp,
-                                        meter_groups),
-                                  &op->nbrp->header_);
-    }
+    ds_clear(match);
+    ds_put_format(match, "inport == %s", op->json_key);
 
-    if (op->lrp_networks.ipv6_addrs) {
-        ds_clear(match);
-        ds_put_format(match, "inport == %s && %sip6 && "REGBIT_PKT_LARGER
-                      " && "REGBIT_EGRESS_LOOPBACK" == 0", op->json_key,
-                      outport ? outport_match : "");
+    if (outport) {
+        ds_put_format(match, " && outport == %s", outport->json_key);
 
-        ds_clear(actions);
-        /* Set icmp6.frag_mtu to gw_mtu */
-        ds_put_format(actions,
-            "icmp6_error {"
-            REGBIT_EGRESS_LOOPBACK" = 1; "
-            REGBIT_PKT_LARGER" = 0; "
-            "eth.dst = %s; "
-            "ip6.dst = ip6.src; "
-            "ip6.src = %s; "
-            "ip.ttl = 255; "
-            "icmp6.type = 2; /* Packet Too Big. */ "
-            "icmp6.code = 0; "
-            "icmp6.frag_mtu = %d; "
-            "next(pipeline=ingress, table=%d); };",
-            op->lrp_networks.ea_s,
-            op->lrp_networks.ipv6_addrs[0].addr_s,
-            mtu, ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
-        ovn_lflow_add_with_hint__(lflows, op->od, stage, 150,
-                                  ds_cstr(match), ds_cstr(actions),
-                                  NULL,
-                                  copp_meter_get(
-                                        COPP_ICMP6_ERR,
-                                        op->od->nbr->copp,
-                                        meter_groups),
-                                  &op->nbrp->header_);
+        create_icmp_need_frag_lflow(op, mtu, actions, match, ipv4_meter,
+                                    lflows, stage, 160, false,
+                                    "ct.trk && ct.rpl && ct.dnat",
+                                    "flags.icmp_snat = 1; ");
+        create_icmp_need_frag_lflow(op, mtu, actions, match, ipv6_meter,
+                                    lflows, stage, 160, true,
+                                    "ct.trk && ct.rpl && ct.dnat",
+                                    "flags.icmp_snat = 1; ");
     }
-    free(outport_match);
+
+    create_icmp_need_frag_lflow(op, mtu, actions, match, ipv4_meter, lflows,
+                                stage, 150, false, "", "");
+    create_icmp_need_frag_lflow(op, mtu, actions, match, ipv6_meter, lflows,
+                                stage, 150, true, "", "");
 }
 
 static void
@@ -13968,8 +14110,8 @@ build_check_pkt_len_flows_for_lrp(struct ovn_port *op,
                                   struct hmap *lflows,
                                   const struct hmap *lr_ports,
                                   const struct shash *meter_groups,
-                                  struct ds *match,
-                                  struct ds *actions)
+                                  struct ds *match, struct ds *actions,
+                                  const struct chassis_features *features)
 {
     int gw_mtu = smap_get_int(&op->nbrp->options, "gateway_mtu", 0);
     if (gw_mtu <= 0) {
@@ -13998,6 +14140,12 @@ build_check_pkt_len_flows_for_lrp(struct ovn_port *op,
                                     match, actions, S_ROUTER_IN_LARGER_PKTS,
                                     op);
     }
+
+    if (features->ct_commit_nat_v2) {
+        ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_OUT_POST_SNAT, 100,
+                                "icmp && flags.icmp_snat == 1",
+                                "ct_commit_nat(snat);", &op->nbrp->header_);
+    }
 }
 
 /* Local router ingress table CHK_PKT_LEN: Check packet length.
@@ -14018,7 +14166,8 @@ build_check_pkt_len_flows_for_lrouter(
         struct ovn_datapath *od, struct hmap *lflows,
         const struct hmap *lr_ports,
         struct ds *match, struct ds *actions,
-        const struct shash *meter_groups)
+        const struct shash *meter_groups,
+        const struct chassis_features *features)
 {
     ovs_assert(od->nbr);
 
@@ -14035,7 +14184,7 @@ build_check_pkt_len_flows_for_lrouter(
             continue;
         }
         build_check_pkt_len_flows_for_lrp(rp, lflows, lr_ports, meter_groups,
-                                          match, actions);
+                                          match, actions, features);
     }
 }
 
@@ -15217,6 +15366,7 @@ build_lrouter_out_snat_in_czone_flow(struct hmap *lflows,
         ds_put_format(&zone_actions, "eth.src = "ETH_ADDR_FMT"; ",
                       ETH_ADDR_ARGS(mac));
     }
+    ds_put_format(match, " && (!ct.trk || !ct.rpl)");
 
     ds_put_cstr(&zone_actions, REGBIT_DST_NAT_IP_LOCAL" = 0; ");
 
@@ -15275,10 +15425,8 @@ build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
             ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ",
                           ETH_ADDR_ARGS(mac));
         }
-    } else {
-        /* Gateway router. */
-        ds_put_cstr(match, " && (!ct.trk || !ct.rpl)");
     }
+    ds_put_cstr(match, " && (!ct.trk || !ct.rpl)");
 
     ds_put_format(actions, "ct_snat(%s", nat->external_ip);
     if (nat->external_port_range[0]) {
@@ -15704,12 +15852,7 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
                                    l3dgw_port, stateless);
 
         /* ARP resolve for NAT IPs. */
-        if (od->is_gw_router) {
-            /* Add the NAT external_ip to the nat_entries for
-             * gateway routers. This is required for adding load balancer
-             * flows.*/
-            sset_add(&nat_entries, nat->external_ip);
-        } else {
+        if (!od->is_gw_router) {
             if (!sset_contains(&nat_entries, nat->external_ip)) {
                 /* Drop packets coming in from external that still has
                  * destination IP equals to the NAT external IP, to avoid loop.
@@ -16006,7 +16149,7 @@ build_lswitch_and_lrouter_iterate_by_lr(struct ovn_datapath *od,
     build_arp_resolve_flows_for_lrouter(od, lsi->lflows);
     build_check_pkt_len_flows_for_lrouter(od, lsi->lflows, lsi->lr_ports,
                                           &lsi->match, &lsi->actions,
-                                          lsi->meter_groups);
+                                          lsi->meter_groups, lsi->features);
     build_gateway_redirect_flows_for_lrouter(od, lsi->lflows, &lsi->match,
                                              &lsi->actions);
     build_arp_request_flows_for_lrouter(od, lsi->lflows, &lsi->match,
@@ -16044,6 +16187,7 @@ build_lswitch_and_lrouter_iterate_by_lsp(struct ovn_port *op,
     build_lswitch_dhcp_options_and_response(op, lflows, meter_groups);
     build_lswitch_external_port(op, lflows);
     build_lswitch_ip_unicast_lookup(op, lflows, actions, match);
+    build_lswitch_icmp_packet_toobig_admin_flows(op, lflows, match, actions);
 
     /* Build Logical Router Flows. */
     build_ip_routing_flows_for_router_type_lsp(op, lr_ports, lflows);
@@ -16080,6 +16224,8 @@ build_lswitch_and_lrouter_iterate_by_lrp(struct ovn_port *op,
                                 &lsi->match, &lsi->actions, lsi->meter_groups);
     build_lrouter_force_snat_flows_op(op, lsi->lflows, &lsi->match,
                                       &lsi->actions);
+    build_lrouter_icmp_packet_toobig_admin_flows(op, lsi->lflows, &lsi->match,
+                                                 &lsi->actions);
 }
 
 static void *
@@ -17655,6 +17801,8 @@ northd_init(struct northd_data *data)
         .mac_binding_timestamp = true,
         .ct_lb_related = true,
         .fdb_timestamp = true,
+        .ls_dpg_column = true,
+        .ct_commit_nat_v2 = true,
     };
     data->ovn_internal_version_changed = false;
     sset_init(&data->svc_monitor_lsps);
@@ -18051,13 +18199,15 @@ static void
 handle_cr_port_binding_changes(const struct sbrec_port_binding *sb,
                 struct ovn_port *orp)
 {
+    const struct nbrec_logical_router_port *nbrec_lrp = orp->l3dgw_port->nbrp;
+
     if (sb->chassis) {
-        nbrec_logical_router_port_update_status_setkey(
-            orp->l3dgw_port->nbrp, "hosting-chassis",
-            sb->chassis->name);
-    } else {
-        nbrec_logical_router_port_update_status_delkey(
-            orp->l3dgw_port->nbrp, "hosting-chassis");
+        nbrec_logical_router_port_update_status_setkey(nbrec_lrp,
+                                                       "hosting-chassis",
+                                                       sb->chassis->name);
+    } else if (smap_get(&nbrec_lrp->status, "hosting-chassis")) {
+        nbrec_logical_router_port_update_status_delkey(nbrec_lrp,
+                                                       "hosting-chassis");
     }
 }
 
diff --git a/northd/northd.h b/northd/northd.h
index 4d030b10d..5bde4bb16 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -70,6 +70,8 @@ struct chassis_features {
     bool mac_binding_timestamp;
     bool ct_lb_related;
     bool fdb_timestamp;
+    bool ls_dpg_column;
+    bool ct_commit_nat_v2;
 };
 
 /* A collection of datapaths. E.g. all logical switch datapaths, or all
@@ -365,7 +367,11 @@ const char *northd_get_svc_monitor_mac(void);
 const struct ovn_datapath *northd_get_datapath_for_port(
     const struct hmap *ls_ports, const char *port_name);
 void sync_lbs(struct ovsdb_idl_txn *, const struct sbrec_load_balancer_table *,
-              struct ovn_datapaths *ls_datapaths, struct hmap *lbs);
+              struct ovn_datapaths *ls_datapaths,
+              struct ovn_datapaths *lr_datapaths,
+              struct hmap *lbs,
+              struct chassis_features *chassis_features);
+bool check_sb_lb_duplicates(const struct sbrec_load_balancer_table *);
 
 void sync_pbs(struct ovsdb_idl_txn *, struct hmap *ls_ports);
 bool sync_pbs_for_northd_ls_changes(struct tracked_ls_changes *);
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index df8ed6750..e6970d6b3 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -341,7 +341,7 @@
       <li>
         For each (enabled) vtep logical port, a priority 70 flow is added which
         matches on all packets and applies the action
-        <code>next(pipeline=ingress, table=S_SWITCH_IN_L2_LKUP) = 1;</code>
+        <code>next(pipeline=ingress, table=S_SWITCH_IN_L3_LKUP) = 1;</code>
         to skip most stages of ingress pipeline and go directly to ingress L2
         lookup table to determine the output port. Packets from VTEP (RAMP)
         switch should not be subjected to any ACL checks. Egress pipeline will
@@ -372,6 +372,30 @@
 
     <h3>Ingress Table 1: Ingress Port Security - Apply</h3>
 
+    <p>
+      For each logical switch port <var>P</var> of type router connected to a
+      gw router a priority-120 flow that matches 'recirculated' icmp{4,6} error
+      'packet too big' and <code>eth.src == <var>D</var> &amp;&amp;
+      outport == <var>P</var> &amp;&amp; flags.tunnel_rx == 1</code> where
+      <var>D</var> is the peer logical router port <var>RP</var> mac address,
+      swaps inport and outport and applies the action <code>
+      next(pipeline=S_SWITCH_IN_L2_LKUP)</code>.
+    </p>
+
+    <p>
+      For each logical switch port <var>P</var> of type router connected to a
+      distributed router a priority-120 flow that matches 'recirculated'
+      icmp{4,6} error 'packet too big' and <code>eth.dst == <var>D</var>
+      &amp;&amp; flags.tunnel_rx == 1</code> where <var>D</var> is the peer
+      logical router port <var>RP</var> mac address, swaps inport and outport
+      and applies the action <code> next(pipeline=S_SWITCH_IN_L2_LKUP)</code>.
+    </p>
+
+    <p>
+      This table adds a priority-110 flow that matches 'recirculated' icmp{4,6}
+      error 'packet too big' to drop the packet.
+    </p>
+
     <p>
       This table drops the packets if the port security check failed
       in the previous stage i.e the register bit
@@ -399,14 +423,16 @@
     <p>
       This table looks up the MAC learning table of the logical switch
       datapath to check if the <code>port-mac</code> pair is present
-      or not. MAC is learnt only for logical switch VIF ports whose
-      port security is disabled and 'unknown' address set.
+      or not. MAC is learnt for logical switch VIF ports whose
+      port security is disabled and 'unknown' address setn as well as
+      for localnet ports with option localnet_learn_fdb. A localnet
+      port entry does not overwrite a VIF port entry.
     </p>
 
     <ul>
       <li>
         <p>
-          For each such logical port <var>p</var> whose port security
+          For each such VIF logical port <var>p</var> whose port security
           is disabled and 'unknown' address set following flow
           is added.
         </p>
@@ -420,6 +446,22 @@
         </ul>
       </li>
 
+      <li>
+        <p>
+          For each such localnet logical port <var>p</var> following flow
+          is added.
+        </p>
+
+        <ul>
+          <li>
+            Priority 100 flow with the match
+            <code>inport == <var>p</var></code> and action
+            <code>flags.localnet = 1;</code>
+            <code>reg0[11] = lookup_fdb(inport, eth.src); next;</code>
+          </li>
+        </ul>
+      </li>
+
       <li>
         One priority-0 fallback flow that matches all packets and advances to
         the next table.
@@ -429,17 +471,20 @@
     <h3>Ingress Table 3: Learn MAC of 'unknown' ports.</h3>
 
     <p>
-      This table learns the MAC addresses seen on the logical ports
-      whose port security is disabled and 'unknown' address set
+      This table learns the MAC addresses seen on the VIF logical ports
+      whose port security is disabled and 'unknown' address set as well
+      as on localnet ports with localnet_learn_fdb option set
       if the <code>lookup_fdb</code> action returned false in the
-      previous table.
+      previous table. For localnet ports (with flags.localnet = 1),
+      lookup_fdb returns true if (port, mac) is found or if a mac
+      is found for a port of type vif.
     </p>
 
     <ul>
       <li>
         <p>
-          For each such logical port <var>p</var> whose port security
-          is disabled and 'unknown' address set following flow
+          For each such VIF logical port <var>p</var> whose port security is
+          disabled and 'unknown' address set and localnet port following flow
           is added.
         </p>
 
@@ -2442,6 +2487,21 @@ output;
           (LBs, NAT).
         </p>
 
+        <p>
+          For each gateway port <var>GW</var> on a distributed logical router
+          a priority-120 flow that matches 'recirculated' icmp{4,6} error
+          'packet too big' and <code>eth.dst == <var>D</var> &amp;&amp;
+          !is_chassis_resident(<var> cr-GW</var>)</code> where <var>D</var>
+          is the gateway port mac address and <var>cr-GW</var> is the chassis
+          resident port of <var>GW</var>, swap inport and outport and stores
+          <var>GW</var> as inport.
+        </p>
+
+        <p>
+          This table adds a priority-110 flow that matches 'recirculated'
+          icmp{4,6} error 'packet too big' to drop the packet.
+        </p>
+
         <p>
           For a distributed logical router or for gateway router where
           the port is configured with <code>options:gateway_mtu</code>
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index 68fc8836e..9d9a1d720 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -47,7 +47,6 @@
 
 VLOG_DEFINE_THIS_MODULE(ovn_northd);
 
-static unixctl_cb_func ovn_northd_exit;
 static unixctl_cb_func ovn_northd_pause;
 static unixctl_cb_func ovn_northd_resume;
 static unixctl_cb_func ovn_northd_is_paused;
@@ -612,6 +611,14 @@ parse_options(int argc OVS_UNUSED, char *argv[] OVS_UNUSED,
             ssl_ca_cert_file = optarg;
             break;
 
+        case OPT_SSL_PROTOCOLS:
+            stream_ssl_set_protocols(optarg);
+            break;
+
+        case OPT_SSL_CIPHERS:
+            stream_ssl_set_ciphers(optarg);
+            break;
+
         case 'd':
             ovnsb_db = optarg;
             break;
@@ -752,7 +759,7 @@ main(int argc, char *argv[])
     int res = EXIT_SUCCESS;
     struct unixctl_server *unixctl;
     int retval;
-    bool exiting;
+    struct ovn_exit_args exit_args = {};
     int n_threads = 1;
     struct northd_state state = {
         .had_lock = false,
@@ -774,7 +781,8 @@ main(int argc, char *argv[])
     if (retval) {
         exit(EXIT_FAILURE);
     }
-    unixctl_command_register("exit", "", 0, 0, ovn_northd_exit, &exiting);
+    unixctl_command_register("exit", "", 0, 0, ovn_exit_command_callback,
+                             &exit_args);
     unixctl_command_register("pause", "", 0, 0, ovn_northd_pause, &state);
     unixctl_command_register("resume", "", 0, 0, ovn_northd_resume, &state);
     unixctl_command_register("is-paused", "", 0, 0, ovn_northd_is_paused,
@@ -868,6 +876,8 @@ main(int argc, char *argv[])
     stopwatch_create(LFLOWS_IGMP_STOPWATCH_NAME, SW_MS);
     stopwatch_create(LFLOWS_DP_GROUPS_STOPWATCH_NAME, SW_MS);
     stopwatch_create(LFLOWS_TO_SB_STOPWATCH_NAME, SW_MS);
+    stopwatch_create(PORT_GROUP_RUN_STOPWATCH_NAME, SW_MS);
+    stopwatch_create(SYNC_METERS_RUN_STOPWATCH_NAME, SW_MS);
 
     /* Initialize incremental processing engine for ovn-northd */
     inc_proc_northd_init(&ovnnb_idl_loop, &ovnsb_idl_loop);
@@ -878,11 +888,9 @@ main(int argc, char *argv[])
     run_update_worker_pool(n_threads);
 
     /* Main loop. */
-    exiting = false;
-
     struct northd_engine_context eng_ctx = {};
 
-    while (!exiting) {
+    while (!exit_args.exiting) {
         update_ssl_config();
         memory_run();
         if (memory_should_report()) {
@@ -1024,7 +1032,7 @@ main(int argc, char *argv[])
         unixctl_server_run(unixctl);
         unixctl_server_wait(unixctl);
         memory_wait();
-        if (exiting) {
+        if (exit_args.exiting) {
             poll_immediate_wake();
         }
 
@@ -1057,15 +1065,16 @@ main(int argc, char *argv[])
         stopwatch_stop(NORTHD_LOOP_STOPWATCH_NAME, time_msec());
         poll_block();
         if (should_service_stop()) {
-            exiting = true;
+            exit_args.exiting = true;
         }
         stopwatch_start(NORTHD_LOOP_STOPWATCH_NAME, time_msec());
     }
     inc_proc_northd_cleanup();
 
-    unixctl_server_destroy(unixctl);
     ovsdb_idl_loop_destroy(&ovnnb_idl_loop);
     ovsdb_idl_loop_destroy(&ovnsb_idl_loop);
+    ovn_exit_args_finish(&exit_args);
+    unixctl_server_destroy(unixctl);
     service_stop();
     run_update_worker_pool(0);
     ovsrcu_exit();
@@ -1073,16 +1082,6 @@ main(int argc, char *argv[])
     exit(res);
 }
 
-static void
-ovn_northd_exit(struct unixctl_conn *conn, int argc OVS_UNUSED,
-                const char *argv[] OVS_UNUSED, void *exiting_)
-{
-    bool *exiting = exiting_;
-    *exiting = true;
-
-    unixctl_command_reply(conn, NULL);
-}
-
 static void
 ovn_northd_pause(struct unixctl_conn *conn, int argc OVS_UNUSED,
                 const char *argv[] OVS_UNUSED, void *state_)
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 9131305ea..f84ad90c4 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -1101,7 +1101,7 @@
 
         <column name="options" key="ethtype">
           Optional. VLAN EtherType field value for encapsulating VLAN
-          headers. Supported values: 802.11q (default), 802.11ad.
+          headers. Supported values: 802.1q (default), 802.1ad.
         </column>
 
         <column name="options" key="localnet_learn_fdb"
diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
index 7b20aa21b..a512e00e5 100644
--- a/ovn-sb.ovsschema
+++ b/ovn-sb.ovsschema
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Southbound",
-    "version": "20.27.4",
-    "cksum": "3194181501 30531",
+    "version": "20.29.0",
+    "cksum": "3967183656 30959",
     "tables": {
         "SB_Global": {
             "columns": {
@@ -534,6 +534,14 @@
                     {"type": {"key": {"type": "uuid",
                                       "refTable": "Logical_DP_Group"},
                               "min": 0, "max": 1}},
+                "ls_datapath_group":
+                    {"type": {"key": {"type": "uuid",
+                                      "refTable": "Logical_DP_Group"},
+                              "min": 0, "max": 1}},
+                "lr_datapath_group":
+                    {"type": {"key": {"type": "uuid",
+                                      "refTable": "Logical_DP_Group"},
+                              "min": 0, "max": 1}},
                 "options": {
                      "type": {"key": "string",
                               "value": "string",
diff --git a/ovn-sb.xml b/ovn-sb.xml
index 46aedf973..3ad7409a3 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -1721,9 +1721,12 @@
 
           <p>
             Looks up <var>A</var> in fdb table. If an entry is found
-            and the the logical port key is <var>P</var>, <code>P</code>,
+            and the logical port key is <var>P</var>, <code>P</code>,
             stores <code>1</code> in the 1-bit subfield
-            <var>R</var>, else 0.
+            <var>R</var>, else 0. If <code>flags.localnet</code> is set
+            then <code>1</code> is stored if an entry is found and the
+            logical port key is <var>P</var> or if an entry is found and
+            the entry port type is VIF.
           </p>
 
           <p>
@@ -4765,8 +4768,7 @@ tcp.flags = RST;
       Each row in this table configures monitoring a service for its liveness.
       The service can be an IPv4 TCP or UDP
       service. <code>ovn-controller</code> periodically sends out service
-      monitor packets and updates the status of the service. Service monitoring
-      for IPv6 services is not supported.
+      monitor packets and updates the status of the service.
     </p>
 
     <p>
@@ -4803,7 +4805,7 @@ tcp.flags = RST;
       </column>
 
       <column name="src_ip">
-        Source IPv4 address to use in the service monitor packet.
+        Source IPv4 or IPv6 address to use in the service monitor packet.
       </column>
 
       <column name="options" key="interval" type='{"type": "integer"}'>
@@ -4888,10 +4890,22 @@ tcp.flags = RST;
     </column>
 
     <column name="datapath_group">
+      Deprecated. The group of datapaths to which this load balancer applies
+      to. This means that the same load balancer applies to all datapaths in
+      a group.
+    </column>
+
+    <column name="ls_datapath_group">
       The group of datapaths to which this load balancer applies to.  This
       means that the same load balancer applies to all datapaths in a group.
     </column>
 
+    <column name="lr_datapath_group">
+      The group of logical router datapaths to which this load balancer
+      applies to. This means that the same load balancer applies to all
+      datapaths in a group.
+    </column>
+
     <group title="Load_Balancer options">
     <column name="options" key="hairpin_snat_ip">
       IP to be used as source IP for packets that have been hair-pinned after
diff --git a/tests/automake.mk b/tests/automake.mk
index eea0d00f4..f6f0f0e33 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -310,7 +310,8 @@ CHECK_PYFILES = \
 	tests/test-l7.py \
 	tests/uuidfilt.py \
 	tests/test-tcp-rst.py \
-	tests/check_acl_log.py
+	tests/check_acl_log.py \
+	tests/scapy-server.py
 
 EXTRA_DIST += $(CHECK_PYFILES)
 PYCOV_CLEAN_FILES += $(CHECK_PYFILES:.py=.py,cover) .coverage
diff --git a/tests/checkpatch.at b/tests/checkpatch.at
index 26f319705..e7322fff4 100755
--- a/tests/checkpatch.at
+++ b/tests/checkpatch.at
@@ -8,7 +8,14 @@ OVS_START_SHELL_HELPERS
 try_checkpatch() {
     # Take the patch to test from $1.  Remove an initial four-space indent
     # from it and, if it is just headers with no body, add a null body.
+    # If it does not have a 'Subject', add a valid one.
     echo "$1" | sed 's/^    //' > test.patch
+    if grep 'Subject\:' test.patch >/dev/null 2>&1; then :
+    else
+        sed -i'' -e '1i\
+Subject: Patch this is.
+' test.patch
+    fi
     if grep '---' expout >/dev/null 2>&1; then :
     else
         printf '\n---\n' >> test.patch
@@ -236,6 +243,22 @@ done
 AT_CLEANUP
 
 
+AT_SETUP([checkpatch - catastrophic backtracking])
+dnl Special case this rather than using the above construct because sometimes a
+dnl warning needs to be generated for line lengths (f.e. when the 'while'
+dnl keyword is used).
+try_checkpatch \
+   "COMMON_PATCH_HEADER
+    +     if (!b_ctx_in->chassis_rec || !b_ctx_in->br_int || !b_ctx_in->ovs_idl_txn)
+    " \
+    "ERROR: Inappropriate bracing around statement
+    #8 FILE: A.c:1:
+         if (!b_ctx_in->chassis_rec || !b_ctx_in->br_int || !b_ctx_in->ovs_idl_txn)
+"
+
+AT_CLEANUP
+
+
 AT_SETUP([checkpatch - parenthesized constructs - for])
 try_checkpatch \
    "COMMON_PATCH_HEADER
@@ -560,3 +583,25 @@ try_checkpatch \
 "
 
 AT_CLEANUP
+
+AT_SETUP([checkpatch - subject])
+try_checkpatch \
+   "Author: A
+    Commit: A
+    Subject: netdev: invalid case and dot ending
+
+    Signed-off-by: A" \
+    "WARNING: The subject summary should start with a capital.
+    WARNING: The subject summary should end with a dot.
+    Subject: netdev: invalid case and dot ending"
+
+try_checkpatch \
+   "Author: A
+    Commit: A
+    Subject: netdev: This is a way to long commit summary and therefor it should report a WARNING!
+
+    Signed-off-by: A" \
+    "WARNING: The subject, '<area>: <summary>', is over 70 characters, i.e., 85.
+    Subject: netdev: This is a way to long commit summary and therefor it should report a WARNING!"
+
+AT_CLEANUP
diff --git a/tests/multinode.at b/tests/multinode.at
index 2b199b4bc..0187382be 100644
--- a/tests/multinode.at
+++ b/tests/multinode.at
@@ -42,7 +42,6 @@ M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.4 | F
 3 packets transmitted, 3 received, 0% packet loss, time 0ms
 ])
 
-
 # Create the second logical switch with one port
 check multinode_nbctl ls-add sw1
 check multinode_nbctl lsp-add sw1 sw1-port1
@@ -72,3 +71,815 @@ M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 20.0.0.3 | F
 ])
 
 AT_CLEANUP
+
+AT_SETUP([ovn multinode pmtu - distributed router - geneve])
+
+# Check that ovn-fake-multinode setup is up and running
+check_fake_multinode_setup
+
+# Delete the multinode NB and OVS resources before starting the test.
+cleanup_multinode_resources
+
+m_as ovn-chassis-1 ip link del sw0p1-p
+m_as ovn-chassis-2 ip link del sw0p2-p
+m_as ovn-chassis-2 ip link del sw1p1-p
+
+# Reset geneve tunnels
+for c in ovn-chassis-1 ovn-chassis-2 ovn-gw-1
+do
+    m_as $c ovs-vsctl set open . external-ids:ovn-encap-type=geneve
+done
+
+OVS_WAIT_UNTIL([m_as ovn-chassis-1 ip link show | grep -q genev_sys])
+OVS_WAIT_UNTIL([m_as ovn-chassis-2 ip link show | grep -q genev_sys])
+OVS_WAIT_UNTIL([m_as ovn-gw-1 ip link show | grep -q genev_sys])
+
+# Test East-West switching
+check multinode_nbctl ls-add sw0
+check multinode_nbctl lsp-add sw0 sw0-port1
+check multinode_nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:03 10.0.0.3 1000::3"
+check multinode_nbctl lsp-add sw0 sw0-port2
+check multinode_nbctl lsp-set-addresses sw0-port2 "50:54:00:00:00:04 10.0.0.4 1000::4"
+
+m_as ovn-chassis-1 /data/create_fake_vm.sh sw0-port1 sw0p1 50:54:00:00:00:03 10.0.0.3 24 10.0.0.1 1000::3/64 1000::a
+m_as ovn-chassis-2 /data/create_fake_vm.sh sw0-port2 sw0p2 50:54:00:00:00:04 10.0.0.4 24 10.0.0.1 1000::4/64 1000::a
+
+m_wait_for_ports_up
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.4 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+# Create the second logical switch with one port
+check multinode_nbctl ls-add sw1
+check multinode_nbctl lsp-add sw1 sw1-port1
+check multinode_nbctl lsp-set-addresses sw1-port1 "40:54:00:00:00:03 20.0.0.3 2000::3"
+
+# Create a logical router and attach both logical switches
+check multinode_nbctl lr-add lr0
+check multinode_nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 1000::a/64
+check multinode_nbctl lsp-add sw0 sw0-lr0
+check multinode_nbctl lsp-set-type sw0-lr0 router
+check multinode_nbctl lsp-set-addresses sw0-lr0 router
+check multinode_nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
+
+check multinode_nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24 2000::a/64
+check multinode_nbctl lsp-add sw1 sw1-lr0
+check multinode_nbctl lsp-set-type sw1-lr0 router
+check multinode_nbctl lsp-set-addresses sw1-lr0 router
+check multinode_nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
+
+m_as ovn-chassis-2 /data/create_fake_vm.sh sw1-port1 sw1p1 40:54:00:00:00:03 20.0.0.3 24 20.0.0.1 2000::3/64 2000::a
+
+# create exteranl connection for N/S traffic
+check multinode_nbctl ls-add public
+check multinode_nbctl lsp-add public ln-lublic
+check multinode_nbctl lsp-set-type ln-lublic localnet
+check multinode_nbctl lsp-set-addresses ln-lublic unknown
+check multinode_nbctl lsp-set-options ln-lublic network_name=public
+
+check multinode_nbctl lrp-add lr0 lr0-public 00:11:22:00:ff:01 172.20.0.100/24
+check multinode_nbctl lsp-add public public-lr0
+check multinode_nbctl lsp-set-type public-lr0 router
+check multinode_nbctl lsp-set-addresses public-lr0 router
+check multinode_nbctl lsp-set-options public-lr0 router-port=lr0-public
+check multinode_nbctl lrp-set-gateway-chassis lr0-public ovn-gw-1 10
+check multinode_nbctl lr-route-add lr0 0.0.0.0/0 172.20.0.1
+
+check multinode_nbctl lr-nat-add lr0 snat 172.20.0.100 10.0.0.0/24
+check multinode_nbctl lr-nat-add lr0 snat 172.20.0.100 20.0.0.0/24
+
+# create some ACLs
+check multinode_nbctl acl-add sw0 from-lport 1002 'ip4 || ip6'  allow-related
+check multinode_nbctl acl-add sw1 from-lport 1002 'ip4 || ip6'  allow-related
+
+m_as ovn-gw-1 ip netns add ovn-ext0
+m_as ovn-gw-1 ovs-vsctl add-port br-ex ext0 -- set interface ext0 type=internal
+m_as ovn-gw-1 ip link set ext0 netns ovn-ext0
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip link set ext0 up
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip addr add 172.20.0.1/24 dev ext0
+
+m_as ovn-gw-1 ovs-vsctl add-port br-ex ext1 -- set interface ext1 type=internal
+m_as ovn-gw-1 ip link set ext1 netns ovn-ext0
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip link set ext1 up
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip addr add 172.20.1.1/24 dev ext1
+
+m_as ovn-gw-1 ip netns add ovn-ext2
+m_as ovn-gw-1 ovs-vsctl add-port br-ex ext2 -- set interface ext2 type=internal
+m_as ovn-gw-1 ip link set ext2 netns ovn-ext2
+m_as ovn-gw-1 ip netns exec ovn-ext2 ip link set ext2 up
+m_as ovn-gw-1 ip netns exec ovn-ext2 ip addr add 172.20.1.2/24 dev ext2
+m_as ovn-gw-1 ip netns exec ovn-ext2 ip route add default via 172.20.1.1 dev ext2
+
+m_as ovn-gw-1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+m_as ovn-chassis-1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+m_as ovn-chassis-2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+
+m_wait_for_ports_up sw1-port1
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 20.0.0.3 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+# Change ptmu for the geneve tunnel
+m_as ovn-chassis-1 ip route change 170.168.0.0/16 mtu 1200 dev eth1
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -c 5 -s 1300 -M do 20.0.0.3 2>&1 |grep -q "message too long, mtu=1142"])
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route flush dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add 10.0.0.0/24 dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add default via 10.0.0.1 dev sw0p1])
+
+m_as ovn-chassis-1 ip route change 170.168.0.0/16 mtu 1400 dev eth1
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping6 -c 5 -s 1450 -M do 2000::3 2>&1 |grep -q "message too long, mtu: 1342"])
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 172.20.1.2 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+M_NS_CHECK_EXEC([ovn-gw-1], [ovn-ext0], [ip link set dev ext1 mtu 1000])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -c 10 -s 1300 -M do 172.20.1.2 2>&1 |grep -q "mtu = 1000"])
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 172.20.1.2 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+AT_CLEANUP
+
+AT_SETUP([ovn multinode pmtu - distributed router - vxlan])
+
+# Check that ovn-fake-multinode setup is up and running
+check_fake_multinode_setup
+
+# Delete the multinode NB and OVS resources before starting the test.
+cleanup_multinode_resources
+
+m_as ovn-chassis-1 ip link del sw0p1-p
+m_as ovn-chassis-2 ip link del sw0p2-p
+m_as ovn-chassis-2 ip link del sw1p1-p
+
+# Reset vxlan tunnels
+for c in ovn-chassis-1 ovn-chassis-2 ovn-gw-1
+do
+    m_as $c ovs-vsctl set open . external-ids:ovn-encap-type=vxlan
+done
+
+OVS_WAIT_UNTIL([m_as ovn-chassis-1 ip link show | grep -q vxlan_sys])
+OVS_WAIT_UNTIL([m_as ovn-chassis-2 ip link show | grep -q vxlan_sys])
+OVS_WAIT_UNTIL([m_as ovn-gw-1 ip link show | grep -q vxlan_sys])
+
+# Test East-West switching
+check multinode_nbctl ls-add sw0
+check multinode_nbctl lsp-add sw0 sw0-port1
+check multinode_nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:03 10.0.0.3 1000::3"
+check multinode_nbctl lsp-add sw0 sw0-port2
+check multinode_nbctl lsp-set-addresses sw0-port2 "50:54:00:00:00:04 10.0.0.4 1000::4"
+
+m_as ovn-chassis-1 /data/create_fake_vm.sh sw0-port1 sw0p1 50:54:00:00:00:03 10.0.0.3 24 10.0.0.1 1000::3/64 1000::a
+m_as ovn-chassis-2 /data/create_fake_vm.sh sw0-port2 sw0p2 50:54:00:00:00:04 10.0.0.4 24 10.0.0.1 1000::4/64 1000::a
+
+m_wait_for_ports_up
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.4 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+# Create the second logical switch with one port
+check multinode_nbctl ls-add sw1
+check multinode_nbctl lsp-add sw1 sw1-port1
+check multinode_nbctl lsp-set-addresses sw1-port1 "40:54:00:00:00:03 20.0.0.3 2000::3"
+
+# Create a logical router and attach both logical switches
+check multinode_nbctl lr-add lr0
+check multinode_nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 1000::a/64
+check multinode_nbctl lsp-add sw0 sw0-lr0
+check multinode_nbctl lsp-set-type sw0-lr0 router
+check multinode_nbctl lsp-set-addresses sw0-lr0 router
+check multinode_nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
+
+check multinode_nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24 2000::a/64
+check multinode_nbctl lsp-add sw1 sw1-lr0
+check multinode_nbctl lsp-set-type sw1-lr0 router
+check multinode_nbctl lsp-set-addresses sw1-lr0 router
+check multinode_nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
+
+m_as ovn-chassis-2 /data/create_fake_vm.sh sw1-port1 sw1p1 40:54:00:00:00:03 20.0.0.3 24 20.0.0.1 2000::3/64 2000::a
+
+# create exteranl connection for N/S traffic
+check multinode_nbctl ls-add public
+check multinode_nbctl lsp-add public ln-lublic
+check multinode_nbctl lsp-set-type ln-lublic localnet
+check multinode_nbctl lsp-set-addresses ln-lublic unknown
+check multinode_nbctl lsp-set-options ln-lublic network_name=public
+
+check multinode_nbctl lrp-add lr0 lr0-public 00:11:22:00:ff:01 172.20.0.100/24
+check multinode_nbctl lsp-add public public-lr0
+check multinode_nbctl lsp-set-type public-lr0 router
+check multinode_nbctl lsp-set-addresses public-lr0 router
+check multinode_nbctl lsp-set-options public-lr0 router-port=lr0-public
+check multinode_nbctl lrp-set-gateway-chassis lr0-public ovn-gw-1 10
+check multinode_nbctl lr-route-add lr0 0.0.0.0/0 172.20.0.1
+
+check multinode_nbctl lr-nat-add lr0 snat 172.20.0.100 10.0.0.0/24
+check multinode_nbctl lr-nat-add lr0 snat 172.20.0.100 20.0.0.0/24
+
+# create some ACLs
+check multinode_nbctl acl-add sw0 from-lport 1002 'ip4 || ip6'  allow-related
+check multinode_nbctl acl-add sw1 from-lport 1002 'ip4 || ip6'  allow-related
+
+m_as ovn-gw-1 ip netns add ovn-ext0
+m_as ovn-gw-1 ovs-vsctl add-port br-ex ext0 -- set interface ext0 type=internal
+m_as ovn-gw-1 ip link set ext0 netns ovn-ext0
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip link set ext0 up
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip addr add 172.20.0.1/24 dev ext0
+
+m_as ovn-gw-1 ovs-vsctl add-port br-ex ext1 -- set interface ext1 type=internal
+m_as ovn-gw-1 ip link set ext1 netns ovn-ext0
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip link set ext1 up
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip addr add 172.20.1.1/24 dev ext1
+
+m_as ovn-gw-1 ip netns add ovn-ext2
+m_as ovn-gw-1 ovs-vsctl add-port br-ex ext2 -- set interface ext2 type=internal
+m_as ovn-gw-1 ip link set ext2 netns ovn-ext2
+m_as ovn-gw-1 ip netns exec ovn-ext2 ip link set ext2 up
+m_as ovn-gw-1 ip netns exec ovn-ext2 ip addr add 172.20.1.2/24 dev ext2
+m_as ovn-gw-1 ip netns exec ovn-ext2 ip route add default via 172.20.1.1 dev ext2
+
+m_as ovn-gw-1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+m_as ovn-chassis-1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+m_as ovn-chassis-2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+
+m_wait_for_ports_up sw1-port1
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 20.0.0.3 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+# Change ptmu for the vxlan tunnel
+m_as ovn-chassis-1 ip route change 170.168.0.0/16 mtu 1200 dev eth1
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -c 5 -s 1300 -M do 20.0.0.3 2>&1 |grep -q "message too long, mtu=1150"])
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route flush dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add 10.0.0.0/24 dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add default via 10.0.0.1 dev sw0p1])
+
+M_NS_CHECK_EXEC([ovn-gw-1], [ovn-ext0], [ip link set dev ext1 mtu 1100])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 172.20.1.2 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -c 20 -i 0.5 -s 1300 -M do 172.20.1.2 2>&1 |grep -q "mtu = 1150"])
+
+AT_CLEANUP
+
+AT_SETUP([ovn multinode pmtu - gw_router_port - geneve])
+
+# Check that ovn-fake-multinode setup is up and running
+check_fake_multinode_setup
+
+# Delete the multinode NB and OVS resources before starting the test.
+cleanup_multinode_resources
+
+m_as ovn-chassis-1 ip link del sw0p1-p
+m_as ovn-chassis-2 ip link del sw0p2-p
+m_as ovn-chassis-2 ip link del sw1p1-p
+
+# Reset geneve tunnels
+for c in ovn-chassis-1 ovn-chassis-2 ovn-gw-1
+do
+    m_as $c ovs-vsctl set open . external-ids:ovn-encap-type=geneve
+done
+
+OVS_WAIT_UNTIL([m_as ovn-chassis-1 ip link show | grep -q genev_sys])
+OVS_WAIT_UNTIL([m_as ovn-chassis-2 ip link show | grep -q genev_sys])
+OVS_WAIT_UNTIL([m_as ovn-gw-1 ip link show | grep -q genev_sys])
+
+# Test East-West switching
+check multinode_nbctl ls-add sw0
+check multinode_nbctl lsp-add sw0 sw0-port1
+check multinode_nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:03 10.0.0.3 1000::3"
+check multinode_nbctl lsp-add sw0 sw0-port2
+check multinode_nbctl lsp-set-addresses sw0-port2 "50:54:00:00:00:04 10.0.0.4 1000::4"
+
+m_as ovn-chassis-1 /data/create_fake_vm.sh sw0-port1 sw0p1 50:54:00:00:00:03 10.0.0.3 24 10.0.0.1 1000::3/64 1000::a
+m_as ovn-chassis-2 /data/create_fake_vm.sh sw0-port2 sw0p2 50:54:00:00:00:04 10.0.0.4 24 10.0.0.1 1000::4/64 1000::a
+
+m_wait_for_ports_up
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.4 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+# Create the second logical switch with one port
+check multinode_nbctl ls-add sw1
+check multinode_nbctl lsp-add sw1 sw1-port1
+check multinode_nbctl lsp-set-addresses sw1-port1 "40:54:00:00:00:03 20.0.0.3 2000::3"
+
+# Create a logical router and attach both logical switches
+check multinode_nbctl lr-add lr0
+check multinode_nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 1000::a/64
+check multinode_nbctl lsp-add sw0 sw0-lr0
+check multinode_nbctl lsp-set-type sw0-lr0 router
+check multinode_nbctl lsp-set-addresses sw0-lr0 router
+check multinode_nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
+
+check multinode_nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24 2000::a/64
+check multinode_nbctl lsp-add sw1 sw1-lr0
+check multinode_nbctl lsp-set-type sw1-lr0 router
+check multinode_nbctl lsp-set-addresses sw1-lr0 router
+check multinode_nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
+
+m_as ovn-chassis-2 /data/create_fake_vm.sh sw1-port1 sw1p1 40:54:00:00:00:03 20.0.0.3 24 20.0.0.1 2000::3/64 2000::a
+
+# create exteranl connection for N/S traffic
+check multinode_nbctl ls-add public
+check multinode_nbctl lsp-add public ln-lublic
+check multinode_nbctl lsp-set-type ln-lublic localnet
+check multinode_nbctl lsp-set-addresses ln-lublic unknown
+check multinode_nbctl lsp-set-options ln-lublic network_name=public
+
+check multinode_nbctl lrp-add lr0 lr0-public 00:11:22:00:ff:01 172.20.0.100/24
+check multinode_nbctl lsp-add public public-lr0
+check multinode_nbctl lsp-set-type public-lr0 router
+check multinode_nbctl lsp-set-addresses public-lr0 router
+check multinode_nbctl lsp-set-options public-lr0 router-port=lr0-public
+check multinode_nbctl lrp-set-gateway-chassis lr0-public ovn-gw-1 10
+check multinode_nbctl lr-route-add lr0 0.0.0.0/0 172.20.0.1
+
+check multinode_nbctl lr-nat-add lr0 snat 172.20.0.100 10.0.0.0/24
+check multinode_nbctl lr-nat-add lr0 snat 172.20.0.100 20.0.0.0/24
+
+check multinode_nbctl lrp-set-gateway-chassis lr0-sw0 ovn-chassis-1 10
+check multinode_nbctl lrp-set-gateway-chassis lr0-sw1 ovn-chassis-2 10
+
+# create some ACLs
+check multinode_nbctl acl-add sw0 from-lport 1002 'ip4 || ip6'  allow-related
+check multinode_nbctl acl-add sw1 from-lport 1002 'ip4 || ip6'  allow-related
+
+m_as ovn-gw-1 ip netns add ovn-ext0
+m_as ovn-gw-1 ovs-vsctl add-port br-ex ext0 -- set interface ext0 type=internal
+m_as ovn-gw-1 ip link set ext0 netns ovn-ext0
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip link set ext0 up
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip addr add 172.20.0.1/24 dev ext0
+
+m_as ovn-gw-1 ovs-vsctl add-port br-ex ext1 -- set interface ext1 type=internal
+m_as ovn-gw-1 ip link set ext1 netns ovn-ext0
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip link set ext1 up
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip addr add 172.20.1.1/24 dev ext1
+
+m_as ovn-gw-1 ip netns add ovn-ext2
+m_as ovn-gw-1 ovs-vsctl add-port br-ex ext2 -- set interface ext2 type=internal
+m_as ovn-gw-1 ip link set ext2 netns ovn-ext2
+m_as ovn-gw-1 ip netns exec ovn-ext2 ip link set ext2 up
+m_as ovn-gw-1 ip netns exec ovn-ext2 ip addr add 172.20.1.2/24 dev ext2
+m_as ovn-gw-1 ip netns exec ovn-ext2 ip route add default via 172.20.1.1 dev ext2
+
+m_as ovn-gw-1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+m_as ovn-chassis-1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+m_as ovn-chassis-2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+
+m_wait_for_ports_up sw1-port1
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 20.0.0.3 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route flush dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add 10.0.0.0/24 dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add default via 10.0.0.1 dev sw0p1])
+
+m_as ovn-chassis-1 ip route change 170.168.0.0/16 mtu 1200 dev eth1
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -c 5 -s 1300 -M do 20.0.0.3 2>&1 |grep -q "message too long, mtu=1142"])
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route flush dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add 10.0.0.0/24 dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add default via 10.0.0.1 dev sw0p1])
+
+m_as ovn-chassis-1 ip route change 170.168.0.0/16 mtu 1400 dev eth1
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping6 -c 5 -s 1450 -M do 2000::3 2>&1 |grep -q "message too long, mtu: 1342"])
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route flush dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add 10.0.0.0/24 dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add default via 10.0.0.1 dev sw0p1])
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 172.20.1.2 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+M_NS_CHECK_EXEC([ovn-gw-1], [ovn-ext0], [ip link set dev ext1 mtu 1100])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -c 20 -i 0.5 -s 1300 -M do 172.20.1.2 2>&1 |grep -q "mtu = 1100"])
+
+AT_CLEANUP
+
+AT_SETUP([ovn multinode pmtu - gw_router_port - vxlan])
+
+# Check that ovn-fake-multinode setup is up and running
+check_fake_multinode_setup
+
+# Delete the multinode NB and OVS resources before starting the test.
+cleanup_multinode_resources
+
+m_as ovn-chassis-1 ip link del sw0p1-p
+m_as ovn-chassis-2 ip link del sw0p2-p
+m_as ovn-chassis-2 ip link del sw1p1-p
+
+# Reset geneve tunnels
+for c in ovn-chassis-1 ovn-chassis-2 ovn-gw-1
+do
+    m_as $c ovs-vsctl set open . external-ids:ovn-encap-type=vxlan
+done
+
+OVS_WAIT_UNTIL([m_as ovn-chassis-1 ip link show | grep -q vxlan_sys])
+OVS_WAIT_UNTIL([m_as ovn-chassis-2 ip link show | grep -q vxlan_sys])
+OVS_WAIT_UNTIL([m_as ovn-gw-1 ip link show | grep -q vxlan_sys])
+
+# Test East-West switching
+check multinode_nbctl ls-add sw0
+check multinode_nbctl lsp-add sw0 sw0-port1
+check multinode_nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:03 10.0.0.3 1000::3"
+check multinode_nbctl lsp-add sw0 sw0-port2
+check multinode_nbctl lsp-set-addresses sw0-port2 "50:54:00:00:00:04 10.0.0.4 1000::4"
+
+m_as ovn-chassis-1 /data/create_fake_vm.sh sw0-port1 sw0p1 50:54:00:00:00:03 10.0.0.3 24 10.0.0.1 1000::3/64 1000::a
+m_as ovn-chassis-2 /data/create_fake_vm.sh sw0-port2 sw0p2 50:54:00:00:00:04 10.0.0.4 24 10.0.0.1 1000::4/64 1000::a
+
+m_wait_for_ports_up
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.4 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+# Create the second logical switch with one port
+check multinode_nbctl ls-add sw1
+check multinode_nbctl lsp-add sw1 sw1-port1
+check multinode_nbctl lsp-set-addresses sw1-port1 "40:54:00:00:00:03 20.0.0.3 2000::3"
+
+# Create a logical router and attach both logical switches
+check multinode_nbctl lr-add lr0
+check multinode_nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 1000::a/64
+check multinode_nbctl lsp-add sw0 sw0-lr0
+check multinode_nbctl lsp-set-type sw0-lr0 router
+check multinode_nbctl lsp-set-addresses sw0-lr0 router
+check multinode_nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
+
+check multinode_nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24 2000::a/64
+check multinode_nbctl lsp-add sw1 sw1-lr0
+check multinode_nbctl lsp-set-type sw1-lr0 router
+check multinode_nbctl lsp-set-addresses sw1-lr0 router
+check multinode_nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
+
+m_as ovn-chassis-2 /data/create_fake_vm.sh sw1-port1 sw1p1 40:54:00:00:00:03 20.0.0.3 24 20.0.0.1 2000::3/64 2000::a
+
+# create exteranl connection for N/S traffic
+check multinode_nbctl ls-add public
+check multinode_nbctl lsp-add public ln-lublic
+check multinode_nbctl lsp-set-type ln-lublic localnet
+check multinode_nbctl lsp-set-addresses ln-lublic unknown
+check multinode_nbctl lsp-set-options ln-lublic network_name=public
+
+check multinode_nbctl lrp-add lr0 lr0-public 00:11:22:00:ff:01 172.20.0.100/24
+check multinode_nbctl lsp-add public public-lr0
+check multinode_nbctl lsp-set-type public-lr0 router
+check multinode_nbctl lsp-set-addresses public-lr0 router
+check multinode_nbctl lsp-set-options public-lr0 router-port=lr0-public
+check multinode_nbctl lrp-set-gateway-chassis lr0-public ovn-gw-1 10
+check multinode_nbctl lr-route-add lr0 0.0.0.0/0 172.20.0.1
+
+check multinode_nbctl lr-nat-add lr0 snat 172.20.0.100 10.0.0.0/24
+check multinode_nbctl lr-nat-add lr0 snat 172.20.0.100 20.0.0.0/24
+
+check multinode_nbctl lrp-set-gateway-chassis lr0-sw0 ovn-chassis-1 10
+check multinode_nbctl lrp-set-gateway-chassis lr0-sw1 ovn-chassis-2 10
+
+# create some ACLs
+check multinode_nbctl acl-add sw0 from-lport 1002 'ip4 || ip6'  allow-related
+check multinode_nbctl acl-add sw1 from-lport 1002 'ip4 || ip6'  allow-related
+
+m_as ovn-gw-1 ip netns add ovn-ext0
+m_as ovn-gw-1 ovs-vsctl add-port br-ex ext0 -- set interface ext0 type=internal
+m_as ovn-gw-1 ip link set ext0 netns ovn-ext0
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip link set ext0 up
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip addr add 172.20.0.1/24 dev ext0
+
+m_as ovn-gw-1 ovs-vsctl add-port br-ex ext1 -- set interface ext1 type=internal
+m_as ovn-gw-1 ip link set ext1 netns ovn-ext0
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip link set ext1 up
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip addr add 172.20.1.1/24 dev ext1
+
+m_as ovn-gw-1 ip netns add ovn-ext2
+m_as ovn-gw-1 ovs-vsctl add-port br-ex ext2 -- set interface ext2 type=internal
+m_as ovn-gw-1 ip link set ext2 netns ovn-ext2
+m_as ovn-gw-1 ip netns exec ovn-ext2 ip link set ext2 up
+m_as ovn-gw-1 ip netns exec ovn-ext2 ip addr add 172.20.1.2/24 dev ext2
+m_as ovn-gw-1 ip netns exec ovn-ext2 ip route add default via 172.20.1.1 dev ext2
+
+m_as ovn-gw-1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+m_as ovn-chassis-1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+m_as ovn-chassis-2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+
+m_wait_for_ports_up sw1-port1
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 20.0.0.3 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route flush dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add 10.0.0.0/24 dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add default via 10.0.0.1 dev sw0p1])
+
+m_as ovn-chassis-1 ip route change 170.168.0.0/16 mtu 1200 dev eth1
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -c 5 -s 1300 -M do 20.0.0.3 2>&1 |grep -q "message too long, mtu=1150"])
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route flush dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add 10.0.0.0/24 dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add default via 10.0.0.1 dev sw0p1])
+
+M_NS_CHECK_EXEC([ovn-gw-1], [ovn-ext0], [ip link set dev ext1 mtu 1100])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 172.20.1.2 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -c 20 -i 0.5 -s 1300 -M do 172.20.1.2 2>&1 |grep -q "mtu = 1150"])
+
+AT_CLEANUP
+
+AT_SETUP([ovn multinode pmtu - gw router - geneve])
+
+# Check that ovn-fake-multinode setup is up and running
+check_fake_multinode_setup
+
+# Delete the multinode NB and OVS resources before starting the test.
+cleanup_multinode_resources
+
+m_as ovn-chassis-1 ip link del sw0p1-p
+m_as ovn-chassis-2 ip link del sw0p2-p
+m_as ovn-chassis-2 ip link del sw1p1-p
+
+# Reset geneve tunnels
+for c in ovn-chassis-1 ovn-chassis-2 ovn-gw-1
+do
+    m_as $c ovs-vsctl set open . external-ids:ovn-encap-type=geneve
+done
+
+OVS_WAIT_UNTIL([m_as ovn-chassis-1 ip link show | grep -q genev_sys])
+OVS_WAIT_UNTIL([m_as ovn-chassis-2 ip link show | grep -q genev_sys])
+OVS_WAIT_UNTIL([m_as ovn-gw-1 ip link show | grep -q genev_sys])
+
+# Test East-West switching
+check multinode_nbctl ls-add sw0
+check multinode_nbctl lsp-add sw0 sw0-port1
+check multinode_nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:03 10.0.0.3 1000::3"
+check multinode_nbctl lsp-add sw0 sw0-port2
+check multinode_nbctl lsp-set-addresses sw0-port2 "50:54:00:00:00:04 10.0.0.4 1000::4"
+
+m_as ovn-chassis-1 /data/create_fake_vm.sh sw0-port1 sw0p1 50:54:00:00:00:03 10.0.0.3 24 10.0.0.1 1000::3/64 1000::a
+m_as ovn-chassis-2 /data/create_fake_vm.sh sw0-port2 sw0p2 50:54:00:00:00:04 10.0.0.4 24 10.0.0.1 1000::4/64 1000::a
+
+m_wait_for_ports_up
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.4 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+# Create the second logical switch with one port
+check multinode_nbctl ls-add sw1
+check multinode_nbctl lsp-add sw1 sw1-port1
+check multinode_nbctl lsp-set-addresses sw1-port1 "40:54:00:00:00:03 20.0.0.3 2000::3"
+
+# Create a logical router and attach both logical switches
+check multinode_nbctl lr-add lr0 -- set Logical_Router lr0 options:chassis=ovn-gw-1
+check multinode_nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 1000::a/64
+check multinode_nbctl lsp-add sw0 sw0-lr0
+check multinode_nbctl lsp-set-type sw0-lr0 router
+check multinode_nbctl lsp-set-addresses sw0-lr0 router
+check multinode_nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
+
+check multinode_nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24 2000::a/64
+check multinode_nbctl lsp-add sw1 sw1-lr0
+check multinode_nbctl lsp-set-type sw1-lr0 router
+check multinode_nbctl lsp-set-addresses sw1-lr0 router
+check multinode_nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
+
+m_as ovn-chassis-2 /data/create_fake_vm.sh sw1-port1 sw1p1 40:54:00:00:00:03 20.0.0.3 24 20.0.0.1 2000::3/64 2000::a
+
+# create exteranl connection for N/S traffic
+check multinode_nbctl ls-add public
+check multinode_nbctl lsp-add public ln-lublic
+check multinode_nbctl lsp-set-type ln-lublic localnet
+check multinode_nbctl lsp-set-addresses ln-lublic unknown
+check multinode_nbctl lsp-set-options ln-lublic network_name=public
+
+check multinode_nbctl lrp-add lr0 lr0-public 00:11:22:00:ff:01 172.20.0.100/24
+check multinode_nbctl lsp-add public public-lr0
+check multinode_nbctl lsp-set-type public-lr0 router
+check multinode_nbctl lsp-set-addresses public-lr0 router
+check multinode_nbctl lsp-set-options public-lr0 router-port=lr0-public
+check multinode_nbctl lr-route-add lr0 0.0.0.0/0 172.20.0.1
+
+check multinode_nbctl lr-nat-add lr0 snat 172.20.0.100 10.0.0.0/24
+check multinode_nbctl lr-nat-add lr0 snat 172.20.0.100 20.0.0.0/24
+
+# create some ACLs
+check multinode_nbctl acl-add sw0 from-lport 1002 'ip4 || ip6'  allow-related
+check multinode_nbctl acl-add sw1 from-lport 1002 'ip4 || ip6'  allow-related
+
+m_as ovn-gw-1 ip netns add ovn-ext0
+m_as ovn-gw-1 ovs-vsctl add-port br-ex ext0 -- set interface ext0 type=internal
+m_as ovn-gw-1 ip link set ext0 netns ovn-ext0
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip link set ext0 up
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip addr add 172.20.0.1/24 dev ext0
+
+m_as ovn-gw-1 ovs-vsctl add-port br-ex ext1 -- set interface ext1 type=internal
+m_as ovn-gw-1 ip link set ext1 netns ovn-ext0
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip link set ext1 up
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip addr add 172.20.1.1/24 dev ext1
+
+m_as ovn-gw-1 ip netns add ovn-ext2
+m_as ovn-gw-1 ovs-vsctl add-port br-ex ext2 -- set interface ext2 type=internal
+m_as ovn-gw-1 ip link set ext2 netns ovn-ext2
+m_as ovn-gw-1 ip netns exec ovn-ext2 ip link set ext2 up
+m_as ovn-gw-1 ip netns exec ovn-ext2 ip addr add 172.20.1.2/24 dev ext2
+m_as ovn-gw-1 ip netns exec ovn-ext2 ip route add default via 172.20.1.1 dev ext2
+
+m_as ovn-gw-1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+m_as ovn-chassis-1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+m_as ovn-chassis-2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+
+m_wait_for_ports_up sw1-port1
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 20.0.0.3 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route flush dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add 10.0.0.0/24 dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add default via 10.0.0.1 dev sw0p1])
+
+m_as ovn-chassis-1 ip route change 170.168.0.0/16 mtu 1200 dev eth1
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -c 5 -s 1300 -M do 20.0.0.3 2>&1 |grep -q "message too long, mtu=1142"])
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route flush dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add 10.0.0.0/24 dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add default via 10.0.0.1 dev sw0p1])
+
+m_as ovn-chassis-1 ip route change 170.168.0.0/16 mtu 1400 dev eth1
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping6 -c 5 -s 1450 -M do 2000::3 2>&1 |grep -q "message too long, mtu: 1342"])
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route flush dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add 10.0.0.0/24 dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add default via 10.0.0.1 dev sw0p1])
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 172.20.1.2 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+M_NS_CHECK_EXEC([ovn-gw-1], [ovn-ext0], [ip link set dev ext1 mtu 1100])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -c 20 -i 0.5 -s 1300 -M do 172.20.1.2 2>&1 |grep -q "mtu = 1100"])
+
+AT_CLEANUP
+
+AT_SETUP([ovn multinode pmtu - gw router - vxlan])
+
+# Check that ovn-fake-multinode setup is up and running
+check_fake_multinode_setup
+
+# Delete the multinode NB and OVS resources before starting the test.
+cleanup_multinode_resources
+
+m_as ovn-chassis-1 ip link del sw0p1-p
+m_as ovn-chassis-2 ip link del sw0p2-p
+m_as ovn-chassis-2 ip link del sw1p1-p
+
+# Reset geneve tunnels
+for c in ovn-chassis-1 ovn-chassis-2 ovn-gw-1
+do
+    m_as $c ovs-vsctl set open . external-ids:ovn-encap-type=vxlan
+done
+
+OVS_WAIT_UNTIL([m_as ovn-chassis-1 ip link show | grep -q vxlan_sys])
+OVS_WAIT_UNTIL([m_as ovn-chassis-2 ip link show | grep -q vxlan_sys])
+OVS_WAIT_UNTIL([m_as ovn-gw-1 ip link show | grep -q vxlan_sys])
+
+# Test East-West switching
+check multinode_nbctl ls-add sw0
+check multinode_nbctl lsp-add sw0 sw0-port1
+check multinode_nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:03 10.0.0.3 1000::3"
+check multinode_nbctl lsp-add sw0 sw0-port2
+check multinode_nbctl lsp-set-addresses sw0-port2 "50:54:00:00:00:04 10.0.0.4 1000::4"
+
+m_as ovn-chassis-1 /data/create_fake_vm.sh sw0-port1 sw0p1 50:54:00:00:00:03 10.0.0.3 24 10.0.0.1 1000::3/64 1000::a
+m_as ovn-chassis-2 /data/create_fake_vm.sh sw0-port2 sw0p2 50:54:00:00:00:04 10.0.0.4 24 10.0.0.1 1000::4/64 1000::a
+
+m_wait_for_ports_up
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.4 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+# Create the second logical switch with one port
+check multinode_nbctl ls-add sw1
+check multinode_nbctl lsp-add sw1 sw1-port1
+check multinode_nbctl lsp-set-addresses sw1-port1 "40:54:00:00:00:03 20.0.0.3 2000::3"
+
+# Create a logical router and attach both logical switches
+check multinode_nbctl lr-add lr0 -- set Logical_Router lr0 options:chassis=ovn-gw-1
+check multinode_nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 1000::a/64
+check multinode_nbctl lsp-add sw0 sw0-lr0
+check multinode_nbctl lsp-set-type sw0-lr0 router
+check multinode_nbctl lsp-set-addresses sw0-lr0 router
+check multinode_nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
+
+check multinode_nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24 2000::a/64
+check multinode_nbctl lsp-add sw1 sw1-lr0
+check multinode_nbctl lsp-set-type sw1-lr0 router
+check multinode_nbctl lsp-set-addresses sw1-lr0 router
+check multinode_nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
+
+m_as ovn-chassis-2 /data/create_fake_vm.sh sw1-port1 sw1p1 40:54:00:00:00:03 20.0.0.3 24 20.0.0.1 2000::3/64 2000::a
+
+# create exteranl connection for N/S traffic
+check multinode_nbctl ls-add public
+check multinode_nbctl lsp-add public ln-lublic
+check multinode_nbctl lsp-set-type ln-lublic localnet
+check multinode_nbctl lsp-set-addresses ln-lublic unknown
+check multinode_nbctl lsp-set-options ln-lublic network_name=public
+
+check multinode_nbctl lrp-add lr0 lr0-public 00:11:22:00:ff:01 172.20.0.100/24
+check multinode_nbctl lsp-add public public-lr0
+check multinode_nbctl lsp-set-type public-lr0 router
+check multinode_nbctl lsp-set-addresses public-lr0 router
+check multinode_nbctl lsp-set-options public-lr0 router-port=lr0-public
+check multinode_nbctl lr-route-add lr0 0.0.0.0/0 172.20.0.1
+
+check multinode_nbctl lr-nat-add lr0 snat 172.20.0.100 10.0.0.0/24
+check multinode_nbctl lr-nat-add lr0 snat 172.20.0.100 20.0.0.0/24
+
+# create some ACLs
+check multinode_nbctl acl-add sw0 from-lport 1002 'ip4 || ip6'  allow-related
+check multinode_nbctl acl-add sw1 from-lport 1002 'ip4 || ip6'  allow-related
+
+m_as ovn-gw-1 ip netns add ovn-ext0
+m_as ovn-gw-1 ovs-vsctl add-port br-ex ext0 -- set interface ext0 type=internal
+m_as ovn-gw-1 ip link set ext0 netns ovn-ext0
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip link set ext0 up
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip addr add 172.20.0.1/24 dev ext0
+
+m_as ovn-gw-1 ovs-vsctl add-port br-ex ext1 -- set interface ext1 type=internal
+m_as ovn-gw-1 ip link set ext1 netns ovn-ext0
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip link set ext1 up
+m_as ovn-gw-1 ip netns exec ovn-ext0 ip addr add 172.20.1.1/24 dev ext1
+
+m_as ovn-gw-1 ip netns add ovn-ext2
+m_as ovn-gw-1 ovs-vsctl add-port br-ex ext2 -- set interface ext2 type=internal
+m_as ovn-gw-1 ip link set ext2 netns ovn-ext2
+m_as ovn-gw-1 ip netns exec ovn-ext2 ip link set ext2 up
+m_as ovn-gw-1 ip netns exec ovn-ext2 ip addr add 172.20.1.2/24 dev ext2
+m_as ovn-gw-1 ip netns exec ovn-ext2 ip route add default via 172.20.1.1 dev ext2
+
+m_as ovn-gw-1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+m_as ovn-chassis-1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+m_as ovn-chassis-2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-ex
+
+m_wait_for_ports_up sw1-port1
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 20.0.0.3 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route flush dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add 10.0.0.0/24 dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add default via 10.0.0.1 dev sw0p1])
+
+m_as ovn-chassis-1 ip route change 170.168.0.0/16 mtu 1200 dev eth1
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -c 5 -s 1300 -M do 20.0.0.3 2>&1 |grep -q "message too long, mtu=1150"])
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route flush dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add 10.0.0.0/24 dev sw0p1])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ip route add default via 10.0.0.1 dev sw0p1])
+
+M_NS_CHECK_EXEC([ovn-gw-1], [ovn-ext0], [ip link set dev ext1 mtu 1100])
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 172.20.1.2 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -c 20 -i 0.5 -s 1300 -M do 172.20.1.2 2>&1 |grep -q "mtu = 1150"])
+
+AT_CLEANUP
diff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at
index 07ef1d092..bccedbaf7 100644
--- a/tests/ofproto-macros.at
+++ b/tests/ofproto-macros.at
@@ -200,14 +200,14 @@ m4_define([_OVS_VSWITCHD_START],
    AT_CHECK([[sed < stderr '
 /vlog|INFO|opened log file/d
 /ovsdb_server|INFO|ovsdb-server (Open vSwitch)/d']])
-   AT_CAPTURE_FILE([ovsdb-server.log])
+  # AT_CAPTURE_FILE([ovsdb-server.log])
 
    dnl Initialize database.
    AT_CHECK([ovs-vsctl --no-wait init $2])
 
    dnl Start ovs-vswitchd.
    AT_CHECK([ovs-vswitchd $1 --detach --no-chdir --pidfile --log-file -vvconn -vofproto_dpif -vunixctl], [0], [], [stderr])
-   AT_CAPTURE_FILE([ovs-vswitchd.log])
+   #AT_CAPTURE_FILE([ovs-vswitchd.log])
    on_exit "kill_ovs_vswitchd `cat ovs-vswitchd.pid`"
    AT_CHECK([[sed < stderr '
 /ovs_numa|INFO|Discovered /d
diff --git a/tests/ovn-controller-vtep.at b/tests/ovn-controller-vtep.at
index 73971b3f4..7f7d1cf40 100644
--- a/tests/ovn-controller-vtep.at
+++ b/tests/ovn-controller-vtep.at
@@ -50,7 +50,7 @@ m4_define([OVN_CONTROLLER_VTEP_START], [
 /vlog|INFO|opened log file/d'
 
    dnl Wait until ovs-vtep starts up.
-   OVS_WAIT_UNTIL([test -n "`vtep-ctl show | grep Physical_Port`"])
+   OVS_WAIT_UNTIL([test 2 -eq "`vtep-ctl show | grep -c Physical_Port`"])
 
    dnl Start ovn-controller-vtep.
    start_daemon ovn-controller-vtep --vtep-db=unix:"$ovs_dir"/db.sock \
@@ -653,9 +653,6 @@ AT_CHECK([grep -c $northd_version vtep1/ovn-controller-vtep.log], [0], [1
 as northd
 OVS_APP_EXIT_AND_WAIT([ovn-northd])
 
-as northd-backup
-OVS_APP_EXIT_AND_WAIT([ovn-northd])
-
 check ovn-sbctl set SB_Global . options:northd_internal_version=foo
 check ovn-sbctl set Chassis vtep1 vtep_logical_switches=foo
 
diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
index 4212d601a..b2a38a969 100644
--- a/tests/ovn-controller.at
+++ b/tests/ovn-controller.at
@@ -60,7 +60,6 @@ check_bridge_mappings () {
 # the RBAC rules programmed into the SB-DB. The test instruments the SB-DB
 # directly and we need to stop northd to avoid overwriting the instrumentation.
 kill `cat northd/ovn-northd.pid`
-kill `cat northd-backup/ovn-northd.pid`
 kill `cat ovn-nb/ovsdb-server.pid`
 
 # Initially there should be no patch ports.
@@ -214,6 +213,13 @@ if test X$HAVE_OPENSSL = Xyes; then
     # TODO implement check for change of certificates in ovn-controller
     # and remove this workaround.
     ovs-vsctl set Open_vSwitch . external-ids:ovn-remote=unix:/dev/null
+    # Make sure that the ovn-remote change is handled by ovn-controller.
+    # Without this, ovn-controller could handle both this change and next ovn-remote change within the same loop,
+    # resulting in no change.
+    # Use 2 ovn-appctl to guarentee that ovn-controller run the full loop, and not just the unixctl handling
+    OVS_WAIT_UNTIL([test x$(ovn-appctl -t ovn-controller debug/status) = "xrunning"])
+    OVS_WAIT_UNTIL([test x$(ovn-appctl -t ovn-controller debug/status) = "xrunning"])
+
 fi
 ovs-vsctl -- set Open_vSwitch . external-ids:hostname="${sysid}" \
           -- set Open_vSwitch . external-ids:system-id="${sysid}" \
@@ -526,6 +532,10 @@ OVS_WAIT_UNTIL([
     test "$(ovn-sbctl get chassis hv1 other_config:port-up-notif)" = '"true"'
 ])
 
+OVS_WAIT_UNTIL([
+    test "$(ovn-sbctl get chassis hv1 other_config:ls-dpg-column)" = '"true"'
+])
+
 OVN_CLEANUP([hv1])
 AT_CLEANUP
 ])
@@ -575,10 +585,8 @@ localport lport : [[lsp1]]
 
 # pause ovn-northd
 check as northd ovn-appctl -t ovn-northd pause
-check as northd-backup ovn-appctl -t ovn-northd pause
 
 as northd ovn-appctl -t ovn-northd status
-as northd-backup ovn-appctl -t ovn-northd status
 
 pb_types=(patch chassisredirect l3gateway localnet localport l2gateway
           virtual external remote vtep)
@@ -2093,7 +2101,6 @@ AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0],
 
 # pause ovn-northd
 check as northd ovn-appctl -t ovn-northd pause
-check as northd-backup ovn-appctl -t ovn-northd pause
 
 # Simulate a SB address set "del and add" notification to ovn-controller in the
 # same IDL iteration. The flows programmed by ovn-controller should reflect the
@@ -2118,7 +2125,7 @@ AT_CLEANUP
 
 AT_SETUP([ovn-controller - I-P handle lb_hairpin_use_ct_mark change])
 
-ovn_start --backup-northd=none
+ovn_start
 
 net_add n1
 sim_add hv1
@@ -2193,14 +2200,18 @@ check ovn-nbctl --wait=hv sync
 AT_CHECK([ovn-appctl -t ovn-controller group-table-list | awk '{print $2}' | sort | uniq | wc -l], [0], [2
 ])
 
-# Set 5 seconds wait time before clearing OVS flows.
-check ovs-vsctl set open . external_ids:ovn-ofctrl-wait-before-clear=5000
-
 # Stop ovn-controller
 OVS_APP_EXIT_AND_WAIT([ovn-controller])
 
+# Set 5 seconds wait time before clearing OVS flows.
+check ovs-vsctl set open . external_ids:ovn-ofctrl-wait-before-clear=5000
+
 # The old OVS flows should remain (this is regardless of the configuration)
-AT_CHECK([ovs-ofctl dump-flows br-int | grep 10.1.2.3], [0], [ignore])
+AT_CHECK([ovs-ofctl dump-flows br-int | grep -F 10.1.2.3], [0], [ignore])
+
+# We should have 2 flows with groups.
+AT_CHECK([ovs-ofctl dump-flows br-int | grep group -c], [0], [2
+])
 
 # Make a change to the ls1-lp1's IP
 check ovn-nbctl --wait=sb lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.1.2.4"
@@ -2208,16 +2219,29 @@ check ovn-nbctl --wait=sb lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.1.2.4"
 # Start ovn-controller, which should compute new flows but not apply them
 # until the wait time is completed.
 start_daemon ovn-controller
-sleep 2
+
+# Wait for octrl to run - it will handle the wait-before-clear
+OVS_WAIT_UNTIL([grep -q 'wait-before-clear' hv1/ovn-controller.log])
+
+# Check that there is no flow using 10.1.2.4 except the lb one (using 2.2.2.2)
+OVS_WAIT_UNTIL([test 0 = $(ovs-ofctl dump-flows br-int | grep -F 10.1.2.4 | grep -cvF 2.2.2.2)])
 
 # Check in the middle of the wait.
 lflow_run_1=$(ovn-appctl -t ovn-controller coverage/read-counter lflow_run)
-AT_CHECK([ovs-ofctl dump-flows br-int | grep 10.1.2.3], [0], [ignore])
+AT_CHECK([ovs-ofctl dump-flows br-int | grep -F 10.1.2.3], [0], [ignore])
+
+# We should have 2 flows with groups.
+AT_CHECK([ovs-ofctl dump-flows br-int | grep group -c], [0], [2
+])
 
 sleep 5
 
 # Check after the wait
-OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 10.1.2.4])
+OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep -F 10.1.2.4 | grep -vF 2.2.2.2])
+
+# We should have 2 flows with groups.
+AT_CHECK([ovs-ofctl dump-flows br-int | grep group -c], [0], [2
+])
 lflow_run_2=$(ovn-appctl -t ovn-controller coverage/read-counter lflow_run)
 
 # Verify that the flow compute completed during the wait (after the wait it
@@ -2228,9 +2252,9 @@ AT_CHECK_UNQUOTED([echo $lflow_run_1], [0], [$lflow_run_2
 # Restart OVS this time, and wait until flows are reinstalled
 OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
 start_daemon ovs-vswitchd --enable-dummy=system -vvconn -vofproto_dpif -vunixctl
-OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 10.1.2.4])
+OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep -F 10.1.2.4 | grep -vF 2.2.2.2])
 
-check ovn-nbctl --wait=hv lb-add lb3 2.2.2.2 10.1.2.5 \
+check ovn-nbctl --wait=hv lb-add lb3 3.3.3.3 10.1.2.5 \
 -- ls-lb-add ls1 lb3
 
 # There should be 3 group IDs allocated (this is to ensure the group ID
@@ -2238,6 +2262,10 @@ check ovn-nbctl --wait=hv lb-add lb3 2.2.2.2 10.1.2.5 \
 AT_CHECK([ovn-appctl -t ovn-controller group-table-list | awk '{print $2}' | sort | uniq | wc -l], [0], [3
 ])
 
+# We should have 3 flows with groups.
+AT_CHECK([ovs-ofctl dump-flows br-int | grep group -c], [0], [3
+])
+
 OVN_CLEANUP([hv1])
 AT_CLEANUP
 
@@ -2651,3 +2679,23 @@ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c in_por
 OVN_CLEANUP([hv1])
 AT_CLEANUP
 ])
+
+AT_SETUP([ovn-controller - ssl ciphers using command line options])
+AT_KEYWORDS([ovn])
+AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
+ovn_start
+
+net_add n1
+sim_add hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.20
+
+# Set cipher and and it should connect
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+start_daemon ovn-controller --ssl-ciphers='HIGH:!aNULL:!MD5:@SECLEVEL=1' --ssl-protocols='TLSv1,TLSv1.1,TLSv1.2'
+
+OVS_WAIT_FOR_OUTPUT([ovn-appctl -t ovn-controller connection-status], [0], [connected
+])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at
index d265bb90b..49e1e39d6 100644
--- a/tests/ovn-ic.at
+++ b/tests/ovn-ic.at
@@ -28,7 +28,31 @@ availability-zone az3
 ])
 
 OVN_CLEANUP_IC([az1], [az2])
+AT_CLEANUP
+])
+
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ovn-ic -- AZ update in GW])
+ovn_init_ic_db
+net_add n1
+
+ovn_start az1
+sim_add gw-az1
+as gw-az1
+
+check ovs-vsctl add-br br-phys
+ovn_az_attach az1 n1 br-phys 192.168.1.1
+check ovs-vsctl set open . external-ids:ovn-is-interconn=true
 
+az_uuid=$(fetch_column ic-sb:availability-zone _uuid name="az1")
+ovn_as az1 ovn-nbctl set NB_Global . name="az2"
+wait_column "$az_uuid" ic-sb:availability-zone _uuid name="az2"
+
+# make sure that gateway still point to the same AZ with new name
+wait_column "$az_uuid" ic-sb:gateway availability_zone name="gw-az1"
+
+OVN_CLEANUP_IC([az1])
 AT_CLEANUP
 ])
 
@@ -92,6 +116,7 @@ check ovn-nbctl lr-add lr1
 check ovn-nbctl lrp-add lr1 lrp1 00:00:00:00:00:01 10.0.0.1/24
 check ovn-nbctl lrp-set-gateway-chassis lrp1 gw-az1
 
+OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1])
 check ovn-nbctl lsp-add ts1 lsp1 -- \
     lsp-set-addresses lsp1 router -- \
     lsp-set-type lsp1 router -- \
@@ -156,6 +181,7 @@ create_ic_infra() {
     ovn_as $az
 
     check ovn-ic-nbctl ts-add $ts
+    OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts])
     check ovn-nbctl lr-add $lr
     check ovn-nbctl lrp-add $lr $lrp 00:00:00:00:00:0$az_id 10.0.$az_id.1/24
     check ovn-nbctl lrp-set-gateway-chassis $lrp gw-$az
@@ -227,6 +253,7 @@ for i in 1 2; do
     check ovn-nbctl lrp-add lr1 lrp$i 00:00:00:00:0$i:01 10.0.$i.1/24
     check ovn-nbctl lrp-set-gateway-chassis lrp$i gw-az$i
 
+    OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1])
     check ovn-nbctl lsp-add ts1 lsp$i -- \
         lsp-set-addresses lsp$i router -- \
         lsp-set-type lsp$i router -- \
@@ -290,7 +317,7 @@ ovs-vsctl set open . external-ids:ovn-is-interconn=true
 OVS_WAIT_UNTIL([ovn_as az2 ovn-sbctl show | grep gw1])
 
 OVN_CLEANUP_SBOX(gw1)
-AT_CHECK([ovn_as az2 ovn-sbctl show], [0], [dnl
+OVS_WAIT_FOR_OUTPUT([ovn_as az2 ovn-sbctl show], [0], [dnl
 ])
 
 # Test encap change
@@ -335,16 +362,17 @@ ovn-nbctl lsp-add ts1 lsp-ts1-lr1
 ovn-nbctl lsp-set-addresses lsp-ts1-lr1 router
 ovn-nbctl lsp-set-type lsp-ts1-lr1 router
 ovn-nbctl --wait=hv lsp-set-options lsp-ts1-lr1 router-port=lrp-lr1-ts1
-
+OVS_WAIT_UNTIL([ovn_as az2 ovn-nbctl show | grep lsp-ts1-lr1])
 ovn_as az2 ovn-nbctl lsp-set-options lsp-ts1-lr1 requested-chassis=gw1
-AT_CHECK([ovn_as az2 ovn-nbctl show | uuidfilt], [0], [dnl
+
+OVS_WAIT_FOR_OUTPUT([ovn_as az2 ovn-nbctl show | uuidfilt], [0], [dnl
 switch <0> (ts1)
     port lsp-ts1-lr1
         type: remote
         addresses: [["aa:aa:aa:aa:aa:01 169.254.100.1/24"]]
 ])
 
-AT_CHECK([ovn_as az2 ovn-sbctl -f csv -d bare --no-headings --columns logical_port,type list port_binding], [0], [dnl
+OVS_WAIT_FOR_OUTPUT([ovn_as az2 ovn-sbctl -f csv -d bare --no-headings --columns logical_port,type list port_binding], [0], [dnl
 lsp-ts1-lr1,remote
 ])
 
@@ -529,6 +557,7 @@ for i in 1 2; do
     for j in 1 2; do
         ts=ts$j$j
         ovn-ic-nbctl --may-exist ts-add $ts
+        OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts])
 
         # Create LRP and connect to TS
         lr=lr$j$i
@@ -552,6 +581,9 @@ ovn_as az1 ovn-nbctl show
 echo az2
 ovn_as az2 ovn-nbctl show
 
+OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep learned | grep 192.168])
+OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep learned | grep 10.10.10])
+
 # Test routes from lr12 were learned to lr11
 AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 |
              grep learned | awk '{print $1, $2}' | sort], [0], [dnl
@@ -584,6 +616,7 @@ for i in 1 2; do
     # Create LRP and connect to TS
     ovn-nbctl lr-add lr$i
     ovn-nbctl lrp-add lr$i lrp-lr$i-ts1 aa:aa:aa:aa:aa:0$i 169.254.100.$i/24
+    OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1])
     ovn-nbctl lsp-add ts1 lsp-ts1-lr$i \
             -- lsp-set-addresses lsp-ts1-lr$i router \
             -- lsp-set-type lsp-ts1-lr$i router \
@@ -909,6 +942,7 @@ for i in 1 2; do
     for j in 1 2 3; do
         ts=ts1$j
         ovn-ic-nbctl --may-exist ts-add $ts
+        OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts])
 
         lrp=lrp-$lr-$ts
         lsp=lsp-$ts-$lr
@@ -934,6 +968,7 @@ for i in 1 2; do
     for j in 1 2; do
         ts=ts2$j
         ovn-ic-nbctl --may-exist ts-add $ts
+        OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts])
 
         lrp=lrp-$lr-$ts
         lsp=lsp-$ts-$lr
@@ -957,7 +992,7 @@ ovn_as az2 ovn-nbctl --route-table=rtb3 lr-route-add lr12 10.10.10.0/24 192.168.
 ovn_as az2 ovn-nbctl --wait=sb lrp-add lr22 lrp-lr22 aa:aa:aa:aa:bb:01 "192.168.0.1/24"
 
 # Test direct routes from lr12 were learned to lr11
-AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 |
+OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 |
              grep learned | awk '{print $1, $2, $5}' | sort ], [0], [dnl
 192.168.0.0/24 169.254.101.2 ecmp
 192.168.0.0/24 169.254.102.2 ecmp
@@ -965,24 +1000,24 @@ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 |
 ])
 
 # Test static routes from lr12 rtbs rtb1,rtb2,rtb3 were learned to lr11
-AT_CHECK([ovn_as az1 ovn-nbctl --route-table=rtb1 lr-route-list lr11], [0], [dnl
+OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl --route-table=rtb1 lr-route-list lr11], [0], [dnl
 IPv4 Routes
 Route Table rtb1:
             10.10.10.0/24             169.254.101.2 dst-ip (learned)
 ])
-AT_CHECK([ovn_as az1 ovn-nbctl --route-table=rtb2 lr-route-list lr11], [0], [dnl
+OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl --route-table=rtb2 lr-route-list lr11], [0], [dnl
 IPv4 Routes
 Route Table rtb2:
             10.10.10.0/24             169.254.102.2 dst-ip (learned)
 ])
-AT_CHECK([ovn_as az1 ovn-nbctl --route-table=rtb3 lr-route-list lr11], [0], [dnl
+OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl --route-table=rtb3 lr-route-list lr11], [0], [dnl
 IPv4 Routes
 Route Table rtb3:
             10.10.10.0/24             169.254.103.2 dst-ip (learned)
 ])
 
 # Test routes from lr12 didn't leak as learned to lr21
-AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr21 | grep 192.168 | sort], [0], [dnl
+OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr21 | grep 192.168 | sort], [0], [dnl
            192.168.0.0/24             169.254.101.2 dst-ip (learned) ecmp
            192.168.0.0/24             169.254.102.2 dst-ip (learned) ecmp
 ])
@@ -1035,6 +1070,7 @@ for i in 1 2; do
     for j in 1 2 3; do
         ts=ts1$j
         ovn-ic-nbctl --may-exist ts-add $ts
+        OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts])
 
         lrp=lrp-$lr-$ts
         lsp=lsp-$ts-$lr
@@ -1060,6 +1096,7 @@ for i in 1 2; do
     for j in 1 2; do
         ts=ts2$j
         ovn-ic-nbctl --may-exist ts-add $ts
+        OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts])
 
         lrp=lrp-$lr-$ts
         lsp=lsp-$ts-$lr
@@ -1083,6 +1120,7 @@ ovn_as az2 ovn-nbctl --route-table=rtb3 lr-route-add lr12 2001:db8:aaaa::/64 200
 ovn_as az2 ovn-nbctl --wait=sb lrp-add lr22 lrp-lr22 aa:aa:aa:aa:bb:01 "2001:db8:200::1/64"
 
 # Test direct routes from lr12 were learned to lr11
+OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 2001:db8:3::2])
 AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 2001:db8:200 |
              grep learned | awk '{print $1, $2, $5}' | sort], [0], [dnl
 2001:db8:200::/64 2001:db8:1::2 ecmp
@@ -1091,16 +1129,19 @@ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 2001:db8:200 |
 ])
 
 # Test static routes from lr12 rtbs rtb1,rtb2,rtb3 were learned to lr11
+OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl --route-table=rtb1 lr-route-list lr11 | grep learned])
 AT_CHECK([ovn_as az1 ovn-nbctl --route-table=rtb1 lr-route-list lr11], [0], [dnl
 IPv6 Routes
 Route Table rtb1:
        2001:db8:aaaa::/64             2001:db8:1::2 dst-ip (learned)
 ])
+OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl --route-table=rtb2 lr-route-list lr11 | grep learned])
 AT_CHECK([ovn_as az1 ovn-nbctl --route-table=rtb2 lr-route-list lr11], [0], [dnl
 IPv6 Routes
 Route Table rtb2:
        2001:db8:aaaa::/64             2001:db8:2::2 dst-ip (learned)
 ])
+OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl --route-table=rtb3 lr-route-list lr11 | grep learned])
 AT_CHECK([ovn_as az1 ovn-nbctl --route-table=rtb3 lr-route-list lr11], [0], [dnl
 IPv6 Routes
 Route Table rtb3:
@@ -1108,6 +1149,7 @@ Route Table rtb3:
 ])
 
 # Test routes from lr12 didn't leak as learned to lr21
+OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl lr-route-list lr21 | grep "2001:db8:2::2" | grep learned])
 AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr21 | grep 2001 | sort], [0], [dnl
         2001:db8:200::/64             2001:db8:1::2 dst-ip (learned) ecmp
         2001:db8:200::/64             2001:db8:2::2 dst-ip (learned) ecmp
@@ -1127,6 +1169,7 @@ ovn-ic-nbctl ts-add ts1
 for i in 1 2; do
     ovn_start az$i
     ovn_as az$i
+    OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1])
 
     # Enable route learning at AZ level
     ovn-nbctl set nb_global . options:ic-route-learn=true
@@ -1152,13 +1195,13 @@ for i in 1 2; do
     check ovn-nbctl --wait=sb lr-route-add $lr 0.0.0.0/0 192.168.$i.11
 done
 
-AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep dst-ip | sort], [0], [dnl
+OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep dst-ip | sort] , [0], [dnl
                 0.0.0.0/0              192.168.1.11 dst-ip
               10.0.0.0/24              192.168.1.10 dst-ip
            192.168.2.0/24             169.254.100.2 dst-ip (learned)
 ])
 
-AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr12 | grep dst-ip | sort], [0], [dnl
+OVS_WAIT_FOR_OUTPUT([ovn_as az2 ovn-nbctl lr-route-list lr12 | grep dst-ip | sort], [0], [dnl
                 0.0.0.0/0              192.168.2.11 dst-ip
               10.0.0.0/24              192.168.2.10 dst-ip
            192.168.1.0/24             169.254.100.1 dst-ip (learned)
@@ -1191,10 +1234,10 @@ done
 # each LR has one connected subnet except TS port
 
 
-# create lr11, lr21, lr22, ts1 and connect them
-ovn-ic-nbctl ts-add ts1
+# create lr11, lr21, lr22 and connect them
 
 ovn_as az1
+OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1])
 
 lr=lr11
 ovn-nbctl lr-add $lr
@@ -1209,6 +1252,7 @@ ovn-nbctl lsp-add ts1 $lsp \
         -- lsp-set-options $lsp router-port=$lrp
 
 ovn_as az2
+OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1])
 for i in 1 2; do
     lr=lr2$i
     ovn-nbctl lr-add $lr
@@ -1230,7 +1274,7 @@ ovn_as az2 ovn-nbctl lrp-add lr21 lrp-lr21 aa:aa:aa:aa:bc:01 "192.168.1.1/24"
 ovn_as az2 ovn-nbctl lrp-add lr22 lrp-lr22 aa:aa:aa:aa:bc:02 "192.168.2.1/24"
 
 # Test direct routes from lr21 and lr22 were learned to lr11
-AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 |
+OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 |
              grep learned | awk '{print $1, $2}' | sort ], [0], [dnl
 192.168.1.0/24 169.254.10.21
 192.168.2.0/24 169.254.10.22
@@ -1254,3 +1298,102 @@ OVN_CLEANUP_IC([az1], [az2])
 
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ovn-ic -- route sync -- IPv6 blacklist filter])
+AT_KEYWORDS([IPv6-route-sync-blacklist])
+
+ovn_init_ic_db
+check ovn-ic-nbctl ts-add ts1
+
+for i in 1 2; do
+    ovn_start az$i
+    ovn_as az$i
+
+    # Enable route learning at AZ level
+    check ovn-nbctl set nb_global . options:ic-route-learn=true
+    # Enable route advertising at AZ level
+    check ovn-nbctl set nb_global . options:ic-route-adv=true
+    # Enable blacklist single filter for IPv6
+    check ovn-nbctl set nb_global . options:ic-route-blacklist=" \
+            2003:db8:1::/64,2004:aaaa::/32,2005:1234::/21"
+
+    OVS_WAIT_UNTIL([ovn-nbctl show | grep ts1])
+
+    # Create LRP and connect to TS
+    check ovn-nbctl lr-add lr$i
+    check ovn-nbctl lrp-add lr$i lrp-lr$i-ts1 aa:aa:aa:aa:aa:0$i \
+            2001:db8:1::$i/64
+    check ovn-nbctl lsp-add ts1 lsp-ts1-lr$i \
+            -- lsp-set-addresses lsp-ts1-lr$i router \
+            -- lsp-set-type lsp-ts1-lr$i router \
+            -- lsp-set-options lsp-ts1-lr$i router-port=lrp-lr$i-ts1
+
+    check ovn-nbctl lrp-add lr$i lrp-lr$i-p$i 00:00:00:00:00:0$i \
+            2002:db8:1::$i/64
+
+    # Create blacklisted LRPs and connect to TS
+    check ovn-nbctl lrp-add lr$i lrp-lr$i-p-ext$i \
+            11:11:11:11:11:1$i 2003:db8:1::$i/64
+
+    check ovn-nbctl lrp-add lr$i lrp-lr$i-p-ext2$i \
+            22:22:22:22:22:2$i 2004:aaaa:bbb::$i/48
+
+    # filtered by 2005:1234::/21 - (2005:1000: - 2005:17ff:)
+    check ovn-nbctl lrp-add lr$i lrp-lr$i-p-ext3$i \
+            33:33:33:33:33:3$i 2005:1734:5678::$i/50
+
+    # additional not filtered prefix -> different subnet bits
+    check ovn-nbctl lrp-add lr$i lrp-lr$i-p-ext4$i \
+            44:44:44:44:44:4$i 2005:1834:5678::$i/50
+done
+
+for i in 1 2; do
+    OVS_WAIT_UNTIL([ovn_as az$i ovn-nbctl lr-route-list lr$i | grep learned])
+done
+
+AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr1 |
+    awk '/learned/{print $1, $2}' ], [0], [dnl
+2002:db8:1::/64 2001:db8:1::2
+2005:1834:5678::/50 2001:db8:1::2
+])
+
+for i in 1 2; do
+    ovn_as az$i
+
+    # Drop blacklist
+    check ovn-nbctl remove nb_global . options ic-route-blacklist
+done
+
+OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr1 |
+    awk '/learned/{print $1, $2}' | sort ], [0], [dnl
+2002:db8:1::/64 2001:db8:1::2
+2003:db8:1::/64 2001:db8:1::2
+2004:aaaa:bbb::/48 2001:db8:1::2
+2005:1734:5678::/50 2001:db8:1::2
+2005:1834:5678::/50 2001:db8:1::2
+])
+
+for i in 1 2; do
+    ovn_as az$i
+
+    check ovn-nbctl set nb_global . \
+            options:ic-route-blacklist="2003:db8:1::/64,2004:db8:1::/64"
+
+    # Create an 'extra' blacklisted LRP and connect to TS
+    check ovn-nbctl lrp-add lr$i lrp-lr$i-p-ext5$i \
+            55:55:55:55:55:5$i 2004:db8:1::$i/64
+done
+
+OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr1 |
+    awk '/learned/{print $1, $2}' | sort ], [0], [dnl
+2002:db8:1::/64 2001:db8:1::2
+2004:aaaa:bbb::/48 2001:db8:1::2
+2005:1734:5678::/50 2001:db8:1::2
+2005:1834:5678::/50 2001:db8:1::2
+])
+
+OVN_CLEANUP_IC([az1], [az2])
+
+AT_CLEANUP
+])
diff --git a/tests/ovn-ipsec.at b/tests/ovn-ipsec.at
index 10ef97878..f8df8d60e 100644
--- a/tests/ovn-ipsec.at
+++ b/tests/ovn-ipsec.at
@@ -48,11 +48,11 @@ ovn-nbctl set nb_global . options:ipsec_encapsulation=true
 
 check ovn-nbctl --wait=hv sync
 
-AT_CHECK([as hv2 ovs-vsctl get Interface ovn-hv1-0 options:remote_ip | tr -d '"\n'], [0], [192.168.0.1])
+OVS_WAIT_UNTIL([test x`as hv2 ovs-vsctl get Interface ovn-hv1-0 options:remote_ip | tr -d '"\n'` = x192.168.0.1])
 AT_CHECK([as hv2 ovs-vsctl get Interface ovn-hv1-0 options:local_ip | tr -d '"\n'], [0], [192.168.0.2])
 AT_CHECK([as hv2 ovs-vsctl get Interface ovn-hv1-0 options:remote_name | tr -d '\n'], [0], [hv1])
 AT_CHECK([as hv2 ovs-vsctl get Interface ovn-hv1-0 options:ipsec_encapsulation | tr -d '\n'], [0], [yes])
-AT_CHECK([as hv1 ovs-vsctl get Interface ovn-hv2-0 options:remote_ip | tr -d '"\n'], [0], [192.168.0.2])
+OVS_WAIT_UNTIL([test x`as hv1 ovs-vsctl get Interface ovn-hv2-0 options:remote_ip | tr -d '"\n'` = x192.168.0.2])
 AT_CHECK([as hv1 ovs-vsctl get Interface ovn-hv2-0 options:local_ip | tr -d '"\n'], [0], [192.168.0.1])
 AT_CHECK([as hv1 ovs-vsctl get Interface ovn-hv2-0 options:remote_name | tr -d '\n'], [0], [hv2])
 AT_CHECK([as hv1 ovs-vsctl get Interface ovn-hv2-0 options:ipsec_encapsulation | tr -d '\n'], [0], [yes])
diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
index 13d5dc3d4..a5a3e87fd 100644
--- a/tests/ovn-macros.at
+++ b/tests/ovn-macros.at
@@ -188,16 +188,16 @@ ovn_start_northd() {
 # ovn-sbctl and ovn-nbctl use them by default, and starts ovn-northd running
 # against them.
 #
-# Normally this starts an active northd and a backup northd.  The following
+# Normally this starts only an active northd and no backup northd.  The following
 # options are accepted to adjust that:
-#   --backup-northd=none    Don't start a backup northd.
+#   --backup-northd         Start a backup northd.
 #   --backup-northd=paused  Start the backup northd in the paused state.
 ovn_start () {
-    local backup_northd=:
+    local backup_northd=false
     local backup_northd_options=
     case $1 in
-        --backup-northd=none) backup_northd=false; shift ;;
-        --backup-northd=paused) backup_northd_options=--paused; shift ;;
+        --backup-northd) backup_northd=true; shift ;;
+        --backup-northd=paused) backup_northd=true; backup_northd_options=--paused; shift ;;
     esac
     local AZ=$1
     local msg_prefix=${AZ:+$AZ: }
@@ -288,11 +288,44 @@ net_attach () {
         || return 1
 }
 
+ovn_wait_for_encaps() {
+    local systemid=$1
+
+    if [[ -f "${OVN_SYSCONFDIR}/system-id-override" ]]; then
+        systemid=$(cat ${OVN_SYSCONFDIR}/system-id-override)
+    fi
+
+    local encap=$(ovs-vsctl get Open_vSwitch . external_ids:ovn-encap-type-$systemid)
+    if [[ -z "$encap" ]]; then
+        encap=$(ovs-vsctl get Open_vSwitch . external_ids:ovn-encap-type)
+    fi
+    encap=$(tr -d '"' <<< $encap)
+
+    local ip=$(ovs-vsctl get Open_vSwitch . external_ids:ovn-encap-ip-$systemid)
+    if [[ -z "$ip" ]]; then
+        ip=$(ovs-vsctl get Open_vSwitch . external_ids:ovn-encap-ip)
+    fi
+    ip=$(tr -d '"' <<< $ip)
+
+    IFS="," read -r -a encap_types <<< "$encap"
+    for e in "${encap_types[[@]]}"; do
+        wait_column "$ip" sb:Encap ip chassis_name="$systemid" type="$e"
+    done
+}
+
 # ovn_az_attach AZ NETWORK BRIDGE IP [MASKLEN] [ENCAP]
 ovn_az_attach() {
-    local az=$1 net=$2 bridge=$3 ip=$4 masklen=${5-24} encap=${6-geneve,vxlan} systemid=${7-$sandbox} cli_args=${@:8}
+    local az=$1 net=$2 bridge=$3 ip=$4 masklen=${5-24} encap=${6-geneve,vxlan}
+    local systemid=${7-$sandbox} systemid_override=$8
     net_attach $net $bridge || return 1
 
+    local expected_encap_id=$systemid
+    local cli_args=""
+    if [[ -n "$systemid_override" ]]; then
+        cli_args="-n $systemid_override"
+        expected_encap_id=$systemid_override
+    fi
+
     mac=`ovs-vsctl get Interface $bridge mac_in_use | sed s/\"//g`
     arp_table="$arp_table $sandbox,$bridge,$ip,$mac"
     if test -z $(echo $ip | sed '/:/d'); then
@@ -332,6 +365,11 @@ ovn_az_attach() {
     fi
 
     start_daemon ovn-controller --enable-dummy-vif-plug ${cli_args} || return 1
+    if test X"$az" = XNONE; then
+        ovn_wait_for_encaps $expected_encap_id
+    else
+        ovn_as $az ovn_wait_for_encaps $expected_encap_id
+    fi
 }
 
 # ovn_attach NETWORK BRIDGE IP [MASKLEN] [ENCAP]
@@ -372,6 +410,7 @@ start_virtual_controller() {
         || return 1
 
     ovn-controller --enable-dummy-vif-plug ${cli_args} -vconsole:off --detach --no-chdir
+    ovn_wait_for_encaps $systemid
 }
 
 # ovn_setenv AZ
@@ -833,15 +872,40 @@ ovn_trace_client() {
 # ovs-appctl netdev-dummy/receive $vif $packet
 #
 fmt_pkt() {
-    echo "from scapy.all import *; \
-          import binascii; \
-          out = binascii.hexlify(raw($1)); \
-          print(out.decode())" | $PYTHON3
+    ctlfile=$ovs_base/scapy.ctl
+    if [[ ! -S $ctlfile ]]; then
+        start_scapy_server
+    fi
+    while [[ ! -S $ctlfile ]]; do sleep 0.1; done
+    ovs-appctl -t $ctlfile payload "$1"
+}
+
+start_scapy_server() {
+    pidfile=$ovs_base/scapy.pid
+    ctlfile=$ovs_base/scapy.ctl
+    logfile=$ovs_base/scapy.log
+    lockfile=$ovs_base/scapy.lock
+
+    flock -n $lockfile "$top_srcdir"/tests/scapy-server.py \
+        --pidfile=$pidfile --unixctl=$ctlfile --log-file=$logfile --detach \
+    && on_exit "test -e \"$pidfile\" && ovs-appctl -t $ctlfile exit"
+}
+
+sleep_northd() {
+  echo Northd going to sleep
+  AT_CHECK([kill -STOP $(cat northd/ovn-northd.pid)])
+  on_exit "kill -CONT $(cat northd/ovn-northd.pid)"
+}
+
+wake_up_northd() {
+  echo Northd waking up
+  AT_CHECK([kill -CONT $(cat northd/ovn-northd.pid)])
 }
 
 sleep_sb() {
   echo SB going to sleep
   AT_CHECK([kill -STOP $(cat ovn-sb/ovsdb-server.pid)])
+  on_exit "kill -CONT $(cat ovn-sb/ovsdb-server.pid)"
 }
 wake_up_sb() {
   echo SB waking up
@@ -865,6 +929,7 @@ sleep_ovs() {
   hv=$1
   echo ovs $hv going to sleep
   AT_CHECK([kill -STOP $(cat $hv/ovs-vswitchd.pid)])
+  on_exit "kill -CONT $(cat $hv/ovs-vswitchd.pid)"
 }
 
 wake_up_ovs() {
@@ -876,12 +941,17 @@ wake_up_ovs() {
 sleep_ovsdb() {
   echo OVSDB $1 going to sleep
   AT_CHECK([kill -STOP $(cat $1/ovsdb-server.pid)])
+  on_exit "kill -CONT $(cat $1/ovsdb-server.pid)"
 }
 wake_up_ovsdb() {
   echo OVSDB $1 waking up
   AT_CHECK([kill -CONT $(cat $1/ovsdb-server.pid)])
 }
 
+trim_zeros() {
+    sed 's/\(00\)\{1,\}$//'
+}
+
 OVS_END_SHELL_HELPERS
 
 m4_define([OVN_POPULATE_ARP], [AT_CHECK(ovn_populate_arp__, [0], [ignore])])
@@ -913,3 +983,9 @@ m4_define([RUN_OVN_NBCTL], [
     check ovn-nbctl ${command}
     unset command
 ])
+
+m4_define([OVN_CHECK_SCAPY_EDNS_CLIENT_SUBNET_SUPPORT],
+[
+    AT_SKIP_IF([test $HAVE_SCAPY = no])
+    AT_SKIP_IF([! echo "from scapy.layers.dns import EDNS0ClientSubnet" | python 2>&1 > /dev/null])
+])
diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
index fde3a28ee..94641f2f0 100644
--- a/tests/ovn-nbctl.at
+++ b/tests/ovn-nbctl.at
@@ -2695,7 +2695,9 @@ dnl ---------------------------------------------------------------------
 
 AT_SETUP([ovn-nbctl - daemon retry connection])
 OVN_NBCTL_TEST_START daemon
-AT_CHECK([kill `cat ovsdb-server.pid`])
+pid=$(cat ovsdb-server.pid)
+AT_CHECK([kill $pid])
+OVS_WAIT_WHILE([kill -0 $pid 2>/dev/null])
 AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --log-file --remote=punix:$OVS_RUNDIR/ovnnb_db.sock ovn-nb.db], [0], [], [stderr])
 AT_CHECK([ovn-nbctl show], [0], [ignore])
 OVN_NBCTL_TEST_STOP "/terminating with signal 15/d"
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 65d3f4b03..7add1aa57 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -20,6 +20,14 @@ m4_define([_DUMP_DB_TABLES], [
 # sure nothing is changed by the recompute. It is used for ensuring the
 # correctness of incremental processing.
 m4_define([CHECK_NO_CHANGE_AFTER_RECOMPUTE], [
+    wait_port_up=$1
+    if test X$wait_port_up = X1; then
+        # Wait for hv to have received all previous commands
+        # Make sure ports are up as otherwise it might come as a difference after recompute
+        echo "waiting for ports up"
+        check ovn-nbctl --wait=hv sync
+        wait_for_ports_up
+    fi
     _DUMP_DB_TABLES(before)
     check as northd ovn-appctl -t NORTHD_TYPE inc-engine/recompute
     check ovn-nbctl --wait=sb sync
@@ -813,7 +821,7 @@ AT_CLEANUP
 
 OVN_FOR_EACH_NORTHD_NO_HV([
 AT_SETUP([ovn-northd restart])
-ovn_start --backup-northd=none
+ovn_start
 
 # Check that ovn-northd is active, by verifying that it creates and
 # destroys southbound datapaths as one would expect.
@@ -832,7 +840,7 @@ sleep 5
 check_row_count Datapath_Binding 1
 
 # Now resume ovn-northd.  Changes should catch up.
-ovn_start_northd primary
+ovn_start_northd
 wait_row_count Datapath_Binding 2
 
 AT_CLEANUP
@@ -841,7 +849,7 @@ AT_CLEANUP
 OVN_FOR_EACH_NORTHD_NO_HV([
 AT_SETUP([northbound database reconnection])
 
-ovn_start --backup-northd=none
+ovn_start
 
 # Check that ovn-northd is active, by verifying that it creates and
 # destroys southbound datapaths as one would expect.
@@ -873,7 +881,7 @@ AT_CLEANUP
 OVN_FOR_EACH_NORTHD_NO_HV([
 AT_SETUP([southbound database reconnection])
 
-ovn_start --backup-northd=none
+ovn_start
 
 # Check that ovn-northd is active, by verifying that it creates and
 # destroys southbound datapaths as one would expect.
@@ -1100,7 +1108,7 @@ AT_CAPTURE_FILE([crflows])
 AT_CHECK([grep -e "lr_out_snat" drflows | sed 's/table=../table=??/' | sort], [0], [dnl
   table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
   table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
-  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $allowed_range), action=(ct_snat(172.16.1.1);)
+  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $allowed_range && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.1);)
 ])
 
 AT_CHECK([grep -e "lr_out_snat" crflows | sed 's/table=../table=??/' | sort], [0], [dnl
@@ -1130,7 +1138,7 @@ AT_CAPTURE_FILE([crflows2])
 AT_CHECK([grep -e "lr_out_snat" drflows2 | sed 's/table=../table=??/' | sort], [0], [dnl
   table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
   table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
-  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_snat(172.16.1.1);)
+  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.1);)
   table=??(lr_out_snat        ), priority=163  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $disallowed_range), action=(next;)
 ])
 
@@ -1159,7 +1167,7 @@ AT_CAPTURE_FILE([crflows2])
 AT_CHECK([grep -e "lr_out_snat" drflows3 | sed 's/table=../table=??/' | sort], [0], [dnl
   table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
   table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
-  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $allowed_range), action=(ct_snat(172.16.1.2);)
+  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $allowed_range && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.2);)
 ])
 
 AT_CHECK([grep -e "lr_out_snat" crflows3 | sed 's/table=../table=??/' | sort], [0], [dnl
@@ -1186,7 +1194,7 @@ AT_CAPTURE_FILE([crflows2])
 AT_CHECK([grep -e "lr_out_snat" drflows4 | sed 's/table=../table=??/' | sort], [0], [dnl
   table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
   table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
-  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_snat(172.16.1.2);)
+  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.2);)
   table=??(lr_out_snat        ), priority=163  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $disallowed_range), action=(next;)
 ])
 
@@ -1554,9 +1562,6 @@ AT_CAPTURE_FILE([sbflows])
 # dnat_and_snat or snat entry.
 AT_CHECK([grep "lr_in_unsnat" sbflows | sort], [0], [dnl
   table=4 (lr_in_unsnat       ), priority=0    , match=(1), action=(next;)
-  table=4 (lr_in_unsnat       ), priority=120  , match=(ip4 && ip4.dst == 192.168.2.1 && tcp && tcp.dst == 8080), action=(next;)
-  table=4 (lr_in_unsnat       ), priority=120  , match=(ip4 && ip4.dst == 192.168.2.4 && udp && udp.dst == 8080), action=(next;)
-  table=4 (lr_in_unsnat       ), priority=120  , match=(ip4 && ip4.dst == 192.168.2.5 && tcp && tcp.dst == 8080), action=(next;)
   table=4 (lr_in_unsnat       ), priority=90   , match=(ip && ip4.dst == 192.168.2.1), action=(ct_snat;)
   table=4 (lr_in_unsnat       ), priority=90   , match=(ip && ip4.dst == 192.168.2.4), action=(ct_snat;)
 ])
@@ -1587,9 +1592,6 @@ AT_CAPTURE_FILE([sbflows])
 # dnat_and_snat or snat entry.
 AT_CHECK([grep "lr_in_unsnat" sbflows | sort], [0], [dnl
   table=4 (lr_in_unsnat       ), priority=0    , match=(1), action=(next;)
-  table=4 (lr_in_unsnat       ), priority=120  , match=(ip4 && ip4.dst == 192.168.2.1 && tcp && tcp.dst == 8080), action=(next;)
-  table=4 (lr_in_unsnat       ), priority=120  , match=(ip4 && ip4.dst == 192.168.2.4 && udp && udp.dst == 8080), action=(next;)
-  table=4 (lr_in_unsnat       ), priority=120  , match=(ip4 && ip4.dst == 192.168.2.5 && tcp && tcp.dst == 8080), action=(next;)
   table=4 (lr_in_unsnat       ), priority=90   , match=(ip && ip4.dst == 192.168.2.1), action=(ct_snat;)
   table=4 (lr_in_unsnat       ), priority=90   , match=(ip && ip4.dst == 192.168.2.4), action=(ct_snat;)
 ])
@@ -2376,6 +2378,24 @@ check_acl_lflow acl_two meter_me__${acl2} sw0
 check_acl_lflow acl_three meter_me__${acl3} sw0
 check_acl_lflow acl_three meter_me__${acl3} sw1
 
+AS_BOX([Disable/enable logging for acl3 while still referencing the meter])
+check_row_count meter 4
+check ovn-nbctl --wait=sb set acl $acl3 log=false
+check_row_count meter 3
+check_meter_by_name meter_me meter_me__${acl1} meter_me__${acl2}
+check_meter_by_name NOT meter_me__${acl3}
+check_acl_lflow acl_one meter_me__${acl1} sw0
+check_acl_lflow acl_two meter_me__${acl2} sw0
+
+check ovn-nbctl --wait=sb set acl $acl3 log=true
+check_row_count meter 4
+check_meter_by_name \
+    meter_me meter_me__${acl1} meter_me__${acl2} meter_me__${acl3}
+check_acl_lflow acl_one meter_me__${acl1} sw0
+check_acl_lflow acl_two meter_me__${acl2} sw0
+check_acl_lflow acl_three meter_me__${acl3} sw0
+check_acl_lflow acl_three meter_me__${acl3} sw1
+
 AS_BOX([Stop using meter for acl1])
 check ovn-nbctl --wait=sb clear acl $acl1 meter
 check_meter_by_name meter_me meter_me__${acl2}
@@ -2860,7 +2880,7 @@ lbg0_uuid=$(fetch_column sb:load_balancer _uuid name=lbg0)
 echo
 echo "__file__:__line__: Check that SB lb0 has sw0 in datapaths column."
 
-lb0_dp_group=$(fetch_column sb:load_balancer datapath_group name=lb0)
+lb0_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lb0)
 AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl
                     | grep -A1 $lb0_dp_group | tail -1], [0], [dnl
 $sw0_sb_uuid
@@ -2871,7 +2891,7 @@ check_column "" sb:datapath_binding load_balancers external_ids:name=sw0
 echo
 echo "__file__:__line__: Check that SB lbg0 has sw0 in datapaths column."
 
-lbg0_dp_group=$(fetch_column sb:load_balancer datapath_group name=lbg0)
+lbg0_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lbg0)
 AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl
                     | grep -A1 $lbg0_dp_group | tail -1], [0], [dnl
 $sw0_sb_uuid
@@ -2897,6 +2917,15 @@ sb:load_balancer vips,protocol name=lbg0
 
 check ovn-nbctl lr-add lr0 -- add logical_router lr0 load_balancer_group $lbg
 check ovn-nbctl --wait=sb lr-lb-add lr0 lb0
+check_row_count sb:load_balancer 2
+
+lr0_sb_uuid=$(fetch_column datapath_binding _uuid external_ids:name=lr0)
+lb0_lr_dp_group=$(fetch_column sb:load_balancer lr_datapath_group name=lb0)
+
+AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl
+                    | grep -A1 $lb0_lr_dp_group | tail -1], [0], [dnl
+$lr0_sb_uuid
+])
 
 echo
 echo "__file__:__line__: Check that SB lb0 has only sw0 in datapaths column."
@@ -2942,7 +2971,13 @@ check_row_count sb:load_balancer 2
 lbg1=$(fetch_column nb:load_balancer _uuid name=lbg1)
 check ovn-nbctl add load_balancer_group $lbg load_balancer $lbg1
 check ovn-nbctl --wait=sb lr-lb-add lr0 lb1
-check_row_count sb:load_balancer 3
+check_row_count sb:load_balancer 4
+
+lb1_lr_dp_group=$(fetch_column sb:load_balancer lr_datapath_group name=lb1)
+AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl
+                    | grep -A1 $lb1_lr_dp_group | tail -1], [0], [dnl
+$lr0_sb_uuid
+])
 
 echo
 echo "__file__:__line__: Associate lb1 to sw1 and check that lb1 is created in SB DB."
@@ -2959,7 +2994,7 @@ echo "__file__:__line__: Check that SB lbg1 has vips and protocol columns are se
 check_column "20.0.0.30:80=20.0.0.50:8080 udp" sb:load_balancer vips,protocol name=lbg1
 
 lb1_uuid=$(fetch_column sb:load_balancer _uuid name=lb1)
-lb1_dp_group=$(fetch_column sb:load_balancer datapath_group name=lb1)
+lb1_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lb1)
 
 echo
 echo "__file__:__line__: Check that SB lb1 has sw1 in datapaths column."
@@ -2970,7 +3005,7 @@ $sw1_sb_uuid
 ])
 
 lbg1_uuid=$(fetch_column sb:load_balancer _uuid name=lbg1)
-lbg1_dp_group=$(fetch_column sb:load_balancer datapath_group name=lbg1)
+lbg1_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lbg1)
 
 echo
 echo "__file__:__line__: Check that SB lbg1 has sw0 and sw1 in datapaths column."
@@ -4596,7 +4631,7 @@ AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
 PKIDIR="$(cd $abs_top_builddir/tests && pwd)"
 AT_SKIP_IF([expr "$PKIDIR" : ".*[[ 	'\"
 \\]]"])
-ovn_start --backup-northd=none
+ovn_start
 
 as northd
 OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
@@ -5095,6 +5130,7 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort],
   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
   table=??(ls_in_l2_lkup      ), priority=75   , match=(eth.src == {00:00:00:00:01:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
 ])
@@ -5109,6 +5145,7 @@ AT_CHECK([grep "ls_in_l2_lkup" ls2_lflows | sed 's/table=../table=??/' | sort],
   table=??(ls_in_l2_lkup      ), priority=75   , match=(eth.src == {00:00:00:00:02:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.2.1), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 20.0.0.100), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
+  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 20.0.0.200), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:201), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
 ])
 
@@ -5129,8 +5166,10 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort],
   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
   table=??(ls_in_l2_lkup      ), priority=75   , match=(eth.src == {00:00:00:00:01:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
 ])
 
@@ -5144,7 +5183,9 @@ AT_CHECK([grep "ls_in_l2_lkup" ls2_lflows | sed 's/table=../table=??/' | sort],
   table=??(ls_in_l2_lkup      ), priority=75   , match=(eth.src == {00:00:00:00:02:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.2.1), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 20.0.0.100), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
+  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 20.0.0.200), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 40.0.0.100), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
+  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 40.0.0.200), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:201), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
 ])
 
@@ -5162,9 +5203,11 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort],
   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
   table=??(ls_in_l2_lkup      ), priority=75   , match=(eth.src == {00:00:00:00:01:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
 ])
 
@@ -5180,9 +5223,11 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort],
   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
   table=??(ls_in_l2_lkup      ), priority=75   , match=(eth.src == {00:00:00:00:01:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
 ])
 
@@ -5204,9 +5249,11 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort],
   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
   table=??(ls_in_l2_lkup      ), priority=75   , match=(eth.src == {00:00:00:00:01:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
 ])
 
@@ -5364,12 +5411,12 @@ AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort],
 AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
   table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
   table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
-  table=? (lr_out_snat        ), priority=153  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.10);)
-  table=? (lr_out_snat        ), priority=154  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.10);)
-  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.30);)
-  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.20);)
-  table=? (lr_out_snat        ), priority=162  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.30);)
-  table=? (lr_out_snat        ), priority=162  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.20);)
+  table=? (lr_out_snat        ), priority=153  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat_in_czone(172.168.0.10);)
+  table=? (lr_out_snat        ), priority=154  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl) && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.10);)
+  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat_in_czone(172.168.0.30);)
+  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat_in_czone(172.168.0.20);)
+  table=? (lr_out_snat        ), priority=162  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl) && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.30);)
+  table=? (lr_out_snat        ), priority=162  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl) && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.20);)
 ])
 
 # Separate zones for DGP
@@ -5412,9 +5459,9 @@ AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort],
 AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
   table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
   table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
-  table=? (lr_out_snat        ), priority=153  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.10);)
-  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.30);)
-  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.20);)
+  table=? (lr_out_snat        ), priority=153  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.10);)
+  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.30);)
+  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.20);)
 ])
 
 # Associate load balancer to lr0
@@ -5494,12 +5541,12 @@ AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort],
 AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
   table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
   table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
-  table=? (lr_out_snat        ), priority=153  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.10);)
-  table=? (lr_out_snat        ), priority=154  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.10);)
-  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.30);)
-  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.20);)
-  table=? (lr_out_snat        ), priority=162  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.30);)
-  table=? (lr_out_snat        ), priority=162  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.20);)
+  table=? (lr_out_snat        ), priority=153  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat_in_czone(172.168.0.10);)
+  table=? (lr_out_snat        ), priority=154  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl) && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.10);)
+  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat_in_czone(172.168.0.30);)
+  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat_in_czone(172.168.0.20);)
+  table=? (lr_out_snat        ), priority=162  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl) && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.30);)
+  table=? (lr_out_snat        ), priority=162  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl) && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.20);)
 ])
 
 # Separate zones for DGP
@@ -5560,9 +5607,9 @@ AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort],
 AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
   table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
   table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
-  table=? (lr_out_snat        ), priority=153  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.10);)
-  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.30);)
-  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.20);)
+  table=? (lr_out_snat        ), priority=153  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.10);)
+  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.30);)
+  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.20);)
 ])
 
 # Make the logical router as Gateway router
@@ -5703,7 +5750,6 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
   table=4 (lr_in_unsnat       ), priority=0    , match=(1), action=(next;)
   table=4 (lr_in_unsnat       ), priority=110  , match=(inport == "lr0-public" && ip4.dst == 172.168.0.10), action=(ct_snat;)
   table=4 (lr_in_unsnat       ), priority=110  , match=(inport == "lr0-sw0" && ip4.dst == 10.0.0.1), action=(ct_snat;)
-  table=4 (lr_in_unsnat       ), priority=120  , match=(ip4 && ip4.dst == 172.168.0.10 && tcp && tcp.dst == 9082), action=(next;)
   table=4 (lr_in_unsnat       ), priority=90   , match=(ip && ip4.dst == 172.168.0.10), action=(ct_snat;)
   table=4 (lr_in_unsnat       ), priority=90   , match=(ip && ip4.dst == 172.168.0.20), action=(ct_snat;)
   table=4 (lr_in_unsnat       ), priority=90   , match=(ip && ip4.dst == 172.168.0.30), action=(ct_snat;)
@@ -5780,7 +5826,6 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
   table=4 (lr_in_unsnat       ), priority=110  , match=(inport == "lr0-public" && ip6.dst == def0::10), action=(ct_snat;)
   table=4 (lr_in_unsnat       ), priority=110  , match=(inport == "lr0-sw0" && ip4.dst == 10.0.0.1), action=(ct_snat;)
   table=4 (lr_in_unsnat       ), priority=110  , match=(inport == "lr0-sw0" && ip6.dst == aef0::1), action=(ct_snat;)
-  table=4 (lr_in_unsnat       ), priority=120  , match=(ip4 && ip4.dst == 172.168.0.10 && tcp && tcp.dst == 9082), action=(next;)
   table=4 (lr_in_unsnat       ), priority=90   , match=(ip && ip4.dst == 172.168.0.10), action=(ct_snat;)
   table=4 (lr_in_unsnat       ), priority=90   , match=(ip && ip4.dst == 172.168.0.20), action=(ct_snat;)
   table=4 (lr_in_unsnat       ), priority=90   , match=(ip && ip4.dst == 172.168.0.30), action=(ct_snat;)
@@ -6019,9 +6064,9 @@ ovn_start
 check ovn-nbctl ls-add ls -- lb-add lb1 10.0.0.1:80 10.0.0.2:80 -- ls-lb-add ls lb1
 check ovn-nbctl --wait=sb sync
 
-dps=$(fetch_column Load_Balancer datapath_group)
+dps=$(fetch_column Load_Balancer ls_datapath_group)
 nlb=$(fetch_column nb:Load_Balancer _uuid)
-AT_CHECK([ovn-sbctl create Load_Balancer name=lb1 datapath_group="$dps" external_ids="lb_id=$nlb"], [0], [ignore])
+AT_CHECK([ovn-sbctl create Load_Balancer name=lb1 ls_datapath_group="$dps" external_ids="lb_id=$nlb"], [0], [ignore])
 
 check ovn-nbctl --wait=sb sync
 check_row_count Load_Balancer 1
@@ -6060,7 +6105,8 @@ OVN_FOR_EACH_NORTHD_NO_HV([
 AT_SETUP([ovn -- gateway mtu check pkt larger flows])
 ovn_start
 
-check ovn-sbctl chassis-add ch1 geneve 127.0.0.1
+check ovn-sbctl chassis-add ch1 geneve 127.0.0.1 --\
+                set chassis ch1 other_config:ct-commit-nat-v2=true
 
 check ovn-nbctl ls-add sw0
 check ovn-nbctl ls-add sw1
@@ -6109,6 +6155,10 @@ AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sed 's/table=.
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
 ])
 
 AT_CHECK([grep -E "lr_in_admission.*check_pkt_larger" lr0flows | sort], [0], [dnl
@@ -6123,6 +6173,10 @@ AT_CHECK([grep -E "lr_in_ip_input.*icmp6_error" lr0flows | sort], [0], [dnl
   table=3 (lr_in_ip_input     ), priority=150  , match=(inport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
 ])
 
+AT_CHECK([grep -E "lr_out_post_snat.*ct_commit_nat" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(lr_out_post_snat   ), priority=100  , match=(icmp && flags.icmp_snat == 1), action=(ct_commit_nat(snat);)
+])
+
 # Clear the gateway-chassis for lr0-public
 check ovn-nbctl --wait=sb clear logical_router_port lr0-public gateway_chassis
 
@@ -6140,6 +6194,10 @@ AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sed 's/table=.
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
 ])
 
 AT_CHECK([grep -E "lr_in_admission.*check_pkt_larger" lr0flows | sort], [0], [dnl
@@ -6154,6 +6212,10 @@ AT_CHECK([grep -E "lr_in_ip_input.*icmp6_error" lr0flows | sort], [0], [dnl
   table=3 (lr_in_ip_input     ), priority=150  , match=(inport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
 ])
 
+AT_CHECK([grep -E "lr_out_post_snat.*ct_commit_nat" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(lr_out_post_snat   ), priority=100  , match=(icmp && flags.icmp_snat == 1), action=(ct_commit_nat(snat);)
+])
+
 # Set gateway_mtu_bypass to avoid check_pkt_larger() for tcp on lr0-public.
 check ovn-nbctl --wait=sb set logical_router_port lr0-public options:gateway_mtu_bypass=tcp
 
@@ -6169,6 +6231,10 @@ AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sed 's/table=.
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
 ])
 
 AT_CHECK([grep "lr_in_admission" lr0flows | grep -e "check_pkt_larger" -e "tcp" | sort], [0], [dnl
@@ -6198,6 +6264,14 @@ AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sed 's/table=.
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
 ])
 
 AT_CHECK([grep "lr_in_admission.*check_pkt_larger" lr0flows | sort], [0], [dnl
@@ -6216,6 +6290,10 @@ AT_CHECK([grep -E "lr_in_ip_input.*icmp6_error" lr0flows | sort], [0], [dnl
   table=3 (lr_in_ip_input     ), priority=150  , match=(inport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
 ])
 
+AT_CHECK([grep -E "lr_out_post_snat.*ct_commit_nat" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(lr_out_post_snat   ), priority=100  , match=(icmp && flags.icmp_snat == 1), action=(ct_commit_nat(snat);)
+])
+
 # Set gateway_mtu_bypass to avoid check_pkt_larger() for tcp on lr0-sw0.
 check ovn-nbctl --wait=sb set logical_router_port lr0-sw0 options:gateway_mtu_bypass=tcp
 
@@ -6237,6 +6315,14 @@ AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sed 's/table=.
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
 ])
 
 AT_CHECK([grep "lr_in_admission" lr0flows | grep -e "check_pkt_larger" -e "tcp" | sort], [0], [dnl
@@ -6264,6 +6350,10 @@ AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sed 's/table=.
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
   table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp4_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+  table=??(lr_in_larger_pkts  ), priority=160  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0 && ct.trk && ct.rpl && ct.dnat), action=(icmp6_error {flags.icmp_snat = 1; reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
 ])
 
 check ovn-nbctl --wait=sb clear logical_router_port lr0-sw0 options
@@ -6296,6 +6386,12 @@ AT_CHECK([grep "lr_in_admission" lr0flows | grep -e "check_pkt_larger" | sort],
   table=0 (lr_in_admission    ), priority=50   , match=(eth.mcast && inport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1518); xreg0[[0..47]] = 00:00:20:20:12:13; next;)
 ])
 
+check ovn-sbctl set chassis ch1 other_config:ct-commit-nat-v2=false
+check ovn-nbctl --wait=sb sync
+
+ovn-sbctl dump-flows lr0 > lr0flows
+AT_CHECK([grep -E "lr_out_post_snat.*ct_commit_nat" lr0flows], [1])
+
 AT_CLEANUP
 ])
 
@@ -6457,6 +6553,9 @@ AT_CAPTURE_FILE([lrflows])
 
 # Check the flows in lr_in_admission stage
 AT_CHECK([grep lr_in_admission lrflows | grep cr-DR | sort], [0], [dnl
+  table=0 (lr_in_admission    ), priority=120  , match=(((ip4 && icmp4.type == 3 && icmp4.code == 4) || (ip6 && icmp6.type == 2 && icmp6.code == 0)) && eth.dst == 02:ac:10:01:00:01 && !is_chassis_resident("cr-DR-S1") && flags.tunnel_rx == 1), action=(outport <-> inport; inport = "DR-S1"; next;)
+  table=0 (lr_in_admission    ), priority=120  , match=(((ip4 && icmp4.type == 3 && icmp4.code == 4) || (ip6 && icmp6.type == 2 && icmp6.code == 0)) && eth.dst == 03:ac:10:01:00:01 && !is_chassis_resident("cr-DR-S2") && flags.tunnel_rx == 1), action=(outport <-> inport; inport = "DR-S2"; next;)
+  table=0 (lr_in_admission    ), priority=120  , match=(((ip4 && icmp4.type == 3 && icmp4.code == 4) || (ip6 && icmp6.type == 2 && icmp6.code == 0)) && eth.dst == 04:ac:10:01:00:01 && !is_chassis_resident("cr-DR-S3") && flags.tunnel_rx == 1), action=(outport <-> inport; inport = "DR-S3"; next;)
   table=0 (lr_in_admission    ), priority=50   , match=(eth.dst == 02:ac:10:01:00:01 && inport == "DR-S1" && is_chassis_resident("cr-DR-S1")), action=(xreg0[[0..47]] = 02:ac:10:01:00:01; next;)
   table=0 (lr_in_admission    ), priority=50   , match=(eth.dst == 03:ac:10:01:00:01 && inport == "DR-S2" && is_chassis_resident("cr-DR-S2")), action=(xreg0[[0..47]] = 03:ac:10:01:00:01; next;)
   table=0 (lr_in_admission    ), priority=50   , match=(eth.dst == 04:ac:10:01:00:01 && inport == "DR-S3" && is_chassis_resident("cr-DR-S3")), action=(xreg0[[0..47]] = 04:ac:10:01:00:01; next;)
@@ -6516,6 +6615,7 @@ AT_CAPTURE_FILE([lrflows])
 
 # Check the flows in lr_in_admission stage
 AT_CHECK([grep lr_in_admission lrflows | grep lrp1 | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(lr_in_admission    ), priority=120  , match=(((ip4 && icmp4.type == 3 && icmp4.code == 4) || (ip6 && icmp6.type == 2 && icmp6.code == 0)) && eth.dst == 00:00:00:00:00:01 && !is_chassis_resident("cr-lrp1") && flags.tunnel_rx == 1), action=(outport <-> inport; inport = "lrp1"; next;)
   table=??(lr_in_admission    ), priority=50   , match=(eth.dst == 00:00:00:00:00:01 && inport == "lrp1" && is_chassis_resident("cr-lrp1")), action=(xreg0[[0..47]] = 00:00:00:00:00:01; next;)
   table=??(lr_in_admission    ), priority=50   , match=(eth.mcast && inport == "lrp1"), action=(xreg0[[0..47]] = 00:00:00:00:00:01; next;)
 ])
@@ -6537,6 +6637,7 @@ AT_CAPTURE_FILE([lrflows])
 
 # Check the flows in lr_in_admission stage
 AT_CHECK([grep lr_in_admission lrflows | grep lrp1 | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(lr_in_admission    ), priority=120  , match=(((ip4 && icmp4.type == 3 && icmp4.code == 4) || (ip6 && icmp6.type == 2 && icmp6.code == 0)) && eth.dst == 00:00:00:00:00:01 && !is_chassis_resident("cr-lrp1") && flags.tunnel_rx == 1), action=(outport <-> inport; inport = "lrp1"; next;)
   table=??(lr_in_admission    ), priority=50   , match=(eth.dst == 00:00:00:00:00:01 && inport == "lrp1"), action=(xreg0[[0..47]] = 00:00:00:00:00:01; next;)
   table=??(lr_in_admission    ), priority=50   , match=(eth.mcast && inport == "lrp1"), action=(xreg0[[0..47]] = 00:00:00:00:00:01; next;)
 ])
@@ -6555,6 +6656,7 @@ AT_CAPTURE_FILE([lrflows])
 
 # Check the flows in lr_in_admission stage
 AT_CHECK([grep lr_in_admission lrflows | grep lrp1 | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(lr_in_admission    ), priority=120  , match=(((ip4 && icmp4.type == 3 && icmp4.code == 4) || (ip6 && icmp6.type == 2 && icmp6.code == 0)) && eth.dst == 00:00:00:00:00:01 && !is_chassis_resident("cr-lrp1") && flags.tunnel_rx == 1), action=(outport <-> inport; inport = "lrp1"; next;)
   table=??(lr_in_admission    ), priority=50   , match=(eth.dst == 00:00:00:00:00:01 && inport == "lrp1" && is_chassis_resident("cr-lrp1")), action=(xreg0[[0..47]] = 00:00:00:00:00:01; next;)
   table=??(lr_in_admission    ), priority=50   , match=(eth.mcast && inport == "lrp1"), action=(xreg0[[0..47]] = 00:00:00:00:00:01; next;)
 ])
@@ -7361,9 +7463,9 @@ AT_CHECK([grep lr_in_unsnat lrflows | grep ct_snat | sed 's/table=../table=??/'
 ])
 
 AT_CHECK([grep lr_out_snat lrflows | grep ct_snat | sed 's/table=../table=??/' | sort], [0], [dnl
-  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_snat(172.16.1.10);)
-  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2")), action=(ct_snat(10.0.0.10);)
-  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3")), action=(ct_snat(192.168.0.10);)
+  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.10);)
+  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2") && (!ct.trk || !ct.rpl)), action=(ct_snat(10.0.0.10);)
+  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3") && (!ct.trk || !ct.rpl)), action=(ct_snat(192.168.0.10);)
 ])
 
 check ovn-nbctl --wait=sb lr-nat-del DR snat 20.0.0.10
@@ -7437,9 +7539,9 @@ AT_CHECK([grep lr_in_unsnat lrflows | grep ct_snat | sed 's/table=../table=??/'
 ])
 
 AT_CHECK([grep lr_out_snat lrflows | grep ct_snat | sed 's/table=../table=??/' | sort], [0], [dnl
-  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_snat(172.16.1.10);)
-  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2")), action=(ct_snat(10.0.0.10);)
-  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3")), action=(ct_snat(192.168.0.10);)
+  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.10);)
+  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2") && (!ct.trk || !ct.rpl)), action=(ct_snat(10.0.0.10);)
+  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3") && (!ct.trk || !ct.rpl)), action=(ct_snat(192.168.0.10);)
 ])
 
 AT_CHECK([grep lr_in_dnat lrflows | grep ct_dnat | sed 's/table=../table=??/' | sort], [0], [dnl
@@ -8283,6 +8385,7 @@ AT_CHECK([cat sw0flows | grep -e port_sec -e ls_in_l2_lkup -e ls_in_l2_unknown |
 sort | sed 's/table=../table=??/' ], [0], [dnl
   table=??(ls_in_check_port_sec), priority=100  , match=(eth.src[[40]]), action=(drop;)
   table=??(ls_in_check_port_sec), priority=100  , match=(vlan.present), action=(drop;)
+  table=??(ls_in_check_port_sec), priority=110  , match=(((ip4 && icmp4.type == 3 && icmp4.code == 4) || (ip6 && icmp6.type == 2 && icmp6.code == 0)) && flags.tunnel_rx == 1), action=(drop;)
   table=??(ls_in_check_port_sec), priority=50   , match=(1), action=(reg0[[15]] = check_in_port_sec(); next;)
   table=??(ls_in_apply_port_sec), priority=0    , match=(1), action=(next;)
   table=??(ls_in_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), action=(drop;)
@@ -8308,6 +8411,7 @@ AT_CHECK([cat sw0flows | grep -e port_sec -e ls_in_l2_lkup -e ls_in_l2_unknown |
 sort | sed 's/table=../table=??/' ], [0], [dnl
   table=??(ls_in_check_port_sec), priority=100  , match=(eth.src[[40]]), action=(drop;)
   table=??(ls_in_check_port_sec), priority=100  , match=(vlan.present), action=(drop;)
+  table=??(ls_in_check_port_sec), priority=110  , match=(((ip4 && icmp4.type == 3 && icmp4.code == 4) || (ip6 && icmp6.type == 2 && icmp6.code == 0)) && flags.tunnel_rx == 1), action=(drop;)
   table=??(ls_in_check_port_sec), priority=50   , match=(1), action=(reg0[[15]] = check_in_port_sec(); next;)
   table=??(ls_in_apply_port_sec), priority=0    , match=(1), action=(next;)
   table=??(ls_in_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), action=(drop;)
@@ -8334,6 +8438,7 @@ AT_CHECK([cat sw0flows | grep -e port_sec -e ls_in_l2_lkup -e ls_in_l2_unknown |
 sort | sed 's/table=../table=??/' ], [0], [dnl
   table=??(ls_in_check_port_sec), priority=100  , match=(eth.src[[40]]), action=(drop;)
   table=??(ls_in_check_port_sec), priority=100  , match=(vlan.present), action=(drop;)
+  table=??(ls_in_check_port_sec), priority=110  , match=(((ip4 && icmp4.type == 3 && icmp4.code == 4) || (ip6 && icmp6.type == 2 && icmp6.code == 0)) && flags.tunnel_rx == 1), action=(drop;)
   table=??(ls_in_check_port_sec), priority=50   , match=(1), action=(reg0[[15]] = check_in_port_sec(); next;)
   table=??(ls_in_apply_port_sec), priority=0    , match=(1), action=(next;)
   table=??(ls_in_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), action=(drop;)
@@ -8361,6 +8466,7 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
   table=??(ls_in_check_port_sec), priority=100  , match=(eth.src[[40]]), action=(drop;)
   table=??(ls_in_check_port_sec), priority=100  , match=(inport == "sw0p1"), action=(reg0[[15]] = 1; next;)
   table=??(ls_in_check_port_sec), priority=100  , match=(vlan.present), action=(drop;)
+  table=??(ls_in_check_port_sec), priority=110  , match=(((ip4 && icmp4.type == 3 && icmp4.code == 4) || (ip6 && icmp6.type == 2 && icmp6.code == 0)) && flags.tunnel_rx == 1), action=(drop;)
   table=??(ls_in_check_port_sec), priority=50   , match=(1), action=(reg0[[15]] = check_in_port_sec(); next;)
   table=??(ls_in_apply_port_sec), priority=0    , match=(1), action=(next;)
   table=??(ls_in_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), action=(drop;)
@@ -8387,6 +8493,7 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
   table=??(ls_in_check_port_sec), priority=100  , match=(eth.src[[40]]), action=(drop;)
   table=??(ls_in_check_port_sec), priority=100  , match=(inport == "sw0p1"), action=(reg0[[15]] = 1; next;)
   table=??(ls_in_check_port_sec), priority=100  , match=(vlan.present), action=(drop;)
+  table=??(ls_in_check_port_sec), priority=110  , match=(((ip4 && icmp4.type == 3 && icmp4.code == 4) || (ip6 && icmp6.type == 2 && icmp6.code == 0)) && flags.tunnel_rx == 1), action=(drop;)
   table=??(ls_in_check_port_sec), priority=50   , match=(1), action=(reg0[[15]] = check_in_port_sec(); next;)
   table=??(ls_in_check_port_sec), priority=70   , match=(inport == "sw0p2"), action=(set_queue(10); reg0[[15]] = check_in_port_sec(); next;)
   table=??(ls_in_apply_port_sec), priority=0    , match=(1), action=(next;)
@@ -8416,6 +8523,7 @@ AT_CHECK([cat sw0flows | grep -e port_sec -e ls_in_l2_lkup -e ls_in_l2_unknown |
 sort | sed 's/table=../table=??/' ], [0], [dnl
   table=??(ls_in_check_port_sec), priority=100  , match=(eth.src[[40]]), action=(drop;)
   table=??(ls_in_check_port_sec), priority=100  , match=(vlan.present), action=(drop;)
+  table=??(ls_in_check_port_sec), priority=110  , match=(((ip4 && icmp4.type == 3 && icmp4.code == 4) || (ip6 && icmp6.type == 2 && icmp6.code == 0)) && flags.tunnel_rx == 1), action=(drop;)
   table=??(ls_in_check_port_sec), priority=50   , match=(1), action=(reg0[[15]] = check_in_port_sec(); next;)
   table=??(ls_in_check_port_sec), priority=70   , match=(inport == "localnetport"), action=(set_queue(10); reg0[[15]] = check_in_port_sec(); next;)
   table=??(ls_in_check_port_sec), priority=70   , match=(inport == "sw0p1"), action=(reg0[[14]] = 1; next(pipeline=ingress, table=17);)
@@ -8618,7 +8726,7 @@ AT_CHECK([ovn-sbctl dump-flows ls0 | grep -e 'ls_in_\(put\|lookup\)_fdb' | sort
 AT_CHECK([ovn-nbctl --wait=sb lsp-set-options ln_port localnet_learn_fdb=true])
 AT_CHECK([ovn-sbctl dump-flows ls0 | grep -e 'ls_in_\(put\|lookup\)_fdb' | sort | sed 's/table=./table=?/'], [0], [dnl
   table=? (ls_in_lookup_fdb   ), priority=0    , match=(1), action=(next;)
-  table=? (ls_in_lookup_fdb   ), priority=100  , match=(inport == "ln_port"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
+  table=? (ls_in_lookup_fdb   ), priority=100  , match=(inport == "ln_port"), action=(flags.localnet = 1; reg0[[11]] = lookup_fdb(inport, eth.src); next;)
   table=? (ls_in_put_fdb      ), priority=0    , match=(1), action=(next;)
   table=? (ls_in_put_fdb      ), priority=100  , match=(inport == "ln_port" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
 ])
@@ -8701,7 +8809,7 @@ AT_CHECK([grep "ls_in_lb " S1flows | sed 's/table=../table=??/' | sort], [0], [d
 
 ovn-sbctl get datapath S0 _uuid > dp_uuids
 ovn-sbctl get datapath S1 _uuid >> dp_uuids
-lb_dp_group=$(ovn-sbctl --bare --columns datapath_group find Load_Balancer name=lb0)
+lb_dp_group=$(ovn-sbctl --bare --columns ls_datapath_group find Load_Balancer name=lb0)
 AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl
                     | grep -A1 $lb_dp_group | tail -1 | tr ' ' '\n' | sort], [0], [dnl
 $(cat dp_uuids | sort)
@@ -8827,8 +8935,8 @@ AT_CHECK([grep "ls_in_lb_aff_check" S0flows | sed 's/table=../table=??/' | sort]
 AT_CHECK([grep "ls_in_lb " S0flows | sed 's/table=../table=??/' | sort], [0], [dnl
   table=??(ls_in_lb           ), priority=0    , match=(1), action=(next;)
   table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 172.16.0.10 && tcp.dst == 80), action=(reg0[[1]] = 0; ct_lb_mark(backends=10.0.0.2:80,20.0.0.2:80);)
-  table=??(ls_in_lb           ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4 && reg4 == 10.0.0.2 && reg8[[0..15]] == 80), action=(reg0[[1]] = 0; reg1 = 172.16.0.10; reg2[[0..15]] = 80; ct_lb_mark(backends=10.0.0.2:80);)
-  table=??(ls_in_lb           ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4 && reg4 == 20.0.0.2 && reg8[[0..15]] == 80), action=(reg0[[1]] = 0; reg1 = 172.16.0.10; reg2[[0..15]] = 80; ct_lb_mark(backends=20.0.0.2:80);)
+  table=??(ls_in_lb           ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4.dst == 172.16.0.10 && reg4 == 10.0.0.2 && reg8[[0..15]] == 80), action=(reg0[[1]] = 0; reg1 = 172.16.0.10; reg2[[0..15]] = 80; ct_lb_mark(backends=10.0.0.2:80);)
+  table=??(ls_in_lb           ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4.dst == 172.16.0.10 && reg4 == 20.0.0.2 && reg8[[0..15]] == 80), action=(reg0[[1]] = 0; reg1 = 172.16.0.10; reg2[[0..15]] = 80; ct_lb_mark(backends=20.0.0.2:80);)
 ])
 AT_CHECK([grep "ls_in_lb_aff_learn" S0flows | sed 's/table=../table=??/' | sort], [0], [dnl
   table=??(ls_in_lb_aff_learn ), priority=0    , match=(1), action=(next;)
@@ -8847,8 +8955,8 @@ AT_CHECK([grep "lr_in_lb_aff_check" R1flows | sed 's/table=../table=??/' | sort]
 AT_CHECK([grep "lr_in_dnat " R1flows | sed 's/table=../table=??/' | sort], [0], [dnl
   table=??(lr_in_dnat         ), priority=0    , match=(1), action=(next;)
   table=??(lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.16.0.10 && tcp && tcp.dst == 80), action=(ct_lb_mark(backends=10.0.0.2:80,20.0.0.2:80);)
-  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4 && reg4 == 10.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; ct_lb_mark(backends=10.0.0.2:80);)
-  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4 && reg4 == 20.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; ct_lb_mark(backends=20.0.0.2:80);)
+  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4.dst == 172.16.0.10 && reg4 == 10.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; ct_lb_mark(backends=10.0.0.2:80);)
+  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4.dst == 172.16.0.10 && reg4 == 20.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; ct_lb_mark(backends=20.0.0.2:80);)
   table=??(lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
   table=??(lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
   table=??(lr_in_dnat         ), priority=70   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; next;)
@@ -8871,8 +8979,8 @@ AT_CAPTURE_FILE([R1flows_skip_snat])
 AT_CHECK([grep "lr_in_dnat " R1flows_skip_snat | sed 's/table=../table=??/' | sort], [0], [dnl
   table=??(lr_in_dnat         ), priority=0    , match=(1), action=(next;)
   table=??(lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.16.0.10 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80,20.0.0.2:80; skip_snat);)
-  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4 && reg4 == 10.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; flags.skip_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80; skip_snat);)
-  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4 && reg4 == 20.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; flags.skip_snat_for_lb = 1; ct_lb_mark(backends=20.0.0.2:80; skip_snat);)
+  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4.dst == 172.16.0.10 && reg4 == 10.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; flags.skip_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80; skip_snat);)
+  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4.dst == 172.16.0.10 && reg4 == 20.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; flags.skip_snat_for_lb = 1; ct_lb_mark(backends=20.0.0.2:80; skip_snat);)
   table=??(lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
   table=??(lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
   table=??(lr_in_dnat         ), priority=70   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; next;)
@@ -8892,8 +9000,8 @@ AT_CAPTURE_FILE([R1flows_force_snat])
 AT_CHECK([grep "lr_in_dnat " R1flows_force_snat | sed 's/table=../table=??/' | sort], [0], [dnl
   table=??(lr_in_dnat         ), priority=0    , match=(1), action=(next;)
   table=??(lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.16.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80,20.0.0.2:80; force_snat);)
-  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4 && reg4 == 10.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80; force_snat);)
-  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4 && reg4 == 20.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; flags.force_snat_for_lb = 1; ct_lb_mark(backends=20.0.0.2:80; force_snat);)
+  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4.dst == 172.16.0.10 && reg4 == 10.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80; force_snat);)
+  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4.dst == 172.16.0.10 && reg4 == 20.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; flags.force_snat_for_lb = 1; ct_lb_mark(backends=20.0.0.2:80; force_snat);)
   table=??(lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
   table=??(lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
   table=??(lr_in_dnat         ), priority=70   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; next;)
@@ -8912,8 +9020,35 @@ AT_CAPTURE_FILE([R1flows_force_skip_snat])
 AT_CHECK([grep "lr_in_dnat " R1flows_force_skip_snat | sed 's/table=../table=??/' | sort], [0], [dnl
   table=??(lr_in_dnat         ), priority=0    , match=(1), action=(next;)
   table=??(lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.16.0.10 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80,20.0.0.2:80; skip_snat);)
-  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4 && reg4 == 10.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; flags.skip_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80; skip_snat);)
-  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4 && reg4 == 20.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; flags.skip_snat_for_lb = 1; ct_lb_mark(backends=20.0.0.2:80; skip_snat);)
+  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4.dst == 172.16.0.10 && reg4 == 10.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; flags.skip_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80; skip_snat);)
+  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4.dst == 172.16.0.10 && reg4 == 20.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; flags.skip_snat_for_lb = 1; ct_lb_mark(backends=20.0.0.2:80; skip_snat);)
+  table=??(lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
+  table=??(lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
+  table=??(lr_in_dnat         ), priority=70   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; next;)
+  table=??(lr_in_dnat         ), priority=70   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; next;)
+  table=??(lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
+  table=??(lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
+])
+
+AS_BOX([Test LR flows - 2 LBs, lb0 skip_snat=true, lb1 lb_force_snat_ip="172.16.0.1"])
+check ovn-nbctl set logical_router R1 options:lb_force_snat_ip="172.16.0.1"
+check ovn-nbctl set load_balancer lb0 options:skip_snat=true
+check ovn-nbctl lb-add lb1 172.16.0.20:80 10.0.0.2:80,20.0.0.2:80 tcp
+check ovn-nbctl set load_balancer lb1 options:affinity_timeout=60
+check ovn-nbctl lr-lb-add R1 lb1
+check ovn-nbctl --wait=sb sync
+
+ovn-sbctl dump-flows R1 > R1flows_2lbs
+AT_CAPTURE_FILE([R1flows_2lbs])
+
+AT_CHECK([grep "lr_in_dnat " R1flows_2lbs | sed 's/table=../table=??/' | sort], [0], [dnl
+  table=??(lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+  table=??(lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.16.0.10 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80,20.0.0.2:80; skip_snat);)
+  table=??(lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.16.0.20 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80,20.0.0.2:80; force_snat);)
+  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4.dst == 172.16.0.10 && reg4 == 10.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; flags.skip_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80; skip_snat);)
+  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4.dst == 172.16.0.10 && reg4 == 20.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; flags.skip_snat_for_lb = 1; ct_lb_mark(backends=20.0.0.2:80; skip_snat);)
+  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4.dst == 172.16.0.20 && reg4 == 10.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.20; flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80; force_snat);)
+  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4.dst == 172.16.0.20 && reg4 == 20.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.20; flags.force_snat_for_lb = 1; ct_lb_mark(backends=20.0.0.2:80; force_snat);)
   table=??(lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
   table=??(lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
   table=??(lr_in_dnat         ), priority=70   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; next;)
@@ -8922,6 +9057,7 @@ AT_CHECK([grep "lr_in_dnat " R1flows_force_skip_snat | sed 's/table=../table=??/
   table=??(lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
 ])
 
+
 AT_CLEANUP
 ])
 
@@ -8985,7 +9121,6 @@ grep -c mutate], [0], [2
 # Pause ovn-northd and add/remove few addresses.  when it is resumed
 # it should use mutate for updating the address sets.
 check as northd ovn-appctl -t NORTHD_TYPE pause
-check as northd-backup ovn-appctl -t NORTHD_TYPE pause
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 check ovn-nbctl add address_set $foo_as_uuid addresses 1.1.1.5
@@ -10008,14 +10143,21 @@ ovs-vsctl add-br br-phys
 ovn_attach n1 br-phys 192.168.0.11
 
 check_recompute_counter() {
+    northd_recomp_min=$1
+    northd_recomp_max=$2
+    lflow_recomp_min=$3
+    lflow_recomp_max=$4
+    sync_sb_pb_recomp_min=$5
+    sync_sb_pb_recomp_max=$6
+
     northd_recomp=$(as northd ovn-appctl -t NORTHD_TYPE inc-engine/show-stats northd recompute)
-    AT_CHECK([test x$northd_recomp = x$1])
+    AT_CHECK([test $northd_recomp -ge $northd_recomp_min && test $northd_recomp -le $northd_recomp_max])
 
     lflow_recomp=$(as northd ovn-appctl -t NORTHD_TYPE inc-engine/show-stats lflow recompute)
-    AT_CHECK([test x$lflow_recomp = x$2])
+    AT_CHECK([test $lflow_recomp -ge $lflow_recomp_min && test $lflow_recomp -le $lflow_recomp_max])
 
     sync_sb_pb_recomp=$(as northd ovn-appctl -t NORTHD_TYPE inc-engine/show-stats sync_to_sb_pb recompute)
-    AT_CHECK([test x$sync_sb_pb_recomp = x$3])
+    AT_CHECK([test $sync_sb_pb_recomp -ge $sync_sb_pb_recomp_min && test $sync_sb_pb_recomp -le $sync_sb_pb_recomp_max])
 }
 
 check ovn-nbctl --wait=hv ls-add ls0
@@ -10032,29 +10174,29 @@ check ovn-nbctl --wait=hv lsp-add ls0 lsp0-0 -- lsp-set-addresses lsp0-0 "unknow
 ovs-vsctl add-port br-int lsp0-0 -- set interface lsp0-0 external_ids:iface-id=lsp0-0
 wait_for_ports_up
 check ovn-nbctl --wait=hv sync
-check_recompute_counter 5 5 5
+check_recompute_counter 4 5 5 5 5 5
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 check ovn-nbctl --wait=hv lsp-add ls0 lsp0-1 -- lsp-set-addresses lsp0-1 "aa:aa:aa:00:00:01 192.168.0.11"
 ovs-vsctl add-port br-int lsp0-1 -- set interface lsp0-1 external_ids:iface-id=lsp0-1
 wait_for_ports_up
 check ovn-nbctl --wait=hv sync
-check_recompute_counter 0 0 0
+check_recompute_counter 0 0 0 0 0 0
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 check ovn-nbctl --wait=hv lsp-add ls0 lsp0-2 -- lsp-set-addresses lsp0-2 "aa:aa:aa:00:00:02 192.168.0.12"
 ovs-vsctl add-port br-int lsp0-2 -- set interface lsp0-2 external_ids:iface-id=lsp0-2
 wait_for_ports_up
 check ovn-nbctl --wait=hv sync
-check_recompute_counter 0 0 0
+check_recompute_counter 0 0 0 0 0 0
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 check ovn-nbctl --wait=hv lsp-del lsp0-1
-check_recompute_counter 0 0 0
+check_recompute_counter 0 0 0 0 0 0
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 check ovn-nbctl --wait=hv lsp-set-addresses lsp0-2 "aa:aa:aa:00:00:88 192.168.0.88"
-check_recompute_counter 0 0 0
+check_recompute_counter 0 0 0 0 0 0
 
 # Delete and re-add a LSP for several times continuously, to ensure
 # frequent operations do not trigger recompute when there are in-flight
@@ -10068,14 +10210,14 @@ for i in $(seq 10); do
     check ovn-nbctl lsp-del lsp0-2
     check ovn-nbctl lsp-add ls0 lsp0-2 -- lsp-set-addresses lsp0-2 "aa:aa:aa:00:00:02 192.168.0.12"
 done
-check_recompute_counter 0 0 0
+check_recompute_counter 0 0 0 0 0 0
 
 # No change, no recompute
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 check ovn-nbctl --wait=sb sync
-check_recompute_counter 0 0 0
+check_recompute_counter 0 0 0 0 0 0
 
-CHECK_NO_CHANGE_AFTER_RECOMPUTE
+CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 
 # Associate DHCP for lsp0-2
 ovn-nbctl dhcp-options-create 192.168.0.0/24
@@ -10085,9 +10227,9 @@ ovn-nbctl dhcp-options-set-options $CIDR_UUID   lease_time=3600  router=192.168.
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 ovn-nbctl --wait=sb lsp-set-dhcpv4-options lsp0-2 $CIDR_UUID
-check_recompute_counter 0 0 0
+check_recompute_counter 0 0 0 0 0 0
 
-CHECK_NO_CHANGE_AFTER_RECOMPUTE
+CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 
 # Add IPv6 address and associate DHCPv6 for lsp0-2
 check ovn-nbctl lsp-set-addresses lsp0-2 "aa:aa:aa:00:00:01 192.168.0.11 aef0::4"
@@ -10096,9 +10238,9 @@ options="\"server_id\"=\"00:00:00:10:00:01\"")"
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 ovn-nbctl --wait=sb lsp-set-dhcpv6-options lsp0-2 ${d1}
-check_recompute_counter 0 0 0
+check_recompute_counter 0 0 0 0 0 0
 
-CHECK_NO_CHANGE_AFTER_RECOMPUTE
+CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 
 check ovn-nbctl --wait=hv ls-del ls0
 
@@ -10125,11 +10267,11 @@ ovn-nbctl lsp-add ls0 ls0-lr0
 ovn-nbctl lsp-set-type ls0-lr0 router
 ovn-nbctl lsp-set-addresses ls0-lr0 router
 check ovn-nbctl --wait=sb lsp-set-options ls0-lr0 router-port=lr0-ls0
-CHECK_NO_CHANGE_AFTER_RECOMPUTE
+CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 
 ovn-nbctl lb-add lb0 192.168.0.10:80 10.0.0.10:8080
 check ovn-nbctl --wait=sb ls-lb-add ls0 lb0
-CHECK_NO_CHANGE_AFTER_RECOMPUTE
+CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 # Add a lsp.  northd and lflow engine shouldn't recompute even though this is
@@ -10216,14 +10358,14 @@ check ovn-nbctl --wait=sb meter-add m drop 1 pktps
 check ovn-nbctl --wait=sb acl-add ls from-lport 1 1 allow
 dnl Only triggers recompute of the sync_meters and lflow nodes.
 check_recompute_counter 0 2 2
-CHECK_NO_CHANGE_AFTER_RECOMPUTE
+CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 check ovn-nbctl --wait=sb meter-del m
 check ovn-nbctl --wait=sb acl-del ls
 dnl Only triggers recompute of the sync_meters and lflow nodes.
 check_recompute_counter 0 2 2
-CHECK_NO_CHANGE_AFTER_RECOMPUTE
+CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 
 AT_CLEANUP
 ])
@@ -10339,7 +10481,7 @@ wait_for_ports_up sw0-r1
 check_column $remote_chassis_uuid Port_Binding chassis logical_port=sw0-r1
 
 # Set the type to router and ovn-northd should not claim it.
-check ovn-nbctl lsp-set-type sw0-r1 router
+check ovn-nbctl --wait=hv lsp-set-type sw0-r1 router
 check_column '' Port_Binding chassis logical_port=sw0-r1
 
 AT_CLEANUP
@@ -10391,34 +10533,39 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 
-CHECK_NO_CHANGE_AFTER_RECOMPUTE
+CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 
 check ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 
 check ovn-nbctl --wait=sb set load_balancer . options:foo=bar
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 
 check ovn-nbctl --wait=sb -- lb-add lb2 20.0.0.10:80 20.0.0.20:80 -- lb-add lb3 30.0.0.10:80 30.0.0.20:80
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 
-CHECK_NO_CHANGE_AFTER_RECOMPUTE
+CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 
 check ovn-nbctl --wait=sb -- lb-del lb2 -- lb-del lb3
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 
-CHECK_NO_CHANGE_AFTER_RECOMPUTE
+CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 
 AT_CHECK([ovn-nbctl --wait=sb \
@@ -10429,6 +10576,7 @@ AT_CHECK([ovn-nbctl --wait=sb \
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 
 # Any change to load balancer health check should also result in full recompute
 # of northd node (but not northd_lb_data node)
@@ -10437,6 +10585,7 @@ check ovn-nbctl --wait=sb set load_balancer_health_check . options:foo=bar1
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 
 # Delete the health check from the load balancer.  northd engine node should do a full recompute.
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
@@ -10444,6 +10593,7 @@ check ovn-nbctl --wait=sb clear Load_Balancer . health_check
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 check ovn-nbctl ls-add sw0
@@ -10457,6 +10607,7 @@ ovn-nbctl --wait=sb lsp-set-options sw0-lr0 router-port=lr0-sw0
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 
 # Associate lb1 to sw0. There should be no recompute of northd engine node
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
@@ -10464,7 +10615,11 @@ check ovn-nbctl --wait=sb ls-lb-add sw0 lb1
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
-CHECK_NO_CHANGE_AFTER_RECOMPUTE
+# A LB applied to a switch/router triggers:
+# - a recompute in the first iteration (handling northd change)
+# - a compute in the second iteration (handling SB update)
+check_engine_stats sync_to_sb_lb recompute compute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 
 # Modify the backend of the lb1 vip
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
@@ -10472,7 +10627,8 @@ check ovn-nbctl --wait=sb set load_balancer lb1 vips:'"10.0.0.10:80"'='"10.0.0.1
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
-CHECK_NO_CHANGE_AFTER_RECOMPUTE
+check_engine_stats sync_to_sb_lb recompute compute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 
 # Cleanup the vip of lb1.
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
@@ -10480,7 +10636,8 @@ check ovn-nbctl --wait=sb clear load_Balancer lb1 vips
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
-CHECK_NO_CHANGE_AFTER_RECOMPUTE
+check_engine_stats sync_to_sb_lb recompute compute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 
 # Set the vips of lb1 back
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
@@ -10488,7 +10645,8 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
-CHECK_NO_CHANGE_AFTER_RECOMPUTE
+check_engine_stats sync_to_sb_lb recompute compute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 
 # Add another vip to lb1
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
@@ -10496,7 +10654,8 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.20:80 10.0.0.30:8080
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
-CHECK_NO_CHANGE_AFTER_RECOMPUTE
+check_engine_stats sync_to_sb_lb recompute compute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 
 # Disassociate lb1 from sw0. There should be a full recompute of northd engine node.
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
@@ -10504,7 +10663,8 @@ check ovn-nbctl --wait=sb ls-lb-del sw0 lb1
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
 check_engine_stats lflow recompute nocompute
-CHECK_NO_CHANGE_AFTER_RECOMPUTE
+check_engine_stats sync_to_sb_lb recompute compute
+CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 
 # Associate lb1 to sw0 and also create a port sw0p1.  This should not result in
 # full recompute of northd, but should rsult in full recompute of lflow node.
@@ -10513,6 +10673,7 @@ check ovn-nbctl --wait=sb ls-lb-add sw0 lb1 -- lsp-add sw0 sw0p1
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
@@ -10523,6 +10684,7 @@ check ovn-nbctl --wait=sb ls-lb-del sw0 lb1
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Add lb1 to lr0 and then disassociate
@@ -10531,6 +10693,7 @@ check ovn-nbctl --wait=sb lr-lb-add lr0 lb1
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Modify the backend of the lb1 vip
@@ -10539,6 +10702,7 @@ check ovn-nbctl --wait=sb set load_balancer lb1 vips:'"10.0.0.10:80"'='"10.0.0.1
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Cleanup the vip of lb1.
@@ -10547,6 +10711,7 @@ check ovn-nbctl --wait=sb clear load_Balancer lb1 vips
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Set the vips of lb1 back
@@ -10555,6 +10720,7 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Add another vip to lb1
@@ -10563,6 +10729,7 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.20:80 10.0.0.30:8080
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
@@ -10570,14 +10737,16 @@ check ovn-nbctl --wait=sb lr-lb-del lr0 lb1
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Test load balancer group now
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
-lbg1_uuid=$(ovn-nbctl create load_balancer_group name=lbg1)
+lbg1_uuid=$(ovn-nbctl --wait=sb create load_balancer_group name=lbg1)
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
@@ -10586,31 +10755,35 @@ lb1_uuid=$(fetch_column nb:Load_Balancer _uuid)
 
 # Add lb to the lbg1 group
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
-check ovn-nbctl add load_balancer_group . load_Balancer $lb1_uuid
+check ovn-nbctl --wait=sb add load_balancer_group . load_Balancer $lb1_uuid
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
-check ovn-nbctl clear load_balancer_group . load_Balancer
+check ovn-nbctl --wait=sb clear load_balancer_group . load_Balancer
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 
 # Add back lb to the lbg1 group
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
-check ovn-nbctl add load_balancer_group . load_Balancer $lb1_uuid
+check ovn-nbctl --wait=sb add load_balancer_group . load_Balancer $lb1_uuid
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
-check ovn-nbctl add logical_switch sw0 load_balancer_group $lbg1_uuid
+check ovn-nbctl --wait=sb add logical_switch sw0 load_balancer_group $lbg1_uuid
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 
 # Update lb and this should not result in northd recompute
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
@@ -10618,6 +10791,7 @@ check ovn-nbctl --wait=sb set load_balancer . options:bar=foo
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 
 # Modify the backend of the lb1 vip
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
@@ -10625,6 +10799,7 @@ check ovn-nbctl --wait=sb set load_balancer lb1 vips:'"10.0.0.10:80"'='"10.0.0.1
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Cleanup the vip of lb1.
@@ -10633,6 +10808,7 @@ check ovn-nbctl --wait=sb clear load_Balancer lb1 vips
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Set the vips of lb1 back
@@ -10641,6 +10817,7 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Add another vip to lb1
@@ -10649,19 +10826,22 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.20:80 10.0.0.30:8080
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
-check ovn-nbctl clear logical_switch sw0 load_balancer_group
+check ovn-nbctl --wait=sb clear logical_switch sw0 load_balancer_group
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 check ovn-nbctl add logical_router lr0 load_balancer_group $lbg1_uuid
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Modify the backend of the lb1 vip
@@ -10670,6 +10850,7 @@ check ovn-nbctl --wait=sb set load_balancer lb1 vips:'"10.0.0.10:80"'='"10.0.0.1
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Cleanup the vip of lb1.
@@ -10678,6 +10859,7 @@ check ovn-nbctl --wait=sb clear load_Balancer lb1 vips
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Set the vips of lb1 back
@@ -10686,6 +10868,7 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Add another vip to lb1
@@ -10694,27 +10877,31 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.20:80 10.0.0.30:8080
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
-check ovn-nbctl clear logical_router lr0 load_balancer_group
+check ovn-nbctl --wait=sb clear logical_router lr0 load_balancer_group
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 
 # Add back lb group to logical switch and then delete it.
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
-check ovn-nbctl add logical_switch sw0 load_balancer_group $lbg1_uuid
+check ovn-nbctl --wait=sb add logical_switch sw0 load_balancer_group $lbg1_uuid
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
-check ovn-nbctl clear logical_switch sw0 load_balancer_group -- \
+check ovn-nbctl --wait=sb clear logical_switch sw0 load_balancer_group -- \
     destroy load_balancer_group $lbg1_uuid
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb compute compute
 
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
@@ -10733,29 +10920,33 @@ lb3_uuid=$(fetch_column nb:Load_Balancer _uuid name=lb3)
 lb4_uuid=$(fetch_column nb:Load_Balancer _uuid name=lb4)
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
-lbg1_uuid=$(ovn-nbctl create load_balancer_group name=lbg1)
+lbg1_uuid=$(ovn-nbctl --wait=sb create load_balancer_group name=lbg1)
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
-check ovn-nbctl set load_balancer_group . load_balancer="$lb2_uuid,$lb3_uuid,$lb4_uuid"
+check ovn-nbctl --wait=sb set load_balancer_group . load_balancer="$lb2_uuid,$lb3_uuid,$lb4_uuid"
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
-check ovn-nbctl set logical_switch sw0 load_balancer_group=$lbg1_uuid
+check ovn-nbctl --wait=sb set logical_switch sw0 load_balancer_group=$lbg1_uuid
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
-check ovn-nbctl set logical_router lr1 load_balancer_group=$lbg1_uuid
+check ovn-nbctl --wait=sb set logical_router lr1 load_balancer_group=$lbg1_uuid
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
@@ -10763,6 +10954,7 @@ check ovn-nbctl --wait=sb ls-lb-add sw0 lb2
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
@@ -10770,6 +10962,7 @@ check ovn-nbctl --wait=sb ls-lb-add sw0 lb3
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
@@ -10778,6 +10971,7 @@ check ovn-nbctl --wait=sb lr-lb-add lr1 lb2
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
@@ -10785,6 +10979,7 @@ check ovn-nbctl --wait=sb ls-lb-del sw0 lb2
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
@@ -10792,6 +10987,7 @@ check ovn-nbctl --wait=sb lr-lb-del lr1 lb2
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Deleting lb4 should not result in lflow recompute as it is
@@ -10801,6 +10997,7 @@ check ovn-nbctl --wait=sb lb-del lb4
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Deleting lb2 should result in lflow recompute as it is
@@ -10810,6 +11007,7 @@ check ovn-nbctl --wait=sb lb-del lb2
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
@@ -10817,7 +11015,61 @@ check ovn-nbctl --wait=sb remove load_balancer_group . load_balancer $lb3_uuid
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([Load balancer incremental processing - batched updates])
+ovn_start
+
+# Check the scenario when a LB is created and quickly deleted (northd
+# processes this in a single iteration).
+
+check ovn-nbctl ls-add sw0
+lbg_uuid=$(ovn-nbctl --wait=sb create load_balancer_group name=lbg)
+check ovn-nbctl --wait=sb add logical_switch sw0 load_balancer_group $lbg_uuid
+
+# Pause ovn-northd.
+sleep_northd
+
+check ovn-nbctl lb-add lb-temp 50.0.0.10:80 50.0.0.20:8080
+lb_temp_uuid=$(fetch_column nb:Load_Balancer _uuid name=lb-temp)
+check ovn-nbctl add load_balancer_group $lbg_uuid load_balancer $lb_temp_uuid
+check ovn-nbctl lb-del lb-temp
+
+# Let ovn-northd process all the updates that happened since it went to sleep.
+wake_up_northd
+
+# Add a new load balancer to make sure northd still functions properly.
+check ovn-nbctl lb-add lb1 50.0.0.10:80 50.0.0.30:8080
+lb1_uuid=$(fetch_column nb:Load_Balancer _uuid name=lb1)
+check ovn-nbctl add load_balancer_group $lbg_uuid load_balancer $lb1_uuid
+
+check ovn-nbctl --wait=sb sync
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+# Re-check the same scenario but now also batch the additional LB creation.
+sleep_northd
+check ovn-nbctl lb-add lb-temp 50.0.0.10:80 50.0.0.20:8080
+lb_temp_uuid=$(fetch_column nb:Load_Balancer _uuid name=lb-temp)
+check ovn-nbctl add load_balancer_group $lbg_uuid load_balancer $lb_temp_uuid
+check ovn-nbctl lb-del lb-temp
+
+# Add a new load balancer to make sure northd still functions properly.
+check ovn-nbctl lb-add lb2 50.0.0.10:80 50.0.0.30:8080
+lb2_uuid=$(fetch_column nb:Load_Balancer _uuid name=lb2)
+check ovn-nbctl add load_balancer_group $lbg_uuid load_balancer $lb2_uuid
+
+wake_up_northd
+check ovn-nbctl --wait=sb sync
+CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
+AT_CHECK([as northd ovn-appctl -t NORTHD_TYPE status], [0], [dnl
+Status: active
+])
+
+AT_CLEANUP
+])
diff --git a/tests/ovn.at b/tests/ovn.at
index e127530f6..07cc0e515 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -23,8 +23,11 @@ m4_divert_text([PREPARE_TESTS],
      diff -u $exp_text.sorted $rcv_text.sorted
    }
    ovn_check_packets__ () {
-     echo
-     echo "$3: checking packets in $1 against $2:"
+     if [[ -n "$4" ]]; then
+       echo "$3: checking packets in $1 against $2: using $4"
+     else
+       echo "$3: checking packets in $1 against $2:"
+     fi
      rcv_pcap=$1
      rcv_text=`echo "$rcv_pcap.packets" | sed 's/\.pcap//'`
      exp_text=$2
@@ -35,7 +38,13 @@ m4_divert_text([PREPARE_TESTS],
         echo "rcv_n=$rcv_n exp_n=$exp_n"
         test $rcv_n -ge $exp_n],
        [dump_diff__ "$rcv_pcap" "$exp_text"])
-     sort $exp_text > expout
+     if [[ -n "$4" ]]; then
+       sort $exp_text | $4 > expout
+       cat $rcv_text | $4 > rcv_tmp
+       mv rcv_tmp $rcv_text
+     else
+       sort $exp_text > expout
+     fi
    }
    ovn_check_packets_remove_broadcast__ () {
      echo "$3: checking packets in $1 against $2:"
@@ -54,14 +63,26 @@ m4_divert_text([PREPARE_TESTS],
    }
    ovn_wait_packets__ () {
      echo "$3: waiting for packets from $2 at $1:"
+     if [[ -n "$4" ]]; then
+       echo "$3: checking packets from $2 at $1: using $4"
+     else
+       echo "$3: checking packets from $2 at $1:"
+     fi
      rcv_pcap=$1
      rcv_text=`echo "$rcv_pcap.packets" | sed 's/\.pcap//'`
      exp_text=$2
+     if [[ -n "$4" ]]; then
+       cmd=$4
+     else
+       cmd=cat
+     fi
      OVS_WAIT_UNTIL(
        [$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $rcv_pcap > $rcv_text
-        sort $exp_text > expout
-        test x"$(sort $rcv_text | comm -2 -3 expout -)" = "x"],
+        sort $exp_text | $cmd > expout
+        test x"$(sort $rcv_text | $cmd | comm -2 -3 expout -)" = "x"],
        [dump_diff__ "$rcv_pcap" "$exp_text"])
+     cat $rcv_text | $cmd > rcv_tmp
+     mv rcv_tmp $rcv_text
    }
    ovn_wait_packets_uniq__ () {
      echo "$3: waiting for packets from $2 at $1:"
@@ -140,7 +161,7 @@ m4_divert_text([PREPARE_TESTS],
 
 m4_define([OVN_CHECK_PACKETS],
   [AT_CHECK([$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $1 ], [0], [ignore])
-   ovn_check_packets__ "$1" "$2" "__file__:__line__"
+   ovn_check_packets__ "$1" "$2" "__file__:__line__" $3
    AT_CHECK([sort $rcv_text], [0], [expout], [ignore], [dump_diff__ "$1" "$2"])])
 
 m4_define([OVN_CHECK_PACKETS_REMOVE_BROADCAST],
@@ -148,7 +169,7 @@ m4_define([OVN_CHECK_PACKETS_REMOVE_BROADCAST],
    AT_CHECK([sort $rcv_text], [0], [expout], [ignore], [dump_diff__ "$1" "$2"])])
 
 m4_define([OVN_CHECK_PACKETS_CONTAIN],
-  [ovn_wait_packets__ "$1" "$2" "__file__:__line__"])
+  [ovn_wait_packets__ "$1" "$2" "__file__:__line__" $3])
 
 # OVN_CHECK_PACKETS_UNIQ succeeds if some expected packets are duplicated.
 # It fails if unexpected packets are received.
@@ -1347,7 +1368,7 @@ ct_dnat(fd11::2);
     has prereqs ip
 ct_dnat(192.168.1.2, 1-3000);
     formats as ct_dnat(192.168.1.2,1-3000);
-    encodes as ct(commit,table=19,zone=NXM_NX_REG11[0..15],nat(dst=192.168.1.2:1-3000))
+    encodes as ct(commit,table=19,zone=NXM_NX_REG11[0..15],nat(dst=192.168.1.2:1-3000,random))
     has prereqs ip
 
 ct_dnat(192.168.1.2, 192.168.1.3);
@@ -1381,7 +1402,7 @@ ct_dnat_in_czone(fd11::2);
     has prereqs ip
 ct_dnat_in_czone(192.168.1.2, 1-3000);
     formats as ct_dnat_in_czone(192.168.1.2,1-3000);
-    encodes as ct(commit,table=19,zone=NXM_NX_REG11[0..15],nat(dst=192.168.1.2:1-3000))
+    encodes as ct(commit,table=19,zone=NXM_NX_REG11[0..15],nat(dst=192.168.1.2:1-3000,random))
     has prereqs ip
 
 ct_dnat_in_czone(192.168.1.2, 192.168.1.3);
@@ -1415,7 +1436,7 @@ ct_snat(fd11::2);
     has prereqs ip
 ct_snat(192.168.1.2, 1-3000);
     formats as ct_snat(192.168.1.2,1-3000);
-    encodes as ct(commit,table=19,zone=NXM_NX_REG12[0..15],nat(src=192.168.1.2:1-3000))
+    encodes as ct(commit,table=19,zone=NXM_NX_REG12[0..15],nat(src=192.168.1.2:1-3000,random))
     has prereqs ip
 
 ct_snat(192.168.1.2, 192.168.1.3);
@@ -1449,7 +1470,7 @@ ct_snat_in_czone(fd11::2);
     has prereqs ip
 ct_snat_in_czone(192.168.1.2, 1-3000);
     formats as ct_snat_in_czone(192.168.1.2,1-3000);
-    encodes as ct(commit,table=19,zone=NXM_NX_REG11[0..15],nat(src=192.168.1.2:1-3000))
+    encodes as ct(commit,table=19,zone=NXM_NX_REG11[0..15],nat(src=192.168.1.2:1-3000,random))
     has prereqs ip
 
 ct_snat_in_czone(192.168.1.2, 192.168.1.3);
@@ -1477,9 +1498,30 @@ ct_clear;
 
 # ct_commit_nat
 ct_commit_nat;
+    formats as ct_commit_nat(dnat);
+    encodes as ct(commit,table=19,zone=NXM_NX_REG13[0..15],nat)
+    has prereqs ip
+
+ct_commit_nat(snat);
+    encodes as ct(commit,table=19,zone=NXM_NX_REG13[0..15],nat)
+    has prereqs ip
+
+ct_commit_nat(dnat);
     encodes as ct(commit,table=19,zone=NXM_NX_REG13[0..15],nat)
     has prereqs ip
 
+ct_commit_nat(snat, dnat);
+    Syntax error at `,' expecting `)'.
+
+ct_commit_nat(dnat, ignore);
+    Syntax error at `,' expecting `)'.
+
+ct_commit_nat(ignore);
+    "ct_commit_nat" action accepts only "dnat" or "snat" parameter.
+
+ct_commit_nat();
+    "ct_commit_nat" action accepts only "dnat" or "snat" parameter.
+
 # clone
 clone { ip4.dst = 255.255.255.255; output; }; next;
     encodes as clone(set_field:255.255.255.255->ip_dst,resubmit(,64)),resubmit(,19)
@@ -2198,13 +2240,13 @@ reg9[5] = chk_ecmp_nh();
 
 # commit_lb_aff
 commit_lb_aff(vip = "172.16.0.123:8080", backend = "10.0.0.3:8080", proto = tcp, timeout = 30);
-    encodes as learn(table=78,idle_timeout=30,delete_learned,OXM_OF_METADATA[],eth_type=0x800,NXM_OF_IP_SRC[],ip_dst=172.16.0.123,nw_proto=6,tcp_dst=8080,load:0x1->NXM_NX_REG10[14],load:0xa000003->NXM_NX_REG4[],load:0x1f90->NXM_NX_REG8[0..15])
+    encodes as learn(table=78,idle_timeout=30,delete_learned,cookie=0xaaaaaaaa,OXM_OF_METADATA[],eth_type=0x800,NXM_OF_IP_SRC[],ip_dst=172.16.0.123,nw_proto=6,tcp_dst=8080,load:0x1->NXM_NX_REG10[14],load:0xa000003->NXM_NX_REG4[],load:0x1f90->NXM_NX_REG8[0..15])
 
 commit_lb_aff(vip = "172.16.0.123", backend = "10.0.0.3", timeout = 30);
-    encodes as learn(table=78,idle_timeout=30,delete_learned,OXM_OF_METADATA[],eth_type=0x800,NXM_OF_IP_SRC[],ip_dst=172.16.0.123,load:0x1->NXM_NX_REG10[14],load:0xa000003->NXM_NX_REG4[])
+    encodes as learn(table=78,idle_timeout=30,delete_learned,cookie=0xaaaaaaaa,OXM_OF_METADATA[],eth_type=0x800,NXM_OF_IP_SRC[],ip_dst=172.16.0.123,load:0x1->NXM_NX_REG10[14],load:0xa000003->NXM_NX_REG4[])
 
 commit_lb_aff(vip = "[::1]:8080", backend = "[::2]:8080", proto = tcp, timeout = 30);
-    encodes as learn(table=78,idle_timeout=30,delete_learned,OXM_OF_METADATA[],eth_type=0x86dd,NXM_NX_IPV6_SRC[],ipv6_dst=::1,nw_proto=6,tcp_dst=8080,load:0x1->NXM_NX_REG10[14],load:0x2->NXM_NX_XXREG0[],load:0x1f90->NXM_NX_REG8[0..15])
+    encodes as learn(table=78,idle_timeout=30,delete_learned,cookie=0xaaaaaaaa,OXM_OF_METADATA[],eth_type=0x86dd,NXM_NX_IPV6_SRC[],ipv6_dst=::1,nw_proto=6,tcp_dst=8080,load:0x1->NXM_NX_REG10[14],load:0x2->NXM_NX_XXREG0[],load:0x1f90->NXM_NX_REG8[0..15])
 
 # chk_lb_aff()
 reg9[6] = chk_lb_aff();
@@ -3849,7 +3891,7 @@ OVN_FOR_EACH_NORTHD([
 AT_SETUP([VLAN transparency, passthru=true, multiple hosts, custom ethtype])
 ovn_start
 
-ethtype=802.11ad
+ethtype=802.1ad
 
 check ovn-nbctl ls-add ls
 check ovn-nbctl --wait=sb add Logical-Switch ls other_config vlan-passthru=true
@@ -4650,6 +4692,12 @@ OVN_POPULATE_ARP
 
 wait_for_ports_up
 check ovn-nbctl --wait=hv sync
+OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv1"],["hv2"])
+OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv2"],["hv1"])
+OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv1"],["hv_gw"])
+OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv_gw"],["hv1"])
+OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv2"],["hv_gw"])
+OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv_gw"],["hv2"])
 
 # test_packet INPORT DST SRC ETHTYPE OUTPORT...
 #
@@ -4849,6 +4897,13 @@ check ovn-nbctl --wait=hv sync
 # for ARP resolution).
 OVN_POPULATE_ARP
 
+OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv1"],["hv2"])
+OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv2"],["hv1"])
+OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv2"],["hv3"])
+OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv3"],["hv2"])
+OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv1"],["hv3"])
+OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv3"],["hv1"])
+
 # test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
 #
 # This shell function causes a packet to be received on INPORT.  The packet's
@@ -5320,10 +5375,11 @@ test_arp() {
 }
 
 test_na() {
-    local inport=$1 sha=$2 spa=$3
+    local inport=$1 sha=$2 spa=$3 src=${4-$3}
     local request=$(fmt_pkt "Ether(dst='ff:ff:ff:ff:ff:ff', src='${sha}')/ \
-                             IPv6(dst='ff01::1', src='${spa}')/ \
-                             ICMPv6ND_NA(tgt='${spa}')")
+                             IPv6(dst='ff01::1', src='${src}')/ \
+                             ICMPv6ND_NA(tgt='${spa}')/ \
+                             ICMPv6NDOptDstLLAddr(lladdr='${sha}')")
 
     hv=hv`vif_to_hv $inport`
     as $hv ovs-appctl netdev-dummy/receive vif$inport $request
@@ -5399,6 +5455,24 @@ for i in 1 2; do
     done
 done
 
+# Make sure that we can update existing entry with
+# "always_learn_from_arp_request=false" even when the source of NA src is LLA.
+check ovn-sbctl --all destroy mac_binding
+check ovn-nbctl --wait=hv set logical_router lr0 options:always_learn_from_arp_request=false
+
+sha="f0:00:00:00:00:11"
+spa6="fd00::abcd:1"
+
+test_na 11 $sha $spa6
+wait_row_count MAC_Binding 1 ip=\"$spa6\" mac=\"$sha\"
+
+sha="f0:00:00:00:00:12"
+lla6="fe80::abcd:1"
+
+test_na 11 $sha $spa6 $lla6
+wait_row_count MAC_Binding 1 ip=\"$spa6\" mac=\"$sha\"
+check_row_count MAC_Binding 0 ip=\"$lla6\"
+
 # Gracefully terminate daemons
 OVN_CLEANUP([hv1], [hv2])
 
@@ -6107,7 +6181,7 @@ ovs-vsctl -- add-port br-int vif2 -- \
 wait_for_ports_up
 check ovn-nbctl --wait=hv sync
 
-ovs-sbctl dump-flows > sbflows
+ovn-sbctl dump-flows > sbflows
 AT_CAPTURE_FILE([sbflows])
 
 # Send ip packets between the two ports.
@@ -6123,7 +6197,7 @@ as hv1 ovs-appctl netdev-dummy/receive vif1 $packet
 #Disable router R1
 ovn-nbctl --wait=hv set Logical_Router R1 enabled=false
 
-ovs-sbctl dump-flows > sbflows2
+ovn-sbctl dump-flows > sbflows2
 AT_CAPTURE_FILE([sbflows2])
 
 as hv1 ovs-appctl netdev-dummy/receive vif1 $packet
@@ -6758,13 +6832,7 @@ test_dhcp() {
 }
 
 compare_dhcp_packets() {
-    received=$($PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif$1-tx.pcap)
-    expected=$(cat $1.expected)
-
-    if test "$received" != "$expected"; then
-        AT_CHECK_UNQUOTED([echo "$received"; tcpdump_hex "$received"], [0],
-            [$(echo "$expected"; tcpdump_hex "$expected")])
-    fi
+    OVN_CHECK_PACKETS([hv1/vif$1-tx.pcap], [$1.expected])
 }
 
 AT_CAPTURE_FILE([sbflows])
@@ -6962,8 +7030,9 @@ expected_dhcp_opts=0
 test_dhcp 11 2 f00000000002 07 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001
 
 # There is no reply for this. Check for the INFO log in ovn-controller.log
-AT_CHECK([test 1 = $(cat hv1/ovn-controller.log | \
-grep "DHCPRELEASE f0:00:00:00:00:02 10.0.0.6" -c)])
+OVS_WAIT_UNTIL(
+    [test 1 = $(cat hv1/ovn-controller.log | grep "DHCPRELEASE f0:00:00:00:00:02 10.0.0.6" -c)
+])
 
 $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
 AT_CHECK([cat 2.packets], [0], [])
@@ -7120,7 +7189,9 @@ ciaddr=`ip_to_hex 0 0 0 0`
 request_ip=0
 expected_dhcp_opts=""
 test_dhcp 18 1 f00000000001 04 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
-AT_CHECK([grep -F -iq 'DHCPDECLINE from f0:00:00:00:00:01, 10.0.0.4 duplicated' hv1/ovn-controller.log], [0], [])
+OVS_WAIT_UNTIL(
+    [test 1 -le $(grep -F -i -c 'DHCPDECLINE from f0:00:00:00:00:01, 10.0.0.4 duplicated' hv1/ovn-controller.log)
+])
 
 # Send Etherboot.
 
@@ -7138,7 +7209,7 @@ ovn-nbctl dhcp-options-set-options $d3 \
    lease_time=3600 router=10.0.0.1 bootfile_name_alt=\"bootfile_name_alt\" \
    bootfile_name=\"bootfile\"
 
-ovn-nbctl lsp-set-dhcpv4-options ls1-lp1 $d3
+ovn-nbctl --wait=hv lsp-set-dhcpv4-options ls1-lp1 $d3
 
 offer_ip=`ip_to_hex 10 0 0 4`
 server_ip=`ip_to_hex 10 0 0 1`
@@ -7156,9 +7227,6 @@ compare_dhcp_packets 1
 as northd
 OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
 
-as northd-backup
-OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
-
 northd_version=$(ovn-sbctl get SB_Global . options:northd_internal_version | sed s/\"//g)
 echo "northd version = $northd_version"
 
@@ -7295,10 +7363,6 @@ check ovn-nbctl --wait=hv sync
 # Start with 0 because the first request will not have NXT_RESUME
 n_resume=0
 
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-
 # This shell function sends a DHCPv6 request packet
 # test_dhcpv6 INPORT SRC_MAC SRC_LLA DHCPv6_MSG_TYPE OFFER_IP OUTPORT...
 # The OUTPORTs (zero or more) list the VIFs on which the original DHCPv6
@@ -7400,12 +7464,8 @@ test_dhcpv6_release() {
 check_packets() {
     local port=$1
 
-    $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif$port-tx.pcap | trim_zeros > $port.packets
     # Skipping UDP checksum
-    cat $port.expected | cut -c 1-120,125- > expout
-    AT_CHECK([cat $port.packets | cut -c 1-120,125- ], [0], [expout])
-
-    rm $port.packets
+    OVN_CHECK_PACKETS([hv1/vif$port-tx.pcap], [$port.expected], ["trim_zeros | cut -c 1-120,125-"])
     rm $port.expected
 }
 
@@ -7508,7 +7568,7 @@ ovn-nbctl dhcp-options-set-options $d1 \
     server_id=00:00:00:10:00:01 \
     bootfile_name_alt=\"bootfile_name_alt\" \
     bootfile_name=\"bootfile_name\"
-ovn-nbctl lsp-set-dhcpv6-options ls1-lp2 ${d1}
+ovn-nbctl --wait=hv lsp-set-dhcpv6-options ls1-lp2 ${d1}
 
 reset_pcap_file hv1-vif2 hv1/vif2
 
@@ -7645,6 +7705,9 @@ ovn-nbctl lsp-add alice alice1 \
 wait_for_ports_up
 check ovn-nbctl --wait=hv sync
 
+OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv1"],["hv2"])
+OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv2"],["hv1"])
+
 # Send ip packets between foo1 and alice1
 src_mac="f00000010203"
 dst_mac="000001010203"
@@ -8418,6 +8481,7 @@ check_dynamic_addresses() {
     check_row_count nb:Logical_Switch_Port 1 name="$1" dynamic_addresses="$arg"
 }
 
+check ovn-nbctl --wait=sb sync
 # Add a port to a switch that does not have a subnet set, then set the
 # subnet which should result in an address being allocated for the port.
 ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="0a:00:00:00:00:00"
@@ -8733,7 +8797,7 @@ OVN_FOR_EACH_NORTHD([
 AT_SETUP([ipam connectivity])
 ovn_start
 
-ovn-nbctl lr-add R1
+ovn-nbctl --wait=sb lr-add R1
 
 # Test for a ping using dynamically allocated addresses.
 ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="0a:00:00:00:00:00"
@@ -9019,10 +9083,6 @@ packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111
 # Send IP packet destined to 8.8.8.8 from lsp1lp2
 as hv1 ovs-appctl netdev-dummy/receive hv1-ls1lp2 $packet
 
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-
 # ARP packet should be received with Target IP Address set to 192.168.1.254 and
 # not 8.8.8.8
 
@@ -9078,9 +9138,6 @@ AT_CAPTURE_FILE([sbflows])
 
 # Wait for packet to be received.
 OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 140])
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
 $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros |sort | uniq > packets
 AT_CHECK([sort packets], [0], [dnl
 fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001
@@ -9277,9 +9334,6 @@ ovn-nbctl list logical_router_port lrp0
 ovn-nbctl show
 # Wait for packet to be received.
 OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 50])
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
 $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | sort | uniq > packets
 expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001"
 echo $expected > expout
@@ -9300,9 +9354,6 @@ ovn-nbctl lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="router" exclud
 
 # Wait for packets to be received.
 OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 250])
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
 
 $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros > packets
 g0="fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001"
@@ -10721,10 +10772,6 @@ OVN_POPULATE_ARP
 wait_for_ports_up
 check ovn-nbctl --wait=hv sync
 
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-
 # Send ip packets between foo1 and bar1
 # (East-west traffic should flow normally)
 src_mac="f00000010203"
@@ -11004,12 +11051,9 @@ test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d
 # NXT_RESUMEs should be 1.
 OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
-cat 1.expected | cut -c -48 > expout
-AT_CHECK([cat 1.packets | cut -c -48], [0], [expout])
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c -48"])
 # Skipping the IPv4 checksum.
-cat 1.expected | cut -c 53- > expout
-AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout])
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c 53-"])
 
 reset_pcap_file hv1-vif1 hv1/vif1
 reset_pcap_file hv1-vif2 hv1/vif2
@@ -11026,12 +11070,9 @@ test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d
 # NXT_RESUMEs should be 2.
 OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
-cat 2.expected | cut -c -48 > expout
-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
+OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c -48"])
 # Skipping the IPv4 checksum.
-cat 2.expected | cut -c 53- > expout
-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
+OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c 53-"])
 
 reset_pcap_file hv1-vif1 hv1/vif1
 reset_pcap_file hv1-vif2 hv1/vif2
@@ -11049,12 +11090,9 @@ test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d
 # NXT_RESUMEs should be 3.
 OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
-cat 2.expected | cut -c -48 > expout
-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
+OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c -48"])
 # Skipping the IPv4 checksum.
-cat 2.expected | cut -c 53- > expout
-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
+OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c 53-"])
 
 reset_pcap_file hv1-vif1 hv1/vif1
 reset_pcap_file hv1-vif2 hv1/vif2
@@ -11131,12 +11169,9 @@ test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d
 # NXT_RESUMEs should be 5.
 OVS_WAIT_UNTIL([test 5 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
-cat 2.expected | cut -c -48 > expout
-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
+OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c -48"])
 # Skipping the IPv4 checksum.
-cat 2.expected | cut -c 53- > expout
-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
+OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c 53-"])
 
 reset_pcap_file hv1-vif1 hv1/vif1
 reset_pcap_file hv1-vif2 hv1/vif2
@@ -11154,12 +11189,9 @@ test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d
 # NXT_RESUMEs should be 6.
 OVS_WAIT_UNTIL([test 6 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
-cat 2.expected | cut -c -48 > expout
-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
+OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c -48"])
 # Skipping the IPv4 checksum.
-cat 2.expected | cut -c 53- > expout
-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
+OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c 53-"])
 
 reset_pcap_file hv1-vif1 hv1/vif1
 reset_pcap_file hv1-vif2 hv1/vif2
@@ -11221,12 +11253,9 @@ test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d
 # NXT_RESUMEs should be 9.
 OVS_WAIT_UNTIL([test 9 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
-cat 1.expected | cut -c -48 > expout
-AT_CHECK([cat 1.packets | cut -c -48], [0], [expout])
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c -48"])
 # Skipping the IPv4 checksum.
-cat 1.expected | cut -c 53- > expout
-AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout])
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c 53-"])
 
 reset_pcap_file hv1-vif1 hv1/vif1
 reset_pcap_file hv1-vif2 hv1/vif2
@@ -11244,10 +11273,8 @@ test_dns6 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $
 # NXT_RESUMEs should be 10
 OVS_WAIT_UNTIL([test 10 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
 # Skipping the UDP checksum.
-cat 1.expected | cut -c 1-120,125- > expout
-AT_CHECK([cat 1.packets | cut -c 1-120,125-], [0], [expout])
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c 1-120,125-"])
 
 reset_pcap_file hv1-vif1 hv1/vif1
 reset_pcap_file hv1-vif2 hv1/vif2
@@ -11273,12 +11300,9 @@ test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d
 # NXT_RESUMEs should be 11.
 OVS_WAIT_UNTIL([test 11 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
-cat 1.expected | cut -c -48 > expout
-AT_CHECK([cat 1.packets | cut -c -48], [0], [expout])
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c -48"])
 # Skipping the IPv4 checksum.
-cat 1.expected | cut -c 53- > expout
-AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout])
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c 53-"])
 
 reset_pcap_file hv1-vif1 hv1/vif1
 reset_pcap_file hv1-vif2 hv1/vif2
@@ -11296,12 +11320,9 @@ test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d
 # NXT_RESUMEs should be 12.
 OVS_WAIT_UNTIL([test 12 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
-cat 1.expected | cut -c -48 > expout
-AT_CHECK([cat 1.packets | cut -c -48], [0], [expout])
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c -48"])
 # Skipping the IPv4 checksum.
-cat 1.expected | cut -c 53- > expout
-AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout])
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c 53-"])
 
 reset_pcap_file hv1-vif1 hv1/vif1
 reset_pcap_file hv1-vif2 hv1/vif2
@@ -11313,6 +11334,57 @@ OVN_CLEANUP([hv1])
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([dns lookup : EDNS])
+OVN_CHECK_SCAPY_EDNS_CLIENT_SUBNET_SUPPORT()
+ovn_start
+
+check ovn-nbctl ls-add ls \
+    -- lsp-add ls lsp     \
+    -- lsp-set-addresses lsp "00:00:00:00:00:01 10.0.0.1"
+
+d=$(ovn-nbctl create dns records={})
+
+check ovn-nbctl set dns $d records:foo.ovn.org="10.0.0.42"
+check ovn-nbctl set Logical_switch ls dns_records="$d"
+
+net_add n1
+sim_add hv1
+
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+check ovs-vsctl add-port br-int hv1-vif1 -- \
+    set interface hv1-vif1 external-ids:iface-id=lsp \
+    options:tx_pcap=hv1/vif1-tx.pcap \
+    options:rxq_pcap=hv1/vif1-rx.pcap
+
+OVN_POPULATE_ARP
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+dns_req=$(fmt_pkt "Ether(dst='00:00:00:00:00:02', src='00:00:00:00:00:01') / \
+                   IP(dst='10.0.0.254', src='10.0.0.1') / \
+                   UDP(sport=42424, dport=53) / \
+                   DNS(rd=1, qd=DNSQR(qname='foo.ovn.org'), arcount=1, ar=[ \
+                       DNSRR(type='OPT', rclass=4096, \
+                             rdata=EDNS0ClientSubnet(source_plen=24, \
+                                                     address='10.0.0.1'))])")
+dns_reply=$(fmt_pkt "Ether(dst='00:00:00:00:00:01', src='00:00:00:00:00:02') / \
+                     IP(dst='10.0.0.1', src='10.0.0.254') / \
+                     UDP(sport=53, dport=42424,chksum=0) / \
+                     DNS(qr=1, qd=DNSQR(qname='foo.ovn.org'), \
+                         an=DNSRR(rrname='foo.ovn.org', type='A', ttl=3600, \
+                                  rdata='10.0.0.42'))")
+echo ${dns_reply} > expected
+
+as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 ${dns_req}
+OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv1/vif1-tx.pcap], [expected])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([4 HV, 1 LS, 1 LR, packet test with HA distributed router gateway port])
 ovn_start
@@ -11481,30 +11553,7 @@ test_ip_packet()
     # Resend packet from foo1 to outside1
     check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
 
-    AT_CAPTURE_FILE([exp])
-    AT_CAPTURE_FILE([rcv])
-    check_packets() {
-        > exp
-        > rcv
-
-        pcap=ext1/vif1-tx.pcap
-        type=ext1-vif1.expected
-        echo "--- $pcap" | tee -a exp >> rcv
-        sort -u "$type" >> exp
-        $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | sort -u >> rcv
-        echo | tee -a exp >> rcv
-
-        pcap=$active_gw/br-phys_n1-tx.pcap
-        echo "--- $pcap" | tee -a exp >> rcv
-        sort -u "$type" >> exp
-        $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap > packets
-        (grep "$expected" packets; grep "$exp_gw_ip_garp" packets) | sort -u >> rcv
-        echo | tee -a exp >> rcv
-
-        $at_diff exp rcv >/dev/null
-    }
-
-    OVS_WAIT_UNTIL([check_packets], [$at_diff -F'^---' exp rcv])
+    OVN_CHECK_PACKETS_CONTAIN([ext1/vif1-tx.pcap], [ext1-vif1.expected])
 
     if test $backup_vswitchd_dead != 1; then
         # Check for backup gw only if vswitchd is alive
@@ -12206,9 +12255,6 @@ OVN_WAIT_PATCH_PORT_FLOWS(["ln_port"], ["hv2"])
 
 # Wait for packets to be received.
 OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 100])
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
 $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros > packets
 expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001"
 echo $expected > expout
@@ -12240,21 +12286,12 @@ OVN_WAIT_PATCH_PORT_FLOWS(["ln_port"], ["hv3"])
 # Re-add nat-addresses option
 ovn-nbctl lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="router"
 
-# Wait for packets to be received.
-OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 250])
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | sort | uniq > packets
 garp_1="fffffffffffff0000000000308060001080006040001f00000000003c0a80003000000000000c0a80003"
-echo $garp_1 > expout
+echo $garp_1 > expected_out
 garp_2="fffffffffffff0000000000408060001080006040001f00000000004c0a80004000000000000c0a80004"
-echo $garp_2 >> expout
+echo $garp_2 >> expected_out
 
-cat packets | grep $garp_1 | head -1 > exp
-cat packets | grep $garp_2 | head -1 >> exp
-AT_CHECK([cat exp], [0], [expout])
+OVN_CHECK_PACKETS_CONTAIN([hv1/snoopvif-tx.pcap], [expected_out], "trim_zeros")
 
 OVN_CLEANUP([hv1],[hv2],[hv3])
 
@@ -12577,6 +12614,7 @@ AT_CLEANUP
 
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([IPv6 ND Router Solicitation responder])
+AT_SKIP_IF([test $HAVE_SCAPY = no])
 AT_KEYWORDS([ovn-nd_ra])
 ovn_start
 
@@ -12643,76 +12681,120 @@ ovs-vsctl -- add-port br-int hv1-vif3 -- \
 wait_for_ports_up
 check ovn-nbctl --wait=hv sync
 
+n_resume=1
+
 # Make sure that ovn-controller has installed the corresponding OF Flow.
 OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
 
 # This shell function sends a Router Solicitation packet.
-# test_ipv6_ra INPORT SRC_MAC SRC_LLA ADDR_MODE MTU RA_PREFIX_OPT RDNSS DNSSL ROUTE_INFO
+# test_ipv6_ra INPORT SRC_MAC SRC_LLA RA_OPT MTU PREFIX RDNSS DNSSL ROUTES
 test_ipv6_ra() {
-    local inport=$1 src_mac=$2 src_lla=$3 addr_mode=$4 mtu=$5 prefix_opt=$6
-    local rdnss=$7 dnssl=$8 route_info=$9
-    local request=333300000002${src_mac}86dd6000000000103aff${src_lla}ff02000000000000000000000000000285000efc000000000101${src_mac}
+    local inport=$1 src_mac=$2 src_ip=$3 ra=$4 mtu=$5 prefix=$6
+    local rdnss=$7 dnssl=$8 routes=$9
 
-    local len=24
-    local mtu_opt=""
-    if test $mtu != 0; then
-        len=`expr $len + 8`
-        mtu_opt=05010000${mtu}
+    local request=$(fmt_pkt "Ether(dst='33:33:00:00:00:02', src='${src_mac}')/ \
+                             IPv6(dst='ff02::2', src='${src_ip}')/ \
+                             ICMPv6ND_RS()/ \
+                             ICMPv6NDOptSrcLLAddr(lladdr='${src_mac}')")
+
+    local reply_dst=$src_ip
+    if test "${reply_dst}" = "::"; then
+        reply_dst="ff02::1"
     fi
 
-    if test ${#rdnss} != 0; then
-        len=`expr $len + ${#rdnss} / 2`
+    local rep_scapy="Ether(dst='${src_mac}', src='fa:16:3e:00:00:01')/ \
+                     IPv6(dst='${reply_dst}', src='fe80::f816:3eff:fe00:1')/ \
+                     ${ra}/ \
+                     ICMPv6NDOptSrcLLAddr(lladdr='fa:16:3e:00:00:01')"
+
+    if test "${mtu}" != "0"; then
+        rep_scapy="${rep_scapy}/ICMPv6NDOptMTU(mtu=${mtu})"
     fi
 
-    if test ${#dnssl} != 0; then
-        len=`expr $len + ${#dnssl} / 2`
+    if test -n "${rdnss}"; then
+        rep_scapy="${rep_scapy}/ICMPv6NDOptRDNSS(dns=[['${rdnss}']])"
     fi
 
-    if test ${#route_info} != 0; then
-        len=`expr $len + ${#route_info} / 2`
+    if test -n "${dnssl}"; then
+        rep_scapy="${rep_scapy}/ICMPv6NDOptDNSSL(searchlist=[['${dnssl}']])"
     fi
 
-    if test ${#prefix_opt} != 0; then
-        prefix_opt=${prefix_opt}fdad1234567800000000000000000000
-        len=`expr $len + ${#prefix_opt} / 2`
+    if test -n "${routes}"; then
+        rep_scapy="${rep_scapy}/${routes}"
     fi
 
-    len=$(printf "%x" $len)
-    local lrp_mac=fa163e000001
-    local lrp_lla=fe80000000000000f8163efffe000001
-    local reply=${src_mac}${lrp_mac}86dd6000000000${len}3aff${lrp_lla}${src_lla}8600XXXXff${addr_mode}ffff00000000000000000101${lrp_mac}${mtu_opt}${rdnss}${dnssl}${route_info}${prefix_opt}
+    if test -n "${prefix}"; then
+        local a_flag=$(echo $ra | grep -vc "M=1")
+        rep_scapy="${rep_scapy}/ICMPv6NDOptPrefixInfo(prefix='${prefix}', A=${a_flag})"
+    fi
+
+    local reply=$(fmt_pkt "${rep_scapy}")
     echo $reply >> $inport.expected
 
     as hv1 ovs-appctl netdev-dummy/receive hv1-vif${inport} $request
+
+    OVS_WAIT_UNTIL([test $n_resume = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+    n_resume=$((n_resume + 1))
+}
+
+check_packets() {
+    local port=$1
+
+    # Skipping UDP checksum
+    OVN_CHECK_PACKETS([hv1/vif$port-tx.pcap], [$port.expected], ["cut -c 1-112,117-"])
+
+    rm $port.packets
+    rm $port.expected
+
+    reset_pcap_file hv1-vif1 hv1/vif1
+    reset_pcap_file hv1-vif2 hv1/vif2
+    reset_pcap_file hv1-vif3 hv1/vif3
+}
+
+prepare_ra_opt() {
+    local mode=$1 priority=$2
+
+    if test "${mode}" = "stateful"; then
+        echo "ICMPv6ND_RA(chlim=255, routerlifetime=65535, M=1, prf=${priority})"
+    elif test "${mode}" = "stateless"; then
+        echo "ICMPv6ND_RA(chlim=255, routerlifetime=65535, O=1, prf=${priority})"
+    else
+        echo "ICMPv6ND_RA(chlim=255, routerlifetime=65535, prf=${priority})"
+    fi
+}
+
+prepare_route_opt() {
+    local prefix=$1 priority=$2 plen=$3
+
+    local len=2
+    if test $plen -gt 64; then
+        len=3
+    fi
+
+    echo "ICMPv6NDOptRouteInfo(len=$len, prf=${priority}, prefix='${prefix}', plen=$plen)"
 }
 
 AT_CAPTURE_FILE([ofctl_monitor0.log])
 as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \
 --pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log
 
-# MTU is not set and the address mode is set to slaac
-addr_mode=00
-default_prefix_option_config=030440c0ffffffffffffffff00000000
-src_mac=fa163e000002
-src_lla=fe80000000000000f8163efffe000002
-test_ipv6_ra 1 $src_mac $src_lla $addr_mode 0 $default_prefix_option_config
+prefix="fdad:1234:5678::"
 
-# NXT_RESUME should be 1.
-OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+# MTU is not set and the address mode is set to slaac
+ra=$(prepare_ra_opt "" 0)
+src_mac="fa:16:3e:00:00:02"
+src_lla="fe80::f816:3eff:fe00:2"
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap  > 1.packets
+test_ipv6_ra 1 $src_mac $src_lla "$ra" 0 $prefix "" "" ""
+check_packets 1
 
-cat 1.expected | cut -c -112 > expout
-AT_CHECK([cat 1.packets | cut -c -112], [0], [expout])
+# Check with RA with src being "::".
+ovn-nbctl --wait=hv lsp-set-port-security lp1 ""
 
-# Skipping the ICMPv6 checksum.
-cat 1.expected | cut -c 117- > expout
-AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout])
+test_ipv6_ra 1 $src_mac "::" "$ra" 0 $prefix "" "" ""
+check_packets 1
 
-rm -f *.expected
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-reset_pcap_file hv1-vif3 hv1/vif3
+ovn-nbctl --wait=hv lsp-set-port-security lp1 "fa:16:3e:00:00:02 10.0.0.12 fdad:1234:5678:0:f816:3eff:fe:2"
 
 # Set the MTU to 1500, send_periodic to false, preference to LOW
 ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:mtu=1500
@@ -12726,33 +12808,14 @@ ovn-nbctl --wait=hv set Logical_Router_port lrp0 ipv6_ra_configs:route_info=HIGH
 OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
 
 # addr_mode byte also includes router preference information
-addr_mode=18
-default_prefix_option_config=030440c0ffffffffffffffff00000000
-src_mac=fa163e000003
-src_lla=fe80000000000000f8163efffe000003
-mtu=000005dc
-rdnss=19030000ffffffff10000000000000000000000000000011
-dnssl=1f030000ffffffff02616102626202636300000000000000
-route_info=18023008ffffffff100100000000000018036018ffffffff10020000000000000000000000000000
-
-test_ipv6_ra 2 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config $rdnss $dnssl $route_info
-
-# NXT_RESUME should be 2.
-OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap  > 2.packets
-
-cat 2.expected | cut -c -112 > expout
-AT_CHECK([cat 2.packets | cut -c -112], [0], [expout])
+ra=$(prepare_ra_opt "" 3)
+routes=$(prepare_route_opt '1001::' 1 48)
+routes="${routes}/$(prepare_route_opt '1002::' 3 96)"
+src_mac="fa:16:3e:00:00:03"
+src_lla="fe80::f816:3eff:fe00:3"
 
-# Skipping the ICMPv6 checksum.
-cat 2.expected | cut -c 117- > expout
-AT_CHECK([cat 2.packets | cut -c 117-], [0], [expout])
-
-rm -f *.expected
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-reset_pcap_file hv1-vif3 hv1/vif3
+test_ipv6_ra 2 $src_mac $src_lla "$ra" 1500 $prefix "1000::11" "aa.bb.cc" "$routes"
+check_packets 2
 
 # Set the address mode to dhcpv6_stateful, router_preference to HIGH
 ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateful
@@ -12763,30 +12826,12 @@ ovn-nbctl --wait=hv remove Logical_Router_Port lrp0 ipv6_ra_configs route_info
 OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
 
 # addr_mode byte also includes router preference information
-addr_mode=88
-default_prefix_option_config=03044080ffffffffffffffff00000000
-src_mac=fa163e000004
-src_lla=fe80000000000000f8163efffe000004
-mtu=000005dc
-
-test_ipv6_ra 3 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config "" $dnssl
-
-# NXT_RESUME should be 3.
-OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+ra=$(prepare_ra_opt "stateful" 1)
+src_mac="fa:16:3e:00:00:04"
+src_lla="fe80::f816:3eff:fe00:4"
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap  > 3.packets
-
-cat 3.expected | cut -c -112 > expout
-AT_CHECK([cat 3.packets | cut -c -112], [0], [expout])
-
-# Skipping the ICMPv6 checksum.
-cat 3.expected | cut -c 117- > expout
-AT_CHECK([cat 3.packets | cut -c 117-], [0], [expout])
-
-rm -f *.expected
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-reset_pcap_file hv1-vif3 hv1/vif3
+test_ipv6_ra 3 $src_mac $src_lla "$ra" 1500 $prefix "" "aa.bb.cc" ""
+check_packets 3
 
 # Set the address mode to dhcpv6_stateless, reset router preference to default
 ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateless
@@ -12795,46 +12840,27 @@ ovn-nbctl --wait=hv remove Logical_Router_Port lrp0 ipv6_ra_configs dnssl
 # Make sure that ovn-controller has installed the corresponding OF Flow.
 OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
 
-addr_mode=40
-default_prefix_option_config=030440c0ffffffffffffffff00000000
-src_mac=fa163e000002
-src_lla=fe80000000000000f8163efffe000002
-mtu=000005dc
-
-test_ipv6_ra 1 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
+ra=$(prepare_ra_opt "stateless" 0)
+src_mac="fa:16:3e:00:00:02"
+src_lla="fe80::f816:3eff:fe00:2"
 
-# NXT_RESUME should be 4.
-OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
-
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap  > 1.packets
-
-cat 1.expected | cut -c -112 > expout
-AT_CHECK([cat 1.packets | cut -c -112], [0], [expout])
-
-# Skipping the ICMPv6 checksum.
-cat 1.expected | cut -c 117- > expout
-AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout])
-
-rm -f *.expected
-reset_pcap_file hv1-vif1 hv1/vif1
-reset_pcap_file hv1-vif2 hv1/vif2
-reset_pcap_file hv1-vif3 hv1/vif3
+test_ipv6_ra 1 $src_mac $src_lla "$ra" 1500 $prefix "" "" ""
+check_packets 1
 
 # Set the address mode to invalid.
 ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=invalid
 # Make sure that ovn-controller has not installed any OF Flow for IPv6 ND RA.
 OVS_WAIT_UNTIL([test 0 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
 
-addr_mode=40
-default_prefix_option_config=""
-src_mac=fa163e000002
-src_lla=fe80000000000000f8163efffe000002
-mtu=000005dc
+ra=$(prepare_ra_opt "stateless" 0)
 
-test_ipv6_ra 1 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
+src_mac="fa:16:3e:00:00:02"
+src_lla="fe80::f816:3eff:fe00:2"
 
-# NXT_RESUME should be 4 only.
-OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+# This shouldn't produce any NXT_RESUME.
+n_resume=$((n_resume - 1))
+
+test_ipv6_ra 1 $src_mac $src_lla "$ra" 1500 "" "" "" ""
 
 $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap  > 1.packets
 AT_CHECK([cat 1.packets], [0], [])
@@ -13612,7 +13638,7 @@ for chassis in gw1 hv1 hv2; do
     echo "checking gw2 -> $chassis"
     OVS_WAIT_UNTIL([
     bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0)
-    test "$bfd_cfg" = "enable=true min_rx=2000"
+    test "$bfd_cfg" = "check_tnl_key=true enable=true min_rx=2000"
 ])
 done
 ovn-nbctl --wait=hv set NB_Global . options:"bfd-min-tx"=1500
@@ -13620,7 +13646,7 @@ for chassis in gw1 hv1 hv2; do
     echo "checking gw2 -> $chassis"
     OVS_WAIT_UNTIL([
     bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0)
-    test "$bfd_cfg" = "enable=true min_rx=2000 min_tx=1500"
+    test "$bfd_cfg" = "check_tnl_key=true enable=true min_rx=2000 min_tx=1500"
 ])
 done
 ovn-nbctl remove NB_Global . options "bfd-min-rx"
@@ -13629,7 +13655,7 @@ for chassis in gw1 hv1 hv2; do
     echo "checking gw2 -> $chassis"
     OVS_WAIT_UNTIL([
     bfd_cfg=$(ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0)
-    test "$bfd_cfg" = "enable=true min_tx=1500 mult=15"
+    test "$bfd_cfg" = "check_tnl_key=true enable=true min_tx=1500 mult=15"
 ])
 done
 
@@ -13879,33 +13905,15 @@ as hv1 reset_pcap_file snoopvif hv1/snoopvif
 # add nat-addresses option
 ovn-nbctl --wait=hv lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="router"
 
-# Wait for packets to be received through hv2.
-OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 100])
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-
 only_broadcast_from_lrp1() {
     grep "fffffffffffff00000000001"
 }
 
 garp="fffffffffffff0000000000108060001080006040001f00000000001c0a80064000000000000c0a80064"
-echo $garp > expout
+echo $garp > expected_out
 
-OVS_WAIT_UNTIL(
-    [$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap > rcv_text
-     exp_rcvd=$(cat rcv_text | grep $garp | wc -l)
-     echo "expected received = $exp_rcvd"
-     test $exp_rcvd -ge 1])
-
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv1_snoop_tx
-echo "packets on hv1-snoopvif:"
-cat hv1_snoop_tx
-AT_CHECK([sort hv1_snoop_tx], [0], [expout])
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv2_br_phys_tx
-echo "packets on hv2 br-phys tx"
-cat hv2_br_phys_tx
-AT_CHECK([grep $garp hv2_br_phys_tx | sort], [0], [expout])
+OVN_CHECK_PACKETS_CONTAIN([hv1/snoopvif-tx.pcap], [expected_out], "trim_zeros")
+OVN_CHECK_PACKETS_CONTAIN([hv2/br-phys_n1-tx.pcap], [expected_out], "trim_zeros")
 $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv3/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv3_br_phys_tx
 echo "packets on hv3 br-phys tx"
 cat hv3_br_phys_tx
@@ -13914,9 +13922,6 @@ AT_CHECK([grep $garp hv3_br_phys_tx | sort], [0], [])
 
 # at this point, we invert the priority of the gw chassis between hv2 and hv3
 
-# Reset hv2/br-phys_n1 before inverting gw chassis, as otherwise packets might
-# be reset (while not resetting hv1/snoopvif because not yet sent).
-as hv2 reset_pcap_file br-phys_n1 hv2/br-phys_n1
 ovn-nbctl --wait=hv \
           --id=@gc0 create Gateway_Chassis \
                     name=outside_gw1 chassis_name=hv2 priority=1 -- \
@@ -13924,24 +13929,17 @@ ovn-nbctl --wait=hv \
                     name=outside_gw2 chassis_name=hv3 priority=10 -- \
           set Logical_Router_Port lrp0 'gateway_chassis=[@gc0,@gc1]'
 
+# We expect not to receive garp on hv2 after inverting the priority.
+# Hence  reset hv2 after inverting priority as otherwise a garp might
+# be received on hv2 between the reset and the priority change.
+
+as hv2 reset_pcap_file br-phys_n1 hv2/br-phys_n1
 as hv3 reset_pcap_file br-phys_n1 hv3/br-phys_n1
 as hv1 reset_pcap_file snoopvif hv1/snoopvif
 
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-
-# Wait for packets to be received.
-OVS_WAIT_UNTIL(
-    [$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap > rcv_text
-     exp_rcvd=$(cat rcv_text | grep $garp | wc -l)
-     echo "expected received = $exp_rcvd"
-     test $exp_rcvd -ge 1])
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq >  hv1_snoopvif_tx
-AT_CHECK([sort hv1_snoopvif_tx], [0], [expout])
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv3/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv3_br_phys_tx
-AT_CHECK([grep $garp hv3_br_phys_tx | sort], [0], [expout])
+OVN_CHECK_PACKETS_CONTAIN([hv1/snoopvif-tx.pcap], [expected_out], "trim_zeros")
+OVN_CHECK_PACKETS_CONTAIN([hv3/br-phys_n1-tx.pcap], [expected_out], "trim_zeros")
 $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv2_br_phys_tx
 AT_CHECK([grep $garp hv2_br_phys_tx | sort], [0], [])
 
@@ -13974,25 +13972,11 @@ ovn-nbctl --wait=hv lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="rout
 OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns nat_addresses find port_binding \
 logical_port=lrp0-rp | grep is_chassis | wc -l`])
 
-# Wait for packets to be received.
-OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 100])
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-
 garp="fffffffffffff00000000001810007de08060001080006040001f00000000001c0a80064000000000000c0a80064"
-echo $garp > expout
-
-OVS_WAIT_UNTIL(
-    [$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap > rcv_text
-     exp_rcvd=$(cat rcv_text | grep $garp | wc -l)
-     echo "expected received = $exp_rcvd"
-     test $exp_rcvd -ge 1])
+echo $garp > expected_out
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq >  hv1_snoopvif_tx
-AT_CHECK([sort hv1_snoopvif_tx], [0], [expout])
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv3/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv3_br_phys_tx
-AT_CHECK([grep $garp hv3_br_phys_tx | sort], [0], [expout])
+OVN_CHECK_PACKETS_CONTAIN([hv1/snoopvif-tx.pcap], [expected_out], "trim_zeros")
+OVN_CHECK_PACKETS_CONTAIN([hv3/br-phys_n1-tx.pcap], [expected_out], "trim_zeros")
 $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv2_br_phys_tx
 AT_CHECK([grep $garp hv2_br_phys_tx | sort], [0], [])
 
@@ -14333,10 +14317,6 @@ wc -l], [0], [4
 chassis_uuid=$(fetch_column Chassis _uuid name=hv1)
 wait_row_count Port_Binding 1 logical_port=cr-ip6_public chassis=$chassis_uuid
 
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-
 # Test the IPv6 Neighbor Solicitation (NS) - nd_ns action for unknown MAC
 # addresses. ovn-controller should generate an IPv6 NS request for IPv6
 # packets whose MAC is unknown (in the ARP_REQUEST router pipeline stage.
@@ -14888,6 +14868,10 @@ wait_column "$hv2_uuid" Port_Binding requested_additional_chassis logical_port=m
 # ovn-installed on hv2 should guarantee that.
 OVS_WAIT_UNTIL([test `as hv2 ovs-vsctl get Interface migrator external_ids:ovn-installed` = '"true"'])
 
+# Still, this does not guarantee that all flows are installed on hv3: hv3 (might) still need to receive and handle
+# additional_chassis for migrator port
+ovn-nbctl --wait=hv sync
+
 # check that...
 # unicast from First arrives to hv1:Migrator
 # unicast from First arrives to hv2:Migrator
@@ -14974,6 +14958,9 @@ wait_column "" Port_Binding requested_additional_chassis logical_port=migrator
 # For instance, migrator port might still be up from prior to complete migration to hv2
 OVS_WAIT_UNTIL([test `as hv2 ovs-vsctl get Interface migrator external_ids:ovn-installed` = '"true"'])
 
+# Give time for hv3 to handle the change of Port_Binding  for migrator port
+ovn-nbctl --wait=hv sync
+
 # check that...
 # unicast from Third doesn't arrive to hv1:Migrator
 # unicast from Third arrives to hv2:Migrator
@@ -16135,15 +16122,13 @@ sim_add hv1
 as hv1
 ovs-vsctl add-br br-phys
 ovn_attach n1 br-phys 192.168.0.11
-ovs-vsctl -- add-port br-int hv1-vif0 -- \
-set Interface hv1-vif0 ofport-request=1
+ovs-vsctl -- add-port br-int hv1-vif0
 
 sim_add hv2
 as hv2
 ovs-vsctl add-br br-phys
 ovn_attach n1 br-phys 192.168.0.12
-ovs-vsctl -- add-port br-int hv2-vif0 -- \
-set Interface hv2-vif0 ofport-request=1
+ovs-vsctl -- add-port br-int hv2-vif0
 
 # Allow only chassis hv1 to bind logical port lsp0.
 ovn-nbctl lsp-set-options lsp0 requested-chassis=hv1
@@ -16151,6 +16136,16 @@ ovn-nbctl lsp-set-options lsp0 requested-chassis=hv1
 # Allow some time for ovn-northd and ovn-controller to catch up.
 check ovn-nbctl --wait=hv sync
 
+OVS_WAIT_UNTIL([
+    hv1_ofport=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=hv1-vif0)
+    test 1 -le $hv1_ofport
+])
+
+OVS_WAIT_UNTIL([
+    hv2_ofport=$(as hv2 ovs-vsctl --bare --columns ofport find Interface name=hv2-vif0)
+    test 1 -le $hv2_ofport
+])
+
 # Retrieve hv1 and hv2 chassis UUIDs from southbound database
 wait_row_count Chassis 1 name=hv1
 wait_row_count Chassis 1 name=hv2
@@ -16166,7 +16161,7 @@ OVS_WAIT_UNTIL([test 1 = $(grep -c "Not claiming lport lsp0" hv2/ovn-controller.
 wait_row_count Port_Binding 1 logical_port=lsp0 'chassis=[[]]'
 
 # (2) Chassis hv2 should not add flows in OFTABLE_PHY_TO_LOG and OFTABLE_LOG_TO_PHY tables.
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [1], [])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=0 | grep in_port=$hv2_ofport], [1], [])
 AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=65 | grep output], [1], [])
 
 # (3) Chassis hv1 should bind lsp0 when physical to logical mapping exists on hv1.
@@ -16179,8 +16174,8 @@ wait_column "$hv1_uuid" Port_Binding chassis logical_port=lsp0
 
 # (4) Chassis hv1 should add flows in OFTABLE_PHY_TO_LOG and OFTABLE_LOG_TO_PHY tables.
 as hv1 ovs-ofctl dump-flows br-int
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [0], [ignore])
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=65 | grep actions=output:1], [0], [ignore])
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=$hv1_ofport], [0], [ignore])
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=65 | grep actions=output:$hv1_ofport], [0], [ignore])
 
 # (5) Chassis hv1 should release lsp0 binding and chassis hv2 should bind lsp0 when
 # the requested chassis for lsp0 is changed from hv1 to hv2.
@@ -16188,15 +16183,15 @@ echo "verifying that lsp0 binding moves when requested-chassis is changed"
 
 ovn-nbctl lsp-set-options lsp0 requested-chassis=hv2
 # We might see multiple "Releasing lport ...", when sb is read only
-OVS_WAIT_UNTIL([test 1 -le $(grep -c "Releasing lport lsp0 from this chassis" hv1/ovn-controller.log)])
+OVS_WAIT_UNTIL([test 1 -le $(grep -c "Releasing lport lsp0" hv1/ovn-controller.log)])
 
 wait_column "$hv2_uuid" Port_Binding chassis logical_port=lsp0
 
 # (6) Chassis hv2 should add flows and hv1 should not.
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [0], [ignore])
-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=65 | grep actions=output:1], [0], [ignore])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=0 | grep in_port=$hv2_ofport], [0], [ignore])
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=65 | grep actions=output:$hv2_ofport], [0], [ignore])
 
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [1], [])
+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=$hv1_ofport], [1], [])
 AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=65 | grep output], [1], [])
 
 OVN_CLEANUP([hv1],[hv2])
@@ -16276,7 +16271,7 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [0], [ig
 AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=65 | grep actions=output:1], [0], [ignore])
 
 check ovn-nbctl --wait=hv lsp-set-options lsp0 requested-chassis=non-existant-chassis
-OVS_WAIT_UNTIL([test 1 -le $(grep -c "Releasing lport lsp0 from this chassis" hv1/ovn-controller.log)])
+OVS_WAIT_UNTIL([test 1 -le $(grep -c "Releasing lport lsp0" hv1/ovn-controller.log)])
 check ovn-nbctl --wait=hv sync
 wait_column '' Port_Binding chasssi logical_port=lsp0
 AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [1], [])
@@ -18989,55 +18984,8 @@ for sf in 0 1; do
     done
 done
 
-# Test allow rule
-#----------------
-ovn-nbctl acl-del lsw0
-# drop all packets to 11 and 21.
-ovn-nbctl acl-add lsw0 to-lport 5000 'outport == @pg1 && eth.src == $set1' drop
-# allow 0x1234 between 11 and 21
-check ovn-nbctl --wait=hv --log --severity=info --name=allow-acl acl-add lsw0 to-lport 6000 'outport == @pg1 && eth.src == $set1 && eth.type == 0x1234' allow
-for sf in 0 1; do
-    if test ${sf} = 1; then
-        # Add a stateful rule and re-run the check to make sure the
-        # allow rule is still effective..
-        check ovn-nbctl --wait=hv acl-add lsw0 from-lport 2000  'inport == "lp31" && ip' allow-related
-    fi
-    # dump information and flows with counters
-    ovn-sbctl dump-flows -- list multicast_group > sbflows$sf
-    AT_CAPTURE_FILE([sbflows0])
-    AT_CAPTURE_FILE([sbflows1])
-    for is in 1 2 3; do
-        s=${is}1
-        for id in 1 2 3; do
-            d=${id}1
-
-            if test $d != $s;
-            then
-                test_packet $s f000000000$d f000000000$s 1234 $d # allow 1234 to 11, 21, and 31
-            fi
-        done
-
-        # Broadcast and multicast. Allow from one to the other 2.
-        if test ${is} = 1; then
-            bcast="21 31"
-        elif test ${is} = 2; then
-            bcast="11 31"
-        else
-            bcast="11 21"
-        fi
-        test_packet $s ffffffffffff f000000000$s 1234 $bcast
-        test_packet $s 010000000000 f000000000$s 1234 $bcast
-    done
-done
-
-as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int > offlows1
-as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int > offlows2
-as hv3 ovs-ofctl -O OpenFlow13 dump-flows br-int > offlows3
-
-# Now check the packets actually received against the ones expected.
-AT_CAPTURE_FILE([expected])
-AT_CAPTURE_FILE([received])
 check_packets() {
+    n_allowed=$1
     > expected
     > received
     for i in 1 2 3; do
@@ -19057,8 +19005,8 @@ check_packets() {
 --- acl logging
 hv1_drop hit 6
 hv2_drop hit 6
-hv1_allow hit 6
-hv2_allow hit 6
+hv1_allow hit $n_allowed
+hv2_allow hit $n_allowed
 EOF
 
 cat >>received <<EOF
@@ -19071,7 +19019,60 @@ EOF
 
     $at_diff expected received >/dev/null
 }
-OVS_WAIT_UNTIL([check_packets], [$at_diff -F'^---' expected received])
+
+# We need to wait and check here that packets are received as they should as otherwise packets
+# which were just sent might by handled after setting next ACL (allow) rules.
+OVS_WAIT_UNTIL([check_packets 0], [$at_diff -F'^---' expected received])
+
+# Test allow rule
+#----------------
+ovn-nbctl acl-del lsw0
+# drop all packets to 11 and 21.
+ovn-nbctl acl-add lsw0 to-lport 5000 'outport == @pg1 && eth.src == $set1' drop
+# allow 0x1234 between 11 and 21
+check ovn-nbctl --wait=hv --log --severity=info --name=allow-acl acl-add lsw0 to-lport 6000 'outport == @pg1 && eth.src == $set1 && eth.type == 0x1234' allow
+for sf in 0 1; do
+    if test ${sf} = 1; then
+        # Add a stateful rule and re-run the check to make sure the
+        # allow rule is still effective..
+        check ovn-nbctl --wait=hv acl-add lsw0 from-lport 2000  'inport == "lp31" && ip' allow-related
+    fi
+    # dump information and flows with counters
+    ovn-sbctl dump-flows -- list multicast_group > sbflows$sf
+    AT_CAPTURE_FILE([sbflows0])
+    AT_CAPTURE_FILE([sbflows1])
+    for is in 1 2 3; do
+        s=${is}1
+        for id in 1 2 3; do
+            d=${id}1
+
+            if test $d != $s;
+            then
+                test_packet $s f000000000$d f000000000$s 1234 $d # allow 1234 to 11, 21, and 31
+            fi
+        done
+
+        # Broadcast and multicast. Allow from one to the other 2.
+        if test ${is} = 1; then
+            bcast="21 31"
+        elif test ${is} = 2; then
+            bcast="11 31"
+        else
+            bcast="11 21"
+        fi
+        test_packet $s ffffffffffff f000000000$s 1234 $bcast
+        test_packet $s 010000000000 f000000000$s 1234 $bcast
+    done
+done
+
+as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int > offlows1
+as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int > offlows2
+as hv3 ovs-ofctl -O OpenFlow13 dump-flows br-int > offlows3
+
+# Now check the packets actually received against the ones expected.
+AT_CAPTURE_FILE([expected])
+AT_CAPTURE_FILE([received])
+OVS_WAIT_UNTIL([check_packets 6], [$at_diff -F'^---' expected received])
 
 OVN_CLEANUP([hv1],[hv2],[hv3])
 
@@ -19790,11 +19791,6 @@ test_dhcp() {
     as hv1 ovs-appctl netdev-dummy/receive hv${inport}-ext${inport} $request
 }
 
-
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-
 # This shell function sends a DHCPv6 request packet
 # test_dhcpv6 INPORT SRC_MAC SRC_LLA DHCPv6_MSG_TYPE OFFER_IP OUTPORT...
 # The OUTPORTs (zero or more) list the VIFs on which the original DHCPv6
@@ -19876,12 +19872,9 @@ OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
 # NXT_RESUMEs should be 0 in hv2.
 OVS_WAIT_UNTIL([test 0 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets
-cat ext1_v4.expected | cut -c -48 > expout
-AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
+OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c -48"])
 # Skipping the IPv4 checksum.
-cat ext1_v4.expected | cut -c 53- > expout
-AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout])
+OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c 53-"])
 
 # ovs-ofctl also resumes the packets and this causes other ports to receive
 # the DHCP request packet. So reset the pcap files so that its easier to test.
@@ -19903,13 +19896,9 @@ OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
 # NXT_RESUMEs should be 0 in hv2.
 OVS_WAIT_UNTIL([test 0 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
-sort > ext1_v6.packets
-cat ext1_v6.expected | cut -c -120 > expout
-AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
+OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c -120"])
 # Skipping the UDP checksum
-cat ext1_v6.expected | cut -c 125- > expout
-AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
+OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c 125-"])
 
 rm -f ext1_v6.expected
 rm -f ext1_v6.packets
@@ -19969,12 +19958,9 @@ OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
 # NXT_RESUMEs should be 1 in hv2.
 OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets
-cat ext1_v4.expected | cut -c -48 > expout
-AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
+OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c -48"])
 # Skipping the IPv4 checksum.
-cat ext1_v4.expected | cut -c 53- > expout
-AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout])
+OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c 53-"])
 
 # ovs-ofctl also resumes the packets and this causes other ports to receive
 # the DHCP request packet. So reset the pcap files so that its easier to test.
@@ -19995,13 +19981,9 @@ OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
 # NXT_RESUMEs should be 2 in hv2.
 OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
-sort > ext1_v6.packets
-cat ext1_v6.expected | cut -c -120 > expout
-AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
+OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c -120"])
 # Skipping the UDP checksum
-cat ext1_v6.expected | cut -c 125- > expout
-AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
+OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c 125-"])
 
 rm -f ext1_v6.expected
 rm -f ext1_v6.packets
@@ -20077,12 +20059,9 @@ OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
 # NXT_RESUMEs should be 2 in hv2.
 OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets
-cat ext1_v4.expected | cut -c -48 > expout
-AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
+OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c -48"])
 # Skipping the IPv4 checksum.
-cat ext1_v4.expected | cut -c 53- > expout
-AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout])
+OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c 53-"])
 
 # ovs-ofctl also resumes the packets and this causes other ports to receive
 # the DHCP request packet. So reset the pcap files so that its easier to test.
@@ -20104,13 +20083,9 @@ OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
 # NXT_RESUMEs should be 2 in hv2.
 OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
-sort > ext1_v6.packets
-cat ext1_v6.expected | cut -c -120 > expout
-AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
+OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c -120"])
 # Skipping the UDP checksum
-cat ext1_v6.expected | cut -c 125- > expout
-AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
+OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c 125-"])
 
 rm -f ext1_v6.expected
 rm -f ext1_v6.packets
@@ -20158,12 +20133,9 @@ OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
 # NXT_RESUMEs should be 1 in hv3.
 OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv3.log | grep -c NXT_RESUME`])
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets
-cat ext1_v4.expected | cut -c -48 > expout
-AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
+OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c -48"])
 # Skipping the IPv4 checksum.
-cat ext1_v4.expected | cut -c 53- > expout
-AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout])
+OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c 53-"])
 
 # ovs-ofctl also resumes the packets and this causes other ports to receive
 # the DHCP request packet. So reset the pcap files so that its easier to test.
@@ -20188,13 +20160,9 @@ OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
 # NXT_RESUMEs should be 2 in hv3.
 OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv3.log | grep -c NXT_RESUME`])
 
-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
-sort > ext1_v6.packets
-cat ext1_v6.expected | cut -c -120 > expout
-AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
+OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c -120"])
 # Skipping the UDP checksum
-cat ext1_v6.expected | cut -c 125- > expout
-AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
+OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c 125-"])
 
 # disconnect hv3 from the network, hv1 should take over
 as hv3
@@ -21223,6 +21191,7 @@ OVN_FOR_EACH_NORTHD([
 AT_SETUP([ipam to non-ipam])
 ovn_start
 
+check ovn-nbctl --wait=sb sync
 ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="0a:00:00:00:00:00"
 ovn-nbctl ls-add sw0
 ovn-nbctl lsp-add sw0 p0 -- lsp-set-addresses p0 dynamic
@@ -23426,7 +23395,7 @@ check ovn-nbctl set Logical_Switch sw2 \
     other_config:mcast_querier="false"
 
 # Enable multicast snooping on sw3.
-check ovn-nbctl --wait=sb set Logical_Switch sw3       \
+check ovn-nbctl --wait=hv set Logical_Switch sw3       \
     other_config:mcast_querier="false" \
     other_config:mcast_snoop="true"
 
@@ -23465,7 +23434,7 @@ OVS_WAIT_UNTIL(
   [$at_diff -F'^---' exp rcv])
 
 # Enable multicast relay on rtr
-check ovn-nbctl --wait=sb set logical_router rtr options:mcast_relay="true"
+check ovn-nbctl --wait=hv set logical_router rtr options:mcast_relay="true"
 
 AT_CAPTURE_FILE([sbflows6])
 ovn-sbctl dump-flows > sbflows6
@@ -26413,10 +26382,13 @@ check ovn-nbctl lsp-add ts ts-lr3 \
 wait_for_ports_up
 
 ovn_as az1
+OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr2])
 check ovn-nbctl lsp-set-options ts-lr2 requested-chassis=hv2
+OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr3])
 check ovn-nbctl lsp-set-options ts-lr3 requested-chassis=hv2
 
 ovn_as az2
+OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr1])
 check ovn-nbctl lsp-set-options ts-lr1 requested-chassis=hv1
 
 dnl Enable unregistered IP multicast flooding and IP multicast relay.
@@ -26625,10 +26597,13 @@ check ovn-nbctl lsp-add ts ts-lr3 \
 wait_for_ports_up
 
 ovn_as az1
+OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr2])
 check ovn-nbctl lsp-set-options ts-lr2 requested-chassis=hv2
+OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr3])
 check ovn-nbctl lsp-set-options ts-lr3 requested-chassis=hv2
 
 ovn_as az2
+OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr1])
 check ovn-nbctl lsp-set-options ts-lr1 requested-chassis=hv1
 
 dnl Enable IP multicast snooping and IP multicast relay.  Reports are
@@ -27984,8 +27959,9 @@ ovn-nbctl ls-add ls1
 ovn-nbctl --wait=sb lsp-add ls1 lsp1
 
 # Simulate the fact that lsp1 had been previously bound on hv1.
-ovn-sbctl --id=@e create encap chassis_name=hv1 ip="192.168.0.1" type="geneve" \
-    -- --id=@c create chassis name=hv1 encaps=@e \
+ovn-sbctl --id=@e1 create encap chassis_name=hv1 ip="192.168.0.1" type="geneve" \
+    --id=@e2 create encap chassis_name=hv1 ip="192.168.0.1" type="vxlan" \
+    -- --id=@c create chassis name=hv1 encaps=@e1,@e2 \
     -- set Port_Binding lsp1 chassis=@c
 
 as hv1
@@ -28011,8 +27987,9 @@ ovn-nbctl ls-add ls1
 ovn-nbctl --wait=sb lsp-add ls1 lsp1
 
 # Simulate the fact that lsp1 had been previously bound on hv1.
-ovn-sbctl --id=@e create encap chassis_name=hv1 ip="192.168.0.1" type="geneve" \
-    -- --id=@c create chassis hostname=hv1 name=hv1 encaps=@e \
+ovn-sbctl --id=@e1 create encap chassis_name=hv1 ip="192.168.0.1" type="geneve" \
+    --id=@e2 create encap chassis_name=hv1 ip="192.168.0.1" type="vxlan" \
+    -- --id=@c create chassis name=hv1 encaps=@e1,@e2 \
     -- set Port_Binding lsp1 chassis=@c
 
 as hv1
@@ -28137,9 +28114,10 @@ OVS_WAIT_UNTIL([
 ])
 
 as hv1 check ovs-ofctl del-flows br-phys
+
 AT_DATA([flows.txt], [dnl
-table=0, priority=0 actions=NORMAL
 table=0, priority=200 arp,actions=drop
+table=0, priority=0 actions=NORMAL
 table=0, priority=100, pkt_mark=0x64 actions=drop
 table=0, priority=100, pkt_mark=0x2 actions=drop
 table=0, priority=100, pkt_mark=0x3 actions=drop
@@ -28966,7 +28944,7 @@ AT_CHECK([
         grep "priority=200" | \
         grep -c "move:NXM_NX_CT_LABEL\\[[\\]]->NXM_NX_XXREG1\\[[\\]],move:NXM_NX_XXREG1\\[[32..79\\]]->NXM_OF_ETH_DST"
     done; :], [0], [dnl
-6
+2
 1
 0
 0
@@ -29091,7 +29069,7 @@ AT_CHECK([
         grep "priority=200" | \
         grep -c "move:NXM_NX_CT_LABEL\\[[\\]]->NXM_NX_XXREG1\\[[\\]],move:NXM_NX_XXREG1\\[[32..79\\]]->NXM_OF_ETH_DST"
     done; :], [0], [dnl
-6
+2
 1
 0
 0
@@ -30785,7 +30763,6 @@ check ovn-nbctl --wait=hv ls-lb-del sw0 lb-ipv6
 # original destination tuple.
 #
 # ovn-controller should fall back to matching on ct_nw_dst()/ct_tp_dst().
-as northd-backup ovn-appctl -t NORTHD_TYPE pause
 as northd ovn-appctl -t NORTHD_TYPE pause
 
 check ovn-sbctl \
@@ -30836,7 +30813,6 @@ OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_a
 
 # Resume ovn-northd.
 as northd ovn-appctl -t NORTHD_TYPE resume
-as northd-backup ovn-appctl -t NORTHD_TYPE resume
 check ovn-nbctl --wait=hv sync
 
 as hv2 ovs-vsctl del-port hv2-vif1
@@ -30957,9 +30933,6 @@ AT_CHECK([grep -c $northd_version hv1/ovn-controller.log], [0], [1
 as northd
 OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
 
-as northd-backup
-OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
-
 check ovn-sbctl set SB_Global . options:northd_internal_version=foo
 
 as hv1
@@ -31101,7 +31074,6 @@ AS_BOX([ovn-controller should not reset Port_Binding.up without northd])
 # Pause northd and clear the "up" field to simulate older ovn-northd
 # versions writing to the Southbound DB.
 as northd ovn-appctl -t NORTHD_TYPE pause
-as northd-backup ovn-appctl -t NORTHD_TYPE pause
 
 as hv1 ovn-appctl -t ovn-controller debug/pause
 check ovn-sbctl clear Port_Binding lsp1 up
@@ -31117,7 +31089,6 @@ check_column "" Port_Binding up logical_port=lsp1
 # Once northd should explicitly set the Port_Binding.up field to 'false' and
 # ovn-controller sets it to 'true' as soon as the update is processed.
 as northd ovn-appctl -t NORTHD_TYPE resume
-as northd-backup ovn-appctl -t NORTHD_TYPE resume
 wait_column "true" Port_Binding up logical_port=lsp1
 wait_column "true" nb:Logical_Switch_Port up name=lsp1
 
@@ -31272,7 +31243,6 @@ check ovn-nbctl lsp-set-addresses sw0-p4 "00:00:00:00:00:04 192.168.47.4"
 # will be sent in one transaction.
 
 check as northd ovn-appctl -t NORTHD_TYPE pause
-check as northd-backup ovn-appctl -t NORTHD_TYPE pause
 
 check ovn-nbctl lsp-add sw0 sw0-p1
 check ovn-nbctl lsp-set-addresses sw0-p1 "00:00:00:00:00:01 192.168.47.1"
@@ -31453,10 +31423,6 @@ send_icmp_packet() {
     as hv$hv ovs-appctl netdev-dummy/receive hv$hv-vif$inport $packet
 }
 
-trim_zeros() {
-    sed 's/\(00\)\{1,\}$//'
-}
-
 AS_BOX([Wait for all ports to be up])
 wait_for_ports_up
 
@@ -33727,7 +33693,6 @@ check as hv1 ovn-appctl -t ovn-controller exit
 
 # Pause northd to guarantee that ovn-controller starts before requested_chassis
 # column is filled.
-check as northd-backup ovn-appctl -t ovn-northd pause
 check as northd ovn-appctl -t ovn-northd pause
 
 # Wait until requested_chassis is empty
@@ -33741,7 +33706,6 @@ wait_column "hv1" Chassis name
 
 # Start northd and wait for events to be processed
 check as northd ovn-appctl -t ovn-northd resume
-check as northd-backup ovn-appctl -t ovn-northd resume
 
 wait_for_ports_up lsp1
 
@@ -34471,7 +34435,7 @@ dnat_zone=$(ovs-ofctl dump-flows br-int table=$DNAT_TABLE,metadata=0x${lr0_dp_ke
 if test -n "$dnat_zone"; then
   dnat_zone=${dnat_zone::-1}
 fi
-snat_zone=$(ovs-ofctl dump-flows br-int table=$SNAT_TABLE,metadata=0x${lr0_dp_key} | grep priority=153 | grep -o zone=.*, | cut -d '=' -f 2)
+snat_zone=$(ovs-ofctl dump-flows br-int table=$SNAT_TABLE,metadata=0x${lr0_dp_key} | grep priority=153 | grep ct_state=-trk | grep -o zone=.*, | cut -d '=' -f 2)
 if test -n "$snat_zone"; then
   snat_zone=${snat_zone::-1}
 fi
@@ -34488,7 +34452,7 @@ dnat_zone=$(ovs-ofctl dump-flows br-int table=$DNAT_TABLE,metadata=0x${lr0_dp_ke
 if test -n "$dnat_zone"; then
   dnat_zone=${dnat_zone::-1}
 fi
-snat_zone=$(ovs-ofctl dump-flows br-int table=$SNAT_TABLE,metadata=0x${lr0_dp_key} | grep priority=153 | grep -o zone=.*, | cut -d '=' -f 2)
+snat_zone=$(ovs-ofctl dump-flows br-int table=$SNAT_TABLE,metadata=0x${lr0_dp_key} | grep priority=153 | grep ct_state=-trk | grep -o zone=.*, | cut -d '=' -f 2)
 if test -n "$snat_zone"; then
   snat_zone=${snat_zone::-1}
 fi
@@ -34572,6 +34536,7 @@ AT_CLEANUP
 
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([MAC binding aging])
+AT_SKIP_IF([test $HAVE_SCAPY = no])
 ovn_start
 
 net_add n1
@@ -34748,8 +34713,8 @@ check ovn-nbctl lsp-add sw0 sw0-p1
 check ovn-nbctl lsp-add sw0 lsp
 check ovn-nbctl lsp-set-type lsp router
 check ovn-nbctl lsp-set-options lsp router-port=lrp
-check ovn-nbctl lsp-set-addresses lsp  00:00:00:00:00:1
-check ovn-nbctl lrp-add ro0 lrp 00:00:00:00:00:1 aef0:0:0:0:0:0:0:1/64
+check ovn-nbctl lsp-set-addresses lsp  00:00:00:00:00:01
+check ovn-nbctl lrp-add ro0 lrp 00:00:00:00:00:01 aef0:0:0:0:0:0:0:1/64
 check ovn-nbctl set Logical_Router_Port lrp ipv6_ra_configs:send_periodic=true \
         -- set Logical_Router_Port lrp ipv6_ra_configs:address_mode=slaac \
         -- set Logical_Router_Port lrp ipv6_ra_configs:mtu=1280 \
@@ -34843,8 +34808,8 @@ check ovn-nbctl lsp-add sw0 sw0-p1
 check ovn-nbctl lsp-add sw0 lsp
 check ovn-nbctl lsp-set-type lsp router
 check ovn-nbctl lsp-set-options lsp router-port=lrp
-check ovn-nbctl lsp-set-addresses lsp  00:00:00:00:00:1
-check ovn-nbctl --wait=hv lrp-add ro0 lrp 00:00:00:00:00:1 aef0:0:0:0:0:0:0:1/64
+check ovn-nbctl lsp-set-addresses lsp  00:00:00:00:00:01
+check ovn-nbctl --wait=hv lrp-add ro0 lrp 00:00:00:00:00:01 aef0:0:0:0:0:0:0:1/64
 check ovn-nbctl --wait=hv lsp-del lsp
 check ovn-nbctl lrp-del lrp
 check ovn-nbctl --wait=hv sync
@@ -35389,37 +35354,6 @@ OVN_CLEANUP([hv1],[hv2])
 AT_CLEANUP
 ])
 
-OVN_FOR_EACH_NORTHD([
-AT_SETUP([feature inactivity probe])
-ovn_start
-net_add n1
-
-sim_add hv1
-as hv1
-check ovs-vsctl add-br br-phys
-ovn_attach n1 br-phys 192.168.0.1
-
-dnl Ensure that there are 4 openflow connections.
-OVS_WAIT_UNTIL([test "$(grep -c 'negotiated OpenFlow version' hv1/ovs-vswitchd.log)" -eq "4"])
-
-dnl "Wait" 3 times 60 seconds and ensure ovn-controller writes to the
-dnl openflow connections in the meantime.  This should allow ovs-vswitchd
-dnl to probe the openflow connections at least twice.
-
-as hv1 ovs-appctl time/warp 60000
-check ovn-nbctl --wait=hv sync
-
-as hv1 ovs-appctl time/warp 60000
-check ovn-nbctl --wait=hv sync
-
-as hv1 ovs-appctl time/warp 60000
-check ovn-nbctl --wait=hv sync
-
-AT_CHECK([test -z "`grep disconnecting hv1/ovs-vswitchd.log`"])
-OVN_CLEANUP([hv1])
-AT_CLEANUP
-])
-
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([Logical flows with Chassis_Template_Var references])
 AT_KEYWORDS([templates])
@@ -35795,7 +35729,7 @@ as hv1 ovs-vsctl set-ssl \
 echo hv3 > ${OVN_SYSCONFDIR}/test_hv
 
 # the last argument is passed to ovn-controller through cli
-ovn_attach n1 br-phys 192.168.0.1 24 vxlan hv1 -n hv3
+ovn_attach n1 br-phys 192.168.0.1 24 vxlan hv1 hv3
 
 sim_add hv2
 as hv2
@@ -36728,7 +36662,6 @@ check ovn-nbctl lsp-add ls2 public2
 check ovn-nbctl lsp-set-addresses public2 unknown
 check ovn-nbctl lsp-set-type public2 localnet
 check ovn-nbctl --wait=sb set Logical_Switch_Port public2 options:qos_min_rate=6000000000 options:qos_max_rate=7000000000 options:qos_burst=8000000000 options:network_name=phys
-check ovn-nbctl --wait=sb lsp-set-options public2 qos_min_rate=6000000000 qos_max_rate=7000000000 qos_burst=8000000000
 
 # Let's now send ovn controller to sleep, so it will receive both ofport notification and ls deletion simultaneously
 sleep_controller hv-1
@@ -36861,14 +36794,14 @@ check ovn-nbctl ls-add sw0
 check ovn-nbctl lsp-add sw0 sw0-port1
 check ovn-nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:01 192.168.0.2"
 
+ovs-vsctl add-port br-int p1 -- \
+    set Interface p1 external_ids:iface-id=sw0-port1
+
 check ovn-nbctl lsp-add sw0 sw0-lr0
 check ovn-nbctl lsp-set-type sw0-lr0 router
 check ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01
 check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
 
-ovs-vsctl add-port br-int p1 -- \
-    set Interface p1 external_ids:iface-id=sw0-port1
-
 check ovn-appctl -t ovn-controller vlog/set dbg:main
 
 wait_for_ports_up
@@ -37005,3 +36938,730 @@ AT_CHECK([grep -c "NXT_CT_FLUSH_ZONE" hv1/ovs-vswitchd.log], [0], [dnl
 OVN_CLEANUP([hv1])
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([virtual port claim race condition])
+AT_KEYWORDS([virtual ports])
+ovn_start
+
+send_garp() {
+    local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 spa=$5 tpa=$6
+    local request=${eth_dst}${eth_src}08060001080006040001${eth_src}${spa}${eth_dst}${tpa}
+    as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request
+}
+
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovn-appctl vlog/set dbg
+ovs-vsctl -- add-port br-int hv1-vif1 -- \
+    set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
+    options:tx_pcap=hv1/vif1-tx.pcap \
+    options:rxq_pcap=hv1/vif1-rx.pcap \
+    ofport-request=1
+ovs-vsctl -- add-port br-int hv1-vif3 -- \
+    set interface hv1-vif3 \
+    options:tx_pcap=hv1/vif3-tx.pcap \
+    options:rxq_pcap=hv1/vif3-rx.pcap \
+    ofport-request=3
+
+ovs-appctl -t ovn-controller vlog/set dbg
+
+ovn-nbctl ls-add sw0
+
+check ovn-nbctl lsp-add sw0 sw0-vir
+check ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10"
+check ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10"
+check ovn-nbctl lsp-set-type sw0-vir virtual
+check ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10
+check ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1
+
+check ovn-nbctl lsp-add sw0 sw0-p1
+check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3 1000::3"
+check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 10.0.0.3 10.0.0.10 1000::3"
+
+# Create a logical router and attach both logical switches
+check ovn-nbctl lr-add lr0
+check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 1000::a/64
+check ovn-nbctl lsp-add sw0 sw0-lr0
+check ovn-nbctl lsp-set-type sw0-lr0 router
+check ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01
+check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
+
+wait_for_ports_up
+ovn-nbctl --wait=hv sync
+hv1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv1"`
+
+# Try to bind sw0-vir directly to an OVS port. This should be ignored by
+# ovn-controller.
+as hv1 ovs-vsctl set interface hv1-vif3 external-ids:iface-id=sw0-vir
+
+# Make sb to sleep, so that claim of sw0-vir (through pinctrl) and hv1-vif3 can be handled within same idl by controller
+sleep_sb
+
+# From sw0-p0 send GARP for 10.0.0.10. hv1 should claim sw0-vir
+# and sw0-p1 should be its virtual_parent.
+eth_src=505400000003
+eth_dst=ffffffffffff
+spa=$(ip_to_hex 10 0 0 10)
+tpa=$(ip_to_hex 10 0 0 10)
+send_garp 1 1 $eth_src $eth_dst $spa $tpa
+
+OVS_WAIT_UNTIL([test 1 = `cat hv1/ovn-controller.log | grep "pinctrl received  packet-in" | \
+grep opcode=BIND_VPORT | grep OF_Table_ID=29 | wc -l`])
+
+sleep_controller hv1
+
+# Cleanup hv1-vif3. This should not interfere with sw0-vir claim
+as hv1 ovs-vsctl del-port hv1-vif3
+
+wake_up_sb
+ovn-nbctl --wait=sb sync
+wake_up_controller hv1
+check ovn-nbctl --wait=hv sync
+
+wait_row_count Port_Binding 1 logical_port=sw0-vir chassis=$hv1_ch_uuid
+check_row_count Port_Binding 1 logical_port=sw0-vir virtual_parent=sw0-p1
+wait_for_ports_up sw0-vir
+check ovn-nbctl --wait=hv sync
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([pod to pod with localnet_learn_fdb])
+AT_SKIP_IF([test $HAVE_SCAPY = no])
+
+# 6 VIFs, 3 per HV: vif11, vif12, vif13 on hv1.
+# vif11 will exchange packets with vif21, vif12 w/ vif22 and so on.
+#
+ovn_start
+net_add n1
+
+check ovn-nbctl ls-add ls0
+
+check ovn-nbctl lsp-add ls0 ln_port -- \
+      lsp-set-addresses ln_port unknown -- \
+      lsp-set-type ln_port localnet -- \
+      lsp-set-options ln_port network_name=physnet1
+
+for i in $(seq 1 3); do
+    check ovn-nbctl lsp-add ls0 vif1$i -- \
+          lsp-set-addresses vif1$i unknown
+    check ovn-nbctl lsp-add ls0 vif2$i -- \
+          lsp-set-addresses vif2$i unknown
+done
+
+n_pkt=(0 0 0 0 0 0)
+
+for hv in 1 2; do
+    sim_add hv${hv}
+    as hv${hv}
+    ovs-vsctl add-br br-phys
+    ovn_attach n1 br-phys 192.168.0.${hv}
+
+    for i in $(seq 1 3); do
+        ovs-vsctl -- add-port br-int vif${hv}${i} -- \
+            set interface vif${hv}${i} external-ids:iface-id=vif${hv}${i} \
+            options:tx_pcap=hv${hv}/vif${hv}${i}-tx.pcap \
+            options:rxq_pcap=hv${hv}/vif${hv}${i}-rx.pcap \
+            ofport-request=$i
+    done
+    ovs-vsctl -- add-port br-phys ext0 -- \
+        set interface ext0 \
+        options:tx_pcap=hv${hv}/ext0-tx.pcap \
+        options:rxq_pcap=hv${hv}/ext0-rx.pcap \
+        ofport-request=4
+    ovs-vsctl set open . external_ids:ovn-bridge-mappings=physnet1:br-phys
+done
+
+OVN_POPULATE_ARP
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+ln_port_key=$(fetch_column port_binding tunnel_key logical_port=ln_port)
+vif11_key=$(fetch_column port_binding tunnel_key logical_port=vif11)
+vif12_key=$(fetch_column port_binding tunnel_key logical_port=vif12)
+vif13_key=$(fetch_column port_binding tunnel_key logical_port=vif13)
+vif21_key=$(fetch_column port_binding tunnel_key logical_port=vif21)
+vif22_key=$(fetch_column port_binding tunnel_key logical_port=vif22)
+vif23_key=$(fetch_column port_binding tunnel_key logical_port=vif23)
+
+ensure_controller_run() {
+# We want to make sure controller could run at least one full loop.
+# We can't use wait=hv as sb might be sleeping.
+# Use 2 ovn-appctl to guarentee that ovn-controller run the full loop, and not just the unixctl handling
+  hv=$1
+  OVS_WAIT_UNTIL([test x$(as $hv ovn-appctl -t ovn-controller debug/status) = "xrunning"])
+  OVS_WAIT_UNTIL([test x$(as $hv ovn-appctl -t ovn-controller debug/status) = "xrunning"])
+}
+
+wait_for_packets() {
+    local hv=$1
+    local vif=$2
+    counter=$(((${hv:2} - 1) * 3 + ${vif:4} - 1))
+    n_pkt[[$counter]]=$((${n_pkt[[$counter]]} + 1))
+    echo "waiting for ${n_pkt[[$counter]]} packets on ${hv}/${vif} using counter $counter"
+    OVS_WAIT_UNTIL([test $(tcpdump ip -nnner ${hv}/${vif}-tx.pcap | wc -l) -eq ${n_pkt[[$counter]]}])
+}
+
+check_no_packets() {
+    local hv=$1
+    local vif=$2
+    counter=$(((${hv:2} - 1) * 3 + ${vif:4} - 1))
+    echo "waiting for ${n_pkt[[$counter]]} packets on ${hv}/${vif} using counter $counter"
+    OVS_WAIT_UNTIL([test $(tcpdump ip -nnner ${hv}/${vif}-tx.pcap | wc -l) -eq ${n_pkt[[$counter]]}])
+}
+
+send_packet() {
+    hv=$1
+    a_src=$2
+    a_dst=$3
+    dev=vif$2
+    AS_BOX([$(date +%H:%M:%S.%03N) sending packet from $dev in $hv $a_src to $a_dst])
+    packet=$(fmt_pkt "
+            Ether(dst='00:00:00:00:10:${a_dst}', src='00:00:00:00:10:${a_src}') /
+            IP(src='192.168.10.${a_src}', dst='192.168.10.${a_dst}') /
+            UDP(sport=1234, dport=1235)
+           ")
+    as $hv ovs-appctl netdev-dummy/receive $dev $packet
+}
+
+check_flow_count() {
+    hv=$1
+    count=$2
+    echo "Checking flow count for $hv is $count"
+    OVS_WAIT_UNTIL([test $count = $(as $hv ovs-ofctl dump-flows br-int table=72 | grep -v "NXST_FLOW reply" | wc -l)])
+}
+
+# Sending packet in both direction. Should create FDB entries for vifs
+# No localnet_learn_fdb yet
+AS_BOX([$(date +%H:%M:%S.%03N) vif11 <=> vif21])
+send_packet hv1 11 21
+for i in 1 2 3; do
+    wait_for_packets hv2 vif2${i}
+done
+for i in 2 3; do
+    wait_for_packets hv1 vif1${i}
+done
+# vif11 should now own the mac
+wait_column "$vif11_key" fdb port_key mac='"00:00:00:00:10:11"'
+
+send_packet hv2 21 11
+wait_for_packets hv1 vif11
+for i in 2 3; do
+    check_no_packets hv1 vif1${i}
+done
+wait_column "$vif21_key" fdb port_key mac='"00:00:00:00:10:21"'
+
+check_flow_count hv1 2
+check_flow_count hv2 2
+
+# We now enable localnet_learn_fdb
+# We check how it behaves with existing vif entries in fdb
+check ovn-nbctl --wait=hv set logical_switch_port ln_port options:localnet_learn_fdb=true
+
+AS_BOX([$(date +%H:%M:%S.%03N) vif11 <=> vif21 after learn_fdb])
+send_packet hv1 11 21
+
+wait_for_packets hv2 vif21
+for i in 2 3; do
+    check_no_packets hv2 vif2${i}
+done
+wait_column "$vif11_key" fdb port_key mac='"00:00:00:00:10:11"'
+
+send_packet hv2 21 11
+wait_for_packets hv1 vif11
+for i in 2 3; do
+    check_no_packets hv1 vif1${i}
+done
+wait_column "$vif21_key" fdb port_key mac='"00:00:00:00:10:21"'
+
+# In both controllers,
+# - 1st packet (in both dir) should have reached controller for the vif, not for localnet (as learn_fdb disabled for localnet)
+# - 2nd packet should not cause any upcall to controller as vif already owns the mac.
+AT_CHECK([test 1 = `cat hv1/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
+AT_CHECK([test 1 = `cat hv2/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
+
+check_flow_count hv1 4
+check_flow_count hv2 4
+
+# Send a few more packets
+send_packet hv1 11 21
+send_packet hv2 21 11
+
+for hv in 1 2; do
+    wait_for_packets hv${hv} vif${hv}1
+    for i in 2 3; do
+        echo CHECK
+        check_no_packets hv${hv} vif${hv}${i}
+    done
+done
+
+# The last packets should have gone through the fast path
+AT_CHECK([test 1 = `cat hv1/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
+AT_CHECK([test 1 = `cat hv2/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
+
+# Check that there are no bad surprises
+wait_column "$vif11_key" fdb port_key mac='"00:00:00:00:10:11"'
+wait_column "$vif21_key" fdb port_key mac='"00:00:00:00:10:21"'
+
+# We will now create fdb entries AFTER enabing localnet_learn_fdb
+# We make ovn_controller (hv1 or hv2) to sleep to control who writes first to fdb
+# as otherwise no guarentee.
+AS_BOX([$(date +%H:%M:%S.%03N) vif12 <=> vif22])
+# We make sure that the fdb update by the vif is done after the localnet update
+sleep_controller hv1
+send_packet hv1 12 22
+for i in 1 3; do
+    wait_for_packets hv1 vif1${i}
+done
+for i in 1 2 3; do
+    wait_for_packets hv2 vif2${i}
+done
+
+# ln_port should own the mac as vif not written yet
+wait_column "$ln_port_key" fdb port_key mac='"00:00:00:00:10:12"'
+
+wake_up_controller hv1
+# vif1 should now own the mac
+wait_column "$vif12_key" fdb port_key mac='"00:00:00:00:10:12"'
+
+sleep_controller hv2
+send_packet hv2 22 12
+wait_for_packets hv1 vif12
+for i in 1 3; do
+    check_no_packets hv1 vif1${i}
+done
+wait_column "$ln_port_key" fdb port_key mac='"00:00:00:00:10:22"'
+
+wake_up_controller hv2
+wait_column "$vif22_key" fdb port_key mac='"00:00:00:00:10:22"'
+
+check_flow_count hv1 8
+check_flow_count hv2 8
+
+AS_BOX([$(date +%H:%M:%S.%03N) vif13 <=> vif23 ])
+# Now we do the other way around: we make sure that the localnet update is done after the vif update.
+# So, when packet is sent from vif1 to vif2, vif1 will be learnt (by hv1) and written in sb
+# Then, when we wake up ovn-controller on hv2, it will learn on localnet.
+# This used to cause localnet entry to overwrite vif entry in sb
+sleep_controller hv2
+send_packet hv1 13 23
+for i in 1 2; do
+    wait_for_packets hv1 vif1${i}
+done
+for i in 1 2 3; do
+    wait_for_packets hv2 vif2${i}
+done
+
+wait_column "$vif13_key" fdb port_key mac='"00:00:00:00:10:13"'
+
+
+# At this point, FDB contains vif1 entry for mac 00:00:00:00:10:13.
+# However, as hv2 controller is sleeping, the flows in hv2 do not
+# contain the flows related to that fdb entry.
+# Hence, the packet which went through still failed the lookup.
+wake_up_controller hv2
+
+# FDB shoud not have changed. Just make sure controller has run and check fdb
+ensure_controller_run hv2
+wait_column "$vif13_key" fdb port_key mac='"00:00:00:00:10:13"'
+
+sleep_controller hv1
+send_packet hv2 23 13
+wait_for_packets hv1 vif13
+for i in 1 2; do
+    check_no_packets hv2 vif2${i}
+done
+for i in 1 2; do
+    check_no_packets hv1 vif1${i}
+done
+wait_column "$vif23_key" fdb port_key mac='"00:00:00:00:10:23"'
+
+wake_up_controller hv1
+ensure_controller_run hv1
+wait_column "$vif23_key" fdb port_key mac='"00:00:00:00:10:23"'
+
+# In both controllers
+# - vif11 <=> vif21: 1 PACKET_IN
+# - vif12 <=> vif22: 2 PACKET_IN
+# - vif13 <=> vif23: 2 PACKET_IN
+# controller + .
+AT_CHECK([test 5 = `cat hv1/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
+AT_CHECK([test 5 = `cat hv2/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
+
+# Send a few more packets
+send_packet hv1 13 23
+send_packet hv2 23 13
+wait_for_packets hv2 vif23
+wait_for_packets hv1 vif13
+for i in 1 2; do
+    check_no_packets hv1 vif1${i}
+    check_no_packets hv2 vif2${i}
+done
+send_packet hv1 13 23
+send_packet hv2 23 13
+wait_for_packets hv2 vif23
+wait_for_packets hv1 vif13
+for i in 1 2; do
+    check_no_packets hv1 vif1${i}
+    check_no_packets hv2 vif2${i}
+done
+
+# The last packets should have gone through the fast path
+AT_CHECK([test 5 = `cat hv1/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
+AT_CHECK([test 5 = `cat hv2/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
+
+# Check that there are no bad surprises
+wait_column "$vif11_key" fdb port_key mac='"00:00:00:00:10:11"'
+wait_column "$vif12_key" fdb port_key mac='"00:00:00:00:10:12"'
+wait_column "$vif13_key" fdb port_key mac='"00:00:00:00:10:13"'
+wait_column "$vif21_key" fdb port_key mac='"00:00:00:00:10:21"'
+wait_column "$vif22_key" fdb port_key mac='"00:00:00:00:10:22"'
+wait_column "$vif23_key" fdb port_key mac='"00:00:00:00:10:23"'
+
+check_flow_count hv1 12
+check_flow_count hv2 12
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([port up with slow northd])
+ovn_start
+
+sleep_northd() {
+  echo northd going to sleep
+  AT_CHECK([kill -STOP $(cat northd/ovn-northd.pid)])
+}
+
+wake_up_northd() {
+  echo northd going to sleep
+  AT_CHECK([kill -CONT $(cat northd/ovn-northd.pid)])
+}
+
+net_add n1
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.11
+
+check ovn-nbctl --wait=hv ls-add ls0
+# Create a pilot port and wait it up to make sure we are ready for the real
+# tests, so that the counters measured are accurate.
+check ovn-nbctl --wait=hv lsp-add ls0 lsp-pilot -- lsp-set-addresses lsp-pilot "unknown"
+ovs-vsctl add-port br-int lsp-pilot -- set interface lsp-pilot external_ids:iface-id=lsp-pilot
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+check ovn-nbctl --wait=hv lsp-add ls0 lsp0-2 -- lsp-set-addresses lsp0-2 "aa:aa:aa:00:00:02 192.168.0.12"
+ovs-vsctl add-port br-int lsp0-2 -- set interface lsp0-2 external_ids:iface-id=lsp0-2
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+sleep_northd
+check ovn-nbctl lsp-del lsp0-2
+check ovn-nbctl lsp-add ls0 lsp0-2 -- lsp-set-addresses lsp0-2 "aa:aa:aa:00:00:02 192.168.0.12"
+wake_up_northd
+
+check ovn-nbctl --wait=sb sync
+wait_for_ports_up
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([gratuitous arp max timeout])
+AT_KEYWORDS([slowtest])
+TAG_UNSTABLE
+AT_SKIP_IF([test $HAVE_TCPDUMP = no])
+ovn_start
+
+check ovn-nbctl ls-add ls0
+check ovn-nbctl lr-add lr0
+check ovn-nbctl lrp-add lr0 lr0-ls0 f0:00:00:00:00:01 192.168.0.1/24
+check ovn-nbctl lsp-add ls0 ls0-lr0 -- set Logical_Switch_Port ls0-lr0 \
+    type=router options:router-port=lr0-ls0 addresses='"f0:00:00:00:00:01"'
+
+check ovn-nbctl lsp-add ls0 ln_port
+check ovn-nbctl lsp-set-addresses ln_port unknown
+check ovn-nbctl lsp-set-type ln_port localnet
+check ovn-nbctl --wait=hv lsp-set-options ln_port network_name=physnet1
+
+net_add n1
+sim_add hv1
+as hv1
+check ovs-vsctl \
+    -- add-br br-phys \
+    -- add-br br-eth0
+
+ovn_attach n1 br-phys 192.168.0.10
+
+check ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-eth0
+check ovs-vsctl add-port br-eth0 snoopvif -- set Interface snoopvif options:tx_pcap=hv1/snoopvif-tx.pcap options:rxq_pcap=hv1/snoopvif-rx.pcap
+
+# set garp max timeout to 2s
+as hv1 check ovs-vsctl set Open_vSwitch . external-ids:garp-max-timeout-sec=2
+
+# Wait until the patch ports are created in hv1 to connect br-int to br-eth0
+check ovn-nbctl set logical_router lr0 options:chassis=hv1
+OVN_WAIT_PATCH_PORT_FLOWS(["ln_port"], ["hv1"])
+
+# sleep for 12s to get a garp every ~ 2s
+sleep 12
+
+n_arp=$(tcpdump -ner hv1/snoopvif-tx.pcap arp | wc -l)
+AT_CHECK([test $n_arp -ge 5 -a $n_arp -lt 10])
+
+# Temporarily remove lr0 chassis
+# Wait for hv confirmation to make sure chassis is removed before we reset pcap
+# Otherwise a garp might be sent after pcap have been reset but before chassis is removed
+check ovn-nbctl --wait=hv remove logical_router lr0 options chassis
+
+as hv1 reset_pcap_file snoopvif hv1/snoopvif
+# set garp max timeout to 1s
+as hv1 check ovs-vsctl set Open_vSwitch . external-ids:garp-max-timeout-sec=1
+check ovn-nbctl set logical_router lr0 options:chassis=hv1
+
+# sleep for 7s to get a garp every ~ 1s
+sleep 7
+
+n_arp=$(tcpdump -ner hv1/snoopvif-tx.pcap arp | wc -l)
+AT_CHECK([test $n_arp -ge 5 -a $n_arp -lt 10])
+
+OVN_CLEANUP([hv1])
+
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Changing tunnel_key])
+ovn_start
+
+net_add n1
+
+sim_add hv1
+as hv1
+check ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.11
+
+check ovn-nbctl --wait=hv ls-add ls \
+          -- lsp-add ls lsp1 \
+          -- lsp-add ls ls-lr \
+          -- lr-add lr \
+          -- lrp-add lr lr-ls f0:00:00:00:00:f1 192.168.1.1/24 \
+          -- set Logical_Switch_Port ls-lr \
+               type=router \
+               options:router-port=lr-ls \
+               addresses=router \
+          -- lrp-set-gateway-chassis lr-ls hv1
+
+sleep_controller hv1
+
+check ovn-nbctl --wait=sb set Logical_Switch ls other_config:requested-tnl-key=1000
+check ovn-nbctl --wait=sb ls-del ls
+wake_up_controller hv1
+
+check ovn-nbctl --wait=hv sync
+
+check ovn-nbctl --wait=hv ls-add ls1 \
+      -- lsp-add ls1 ls1-lr \
+      -- lrp-add lr lr-ls1 f0:00:00:00:00:f2 192.168.2.1/24 \
+      -- set Logical_Switch_Port ls1-lr type=router options:router-port=lr-ls1 addresses=router \
+      -- lrp-set-gateway-chassis lr-ls1 hv1
+
+check ovn-nbctl --wait=hv set Logical_Switch ls1 other_config:requested-tnl-key=1001
+
+OVN_CLEANUP([hv1])
+
+AT_CLEANUP
+])
+
+AT_SETUP([read-only sb db:pssl access with ssl-ciphers and ssl-protocols])
+AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
+PKIDIR="$(cd $abs_top_builddir/tests && pwd)"
+AT_SKIP_IF([expr "$PKIDIR" : ".*[[ 	'\"
+\\]]"])
+
+: > .$1.db.~lock~
+ovsdb-tool create ovn-sb.db "$abs_top_srcdir"/ovn-sb.ovsschema
+
+# Add read-only remote to sb ovsdb-server
+AT_CHECK(
+  [ovsdb-tool transact ovn-sb.db \
+     ['["OVN_Southbound",
+       {"op": "insert",
+        "table": "SB_Global",
+        "row": {
+          "connections": ["set", [["named-uuid", "xyz"]]]}},
+       {"op": "insert",
+        "table": "Connection",
+        "uuid-name": "xyz",
+        "row": {"target": "pssl:0:127.0.0.1",
+               "read_only": true}}]']], [0], [ignore], [ignore])
+
+start_daemon ovsdb-server --remote=punix:ovn-sb.sock \
+                          --remote=db:OVN_Southbound,SB_Global,connections \
+                          --private-key="$PKIDIR/testpki-test2-privkey.pem" \
+                          --certificate="$PKIDIR/testpki-test2-cert.pem" \
+                          --ca-cert="$PKIDIR/testpki-cacert.pem" \
+                          --ssl-ciphers='HIGH:!aNULL:!MD5:@SECLEVEL=1' \
+                          --ssl-protocols='TLSv1,TLSv1.1,TLSv1.2' \
+                          ovn-sb.db
+
+PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT])
+
+# read-only accesses should succeed
+AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \
+                    --private-key=$PKIDIR/testpki-test-privkey.pem \
+                    --certificate=$PKIDIR/testpki-test-cert.pem \
+                    --ca-cert=$PKIDIR/testpki-cacert.pem \
+                    --ssl-ciphers='HIGH:!aNULL:!MD5:@SECLEVEL=1' \
+                    --ssl-protocols='TLSv1,TLSv1.1,TLSv1.2' \
+                    list SB_Global], [0], [stdout], [ignore])
+AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \
+                    --private-key=$PKIDIR/testpki-test-privkey.pem \
+                    --certificate=$PKIDIR/testpki-test-cert.pem \
+                    --ca-cert=$PKIDIR/testpki-cacert.pem \
+                    --ssl-ciphers='HIGH:!aNULL:!MD5:@SECLEVEL=1' \
+                    --ssl-protocols='TLSv1,TLSv1.1,TLSv1.2' \
+                    list Connection], [0], [stdout], [ignore])
+
+# write access should fail
+AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \
+                    --private-key=$PKIDIR/testpki-test-privkey.pem \
+                    --certificate=$PKIDIR/testpki-test-cert.pem \
+                    --ca-cert=$PKIDIR/testpki-cacert.pem \
+                    --ssl-ciphers='HIGH:!aNULL:!MD5:@SECLEVEL=1' \
+                    --ssl-protocols='TLSv1,TLSv1.1,TLSv1.2' \
+                    chassis-add ch vxlan 1.2.4.8], [1], [ignore],
+[ovn-sbctl: transaction error: {"details":"insert operation not allowed when database server is in read only mode","error":"not allowed"}
+])
+
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+AT_CLEANUP
+
+AT_SETUP([nb connection/ssl commands with ssl-ciphers and ssl-protocols])
+AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
+PKIDIR="$(cd $abs_top_builddir/tests && pwd)"
+AT_SKIP_IF([expr "$PKIDIR" : ".*[[ 	'\"
+\\]]"])
+
+: > .$1.db.~lock~
+ovsdb-tool create ovn-nb.db "$abs_top_srcdir"/ovn-nb.ovsschema
+
+# Start nb db server using db connection/ssl entries (unpopulated initially)
+start_daemon ovsdb-server --remote=punix:ovnnb_db.sock \
+                          --remote=db:OVN_Northbound,NB_Global,connections \
+                          --private-key=db:OVN_Northbound,SSL,private_key \
+                          --certificate=db:OVN_Northbound,SSL,certificate \
+                          --ca-cert=db:OVN_Northbound,SSL,ca_cert \
+                          --ssl-ciphers='HIGH:!aNULL:!MD5:@SECLEVEL=1' \
+                          --ssl-protocols='TLSv1,TLSv1.1,TLSv1.2' \
+                          ovn-nb.db
+
+# Populate SSL configuration entries in nb db
+AT_CHECK(
+    [ovn-nbctl set-ssl $PKIDIR/testpki-test-privkey.pem \
+                       $PKIDIR/testpki-test-cert.pem \
+                       $PKIDIR/testpki-cacert.pem], [0], [stdout], [ignore])
+
+# Populate a passive SSL connection in nb db
+AT_CHECK([ovn-nbctl set-connection pssl:0:127.0.0.1], [0], [stdout], [ignore])
+
+PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT])
+
+# Verify SSL connetivity to nb db server
+AT_CHECK([ovn-nbctl --db=ssl:127.0.0.1:$TCP_PORT \
+                    --private-key=$PKIDIR/testpki-test-privkey.pem \
+                    --certificate=$PKIDIR/testpki-test-cert.pem \
+                    --ca-cert=$PKIDIR/testpki-cacert.pem \
+                    --ssl-ciphers='HIGH:!aNULL:!MD5:@SECLEVEL=1' \
+                    --ssl-protocols='TLSv1,TLSv1.1,TLSv1.2' \
+          list NB_Global],
+         [0], [stdout], [ignore])
+AT_CHECK([ovn-nbctl --db=ssl:127.0.0.1:$TCP_PORT \
+                    --private-key=$PKIDIR/testpki-test-privkey.pem \
+                    --certificate=$PKIDIR/testpki-test-cert.pem \
+                    --ca-cert=$PKIDIR/testpki-cacert.pem \
+                    --ssl-ciphers='HIGH:!aNULL:!MD5:@SECLEVEL=1' \
+                    --ssl-protocols='TLSv1,TLSv1.1,TLSv1.2' \
+          list Connection],
+         [0], [stdout], [ignore])
+AT_CHECK([ovn-nbctl --db=ssl:127.0.0.1:$TCP_PORT \
+                    --private-key=$PKIDIR/testpki-test-privkey.pem \
+                    --certificate=$PKIDIR/testpki-test-cert.pem \
+                    --ca-cert=$PKIDIR/testpki-cacert.pem \
+                    --ssl-ciphers='HIGH:!aNULL:!MD5:@SECLEVEL=1' \
+                    --ssl-protocols='TLSv1,TLSv1.1,TLSv1.2' \
+          get-connection],
+         [0], [stdout], [ignore])
+
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+AT_CLEANUP
+
+AT_SETUP([sb connection/ssl commands with ssl-ciphers and ssl-protocols])
+AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
+PKIDIR="$(cd $abs_top_builddir/tests && pwd)"
+AT_SKIP_IF([expr "$PKIDIR" : ".*[[ 	'\"
+\\]]"])
+
+: > .$1.db.~lock~
+ovsdb-tool create ovn-sb.db "$abs_top_srcdir"/ovn-sb.ovsschema
+
+# Start sb db server using db connection/ssl entries (unpopulated initially)
+start_daemon ovsdb-server --remote=punix:ovnsb_db.sock \
+                          --remote=db:OVN_Southbound,SB_Global,connections \
+                          --private-key=db:OVN_Southbound,SSL,private_key \
+                          --certificate=db:OVN_Southbound,SSL,certificate \
+                          --ca-cert=db:OVN_Southbound,SSL,ca_cert \
+                          --ssl-ciphers='HIGH:!aNULL:!MD5:@SECLEVEL=1' \
+                          --ssl-protocols='TLSv1,TLSv1.1,TLSv1.2' \
+                          ovn-sb.db
+
+# Populate SSL configuration entries in sb db
+AT_CHECK(
+    [ovn-sbctl set-ssl $PKIDIR/testpki-test-privkey.pem \
+                       $PKIDIR/testpki-test-cert.pem \
+                       $PKIDIR/testpki-cacert.pem], [0], [stdout], [ignore])
+
+# Populate a passive SSL connection in sb db
+AT_CHECK([ovn-sbctl set-connection pssl:0:127.0.0.1], [0], [stdout], [ignore])
+
+PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT])
+
+# Verify SSL connetivity to sb db server
+AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \
+                    --private-key=$PKIDIR/testpki-test-privkey.pem \
+                    --certificate=$PKIDIR/testpki-test-cert.pem \
+                    --ca-cert=$PKIDIR/testpki-cacert.pem \
+                    --ssl-ciphers='HIGH:!aNULL:!MD5:@SECLEVEL=1' \
+                    --ssl-protocols='TLSv1,TLSv1.1,TLSv1.2' \
+          list SB_Global],
+         [0], [stdout], [ignore])
+AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \
+                    --private-key=$PKIDIR/testpki-test-privkey.pem \
+                    --certificate=$PKIDIR/testpki-test-cert.pem \
+                    --ca-cert=$PKIDIR/testpki-cacert.pem \
+                    --ssl-ciphers='HIGH:!aNULL:!MD5:@SECLEVEL=1' \
+                    --ssl-protocols='TLSv1,TLSv1.1,TLSv1.2' \
+          list Connection],
+         [0], [stdout], [ignore])
+AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \
+                    --private-key=$PKIDIR/testpki-test-privkey.pem \
+                    --certificate=$PKIDIR/testpki-test-cert.pem \
+                    --ca-cert=$PKIDIR/testpki-cacert.pem \
+                    --ssl-ciphers='HIGH:!aNULL:!MD5:@SECLEVEL=1' \
+                    --ssl-protocols='TLSv1,TLSv1.1,TLSv1.2' \
+          get-connection],
+         [0], [stdout], [ignore])
+
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+AT_CLEANUP
diff --git a/tests/scapy-server.py b/tests/scapy-server.py
new file mode 100755
index 000000000..1cc616f70
--- /dev/null
+++ b/tests/scapy-server.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python3
+
+import argparse
+import time
+
+import ovs.daemon
+import ovs.unixctl
+import ovs.unixctl.server
+
+import binascii
+from scapy.all import *  # noqa: F401,F403
+from scapy.all import raw
+
+
+vlog = ovs.vlog.Vlog("scapy-server")
+exiting = False
+
+
+def exit(conn, argv, aux):
+    global exiting
+
+    exiting = True
+    conn.reply(None)
+
+
+def process(data):
+    start_time = time.perf_counter()
+    vlog.info(f"received payload request: {data}")
+    try:
+        data = data.replace('\n', '')
+        return binascii.hexlify(raw(eval(data))).decode()
+    except Exception as e:
+        vlog.exception(f"failed to process payload request: {e}")
+        return ""
+    finally:
+        total_time = (time.perf_counter() - start_time) * 1000
+        vlog.info(f"took {total_time:.2f}ms to process payload request")
+
+
+def payload(conn, argv, aux):
+    try:
+        conn.reply(process(argv[0]))
+    except Exception as e:
+        vlog.exception(f"failed to reply to payload request: {e}")
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        description="Scapy-based Frame Payload Generator")
+    parser.add_argument("--unixctl", help="UNIXCTL socket location or 'none'.")
+
+    ovs.daemon.add_args(parser)
+    ovs.vlog.add_args(parser)
+    args = parser.parse_args()
+    ovs.daemon.handle_args(args)
+    ovs.vlog.handle_args(args)
+
+    ovs.daemon.daemonize_start()
+    error, server = ovs.unixctl.server.UnixctlServer.create(args.unixctl)
+    if error:
+        ovs.util.ovs_fatal(error, "could not create unixctl server at %s"
+                           % args.unixctl, vlog)
+
+    ovs.unixctl.command_register("exit", "", 0, 0, exit, None)
+    ovs.unixctl.command_register("payload", "", 1, 1, payload, None)
+    ovs.daemon.daemonize_complete()
+
+    vlog.info("scapy server ready")
+
+    poller = ovs.poller.Poller()
+    while not exiting:
+        server.run()
+        server.wait(poller)
+        if exiting:
+            poller.immediate_wake()
+        poller.block()
+    server.close()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/tests/system-common-macros.at b/tests/system-common-macros.at
index 97c6e433b..2dfad32bb 100644
--- a/tests/system-common-macros.at
+++ b/tests/system-common-macros.at
@@ -59,7 +59,7 @@ m4_define([ADD_BR], [ovs-vsctl _ADD_BR([$1]) -- $2])
 # The existing 'port' or 'ovs-port' will be removed before new ones are added.
 #
 m4_define([ADD_VETH],
-    [ AT_CHECK([ip link add $1 type veth peer name ovs-$1 || return 77])
+    [ AT_CHECK([ip link add $1 type veth peer name ovs-$1])
       CONFIGURE_VETH_OFFLOADS([$1])
       AT_CHECK([ip link set $1 netns $2])
       AT_CHECK([ip link set dev ovs-$1 up])
@@ -372,7 +372,7 @@ ADD_VETH(sw11, sw11, br-int, "192.168.2.2/24", "f0:00:00:02:02:03", \
          "192.168.2.1")
 ADD_NAMESPACES(server)
 ADD_VETH(s1, server, br-ext, "2001:1db8:3333::2/64", "f0:00:00:01:02:05", \
-         "2001:1db8:3333::1")
+         "2001:1db8:3333::1", "nodad")
 
 if test X"$1" = X"GR"; then
    ovn-nbctl create Logical_Router name=R1 options:chassis=hv1
@@ -409,9 +409,6 @@ ovn-nbctl lsp-add sw0 sw01 \
 ovn-nbctl lsp-add sw1 sw11 \
     -- lsp-set-addresses sw11 "f0:00:00:02:02:03 192.168.2.2"
 
-OVS_WAIT_UNTIL([test "$(ip netns exec server ip a | grep 2001:1db8:3333::2 | grep tentative)" = ""])
-OVS_WAIT_UNTIL([test "$(ip netns exec server ip a | grep fe80 | grep tentative)" = ""])
-
 AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext])
 ovn-nbctl lsp-add public public1 \
         -- lsp-set-addresses public1 unknown \
diff --git a/tests/system-ovn-kmod.at b/tests/system-ovn-kmod.at
index b29e6b55a..3a533d5e6 100644
--- a/tests/system-ovn-kmod.at
+++ b/tests/system-ovn-kmod.at
@@ -1,224 +1,5 @@
 AT_BANNER([system-ovn-kmod])
 
-# SCTP and userspace conntrack do not mix. Therefore this
-# test only can be run with kernel datapath. Otherwise,
-# this is mostly a copy of existing load balancer tests
-# in system-ovn.at
-AT_SETUP([load balancing in gateway router - SCTP])
-AT_SKIP_IF([test $HAVE_SCTP = no])
-AT_SKIP_IF([test $HAVE_NC = no])
-AT_KEYWORDS([ovnlb sctp])
-
-# Make sure the SCTP kernel module is loaded.
-LOAD_MODULE([sctp])
-
-CHECK_CONNTRACK()
-CHECK_CONNTRACK_NAT()
-ovn_start
-OVS_TRAFFIC_VSWITCHD_START()
-ADD_BR([br-int])
-
-# Set external-ids in br-int needed for ovn-controller
-ovs-vsctl \
-        -- set Open_vSwitch . external-ids:system-id=hv1 \
-        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
-
-# Start ovn-controller
-start_daemon ovn-controller
-
-# Logical network:
-# Two LRs - R1 and R2 that are connected to each other via LS "join"
-# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) and
-# bar (192.168.2.0/24) connected to it. R2 has alice (172.16.1.0/24) connected
-# to it.  R2 is a gateway router on which we add load-balancing rules.
-#
-#    foo -- R1 -- join - R2 -- alice
-#           |
-#    bar ----
-
-ovn-nbctl create Logical_Router name=R1
-ovn-nbctl create Logical_Router name=R2 options:chassis=hv1
-
-ovn-nbctl ls-add foo
-ovn-nbctl ls-add bar
-ovn-nbctl ls-add alice
-ovn-nbctl ls-add join
-
-# Connect foo to R1
-ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
-    type=router options:router-port=foo addresses=\"00:00:01:01:02:03\"
-
-# Connect bar to R1
-ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24
-ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \
-    type=router options:router-port=bar addresses=\"00:00:01:01:02:04\"
-
-# Connect alice to R2
-ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24
-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
-    type=router options:router-port=alice addresses=\"00:00:02:01:02:03\"
-
-# Connect R1 to join
-ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24
-ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \
-    type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"'
-
-# Connect R2 to join
-ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24
-ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \
-    type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"'
-
-# Static routes.
-ovn-nbctl lr-route-add R1 172.16.1.0/24 20.0.0.2
-ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1
-
-# Logical port 'foo1' in switch 'foo'.
-ADD_NAMESPACES(foo1)
-ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
-         "192.168.1.1")
-ovn-nbctl lsp-add foo foo1 \
--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
-
-# Logical port 'alice1' in switch 'alice'.
-ADD_NAMESPACES(alice1)
-ADD_VETH(alice1, alice1, br-int, "172.16.1.2/24", "f0:00:00:01:02:04", \
-         "172.16.1.1")
-ovn-nbctl lsp-add alice alice1 \
--- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2"
-
-# Logical port 'bar1' in switch 'bar'.
-ADD_NAMESPACES(bar1)
-ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:05", \
-"192.168.2.1")
-ovn-nbctl lsp-add bar bar1 \
--- lsp-set-addresses bar1 "f0:00:00:01:02:05 192.168.2.2"
-
-# Config OVN load-balancer with a VIP.
-uuid=`ovn-nbctl  create load_balancer protocol=sctp vips:30.0.0.1="192.168.1.2,192.168.2.2"`
-ovn-nbctl set logical_router R2 load_balancer=$uuid
-
-# Config OVN load-balancer with another VIP (this time with ports).
-ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:12345,192.168.2.2:12345"'
-
-# Add SNAT rule to make sure that Load-balancing still works with a SNAT rule.
-ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.2.2 \
-    external_ip=30.0.0.2 -- add logical_router R2 nat @nat
-
-# Wait for ovn-controller to catch up.
-ovn-nbctl --wait=hv sync
-OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \
-grep 'nat(dst=192.168.2.2:12345)'])
-
-# Start webservers in 'foo1', 'bar1'.
-OVS_START_L7([foo1], [sctp])
-OVS_START_L7([bar1], [sctp])
-
-on_exit "ovs-ofctl -O OpenFlow13 dump-flows br-int"
-
-dnl Should work with the virtual IP address through NAT
-for i in `seq 1 20`; do
-    echo Request $i
-    NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.1 12345 > client$i.log])
-done
-
-dnl Each server should have at least one connection.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) |
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
-sed -e 's/vtag_orig=[[0-9]]*/vtag_orig=<cleared>/' |
-sed -e 's/vtag_reply=[[0-9]]*/vtag_reply=<cleared>/' | uniq], [0], [dnl
-sctp,orig=(src=172.16.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>,vtag_orig=<cleared>,vtag_reply=<cleared>)
-sctp,orig=(src=172.16.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>,vtag_orig=<cleared>,vtag_reply=<cleared>)
-])
-
-dnl Test load-balancing that includes L4 ports in NAT.
-for i in `seq 1 20`; do
-    echo Request $i
-    NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.2 8000 > clients$i.log])
-done
-
-dnl Each server should have at least one connection.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) |
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
-sed -e 's/vtag_orig=[[0-9]]*/vtag_orig=<cleared>/' |
-sed -e 's/vtag_reply=[[0-9]]*/vtag_reply=<cleared>/' | uniq], [0], [dnl
-sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>,vtag_orig=<cleared>,vtag_reply=<cleared>)
-sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>,vtag_orig=<cleared>,vtag_reply=<cleared>)
-])
-
-check_est_flows () {
-    n=$(ovs-ofctl dump-flows br-int table=15 | grep "+est" \
-        | grep "ct_mark=$1" | sed -n 's/.*n_packets=\([[0-9]]\{1,\}\).*/\1/p')
-
-    echo "n_packets=$n"
-    test -n "$n" && test "$n" != "0"
-}
-
-OVS_WAIT_UNTIL([check_est_flows 0x2], [check established flows])
-
-
-ovn-nbctl set logical_router R2 options:lb_force_snat_ip="20.0.0.2"
-
-# Destroy the load balancer and create again. ovn-controller will
-# clear the OF flows and re add again and clears the n_packets
-# for these flows.
-ovn-nbctl destroy load_balancer $uuid
-uuid=`ovn-nbctl  create load_balancer protocol=sctp vips:30.0.0.1="192.168.1.2,192.168.2.2"`
-ovn-nbctl set logical_router R2 load_balancer=$uuid
-
-check ovs-appctl dpctl/flush-conntrack
-
-# Config OVN load-balancer with another VIP (this time with ports).
-ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:12345,192.168.2.2:12345"'
-
-ovn-nbctl list load_balancer
-ovn-sbctl dump-flows R2
-OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=45 | grep 'nat(src=20.0.0.2)'])
-
-dnl Test load-balancing that includes L4 ports in NAT.
-for i in `seq 1 20`; do
-    echo Request $i
-    NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.2 8000 > clients$i.log])
-done
-
-dnl Each server should have at least one connection.
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) |
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
-sed -e 's/vtag_orig=[[0-9]]*/vtag_orig=<cleared>/' |
-sed -e 's/vtag_reply=[[0-9]]*/vtag_reply=<cleared>/' | uniq], [0], [dnl
-sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10,protoinfo=(state=<cleared>,vtag_orig=<cleared>,vtag_reply=<cleared>)
-sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10,protoinfo=(state=<cleared>,vtag_orig=<cleared>,vtag_reply=<cleared>)
-])
-
-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0.2) |
-sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
-sed -e 's/vtag_orig=[[0-9]]*/vtag_orig=<cleared>/' |
-sed -e 's/vtag_reply=[[0-9]]*/vtag_reply=<cleared>/' | uniq], [0], [dnl
-sctp,orig=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=20.0.0.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>,vtag_orig=<cleared>,vtag_reply=<cleared>)
-sctp,orig=(src=172.16.1.2,dst=192.168.2.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=20.0.0.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>,vtag_orig=<cleared>,vtag_reply=<cleared>)
-])
-
-OVS_WAIT_UNTIL([check_est_flows 0xa], [check established flows])
-
-OVS_APP_EXIT_AND_WAIT([ovn-controller])
-
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as ovn-nb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as northd
-OVS_APP_EXIT_AND_WAIT([ovn-northd])
-
-as
-OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-/connection dropped.*/d"])
-AT_CLEANUP
-
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([load balancing affinity sessions - IPv4])
 AT_KEYWORDS([ovnlb])
@@ -365,13 +146,13 @@ tcp,orig=(src=172.16.1.2,dst=172.16.1.100,sport=<cleared>,dport=<cleared>),reply
 ])
 
 dp_key=$(printf "0x%x" $(fetch_column datapath tunnel_key external_ids:name=R2))
-AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=78 --no-stats | sed -e 's/load:0xc0a80[[0-9]]02/load:0xc0a80<cleared>02/'], [0], [dnl
+AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=78 --no-stats | strip_cookie | sed -e 's/load:0xc0a80[[0-9]]02/load:0xc0a80<cleared>02/'], [0], [dnl
  table=78, idle_timeout=60, tcp,metadata=$dp_key,nw_src=172.16.1.2,nw_dst=172.16.1.100,tp_dst=8080 actions=load:0x1->NXM_NX_REG10[[14]],load:0xc0a80<cleared>02->NXM_NX_REG4[[]],load:0x50->NXM_NX_REG8[[0..15]]
 ])
 
 check_affinity_flows () {
-n1=$(ovs-ofctl dump-flows br-int table=15 |awk '/priority=150,ct_state=\+new\+trk,ip,reg4=0xc0a80102/{print substr($4,11,length($4)-11)}')
-n2=$(ovs-ofctl dump-flows br-int table=15 |awk '/priority=150,ct_state=\+new\+trk,ip,reg4=0xc0a80202/{print substr($4,11,length($4)-11)}')
+n1=$(ovs-ofctl dump-flows br-int table=15 |awk '/priority=150,ct_state=\+new\+trk,ip,reg4=0xc0a80102,.*nw_dst=172.16.1.100/{print substr($4,11,length($4)-11)}')
+n2=$(ovs-ofctl dump-flows br-int table=15 |awk '/priority=150,ct_state=\+new\+trk,ip,reg4=0xc0a80202,.*nw_dst=172.16.1.100/{print substr($4,11,length($4)-11)}')
 [[ $n1 -gt 0 -a $n2 -eq 0 ]] || [[ $n1 -eq 0 -a $n2 -gt 0 ]]
 echo $?
 }
@@ -588,31 +369,27 @@ ovn-nbctl lr-route-add R2 fd12::/64 fd20::1
 # Logical port 'foo1' in switch 'foo'.
 ADD_NAMESPACES(foo1)
 ADD_VETH(foo1, foo1, br-int, "fd11::2/64", "f0:00:00:01:02:03", \
-         "fd11::1")
-OVS_WAIT_UNTIL([test "$(ip -n foo1 a | grep fd11::2 | grep tentative)" = ""])
+         "fd11::1", "nodad")
 ovn-nbctl lsp-add foo foo1 \
 -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd11::2"
 
 # Logical port 'alice1' in switch 'alice'.
 ADD_NAMESPACES(alice1)
 ADD_VETH(alice1, alice1, br-int, "fd72::2/64", "f0:00:00:01:02:04", \
-         "fd72::1")
-OVS_WAIT_UNTIL([test "$(ip -n alice1 a | grep fd72::2 | grep tentative)" = ""])
+         "fd72::1", "nodad")
 ovn-nbctl lsp-add alice alice1 \
 -- lsp-set-addresses alice1 "f0:00:00:01:02:04 fd72::2"
 
 # Logical port 'bar1' in switch 'bar'.
 ADD_NAMESPACES(bar1)
 ADD_VETH(bar1, bar1, br-int, "fd12::2/64", "f0:00:00:01:02:05", \
-"fd12::1")
-OVS_WAIT_UNTIL([test "$(ip -n bar1 a | grep fd12::2 | grep tentative)" = ""])
+         "fd12::1", "nodad")
 ovn-nbctl lsp-add bar bar1 \
 -- lsp-set-addresses bar1 "f0:00:00:01:02:05 fd12::2"
 
 ADD_NAMESPACES(bar2)
 ADD_VETH(bar2, bar2, br-int, "fd12::3/64", "e0:00:00:01:02:05", \
-"fd12::1")
-OVS_WAIT_UNTIL([test "$(ip -n bar2 a | grep fd12::3 | grep tentative)" = ""])
+         "fd12::1", "nodad")
 ovn-nbctl lsp-add bar bar2 \
 -- lsp-set-addresses bar2 "e0:00:00:01:02:05 fd12::3"
 
@@ -666,13 +443,13 @@ tcp,orig=(src=fd72::2,dst=fd30::1,sport=<cleared>,dport=<cleared>),reply=(src=fd
 ])
 
 dp_key=$(printf "0x%x" $(fetch_column datapath tunnel_key external_ids:name=R2))
-AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=78 --no-stats | sed -e 's/load:0xfd1[[0-9]]000000000000/load:0xfd1<cleared>000000000000/'], [0], [dnl
+AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=78 --no-stats | strip_cookie | sed -e 's/load:0xfd1[[0-9]]000000000000/load:0xfd1<cleared>000000000000/'], [0], [dnl
  table=78, idle_timeout=60, tcp6,metadata=$dp_key,ipv6_src=fd72::2,ipv6_dst=fd30::1,tp_dst=8080 actions=load:0x1->NXM_NX_REG10[[14]],load:0x2->NXM_NX_XXREG1[[0..63]],load:0xfd1<cleared>000000000000->NXM_NX_XXREG1[[64..127]],load:0x50->NXM_NX_REG8[[0..15]]
 ])
 
 check_affinity_flows () {
-n1=$(ovs-ofctl dump-flows br-int table=15 |awk '/priority=150,ct_state=\+new\+trk,ipv6,reg4=0xfd110000/{print substr($4,11,length($4)-11)}')
-n2=$(ovs-ofctl dump-flows br-int table=15 |awk '/priority=150,ct_state=\+new\+trk,ipv6,reg4=0xfd120000/{print substr($4,11,length($4)-11)}')
+n1=$(ovs-ofctl dump-flows br-int table=15 |awk '/priority=150,ct_state=\+new\+trk,ipv6,reg4=0xfd110000,.*ipv6_dst=fd30::1\s/{print substr($4,11,length($4)-11)}')
+n2=$(ovs-ofctl dump-flows br-int table=15 |awk '/priority=150,ct_state=\+new\+trk,ipv6,reg4=0xfd120000,.*ipv6_dst=fd30::1\s/{print substr($4,11,length($4)-11)}')
 [[ $n1 -gt 0 -a $n2 -eq 0 ]] || [[ $n1 -eq 0 -a $n2 -gt 0 ]]
 echo $?
 }
@@ -1115,3 +892,156 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
 /connection dropped.*/d"])
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([LR with SNAT fragmentation needed for external server])
+AT_KEYWORDS([ovnlb])
+
+CHECK_CONNTRACK()
+CHECK_CONNTRACK_NAT()
+
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+ADD_BR([br-ext])
+
+dnl Logical network:
+dnl 2 logical switches "public" (192.168.1.0/24) and "internal" (172.16.1.0/24)
+dnl connected to a router lr.
+dnl internal has a client.
+dnl server is connected through localnet.
+dnl
+dnl Server IP 192.168.1.2 MTU 900
+dnl Client IP  172.16.1.2 MTU 800
+dnl
+dnl SNAT for internal 172.16.1.2/24 with router ip 192.168.1.1.
+
+check ovs-ofctl add-flow br-ext action=normal
+# Set external-ids in br-int needed for ovn-controller
+check ovs-vsctl \
+        -- set Open_vSwitch . external-ids:system-id=hv1 \
+        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true \
+        -- set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext
+
+dnl Start ovn-controller
+start_daemon ovn-controller
+
+check ovn-nbctl lr-add lr
+check ovn-nbctl ls-add internal
+check ovn-nbctl ls-add public
+
+check ovn-nbctl lrp-add lr lr-pub 00:00:01:01:02:03 192.168.1.1/24
+check ovn-nbctl lsp-add  public pub-lr -- set Logical_Switch_Port pub-lr \
+    type=router options:router-port=lr-pub addresses=\"00:00:01:01:02:03\"
+
+check ovn-nbctl lrp-add lr lr-internal 00:00:01:01:02:04 172.16.1.1/24
+check ovn-nbctl lsp-add internal internal-lr -- set Logical_Switch_Port internal-lr \
+    type=router options:router-port=lr-internal addresses=\"00:00:01:01:02:04\"
+
+ovn-nbctl lsp-add public ln_port \
+                -- lsp-set-addresses ln_port unknown \
+                -- lsp-set-type ln_port localnet \
+                -- lsp-set-options ln_port network_name=phynet
+
+ADD_NAMESPACES(server)
+ADD_VETH([server], [server], [br-ext], ["192.168.1.2/24"],
+         ["f0:00:00:01:02:03"], ["192.168.1.1"])
+NS_EXEC([server], [ip l set dev server mtu 900])
+NS_EXEC([server], [ip l show dev server])
+
+ADD_NAMESPACES(client)
+ADD_VETH([client], [client], [br-int], ["172.16.1.2/24"],
+         ["f0:00:0f:01:02:03"], ["172.16.1.1"])
+NS_EXEC([client], [ip l set dev client mtu 800])
+NS_EXEC([client], [ip l show dev client])
+check ovn-nbctl lsp-add internal client \
+  -- lsp-set-addresses client "f0:00:0f:01:02:03 172.16.1.2"
+
+dnl Config OVN load-balancer with a VIP.  (not necessary, but if we do not
+dnl have a load balancer and comment out snat, we will receive a stray fragment
+dnl on the client side.)
+dnl check ovn-nbctl lb-add lb1 192.168.1.20:4242 172.16.1.2:4242 udp
+dnl check ovn-nbctl lr-lb-add lr lb1
+check ovn-nbctl set logical_router lr options:chassis=hv1
+check ovn-nbctl set logical_router_port lr-internal options:gateway_mtu=800
+
+check ovn-nbctl lr-nat-add lr snat 192.168.1.1 172.16.1.2
+
+check ovn-nbctl --wait=hv sync
+
+ovn-nbctl show
+ovs-vsctl show
+ovn-appctl -t ovn-controller vlog/set vconn:file:dbg pinctrl:file:dbg
+
+AT_DATA([server.py], [dnl
+import socket
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+
+server_address = '192.168.1.2'
+server_port = 4242
+
+server = (server_address, server_port)
+sock.bind(server)
+print("Listening on ", server_address, ":", str(server_port), flush=True)
+
+while True:
+  payload, client_address = sock.recvfrom(1000)
+  print("Received data from ", str(client_address), ": ", payload)
+  sent = sock.sendto(b"x" * 1017, client_address)
+  print("Sent back: ", str(sent), "bytes", flush=True)
+])
+NETNS_DAEMONIZE([server], [$PYTHON3 ./server.py > server.log], [server.pid])
+
+dnl Collect packets on server side.
+NETNS_DAEMONIZE([server], [tcpdump -l -U -i server -vnne \
+          'ip and (icmp or udp)' > server.tcpdump 2>server_err], [tcpdump0.pid])
+OVS_WAIT_UNTIL([grep "listening" server_err])
+
+dnl Collect packets on client side.
+NETNS_DAEMONIZE([client], [tcpdump -l -U -i client -vnne \
+          'ip and (icmp or udp)' > client.tcpdump 2>client_err], [tcpdump1.pid])
+OVS_WAIT_UNTIL([grep "listening" client_err])
+
+dnl Send two packets to the server with a short interval.
+dnl First packet should generate 'needs frag', the second should result in
+dnl corectly fragmented reply.
+AT_DATA([client.py], [dnl
+import socket
+import time
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+sock.sendto(b"x" * 7, ("192.168.1.2", 4242))
+time.sleep(1)
+sock.sendto(b"x" * 7, ("192.168.1.2", 4242))
+time.sleep(5)
+])
+NS_CHECK_EXEC([client], [$PYTHON3 ./client.py])
+
+dnl Expecting 2 outgoing packets and 2 fragments back - 8 lines total.
+OVS_WAIT_UNTIL([test "$(cat client.tcpdump | wc -l)" = "8"])
+AT_CHECK([test $(grep -c "need to frag (mtu 800)" server.tcpdump) -eq 1])
+
+ovn-appctl -t ovn-controller vlog/set info
+
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["
+  /failed to query port patch-.*/d
+  /connection dropped.*/d
+"])
+AT_CLEANUP
+])
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index 59d0cb2a0..213483176 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -251,24 +251,21 @@ ovn-nbctl lr-route-add R2 fd12::/64 fd00::1
 # Logical port 'foo1' in switch 'foo'.
 ADD_NAMESPACES(foo1)
 ADD_VETH(foo1, foo1, br-int, "fd11::2/64", "f0:00:00:01:02:03", \
-         "fd11::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd11::2 | grep tentative)" = ""])
+         "fd11::1", "nodad")
 ovn-nbctl lsp-add foo foo1 \
 -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd11::2"
 
 # Logical port 'alice1' in switch 'alice'.
 ADD_NAMESPACES(alice1)
 ADD_VETH(alice1, alice1, br-int, "fd21::2/64", "f0:00:00:01:02:04", \
-         "fd21::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec alice1 ip a | grep fd21::2 | grep tentative)" = ""])
+         "fd21::1", "nodad")
 ovn-nbctl lsp-add alice alice1 \
 -- lsp-set-addresses alice1 "f0:00:00:01:02:04 fd21::2"
 
 # Logical port 'bar1' in switch 'bar'.
 ADD_NAMESPACES(bar1)
 ADD_VETH(bar1, bar1, br-int, "fd12::2/64", "f0:00:00:01:02:05", \
-         "fd12::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec bar1 ip a | grep fd12::2 | grep tentative)" = ""])
+         "fd12::1", "nodad")
 ovn-nbctl lsp-add bar bar1 \
 -- lsp-set-addresses bar1 "f0:00:00:01:02:05 fd12::2"
 
@@ -538,16 +535,14 @@ ovn-nbctl lr-route-add R2 fd10::/64 fd20::1
 # Logical port 'foo1' in switch 'foo'.
 ADD_NAMESPACES(foo1)
 ADD_VETH(foo1, foo1, br-int, "fd10::2/64", "f0:00:00:01:02:03", \
-         "fd10::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd10::2 | grep tentative)" = ""])
+         "fd10::1", "nodad")
 ovn-nbctl lsp-add foo foo1 \
 -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd10::2"
 
 # Logical port 'alice1' in switch 'alice'.
 ADD_NAMESPACES(alice1)
 ADD_VETH(alice1, alice1, br-int, "fd30::2/64", "f0:00:00:01:02:04", \
-         "fd30::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec alice1 ip a | grep fd30::2 | grep tentative)" = ""])
+         "fd30::1", "nodad")
 ovn-nbctl lsp-add alice alice1 \
 -- lsp-set-addresses alice1 "f0:00:00:01:02:04 fd30::2"
 
@@ -908,32 +903,28 @@ ovn-nbctl set logical_router R3 options:dnat_force_snat_ip=fd20::3
 # Logical port 'foo1' in switch 'foo'.
 ADD_NAMESPACES(foo1)
 ADD_VETH(foo1, foo1, br-int, "fd11::2/64", "f0:00:00:01:02:03", \
-         "fd11::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd11::2 | grep tentative)" = ""])
+         "fd11::1", "nodad")
 ovn-nbctl lsp-add foo foo1 \
 -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd11::2"
 
 # Logical port 'alice1' in switch 'alice'.
 ADD_NAMESPACES(alice1)
 ADD_VETH(alice1, alice1, br-int, "fd30::3/64", "f0:00:00:01:02:04", \
-         "fd30::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec alice1 ip a | grep fd30::3 | grep tentative)" = ""])
+         "fd30::1", "nodad")
 ovn-nbctl lsp-add alice alice1 \
 -- lsp-set-addresses alice1 "f0:00:00:01:02:04 fd30::3"
 
 # Logical port 'bar1' in switch 'bar'.
 ADD_NAMESPACES(bar1)
 ADD_VETH(bar1, bar1, br-int, "fd12::2/64", "f0:00:00:01:02:05", \
-         "fd12::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec bar1 ip a | grep fd12::2 | grep tentative)" = ""])
+         "fd12::1", "nodad")
 ovn-nbctl lsp-add bar bar1 \
 -- lsp-set-addresses bar1 "f0:00:00:01:02:05 fd12::2"
 
 # Logical port 'bob1' in switch 'bob'.
 ADD_NAMESPACES(bob1)
 ADD_VETH(bob1, bob1, br-int, "fd30::4/64", "f0:00:00:01:02:06", \
-         "fd30::2")
-OVS_WAIT_UNTIL([test "$(ip netns exec bob1 ip a | grep fd30::4 | grep tentative)" = ""])
+         "fd30::2", "nodad")
 ovn-nbctl lsp-add bob bob1 \
 -- lsp-set-addresses bob1 "f0:00:00:01:02:06 fd30::4"
 
@@ -1149,8 +1140,7 @@ ovn-nbctl lsp-add foo foo1 \
 
 ADD_NAMESPACES(foo16)
 ADD_VETH(foo16, foo16, br-int, "fd11::2/64", "f0:00:00:02:02:03", \
-         "fd11::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec foo16 ip a | grep fd11::2 | grep tentative)" = ""])
+         "fd11::1", "nodad")
 ovn-nbctl lsp-add foo foo16 \
 -- lsp-set-addresses foo16 "f0:00:00:02:02:03 fd11::2"
 
@@ -1163,8 +1153,7 @@ ovn-nbctl lsp-add alice alice1 \
 
 ADD_NAMESPACES(alice16)
 ADD_VETH(alice16, alice16, br-int, "fd30::3/64", "f0:00:00:02:02:04", \
-         "fd30::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec alice16 ip a | grep fd30::3 | grep tentative)" = ""])
+         "fd30::1", "nodad")
 ovn-nbctl lsp-add alice alice16 \
 -- lsp-set-addresses alice16 "f0:00:00:02:02:04 fd30::3"
 
@@ -1177,8 +1166,7 @@ ovn-nbctl lsp-add bar bar1 \
 
 ADD_NAMESPACES(bar16)
 ADD_VETH(bar16, bar16, br-int, "fd12::2/64", "f0:00:00:02:02:05", \
-         "fd12::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec bar16 ip a | grep fd12::2 | grep tentative)" = ""])
+         "fd12::1", "nodad")
 ovn-nbctl lsp-add bar bar16 \
 -- lsp-set-addresses bar16 "f0:00:00:02:02:05 fd12::2"
 
@@ -1191,8 +1179,7 @@ ovn-nbctl lsp-add bob bob1 \
 
 ADD_NAMESPACES(bob16)
 ADD_VETH(bob16, bob16, br-int, "fd30::4/64", "f0:00:00:02:02:06", \
-         "fd30::2")
-OVS_WAIT_UNTIL([test "$(ip netns exec bob16 ip a | grep fd30::4 | grep tentative)" = ""])
+         "fd30::2", "nodad")
 ovn-nbctl lsp-add bob bob16 \
 -- lsp-set-addresses bob16 "f0:00:00:02:02:06 fd30::4"
 
@@ -2376,14 +2363,10 @@ ADD_NAMESPACES(server)
 ADD_VETH(s1, server, br-ext, "172.16.1.100/24", "1a:00:00:00:00:01", \
          "172.16.1.1")
 
-OVS_WAIT_UNTIL([test "$(ip netns exec server ip a | grep fe80 | grep tentative)" = ""])
-
 ADD_NAMESPACES(client)
 ADD_VETH(c1, client, br-ext, "172.16.1.110/24", "1a:00:00:00:00:02", \
          "172.16.1.1")
 
-OVS_WAIT_UNTIL([test "$(ip netns exec client ip a | grep fe80 | grep tentative)" = ""])
-
 # Start webservers in 'server'.
 OVS_START_L7([server], [http])
 
@@ -3670,32 +3653,28 @@ ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
 # Logical port 'foo1' in switch 'foo'.
 ADD_NAMESPACES(foo1)
 ADD_VETH(foo1, foo1, br-int, "fd11::2/64", "f0:00:00:01:02:03", \
-         "fd11::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd11::2 | grep tentative)" = ""])
+         "fd11::1", "nodad")
 ovn-nbctl lsp-add foo foo1 \
 -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd11::2"
 
 # Logical port 'foo2' in switch 'foo'.
 ADD_NAMESPACES(foo2)
 ADD_VETH(foo2, foo2, br-int, "fd11::3/64", "f0:00:00:01:02:06", \
-         "fd11::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec foo2 ip a | grep fd11::3 | grep tentative)" = ""])
+         "fd11::1", "nodad")
 ovn-nbctl lsp-add foo foo2 \
 -- lsp-set-addresses foo2 "f0:00:00:01:02:06 fd11::3"
 
 # Logical port 'bar1' in switch 'bar'.
 ADD_NAMESPACES(bar1)
 ADD_VETH(bar1, bar1, br-int, "fd12::2/64", "f0:00:00:01:02:04", \
-         "fd12::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec bar1 ip a | grep fd12::2 | grep tentative)" = ""])
+         "fd12::1", "nodad")
 ovn-nbctl lsp-add bar bar1 \
 -- lsp-set-addresses bar1 "f0:00:00:01:02:04 fd12::2"
 
 # Logical port 'alice1' in switch 'alice'.
 ADD_NAMESPACES(alice1)
 ADD_VETH(alice1, alice1, br-int, "fd20::2/64", "f0:00:00:01:02:05", \
-         "fd20::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec alice1 ip a | grep fd20::2 | grep tentative)" = ""])
+         "fd20::1", "nodad")
 ovn-nbctl lsp-add alice alice1 \
 -- lsp-set-addresses alice1 "f0:00:00:01:02:05 fd20::2"
 
@@ -4018,32 +3997,28 @@ ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
 # Logical port 'foo1' in switch 'foo'.
 ADD_NAMESPACES(foo1)
 ADD_VETH(foo1, foo1, br-int, "fd11::2/64", "f0:00:00:01:02:03", \
-         "fd11::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd11::2 | grep tentative)" = ""])
+         "fd11::1", "nodad")
 ovn-nbctl lsp-add foo foo1 \
 -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd11::2"
 
 # Logical port 'foo2' in switch 'foo'.
 ADD_NAMESPACES(foo2)
 ADD_VETH(foo2, foo2, br-int, "fd11::3/64", "f0:00:00:01:02:06", \
-         "fd11::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec foo2 ip a | grep fd11::3 | grep tentative)" = ""])
+         "fd11::1", "nodad")
 ovn-nbctl lsp-add foo foo2 \
 -- lsp-set-addresses foo2 "f0:00:00:01:02:06 fd11::3"
 
 # Logical port 'bar1' in switch 'bar'.
 ADD_NAMESPACES(bar1)
 ADD_VETH(bar1, bar1, br-int, "fd12::2/64", "f0:00:00:01:02:04", \
-         "fd12::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec bar1 ip a | grep fd12::2 | grep tentative)" = ""])
+         "fd12::1", "nodad")
 ovn-nbctl lsp-add bar bar1 \
 -- lsp-set-addresses bar1 "f0:00:00:01:02:04 fd12::2"
 
 # Logical port 'alice1' in switch 'alice'.
 ADD_NAMESPACES(alice1)
 ADD_VETH(alice1, alice1, br-int, "fd20::2/64", "f0:00:00:01:02:05", \
-         "fd20::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec alice1 ip a | grep fd20::2 | grep tentative)" = ""])
+         "fd20::1", "nodad")
 ovn-nbctl lsp-add alice alice1 \
 -- lsp-set-addresses alice1 "f0:00:00:01:02:05 fd20::2"
 
@@ -4928,8 +4903,7 @@ ovn-nbctl lsp-add sw sw-rtr                       \
     -- lsp-set-options sw-rtr router-port=rtr-sw
 
 ADD_NAMESPACES(lsp)
-ADD_VETH(lsp, lsp, br-int, "4200::1/64", "00:00:00:00:00:01", "4200::00ff")
-OVS_WAIT_UNTIL([test "$(ip netns exec lsp ip a | grep 4200::1 | grep tentative)" = ""])
+ADD_VETH(lsp, lsp, br-int, "4200::1/64", "00:00:00:00:00:01", "4200::00ff", "nodad")
 ovn-nbctl --wait=hv -t 3 sync
 
 # Start IPv6 TCP server on lsp.
@@ -5058,8 +5032,8 @@ ADD_NAMESPACES(sw0-p2-rej)
 ADD_VETH(sw0-p2-rej, sw0-p2-rej, br-int, "10.0.0.4/24", "50:54:00:00:00:04", \
          "10.0.0.1")
 
-NS_CHECK_EXEC([sw0-p1-rej], [ip a a aef0::3/64 dev sw0-p1-rej], [0])
-NS_CHECK_EXEC([sw0-p2-rej], [ip a a aef0::4/64 dev sw0-p2-rej], [0])
+NS_CHECK_EXEC([sw0-p1-rej], [ip a a aef0::3/64 dev sw0-p1-rej nodad], [0])
+NS_CHECK_EXEC([sw0-p2-rej], [ip a a aef0::4/64 dev sw0-p2-rej nodad], [0])
 
 ADD_NAMESPACES(sw1-p1-rej)
 ADD_VETH(sw1-p1-rej, sw1-p1-rej, br-int, "20.0.0.3/24", "40:54:00:00:00:03", \
@@ -5099,9 +5073,6 @@ NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -l -nn -i sw0-p2-rej tcp port 80 > sw0-p2-r
 #Wait for tcpdump to get started before generating first packets
 OVS_WAIT_UNTIL([test 1 = $(cat err | grep -c listening)])
 
-OVS_WAIT_UNTIL([test "$(ip netns exec sw0-p1-rej ip a | grep aef0::3 | grep tentative)" = ""])
-OVS_WAIT_UNTIL([test "$(ip netns exec sw0-p2-rej ip a | grep aef0::4 | grep tentative)" = ""])
-
 OVS_WAIT_UNTIL([
     ip netns exec sw0-p2-rej nc -vz6 aef0::3 80 2>&1 | grep -i 'connection refused'
 ])
@@ -5308,8 +5279,8 @@ ADD_NAMESPACES(sw0-p2-rej)
 ADD_VETH(sw0-p2-rej, sw0-p2-rej, br-int, "10.0.0.4/24", "50:54:00:00:00:04", \
          "10.0.0.1")
 
-NS_CHECK_EXEC([sw0-p1-rej], [ip a a aef0::3/64 dev sw0-p1-rej], [0])
-NS_CHECK_EXEC([sw0-p2-rej], [ip a a aef0::4/64 dev sw0-p2-rej], [0])
+NS_CHECK_EXEC([sw0-p1-rej], [ip a a aef0::3/64 dev sw0-p1-rej nodad], [0])
+NS_CHECK_EXEC([sw0-p2-rej], [ip a a aef0::4/64 dev sw0-p2-rej nodad], [0])
 
 ADD_NAMESPACES(sw1-p1-rej)
 ADD_VETH(sw1-p1-rej, sw1-p1-rej, br-int, "20.0.0.3/24", "40:54:00:00:00:03", \
@@ -5349,9 +5320,6 @@ NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -l -nn -i sw0-p2-rej tcp port 80 > sw0-p2-r
 #Wait for tcpdump to get started before generating first packets
 OVS_WAIT_UNTIL([test 1 = $(cat err | grep -c listening)])
 
-OVS_WAIT_UNTIL([test "$(ip netns exec sw0-p1-rej ip a | grep aef0::3 | grep tentative)" = ""])
-OVS_WAIT_UNTIL([test "$(ip netns exec sw0-p2-rej ip a | grep aef0::4 | grep tentative)" = ""])
-
 OVS_WAIT_UNTIL([
     ip netns exec sw0-p2-rej nc -vz6 aef0::3 80 2>&1 | grep -i 'connection refused'
 ])
@@ -5878,12 +5846,10 @@ check ovn-nbctl                                                  \
     -- ls-lb-add ls lb-test
 
 ADD_NAMESPACES(vm1)
-ADD_VETH(vm1, vm1, br-int, "4242::2/64", "00:00:00:00:00:01", "4242::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec vm1 ip a | grep 4242::2 | grep tentative)" = ""])
+ADD_VETH(vm1, vm1, br-int, "4242::2/64", "00:00:00:00:00:01", "4242::1", "nodad")
 
 ADD_NAMESPACES(vm2)
-ADD_VETH(vm2, vm2, br-int, "4242::3/64", "00:00:00:00:00:02", "4242::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec vm2 ip a | grep 4242::3 | grep tentative)" = ""])
+ADD_VETH(vm2, vm2, br-int, "4242::3/64", "00:00:00:00:00:02", "4242::1", "nodad")
 
 # Wait for ovn-controller to catch up.
 wait_for_ports_up
@@ -6145,6 +6111,10 @@ on_exit 'ovs-ofctl dump-flows br-int'
 
 NETNS_DAEMONIZE([alice1], [nc -l -k 80], [alice1.pid])
 NS_CHECK_EXEC([bob1], [nc -z 10.0.0.2 80], [0])
+NS_CHECK_EXEC([bob1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.2 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
 
 # Ensure conntrack entry is present. We should not try to predict
 # the tunnel key for the output port, so we strip it from the labels
@@ -6152,17 +6122,19 @@ NS_CHECK_EXEC([bob1], [nc -z 10.0.0.2 80], [0])
 AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.0.1) | \
 sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
 sed -e 's/mark=[[0-9]]*/mark=<cleared>/'], [0], [dnl
+icmp,orig=(src=172.16.0.1,dst=10.0.0.2,id=<cleared>,type=8,code=0),reply=(src=10.0.0.2,dst=172.16.0.1,id=<cleared>,type=0,code=0),zone=<cleared>,mark=<cleared>,labels=0x401020400000000
 tcp,orig=(src=172.16.0.1,dst=10.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=10.0.0.2,dst=172.16.0.1,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=<cleared>,labels=0x401020400000000,protoinfo=(state=<cleared>)
 ])
 
 # Ensure datapaths show conntrack states as expected
 # Like with conntrack entries, we shouldn't try to predict
 # port binding tunnel keys. So omit them from expected labels.
+ovs-appctl dpctl/dump-flows | grep 'ct_state(+new-est-rpl+trk).*ct(.*label=0x401020400000000/.*)'
 AT_CHECK([ovs-appctl dpctl/dump-flows | grep 'ct_state(+new-est-rpl+trk).*ct(.*label=0x401020400000000/.*)' -c], [0], [dnl
-1
+2
 ])
 AT_CHECK([ovs-appctl dpctl/dump-flows | grep 'ct_state(-new+est+rpl+trk).*ct_label(0x401020400000000)' -c], [0], [dnl
-1
+2
 ])
 
 # Flush conntrack entries for easier output parsing of next test.
@@ -6176,16 +6148,21 @@ ovn-nbctl set Logical_Switch_Port r2-ext \
 ovn-nbctl --wait=hv sync
 
 NS_CHECK_EXEC([bob1], [nc -z 10.0.0.2 80], [0])
+NS_CHECK_EXEC([bob1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.2 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
 AT_CHECK([ovs-appctl dpctl/dump-flows | grep 'ct_state(+new-est-rpl+trk).*ct(.*label=0x1001020400000000/.*)' -c], [0], [dnl
-1
+2
 ])
 AT_CHECK([ovs-appctl dpctl/dump-flows | grep 'ct_state(-new+est+rpl+trk).*ct_label(0x1001020400000000)' -c], [0], [dnl
-1
+2
 ])
 
 AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep 0x1001020400000000 | FORMAT_CT(172.16.0.1) | \
 sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
-sed -e 's/mark=[[0-9]]*/mark=<cleared>/'], [0], [dnl
+sed -e 's/mark=[[0-9]]*/mark=<cleared>/' | sort], [0], [dnl
+icmp,orig=(src=172.16.0.1,dst=10.0.0.2,id=<cleared>,type=8,code=0),reply=(src=10.0.0.2,dst=172.16.0.1,id=<cleared>,type=0,code=0),zone=<cleared>,mark=<cleared>,labels=0x1001020400000000
 tcp,orig=(src=172.16.0.1,dst=10.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=10.0.0.2,dst=172.16.0.1,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=<cleared>,labels=0x1001020400000000,protoinfo=(state=<cleared>)
 ])
 # Check entries in table 76 and 77 expires w/o traffic
@@ -6306,8 +6283,7 @@ NS_CHECK_EXEC([alice1], [sysctl -w net.ipv6.conf.default.router_solicitations=1]
 net.ipv6.conf.default.router_solicitations = 1
 ])
 ADD_VETH(alice1, alice1, br-int, "fd01::2/64", "f0:00:00:01:02:04", \
-         "fd01::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec alice1 ip a | grep fd01::2 | grep tentative)" = ""])
+         "fd01::1", "nodad")
 ovn-nbctl lsp-add alice alice1 \
 -- lsp-set-addresses alice1 "f0:00:00:01:02:04 fd01::2"
 # Add neighbour MAC address to avoid sending IPv6 NS messages which could
@@ -6322,8 +6298,7 @@ NS_CHECK_EXEC([bob1], [sysctl -w net.ipv6.conf.default.router_solicitations=1],
 net.ipv6.conf.default.router_solicitations = 1
 ])
 ADD_VETH(bob1, bob1, br-int, "fd07::1/64", "f0:00:00:01:02:06", \
-         "fd07::2")
-OVS_WAIT_UNTIL([test "$(ip netns exec bob1 ip a | grep fd07::1 | grep tentative)" = ""])
+         "fd07::2", "nodad")
 # Add neighbour MAC addresses to avoid sending IPv6 NS messages which could
 # cause datapath flows to be evicted
 NS_CHECK_EXEC([bob1], [ip -6 neigh add fd07::2 lladdr 00:00:02:01:02:03 dev bob1], [0])
@@ -6339,16 +6314,20 @@ on_exit 'ovs-ofctl dump-flows br-int'
 
 NETNS_DAEMONIZE([alice1], [nc -6 -l -k 80], [alice1.pid])
 NS_CHECK_EXEC([bob1], [nc -6 -z fd01::2 80], [0])
+NS_CHECK_EXEC([bob1], [ping -q -c 3 -i 0.3 -w 2 fd01::2 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
 
 # Ensure datapaths show conntrack states as expected
 # Like with conntrack entries, we shouldn't try to predict
 # port binding tunnel keys. So omit them from expected labels.
 AT_CHECK([ovs-appctl dpctl/dump-flows | grep 'ct_state(+new-est-rpl+trk).*ct(.*label=0x401020400000000/.*)' -c], [0], [dnl
-1
+2
 ])
 
 AT_CHECK([ovs-appctl dpctl/dump-flows | grep 'ct_state(-new+est+rpl+trk).*ct_label(0x401020400000000)' -c], [0], [dnl
-1
+2
 ])
 
 # Ensure conntrack entry is present. We should not try to predict
@@ -6356,7 +6335,8 @@ AT_CHECK([ovs-appctl dpctl/dump-flows | grep 'ct_state(-new+est+rpl+trk).*ct_lab
 # and just ensure that the known ethernet address is present.
 AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd01::2) | \
 sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
-sed -e 's/mark=[[0-9]]*/mark=<cleared>/'], [0], [dnl
+sed -e 's/mark=[[0-9]]*/mark=<cleared>/' | sort], [0], [dnl
+icmpv6,orig=(src=fd07::1,dst=fd01::2,id=<cleared>,type=128,code=0),reply=(src=fd01::2,dst=fd07::1,id=<cleared>,type=129,code=0),zone=<cleared>,mark=<cleared>,labels=0x401020400000000
 tcp,orig=(src=fd07::1,dst=fd01::2,sport=<cleared>,dport=<cleared>),reply=(src=fd01::2,dst=fd07::1,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=<cleared>,labels=0x401020400000000,protoinfo=(state=<cleared>)
 ])
 
@@ -6369,17 +6349,22 @@ ovn-nbctl --wait=hv set Logical_Switch_Port r2-ext \
      type=router options:router-port=R2_ext addresses='"00:00:10:01:02:04"'
 
 NS_CHECK_EXEC([bob1], [nc -6 -z fd01::2 80], [0])
+NS_CHECK_EXEC([bob1], [ping -q -c 3 -i 0.3 -w 2 fd01::2 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
 
 AT_CHECK([ovs-appctl dpctl/dump-flows | grep 'ct_state(+new-est-rpl+trk).*ct(.*label=0x1001020400000000/.*)' -c], [0], [dnl
-1
+2
 ])
 AT_CHECK([ovs-appctl dpctl/dump-flows | grep 'ct_state(-new+est+rpl+trk).*ct_label(0x1001020400000000)' -c], [0], [dnl
-1
+2
 ])
 
 AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep 0x1001020400000000 | FORMAT_CT(fd01::2) | \
 sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
 sed -e 's/mark=[[0-9]]*/mark=<cleared>/'], [0], [dnl
+icmpv6,orig=(src=fd07::1,dst=fd01::2,id=<cleared>,type=128,code=0),reply=(src=fd01::2,dst=fd07::1,id=<cleared>,type=129,code=0),zone=<cleared>,mark=<cleared>,labels=0x1001020400000000
 tcp,orig=(src=fd07::1,dst=fd01::2,sport=<cleared>,dport=<cleared>),reply=(src=fd01::2,dst=fd07::1,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=<cleared>,labels=0x1001020400000000,protoinfo=(state=<cleared>)
 ])
 
@@ -6513,8 +6498,6 @@ ADD_NAMESPACES(ext-foo)
 ADD_VETH(ext-foo, ext-foo, br-ext, "172.16.1.100/24", "00:10:10:01:02:13", \
          "172.16.1.1")
 
-OVS_WAIT_UNTIL([test "$(ip netns exec ext-foo ip a | grep fe80 | grep tentative)" = ""])
-
 AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext])
 ovn-nbctl lsp-add public public1 \
         -- lsp-set-addresses public1 unknown \
@@ -6630,6 +6613,28 @@ AT_CHECK([ovn-nbctl set Logical_Switch_Port ext options:qos_min_rate=400000])
 AT_CHECK([ovn-nbctl set Logical_Switch_Port ext options:qos_max_rate=600000])
 AT_CHECK([ovn-nbctl set Logical_Switch_Port ext options:qos_burst=6000000])
 
+OVS_WAIT_UNTIL([tc qdisc show | grep -q 'htb 1: dev ovs-public'])
+OVS_WAIT_UNTIL([tc class show dev ovs-public | \
+                grep -q 'class htb .* rate 200Kbit ceil 300Kbit burst 375000b cburst 375000b'])
+
+OVS_WAIT_UNTIL([tc qdisc show | grep -q 'htb 1: dev ovs-ext'])
+OVS_WAIT_UNTIL([tc class show dev ovs-ext | \
+                grep -q 'class htb .* rate 400Kbit ceil 600Kbit burst 750000b cburst 750000b'])
+
+# The same now with ovs db read only
+#
+AT_CHECK([ovn-nbctl remove Logical_Switch_Port ext options qos_min_rate=400000])
+AT_CHECK([ovn-nbctl remove Logical_Switch_Port ext options qos_max_rate=600000])
+AT_CHECK([ovn-nbctl remove Logical_Switch_Port ext options qos_burst=6000000])
+OVS_WAIT_UNTIL([test "$(tc class show dev ovs-ext | grep 'class htb')" == ""])
+
+sleep_ovsdb .
+
+AT_CHECK([ovn-nbctl set Logical_Switch_Port ext options:qos_min_rate=400000])
+AT_CHECK([ovn-nbctl set Logical_Switch_Port ext options:qos_max_rate=600000])
+AT_CHECK([ovn-nbctl set Logical_Switch_Port ext options:qos_burst=6000000])
+wake_up_ovsdb .
+
 OVS_WAIT_UNTIL([tc qdisc show | grep -q 'htb 1: dev ovs-public'])
 OVS_WAIT_UNTIL([tc class show dev ovs-public | \
                 grep -q 'class htb .* rate 200Kbit ceil 300Kbit burst 375000b cburst 375000b'])
@@ -7348,9 +7353,9 @@ check ovn-nbctl lsp-add public public1 \
 
 NS_EXEC([sw01], [tcpdump -l -n -i sw01 icmp -Q in > reject.pcap &])
 check ovn-nbctl meter-add acl-meter drop 1 pktps 0
-check ovn-nbctl --wait=hv copp-add copp0 reject acl-meter
-check ovn-nbctl --wait=hv ls-copp-add copp0 sw0
-check ovn-nbctl acl-add sw0 from-lport 1002 'inport == "sw01" && ip && udp' reject
+check ovn-nbctl copp-add copp0 reject acl-meter
+check ovn-nbctl ls-copp-add copp0 sw0
+check ovn-nbctl --wait=hv acl-add sw0 from-lport 1002 'inport == "sw01" && ip && udp' reject
 
 AT_CHECK([ovn-nbctl copp-list copp0], [0], [dnl
 reject: acl-meter
@@ -7371,7 +7376,7 @@ rm -f reject.pcap
 
 # Let's update the meter
 NS_EXEC([sw01], [tcpdump -l -n -i sw01 icmp -Q in > reject.pcap &])
-check ovn-nbctl --may-exist meter-add acl-meter drop 10 pktps 0
+check ovn-nbctl --may-exist --wait=hv meter-add acl-meter drop 10 pktps 0
 ip netns exec sw01 scapy -H <<-EOF
 p = IP(src="192.168.1.2", dst="192.168.1.1") / UDP(dport = 12345) / Raw(b"X"*64)
 send (p, iface='sw01', loop = 0, verbose = 0, count = 40)
@@ -7402,7 +7407,7 @@ kill $(pidof tcpdump)
 
 NS_EXEC([server], [tcpdump -l -n -i s1 arp[[24:4]]=0xac100164 > arp.pcap &])
 check ovn-nbctl meter-add arp-meter drop 1 pktps 0
-check ovn-nbctl --wait=hv copp-add copp1 arp-resolve arp-meter
+check ovn-nbctl copp-add copp1 arp-resolve arp-meter
 check ovn-nbctl --wait=hv lr-copp-add copp1 R1
 AT_CHECK([ovn-nbctl copp-list copp1], [0], [dnl
 arp-resolve: arp-meter
@@ -7421,7 +7426,7 @@ OVS_WAIT_UNTIL([
 kill $(pidof tcpdump)
 
 check ovn-nbctl meter-add icmp-meter drop 1 pktps 0
-check ovn-nbctl --wait=hv copp-add copp2 icmp4-error icmp-meter
+check ovn-nbctl copp-add copp2 icmp4-error icmp-meter
 check ovn-nbctl --wait=hv lr-copp-add copp2 R1
 AT_CHECK([ovn-nbctl copp-list copp2 |grep icmp4-error], [0], [dnl
 icmp4-error: icmp-meter
@@ -7441,7 +7446,7 @@ OVS_WAIT_UNTIL([
 kill $(pidof tcpdump)
 
 check ovn-nbctl meter-add bfd-meter drop 1 pktps 0
-check ovn-nbctl --wait=hv copp-add copp3 bfd bfd-meter
+check ovn-nbctl copp-add copp3 bfd bfd-meter
 check ovn-nbctl --wait=hv lr-copp-add copp3 R1
 AT_CHECK([ovn-nbctl copp-list copp3 |grep bfd], [0], [dnl
 bfd: bfd-meter
@@ -7471,7 +7476,7 @@ kill $(pidof tcpdump)
 
 check ovn-nbctl set nb_global . options:svc_monitor_mac="33:33:33:33:33:33"
 check ovn-nbctl meter-add svc-meter drop 1 pktps 0
-check ovn-nbctl --wait=hv copp-add copp4 svc-monitor svc-meter
+check ovn-nbctl copp-add copp4 svc-monitor svc-meter
 check ovn-nbctl --wait=hv ls-copp-add copp4 sw0
 check ovn-appctl -t ovn-controller vlog/set vconn:dbg
 AT_CHECK([ovn-nbctl copp-list copp4], [0], [dnl
@@ -8696,22 +8701,19 @@ check ovn-nbctl lr-nat-add lr1 snat 172.16.1.10 192.168.1.0/24
 check ovn-nbctl lr-nat-add lr1 snat 1711::10 2001::/64
 
 ADD_NAMESPACES(ls1p1)
-ADD_VETH(ls1p1, ls1p1, br-int, "192.168.1.1/24", "00:00:00:01:01:01", \
-         "192.168.1.254", , "2001::1/64", "2001::a")
+ADD_VETH(ls1p1, ls1p1, br-int, "2001::1/64", "00:00:00:01:01:01", \
+         "2001::a", "nodad", "192.168.1.1/24", "192.168.1.254")
 
 ADD_NAMESPACES(ls1p2)
-ADD_VETH(ls1p2, ls1p2, br-int, "192.168.1.2/24", "00:00:00:01:01:02", \
-         "192.168.1.254", , "2001::2/64", "2001::a")
+ADD_VETH(ls1p2, ls1p2, br-int, "2001::2/64", "00:00:00:01:01:02", \
+         "2001::a", "nodad", "192.168.1.2/24", "192.168.1.254")
 
 ADD_NAMESPACES(ext1)
-ADD_VETH(ext1, ext1, br0, "172.16.1.1/24", "00:ee:00:01:01:01", \
-         "172.16.1.254", , "1711::1/64", "1711::a")
+ADD_VETH(ext1, ext1, br0, "1711::1/64", "00:ee:00:01:01:01", \
+         "1711::a", "nodad", "172.16.1.1/24", "172.16.1.254")
 
 check ovn-nbctl --wait=hv sync
 wait_for_ports_up
-OVS_WAIT_UNTIL([test "$(ip netns exec ls1p1 ip a | grep 2001::1 | grep tentative)" = ""])
-OVS_WAIT_UNTIL([test "$(ip netns exec ls1p2 ip a | grep 2002::2 | grep tentative)" = ""])
-OVS_WAIT_UNTIL([test "$(ip netns exec ext1 ip a | grep 1711::1 | grep tentative)" = ""])
 
 NS_CHECK_EXEC([ls1p1], [ping -q -c 3 -i 0.3 -w 2  172.16.1.1 | FORMAT_PING], \
 [0], [dnl
@@ -9231,8 +9233,9 @@ name: 'vport4' value: '999'
 NETNS_DAEMONIZE([vm1], [nc -k -l 42.42.42.2 4242], [nc-vm1.pid])
 
 NETNS_DAEMONIZE([vm1],
-    [tcpdump -n -i vm1 -nnleX -c6 udp and dst 42.42.42.2 and dst port 4343 > vm1.pcap 2>/dev/null],
+    [tcpdump -n -i vm1 -nnleX -c6 udp and dst 42.42.42.2 and dst port 4343 > vm1.pcap 2> vm1.pcap.stderr],
     [tcpdump1.pid])
+OVS_WAIT_UNTIL([grep "listening" vm1.pcap.stderr])
 
 # Make sure connecting to the VIP works (hairpin, via ls and via lr).
 NS_CHECK_EXEC([vm1], [nc 66.66.66.66 666 -z], [0], [ignore], [ignore])
@@ -9355,16 +9358,13 @@ check ovn-nbctl --template lb-add lb-test-udp2 "^vip:^vport4" "[[4242::2]]:4343"
     -- lr-lb-add rtr lb-test-udp2
 
 ADD_NAMESPACES(vm1)
-ADD_VETH(vm1, vm1, br-int, "4242::2/64", "00:00:00:00:00:01", "4242::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec vm1 ip a | grep 4242::2 | grep tentative)" = ""])
+ADD_VETH(vm1, vm1, br-int, "4242::2/64", "00:00:00:00:00:01", "4242::1", "nodad")
 
 ADD_NAMESPACES(vm2)
-ADD_VETH(vm2, vm2, br-int, "4242::3/64", "00:00:00:00:00:02", "4242::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec vm2 ip a | grep 4242::3 | grep tentative)" = ""])
+ADD_VETH(vm2, vm2, br-int, "4242::3/64", "00:00:00:00:00:02", "4242::1", "nodad")
 
 ADD_NAMESPACES(vm3)
-ADD_VETH(vm3, vm3, br-int, "4343::2/64", "00:00:00:00:00:03", "4343::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec vm3 ip a | grep 4343::2 | grep tentative)" = ""])
+ADD_VETH(vm3, vm3, br-int, "4343::2/64", "00:00:00:00:00:03", "4343::1", "nodad")
 
 # Wait for ovn-controller to catch up.
 wait_for_ports_up
@@ -9385,8 +9385,9 @@ name: 'vport4' value: '999'
 NETNS_DAEMONIZE([vm1], [nc -k -l 4242::2 4242], [nc-vm1.pid])
 
 NETNS_DAEMONIZE([vm1],
-    [tcpdump -n -i vm1 -nnleX -c6 udp and dst 4242::2 and dst port 4343 > vm1.pcap 2>/dev/null],
+    [tcpdump -n -i vm1 -nnleX -c6 udp and dst 4242::2 and dst port 4343 > vm1.pcap 2> vm1.pcap.stderr],
     [tcpdump1.pid])
+OVS_WAIT_UNTIL([grep "listening" vm1.pcap.stderr])
 
 # Make sure connecting to the VIP works (hairpin, via ls and via lr).
 NS_CHECK_EXEC([vm1], [nc 6666::1 666 -z], [0], [ignore], [ignore])
@@ -10825,32 +10826,28 @@ check ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \
 # Logical port 'foo1' in switch 'foo'.
 ADD_NAMESPACES(foo1)
 ADD_VETH(foo1, foo1, br-int, "fd11::2/64", "f0:00:00:01:02:03", \
-         "fd7b:6b4d:7b25:d22f::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd11::2 | grep tentative)" = ""])
+         "fd7b:6b4d:7b25:d22f::1", "nodad")
 check ovn-nbctl lsp-add foo foo1 \
 -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd11::2"
 
 # Logical port 'foo2' in switch 'foo'.
 ADD_NAMESPACES(foo2)
 ADD_VETH(foo2, foo2, br-int, "fd11::3/64", "f0:00:00:01:02:04", \
-         "fd7b:6b4d:7b25:d22f::2")
-OVS_WAIT_UNTIL([test "$(ip netns exec foo2 ip a | grep fd11::3 | grep tentative)" = ""])
+         "fd7b:6b4d:7b25:d22f::2", "nodad")
 check ovn-nbctl lsp-add foo foo2 \
 -- lsp-set-addresses foo2 "f0:00:00:01:02:04 fd11::3"
 
 # Logical port 'foo3' in switch 'foo'.
 ADD_NAMESPACES(foo3)
 ADD_VETH(foo3, foo3, br-int, "fd11::4/64", "f0:00:00:01:02:05", \
-         "fd7b:6b4d:7b25:d22d::1")
-OVS_WAIT_UNTIL([test "$(ip netns exec foo3 ip a | grep fd11::4 | grep tentative)" = ""])
+         "fd7b:6b4d:7b25:d22d::1", "nodad")
 check ovn-nbctl lsp-add foo foo3 \
 -- lsp-set-addresses foo3 "f0:00:00:01:02:05 fd11::4"
 
 # Logical port 'bar1' in switch 'bar'.
 ADD_NAMESPACES(bar1)
 ADD_VETH(bar1, bar1, br-int, "fd12::2/64", "f0:00:00:01:02:06", \
-"fd7b:6b4d:7b25:d22f::3")
-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd12::2 | grep tentative)" = ""])
+         "fd7b:6b4d:7b25:d22f::3", "nodad")
 check ovn-nbctl lsp-add bar bar1 \
 -- lsp-set-addresses bar1 "f0:00:00:01:02:06 fd12::2"
 
@@ -11686,7 +11683,7 @@ check ovn-nbctl lsp-add ls0 ls0-lr0 \
     -- lsp-set-options ls0-lr0 router-port=lr0-ls0
 
 ADD_NAMESPACES(vif0)
-ADD_VETH(vif0, vif0, br-int, "fd00::2/64", "00:00:00:00:00:02", "fd00::1")
+ADD_VETH(vif0, vif0, br-int, "fd00::2/64", "00:00:00:00:00:02", "fd00::1", "nodad")
 OVS_WAIT_UNTIL([test "$(ip netns exec vif0 ip a | grep fe80:: | grep tentative)" = ""])
 
 check ovn-nbctl set logical_router lr0 options:always_learn_from_arp_request=false
@@ -11729,3 +11726,497 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
 
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ct_flush on logical router load balancer])
+AT_KEYWORDS([ct-lr-flush])
+CHECK_CONNTRACK()
+CHECK_CONNTRACK_NAT()
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+
+# Set external-ids in br-int needed for ovn-controller
+ovs-vsctl \
+        -- set Open_vSwitch . external-ids:system-id=hv1 \
+        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+start_daemon ovn-controller
+
+check ovn-nbctl lr-add R1
+
+check ovn-nbctl ls-add sw0
+check ovn-nbctl ls-add public
+
+check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24
+check ovn-nbctl lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24
+
+check ovn-nbctl set logical_router R1 options:chassis=hv1
+
+check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \
+    type=router options:router-port=rp-sw0 \
+    -- lsp-set-addresses sw0-rp router
+
+check ovn-nbctl lsp-add sw0 sw0-vm \
+    -- lsp-set-addresses sw0-vm "00:00:01:01:02:04 192.168.1.2/24"
+
+check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port public-rp \
+    type=router options:router-port=rp-public \
+    -- lsp-set-addresses public-rp router
+
+check ovn-nbctl lsp-add public public-vm \
+   -- lsp-set-addresses public-vm "00:00:02:01:02:04 172.16.1.2/24"
+
+ADD_NAMESPACES(sw0-vm)
+ADD_VETH(sw0-vm, sw0-vm, br-int, "192.168.1.2/24", "00:00:01:01:02:04", \
+         "192.168.1.1")
+
+ADD_NAMESPACES(public-vm)
+ADD_VETH(public-vm, public-vm, br-int, "172.16.1.2/24", "00:00:02:01:02:04", \
+         "172.16.1.1")
+
+# Start webservers in 'server'.
+OVS_START_L7([sw0-vm], [http])
+
+# Create a load balancer and associate to R1
+check ovn-nbctl lb-add lb1 172.16.1.150:80 192.168.1.2:80 \
+    -- set load_balancer lb1 options:ct_flush="true"
+check ovn-nbctl lr-lb-add R1 lb1
+
+check ovn-nbctl --wait=hv sync
+
+for i in $(seq 1 5); do
+    echo Request $i
+    NS_CHECK_EXEC([public-vm], [wget 172.16.1.150 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+done
+
+OVS_WAIT_FOR_OUTPUT([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.150) | wc -l ], [0], [dnl
+1
+])
+
+check ovn-nbctl --wait=hv lb-del lb1
+
+OVS_WAIT_FOR_OUTPUT([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.150) | wc -l ], [0], [dnl
+0
+])
+
+check ovn-nbctl lb-add lb2 172.16.1.151:80 192.168.1.2:80
+check ovn-nbctl lr-lb-add R1 lb2
+
+check ovn-nbctl --wait=hv sync
+
+for i in $(seq 1 5); do
+    echo Request $i
+    NS_CHECK_EXEC([public-vm], [wget 172.16.1.151 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
+done
+
+OVS_WAIT_FOR_OUTPUT([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.151) | wc -l ], [0], [dnl
+1
+])
+
+check ovn-nbctl --wait=hv lb-del lb2
+
+OVS_WAIT_FOR_OUTPUT([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.151) | wc -l ], [0], [dnl
+1
+])
+
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/Failed to acquire.*/d
+/connection dropped.*/d"])
+AT_CLEANUP
+])
+
+AT_SETUP([load balancing in gateway router - SCTP])
+AT_SKIP_IF([test $HAVE_SCTP = no])
+AT_SKIP_IF([test $HAVE_NC = no])
+AT_KEYWORDS([ovnlb sctp])
+
+# Make sure the SCTP kernel module is loaded.
+LOAD_MODULE([sctp])
+
+CHECK_CONNTRACK()
+CHECK_CONNTRACK_NAT()
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+
+# Set external-ids in br-int needed for ovn-controller.
+ovs-vsctl \
+        -- set Open_vSwitch . external-ids:system-id=hv1 \
+        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+# Start the ovn-controller.
+start_daemon ovn-controller
+
+# Logical network:
+# Two LRs - R1 and R2 that are connected to each other via LS "join"
+# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) and
+# bar (192.168.2.0/24) connected to it. R2 has alice (172.16.1.0/24) connected
+# to it.  R2 is a gateway router on which we add load-balancing rules.
+#
+#    foo -- R1 -- join - R2 -- alice
+#           |
+#    bar ----
+
+ovn-nbctl create Logical_Router name=R1
+ovn-nbctl create Logical_Router name=R2 options:chassis=hv1
+
+ovn-nbctl ls-add foo
+ovn-nbctl ls-add bar
+ovn-nbctl ls-add alice
+ovn-nbctl ls-add join
+
+# Connect foo to R1
+ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
+ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
+    type=router options:router-port=foo addresses=\"00:00:01:01:02:03\"
+
+# Connect bar to R1
+ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24
+ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \
+    type=router options:router-port=bar addresses=\"00:00:01:01:02:04\"
+
+# Connect alice to R2
+ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24
+ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
+    type=router options:router-port=alice addresses=\"00:00:02:01:02:03\"
+
+# Connect R1 to join
+ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24
+ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \
+    type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"'
+
+# Connect R2 to join
+ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24
+ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \
+    type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"'
+
+# Static routes.
+ovn-nbctl lr-route-add R1 172.16.1.0/24 20.0.0.2
+ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1
+
+# Logical port 'foo1' in switch 'foo'.
+ADD_NAMESPACES(foo1)
+ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
+         "192.168.1.1")
+ovn-nbctl lsp-add foo foo1 \
+-- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
+
+# Logical port 'alice1' in switch 'alice'.
+ADD_NAMESPACES(alice1)
+ADD_VETH(alice1, alice1, br-int, "172.16.1.2/24", "f0:00:00:01:02:04", \
+         "172.16.1.1")
+ovn-nbctl lsp-add alice alice1 \
+-- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2"
+
+# Logical port 'bar1' in switch 'bar'.
+ADD_NAMESPACES(bar1)
+ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:05", \
+"192.168.2.1")
+ovn-nbctl lsp-add bar bar1 \
+-- lsp-set-addresses bar1 "f0:00:00:01:02:05 192.168.2.2"
+
+# Config OVN load-balancer with a VIP.
+uuid=`ovn-nbctl  create load_balancer protocol=sctp vips:30.0.0.1="192.168.1.2,192.168.2.2"`
+ovn-nbctl set logical_router R2 load_balancer=$uuid
+
+# Config OVN load-balancer with another VIP (this time with ports).
+ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:12345,192.168.2.2:12345"'
+
+# Add SNAT rule to make sure that Load-balancing still works with a SNAT rule.
+ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.2.2 \
+    external_ip=30.0.0.2 -- add logical_router R2 nat @nat
+
+# Wait for ovn-controller to catch up.
+ovn-nbctl --wait=hv sync
+OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \
+grep 'nat(dst=192.168.2.2:12345)'])
+
+# Start webservers in 'foo1', 'bar1'.
+OVS_START_L7([foo1], [sctp])
+OVS_START_L7([bar1], [sctp])
+
+on_exit "ovs-ofctl -O OpenFlow13 dump-flows br-int"
+
+dnl Should work with the virtual IP address through NAT
+for i in `seq 1 20`; do
+    echo Request $i
+    NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.1 12345 > client$i.log])
+done
+
+dnl Each server should have at least one connection.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) |
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
+sed 's/,protoinfo=.*$//' | uniq], [0], [dnl
+sctp,orig=(src=172.16.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2
+sctp,orig=(src=172.16.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2
+])
+
+dnl Test load-balancing that includes L4 ports in NAT.
+for i in `seq 1 20`; do
+    echo Request $i
+    NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.2 8000 > clients$i.log])
+done
+
+dnl Each server should have at least one connection.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) |
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
+sed 's/,protoinfo=.*$//' | uniq], [0], [dnl
+sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2
+sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2
+])
+
+check_est_flows () {
+    n=$(ovs-ofctl dump-flows br-int table=15 | grep "+est" \
+        | grep "ct_mark=$1" | sed -n 's/.*n_packets=\([[0-9]]\{1,\}\).*/\1/p')
+
+    echo "n_packets=$n"
+    test -n "$n" && test "$n" != "0"
+}
+
+OVS_WAIT_UNTIL([check_est_flows 0x2], [check established flows])
+
+
+ovn-nbctl set logical_router R2 options:lb_force_snat_ip="20.0.0.2"
+
+# Destroy the load balancer and create again. ovn-controller will
+# clear the OF flows and re add again and clears the n_packets
+# for these flows.
+ovn-nbctl destroy load_balancer $uuid
+uuid=`ovn-nbctl  create load_balancer protocol=sctp vips:30.0.0.1="192.168.1.2,192.168.2.2"`
+ovn-nbctl set logical_router R2 load_balancer=$uuid
+
+check ovs-appctl dpctl/flush-conntrack
+
+# Config OVN load-balancer with another VIP (this time with ports).
+ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:12345,192.168.2.2:12345"'
+
+ovn-nbctl list load_balancer
+ovn-sbctl dump-flows R2
+OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=45 | grep 'nat(src=20.0.0.2)'])
+
+dnl Test load-balancing that includes L4 ports in NAT.
+for i in `seq 1 20`; do
+    echo Request $i
+    NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.2 8000 > clients$i.log])
+done
+
+dnl Each server should have at least one connection.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) |
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
+sed 's/,protoinfo=.*$//' | uniq], [0], [dnl
+sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10
+sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10
+])
+
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0.2) |
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
+sed 's/,protoinfo=.*$//' | uniq], [0], [dnl
+sctp,orig=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=20.0.0.2,sport=<cleared>,dport=<cleared>),zone=<cleared>
+sctp,orig=(src=172.16.1.2,dst=192.168.2.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=20.0.0.2,sport=<cleared>,dport=<cleared>),zone=<cleared>
+])
+
+OVS_WAIT_UNTIL([check_est_flows 0xa], [check established flows])
+
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([ovn-northd])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d"])
+AT_CLEANUP
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([load balancing in gateway router - client behind LB with SNAT])
+AT_SKIP_IF([test $HAVE_NC = no])
+AT_KEYWORDS([lb])
+
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+
+check ovs-vsctl \
+        -- set Open_vSwitch . external-ids:system-id=hv1 \
+        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+start_daemon ovn-controller
+
+check ovn-nbctl lr-add lr \
+    -- set logical_router lr options:chassis=hv1
+check ovn-nbctl lrp-add lr lr-ls1 00:00:00:00:01:00 41.41.41.2/24
+check ovn-nbctl lrp-add lr lr-ls2 00:00:00:00:02:00 42.42.42.2/24
+check ovn-nbctl ls-add ls1
+check ovn-nbctl ls-add ls2
+
+check ovn-nbctl lsp-add ls1 ls1-lr
+check ovn-nbctl lsp-set-addresses ls1-lr 00:00:00:00:01:00
+check ovn-nbctl lsp-set-type ls1-lr router
+check ovn-nbctl lsp-set-options ls1-lr router-port=lr-ls1
+check ovn-nbctl lsp-add ls1 vm1
+check ovn-nbctl lsp-set-addresses vm1 00:00:00:00:00:01
+
+check ovn-nbctl lsp-add ls2 ls2-lr
+check ovn-nbctl lsp-set-addresses ls2-lr 00:00:00:00:02:00
+check ovn-nbctl lsp-set-type ls2-lr router
+check ovn-nbctl lsp-set-options ls2-lr router-port=lr-ls2
+check ovn-nbctl lsp-add ls2 vm2
+check ovn-nbctl lsp-set-addresses vm2 00:00:00:00:00:02
+
+dnl LB using the router IP connected to vm2 as VIP.
+check ovn-nbctl lb-add lb-test 42.42.42.2:8080 41.41.41.1:8080 tcp \
+    -- lr-lb-add lr lb-test
+
+dnl SNAT everything coming from vm1 to the router IP (towards vm2).
+check ovn-nbctl lr-nat-add lr snat 42.42.42.2 41.41.41.1
+
+ADD_NAMESPACES(vm1)
+ADD_VETH(vm1, vm1, br-int, "41.41.41.1/24", "00:00:00:00:00:01", "41.41.41.2")
+
+ADD_NAMESPACES(vm2)
+ADD_VETH(vm2, vm2, br-int, "42.42.42.1/24", "00:00:00:00:00:02", "42.42.42.2")
+
+dnl Start a server on vm2.
+NETNS_DAEMONIZE([vm2], [nc -l -k 42.42.42.1 80], [vm2.pid])
+
+dnl Wait for ovn-controller to catch up.
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+dnl Test the connection originating something that uses the same source port
+dnl as the LB VIP.
+NS_CHECK_EXEC([vm1], [nc -z -p 8080 42.42.42.1 80], 0, [ignore], [ignore])
+
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([ovn-northd])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d"])
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([load balancing affinity sessions - auto clear learnt flows])
+AT_SKIP_IF([test $HAVE_NC = no])
+AT_KEYWORDS([lb])
+
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+
+check ovs-vsctl \
+        -- set Open_vSwitch . external-ids:system-id=hv1 \
+        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+start_daemon ovn-controller
+
+check ovn-nbctl lr-add lr
+check ovn-nbctl lrp-add lr lr-ls 00:00:00:00:01:00 42.42.42.3/24
+check ovn-nbctl ls-add ls
+
+check ovn-nbctl lsp-add ls ls-lr
+check ovn-nbctl lsp-set-addresses ls-lr 00:00:00:00:01:00
+check ovn-nbctl lsp-set-type ls-lr router
+check ovn-nbctl lsp-set-options ls-lr router-port=lr-ls
+check ovn-nbctl lsp-add ls vm1
+check ovn-nbctl lsp-set-addresses vm1 00:00:00:00:00:01
+check ovn-nbctl lsp-add ls vm2
+check ovn-nbctl lsp-set-addresses vm2 00:00:00:00:00:02
+check ovn-nbctl lb-add lb-test 43.43.43.43:80 42.42.42.1:8080,42.42.42.2:8080 tcp \
+    -- set load_balancer lb-test options:affinity_timeout=65535 \
+    -- ls-lb-add ls lb-test
+
+dnl Start a server on vm1.
+ADD_NAMESPACES(vm1)
+ADD_VETH(vm1, vm1, br-int, "42.42.42.1/24", "00:00:00:00:00:01", "42.42.42.3")
+NETNS_DAEMONIZE([vm1], [nc -l -k 42.42.42.1 8080], [vm1.pid])
+
+dnl Start a server on vm2.
+ADD_NAMESPACES(vm2)
+ADD_VETH(vm2, vm2, br-int, "42.42.42.2/24", "00:00:00:00:00:02", "42.42.42.3")
+NETNS_DAEMONIZE([vm2], [nc -l -k 42.42.42.2 8080], [vm2.pid])
+
+dnl Wait for ovn-controller to catch up.
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+dnl Test the connection.
+OVS_WAIT_UNTIL([
+    ip netns exec vm1 nc -z 43.43.43.43 80 &> /dev/null
+])
+
+OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int | grep 'table=78, n_packets' -c) -eq 1])
+
+dnl Find the backend that was hit.
+backend=$(ovs-ofctl dump-flows br-int table=78 | \
+    grep -oE 'load:0x2a2a2a0[[12]]' | sed -n 's/load:0x2a2a2a0\(.*\)/\1/p')
+
+dnl Remove the backend that was hit.
+if [[ "$backend" == "1" ]]; then
+    check ovn-nbctl set load_balancer lb-test vip:\"43.43.43.43:80\"=\"42.42.42.2:8080\"
+else
+    check ovn-nbctl set load_balancer lb-test vip:\"43.43.43.43:80\"=\"42.42.42.1:8080\"
+fi
+check ovn-nbctl --wait=hv sync
+
+dnl The learnt flow should also be auto deleted.
+AT_CHECK([ovs-ofctl dump-flows br-int | grep 'table=78, n_packets' -c], [1], [dnl
+0
+])
+
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([ovn-northd])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d"])
+AT_CLEANUP
+])
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
index 1f1e27b51..aaf2825ed 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -1300,11 +1300,11 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
 
     /* Initialize group ids. */
     struct ovn_extend_table group_table;
-    ovn_extend_table_init(&group_table);
+    ovn_extend_table_init(&group_table, "group-table", OFPG_MAX);
 
     /* Initialize meter ids for QoS. */
     struct ovn_extend_table meter_table;
-    ovn_extend_table_init(&meter_table);
+    ovn_extend_table_init(&meter_table, "meter-table", OFPM13_MAX);
 
     /* Initialize collector sets. */
     struct flow_collector_ids collector_ids;
diff --git a/utilities/checkpatch.py b/utilities/checkpatch.py
index 5467d604d..52d3fa845 100755
--- a/utilities/checkpatch.py
+++ b/utilities/checkpatch.py
@@ -40,6 +40,15 @@ MAX_LINE_LEN = 79
 def open_spell_check_dict():
     import enchant
 
+    try:
+        import codespell_lib
+        codespell_dir = os.path.dirname(codespell_lib.__file__)
+        codespell_file = os.path.join(codespell_dir, 'data', 'dictionary.txt')
+        if not os.path.exists(codespell_file):
+            codespell_file = ''
+    except:
+        codespell_file = ''
+
     try:
         extra_keywords = ['ovs', 'vswitch', 'vswitchd', 'ovs-vswitchd',
                           'netdev', 'selinux', 'ovs-ctl', 'dpctl', 'ofctl',
@@ -92,9 +101,18 @@ def open_spell_check_dict():
                           'syscall', 'lacp', 'ipf', 'skb', 'valgrind']
 
         global spell_check_dict
+
         spell_check_dict = enchant.Dict("en_US")
+
+        if codespell_file:
+            with open(codespell_file) as f:
+                for line in f.readlines():
+                    words = line.strip().split('>')[1].strip(', ').split(',')
+                    for word in words:
+                        spell_check_dict.add_to_session(word.strip())
+
         for kw in extra_keywords:
-            spell_check_dict.add(kw)
+            spell_check_dict.add_to_session(kw)
 
         return True
     except:
@@ -190,6 +208,7 @@ skip_trailing_whitespace_check = False
 skip_gerrit_change_id_check = False
 skip_block_whitespace_check = False
 skip_signoff_check = False
+skip_committer_signoff_check = False
 
 # Don't enforce character limit on files that include these characters in their
 # name, as they may have legitimate reasons to have longer lines.
@@ -282,9 +301,13 @@ def if_and_for_end_with_bracket_check(line):
         if len(line) == MAX_LINE_LEN - 1 and line[-1] == ')':
             return True
 
-        if __regex_ends_with_bracket.search(line) is None and \
-           __regex_if_macros.match(line) is None:
-            return False
+        if __regex_ends_with_bracket.search(line) is None:
+            if line.endswith("\\") and \
+               __regex_if_macros.match(line) is not None:
+                return True
+            else:
+                return False
+
     if __regex_conditional_else_bracing.match(line) is not None:
         return False
     if __regex_conditional_else_bracing2.match(line) is not None:
@@ -410,9 +433,15 @@ def check_spelling(line, comment):
     if not spell_check_dict or not spellcheck:
         return False
 
+    if line.startswith('Fixes: '):
+        return False
+
     words = filter_comments(line, True) if comment else line
     words = words.replace(':', ' ').split(' ')
 
+    flagged_words = []
+    num_suggestions = 3
+
     for word in words:
         skip = False
         strword = re.subn(r'\W+', '', word)[0].replace(',', '')
@@ -437,9 +466,15 @@ def check_spelling(line, comment):
                 skip = True
 
             if not skip:
-                print_warning("Check for spelling mistakes (e.g. \"%s\")"
-                              % strword)
-                return True
+                flagged_words.append(strword)
+
+    if len(flagged_words) > 0:
+        for mistake in flagged_words:
+            print_warning("Possible misspelled word: \"%s\"" % mistake)
+            print("Did you mean: ",
+                  spell_check_dict.suggest(mistake)[:num_suggestions])
+
+        return True
 
     return False
 
@@ -780,6 +815,36 @@ def run_file_checks(text):
             check['check'](text)
 
 
+def run_subject_checks(subject, spellcheck=False):
+    warnings = False
+
+    if spellcheck and check_spelling(subject, False):
+        warnings = True
+
+    summary = subject[subject.rindex(': ') + 2:]
+    area_summary = subject[subject.index(': ') + 2:]
+    area_summary_len = len(area_summary)
+    if area_summary_len > 70:
+        print_warning("The subject, '<area>: <summary>', is over 70 "
+                      "characters, i.e., %u." % area_summary_len)
+        warnings = True
+
+    if summary[0].isalpha() and summary[0].islower():
+        print_warning(
+            "The subject summary should start with a capital.")
+        warnings = True
+
+    if subject[-1] not in [".", "?", "!"]:
+        print_warning(
+            "The subject summary should end with a dot.")
+        warnings = True
+
+    if warnings:
+        print(subject)
+
+    return warnings
+
+
 def ovs_checkpatch_parse(text, filename, author=None, committer=None):
     global print_file_name, total_line, checking_file, \
         empty_return_check_state
@@ -800,6 +865,7 @@ def ovs_checkpatch_parse(text, filename, author=None, committer=None):
         r'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@')
     is_author = re.compile(r'^(Author|From): (.*)$', re.I | re.M | re.S)
     is_committer = re.compile(r'^(Commit: )(.*)$', re.I | re.M | re.S)
+    is_subject = re.compile(r'^(Subject: )(.*)$', re.I | re.M | re.S)
     is_signature = re.compile(r'^(Signed-off-by: )(.*)$',
                               re.I | re.M | re.S)
     is_co_author = re.compile(r'^(Co-authored-by: )(.*)$',
@@ -874,7 +940,8 @@ def ovs_checkpatch_parse(text, filename, author=None, committer=None):
                             break
                     if (committer
                         and author != committer
-                        and committer not in signatures):
+                        and committer not in signatures
+                        and not skip_committer_signoff_check):
                         print_error("Committer %s needs to sign off."
                                     % committer)
 
@@ -899,6 +966,8 @@ def ovs_checkpatch_parse(text, filename, author=None, committer=None):
                 committer = is_committer.match(line).group(2)
             elif is_author.match(line):
                 author = is_author.match(line).group(2)
+            elif is_subject.match(line):
+                run_subject_checks(line, spellcheck)
             elif is_signature.match(line):
                 m = is_signature.match(line)
                 signatures.append(m.group(2))
@@ -990,7 +1059,8 @@ Check options:
 -S|--spellcheck                Check C comments and commit-message for possible
                                spelling mistakes
 -t|--skip-trailing-whitespace  Skips the trailing whitespace test
-   --skip-gerrit-change-id     Skips the gerrit change id test"""
+   --skip-gerrit-change-id     Skips the gerrit change id test
+   --skip-committer-signoff    Skips the committer sign-off test"""
           % sys.argv[0])
 
 
@@ -1017,6 +1087,19 @@ def ovs_checkpatch_file(filename):
     result = ovs_checkpatch_parse(part.get_payload(decode=False), filename,
                                   mail.get('Author', mail['From']),
                                   mail['Commit'])
+
+    if not mail['Subject'] or not mail['Subject'].strip():
+        if mail['Subject']:
+            mail.replace_header('Subject', sys.argv[-1])
+        else:
+            mail.add_header('Subject', sys.argv[-1])
+
+        print("Subject missing! Your provisional subject is",
+              mail['Subject'])
+
+    if run_subject_checks('Subject: ' + mail['Subject'], spellcheck):
+        result = True
+
     ovs_checkpatch_print_result()
     return result
 
@@ -1048,6 +1131,7 @@ if __name__ == '__main__':
                                        "skip-signoff-lines",
                                        "skip-trailing-whitespace",
                                        "skip-gerrit-change-id",
+                                       "skip-committer-signoff",
                                        "spellcheck",
                                        "quiet"])
     except:
@@ -1068,6 +1152,8 @@ if __name__ == '__main__':
             skip_trailing_whitespace_check = True
         elif o in ("--skip-gerrit-change-id"):
             skip_gerrit_change_id_check = True
+        elif o in ("--skip-committer-signoff"):
+            skip_committer_signoff_check = True
         elif o in ("-f", "--check-file"):
             checking_file = True
         elif o in ("-S", "--spellcheck"):
diff --git a/utilities/containers/fedora/Dockerfile b/utilities/containers/fedora/Dockerfile
index 4058d7f5b..c11ea37b7 100755
--- a/utilities/containers/fedora/Dockerfile
+++ b/utilities/containers/fedora/Dockerfile
@@ -1,4 +1,4 @@
-FROM quay.io/fedora/fedora:latest
+FROM quay.io/fedora/fedora:38
 
 ARG CONTAINERS_PATH
 
diff --git a/utilities/containers/py-requirements.txt b/utilities/containers/py-requirements.txt
index 0d90765c9..aac98443b 100644
--- a/utilities/containers/py-requirements.txt
+++ b/utilities/containers/py-requirements.txt
@@ -1,5 +1,4 @@
-flake8
-hacking>=3.0
+flake8==5.0.4
 scapy
 sphinx
 setuptools
diff --git a/utilities/containers/ubuntu/Dockerfile b/utilities/containers/ubuntu/Dockerfile
index 5d5bedbd9..3c7fe7775 100755
--- a/utilities/containers/ubuntu/Dockerfile
+++ b/utilities/containers/ubuntu/Dockerfile
@@ -1,4 +1,4 @@
-FROM registry.hub.docker.com/library/ubuntu:latest
+FROM registry.hub.docker.com/library/ubuntu:22.04
 
 ARG CONTAINERS_PATH
 
diff --git a/utilities/ovn-ctl.8.xml b/utilities/ovn-ctl.8.xml
index 82804096f..01f4aa26b 100644
--- a/utilities/ovn-ctl.8.xml
+++ b/utilities/ovn-ctl.8.xml
@@ -136,12 +136,14 @@
     <p><code>--db-nb-cluster-remote-addr=<var>IP ADDRESS</var></code></p>
     <p><code>--db-nb-cluster-remote-port=<var>PORT NUMBER</var></code></p>
     <p><code>--db-nb-cluster-remote-proto=<var>PROTO (tcp/ssl)</var></code></p>
+    <p><code>--db-nb-election-timer=<var>Timeout in milliseconds</var></code></p>
     <p><code>--db-sb-cluster-local-addr=<var>IP ADDRESS</var></code></p>
     <p><code>--db-sb-cluster-local-port=<var>PORT NUMBER</var></code></p>
     <p><code>--db-sb-cluster-local-proto=<var>PROTO (tcp/ssl)</var></code></p>
     <p><code>--db-sb-cluster-remote-addr=<var>IP ADDRESS</var></code></p>
     <p><code>--db-sb-cluster-remote-port=<var>PORT NUMBER</var></code></p>
     <p><code>--db-sb-cluster-remote-proto=<var>PROTO (tcp/ssl)</var></code></p>
+    <p><code>--db-sb-election-timer=<var>Timeout in milliseconds</var></code></p>
     <p><code>--db-ic-nb-cluster-local-addr=<var>IP ADDRESS</var></code></p>
     <p><code>--db-ic-nb-cluster-local-port=<var>PORT NUMBER</var></code></p>
     <p><code>--db-ic-nb-cluster-local-proto=<var>PROTO (tcp/ssl)</var></code></p>
diff --git a/utilities/ovn-dbctl.c b/utilities/ovn-dbctl.c
index 2e9348c47..92be27b2c 100644
--- a/utilities/ovn-dbctl.c
+++ b/utilities/ovn-dbctl.c
@@ -610,6 +610,14 @@ apply_options_direct(const struct ovn_dbctl_options *dbctl_options,
             ssl_ca_cert_file = optarg;
             break;
 
+        case OPT_SSL_PROTOCOLS:
+            stream_ssl_set_protocols(optarg);
+            break;
+
+        case OPT_SSL_CIPHERS:
+            stream_ssl_set_ciphers(optarg);
+            break;
+
         case OPT_BOOTSTRAP_CA_CERT:
             stream_ssl_set_ca_cert_file(po->arg, true);
             break;
diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
index 444fbd2fe..821ad44dd 100644
--- a/utilities/ovn-nbctl.c
+++ b/utilities/ovn-nbctl.c
@@ -4693,6 +4693,7 @@ static void
             nexthop = normalize_prefix_str(ctx->argv[3]);
             if (!nexthop) {
                 ctl_error(ctx, "bad nexthop argument: %s", ctx->argv[3]);
+                free(prefix);
                 return;
             }
         }
diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
index 3948cae3f..f1f8c2b42 100644
--- a/utilities/ovn-sbctl.c
+++ b/utilities/ovn-sbctl.c
@@ -396,7 +396,9 @@ pre_get_info(struct ctl_context *ctx)
     ovsdb_idl_add_column(ctx->idl, &sbrec_mac_binding_col_mac);
 
     ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_datapaths);
+    /* datapath_group column is deprecated. */
     ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_datapath_group);
+    ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_ls_datapath_group);
     ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_vips);
     ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_name);
     ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_protocol);
@@ -932,10 +934,15 @@ cmd_lflow_list_load_balancers(struct ctl_context *ctx, struct vconn *vconn,
                     break;
                 }
             }
+            /* datapath_group column is deprecated. */
             if (lb->datapath_group && !dp_found) {
                 dp_found = datapath_group_contains_datapath(lb->datapath_group,
                                                             datapath);
             }
+            if (lb->ls_datapath_group && !dp_found) {
+                dp_found = datapath_group_contains_datapath(
+                        lb->ls_datapath_group, datapath);
+            }
             if (!dp_found) {
                 continue;
             }
@@ -954,11 +961,17 @@ cmd_lflow_list_load_balancers(struct ctl_context *ctx, struct vconn *vconn,
                 print_vflow_datapath_name(lb->datapaths[i], true,
                                           &ctx->output);
             }
+            /* datapath_group column is deprecated. */
             for (size_t i = 0; lb->datapath_group
                                && i < lb->datapath_group->n_datapaths; i++) {
                 print_vflow_datapath_name(lb->datapath_group->datapaths[i],
                                           true, &ctx->output);
             }
+            for (size_t i = 0; lb->ls_datapath_group
+                    && i < lb->ls_datapath_group->n_datapaths; i++) {
+                print_vflow_datapath_name(lb->ls_datapath_group->datapaths[i],
+                                          true, &ctx->output);
+            }
         }
 
         ds_put_cstr(&ctx->output, "\n  vips:\n");
diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
index 0b86eae7b..e0f1c3ec9 100644
--- a/utilities/ovn-trace.c
+++ b/utilities/ovn-trace.c
@@ -983,6 +983,7 @@ parse_lflow_for_datapath(const struct sbrec_logical_flow *sblf,
         if (error) {
             VLOG_WARN("%s: parsing expression failed (%s)",
                       sblf->match, error);
+            expr_destroy(match);
             free(error);
             return;
         }
@@ -2462,7 +2463,7 @@ execute_ct_nat(const struct ovnact_ct_nat *ct_nat,
 }
 
 static void
-execute_ct_commit_nat(const struct ovnact_ct_nat *ct_nat,
+execute_ct_commit_nat(const struct ovnact_ct_commit_nat *ct_nat,
                       const struct ovntrace_datapath *dp, struct flow *uflow,
                       enum ovnact_pipeline pipeline, struct ovs_list *super)
 {