diff --git a/ovn/controller/automake.mk b/ovn/controller/automake.mk
index d3828f5..cd5505c 100644
--- a/ovn/controller/automake.mk
+++ b/ovn/controller/automake.mk
@@ -1,5 +1,7 @@
 bin_PROGRAMS += ovn/controller/ovn-controller
 ovn_controller_ovn_controller_SOURCES = \
+	ovn/controller/bfd.c \
+	ovn/controller/bfd.h \
 	ovn/controller/binding.c \
 	ovn/controller/binding.h \
 	ovn/controller/chassis.c \
diff --git a/ovn/controller/bfd.c b/ovn/controller/bfd.c
new file mode 100644
index 0000000..1a5d4bd
--- /dev/null
+++ b/ovn/controller/bfd.c
@@ -0,0 +1,201 @@
+/* Copyright (c) 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "bfd.h"
+#include "gchassis.h"
+#include "lport.h"
+#include "ovn-controller.h"
+
+#include "lib/hash.h"
+#include "lib/sset.h"
+#include "lib/util.h"
+#include "lib/vswitch-idl.h"
+#include "openvswitch/vlog.h"
+#include "ovn/lib/ovn-sb-idl.h"
+#include "ovn-controller.h"
+
+VLOG_DEFINE_THIS_MODULE(ovn_bfd);
+
+void
+bfd_register_ovs_idl(struct ovsdb_idl *ovs_idl)
+{
+    /* NOTE: this assumes that binding.c has added the
+     * ovsrec_interface table */
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_interface_col_bfd);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_interface_col_bfd_status);
+}
+
+
+static void
+interface_set_bfd(const struct ovsrec_interface *iface, bool bfd_setting)
+{
+    const char *new_setting = bfd_setting ? "true":"false";
+    const char *current_setting = smap_get(&iface->bfd, "enable");
+    if (current_setting && !strcmp(current_setting, new_setting)) {
+        /* If already set to true we skip setting it again
+         * to avoid flapping to bfd initialization state */
+        return;
+    }
+    const struct smap bfd = SMAP_CONST1(&bfd, "enable", new_setting);
+    ovsrec_interface_verify_bfd(iface);
+    ovsrec_interface_set_bfd(iface, &bfd);
+    VLOG_INFO("%s BFD on interface %s", bfd_setting?"Enabled":"Disabled",
+                                        iface->name);
+}
+
+void 
+bfd_calculate_active_tunnels(const struct ovsrec_bridge *br_int,
+                             struct sset *active_tunnels)
+{
+    int i;
+
+    for (i = 0; i < br_int->n_ports; i++) {
+        const struct ovsrec_port *port_rec = br_int->ports[i];
+
+        if (!strcmp(port_rec->name, br_int->name)) {
+            continue;
+        }
+
+        int j;
+        for (j = 0; j < port_rec->n_interfaces; j++) {
+            const struct ovsrec_interface *iface_rec;
+            iface_rec = port_rec->interfaces[j];
+
+            /* Check if this is a tunnel interface. */
+            if (smap_get(&iface_rec->options, "remote_ip")) {
+                /* Add ovn-chassis-id if the bfd_status of the tunnel
+                 * is active */
+                const char *bfd = smap_get(&iface_rec->bfd, "enable");
+                if (bfd && !strcmp(bfd, "true")) {
+                    const char *status = smap_get(&iface_rec->bfd_status,
+                                                  "state");
+                    if (status && !strcmp(status, "up")) {
+                        const char *id = smap_get(&port_rec->external_ids,
+                                                  "ovn-chassis-id");
+                        if (id) {
+                            sset_add(active_tunnels, id);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+static void
+bfd_calculate_chassis(const struct sbrec_chassis *our_chassis,
+                      struct hmap *local_datapaths,
+                      const struct chassis_index *chassis_index,
+                      struct sset *bfd_chassis)
+{
+    /* Identify all chassis nodes to which we need to enable bfd.
+     * 1) Any chassis hosting the chassisredirect ports for known
+     *    router datapaths.
+     * 2) Chassis hosting peer datapaths (with ports) connected
+     *    to a router datapath  when our chassis is hosting a router
+     *    with a chassis redirect port. */
+    struct local_datapath *dp;
+    HMAP_FOR_EACH (dp, hmap_node, local_datapaths) {
+        const char *is_router = smap_get(&dp->datapath->external_ids,
+                                         "logical-router");
+        bool our_chassis_is_gw_for_dp = false;
+        if (is_router) {
+            for (size_t j = 0; j < dp->ldatapath->n_lports; j++) {
+                const struct sbrec_port_binding *pb = dp->ldatapath->lports[j];
+                if (!strcmp(pb->type, "chassisredirect")) {
+                    struct ovs_list *gateway_chassis = NULL;
+                    gateway_chassis =
+                        gateway_chassis_get_ordered(pb, chassis_index);
+                    /* we don't need BFD for non-HA  chassisredirect */
+                    if (!gateway_chassis ||
+                        ovs_list_is_short(gateway_chassis)) {
+                        continue;
+                    }
+                    our_chassis_is_gw_for_dp = gateway_chassis_contains(
+                            gateway_chassis, our_chassis);
+                    struct gateway_chassis *gwc;
+                    LIST_FOR_EACH (gwc, node, gateway_chassis) {
+                        if (gwc->db->chassis) {
+                            sset_add(bfd_chassis, gwc->db->chassis->name);
+                        }
+                    }
+                    gateway_chassis_destroy(gateway_chassis);
+                    break;
+                }
+            }
+        }
+        if (our_chassis_is_gw_for_dp) {
+            for (size_t i = 0; i < dp->n_peer_dps; i++) {
+                const struct ldatapath *pdp = dp->peer_dps[i];
+                if (!pdp) {
+                    continue;
+                }
+                for (size_t j = 0; j < pdp->n_lports; j++) {
+                    const struct sbrec_port_binding *pb = pdp->lports[j];
+                    if (pb->chassis) {
+                        /* Gateway node has to enable bfd to all nodes hosting
+                         * connected network ports */
+                        const char *chassis_name = pb->chassis->name;
+                        if (chassis_name) {
+                            sset_add(bfd_chassis, chassis_name);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+void
+bfd_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int,
+        const struct sbrec_chassis *chassis_rec, struct hmap *local_datapaths,
+        const struct chassis_index *chassis_index)
+{
+
+    if (!chassis_rec) {
+        return;
+    }
+    struct sset bfd_chassis = SSET_INITIALIZER(&bfd_chassis);
+    bfd_calculate_chassis(chassis_rec, local_datapaths, chassis_index,
+                          &bfd_chassis);
+    /* Identify tunnels ports(connected to remote chassis id) to enable bfd */
+    struct sset tunnels = SSET_INITIALIZER(&tunnels);
+    struct sset bfd_ifaces = SSET_INITIALIZER(&bfd_ifaces);
+    for (size_t k = 0; k < br_int->n_ports; k++) {
+        const char *chassis_id = smap_get(&br_int->ports[k]->external_ids,
+                                          "ovn-chassis-id");
+        if (chassis_id) {
+            char *port_name = br_int->ports[k]->name;
+            sset_add(&tunnels, port_name);
+            if (sset_contains(&bfd_chassis, chassis_id)) {
+                sset_add(&bfd_ifaces, port_name);
+            }
+        }
+    }
+
+    /* Enable or disable bfd */
+    const struct ovsrec_interface *iface;
+    OVSREC_INTERFACE_FOR_EACH (iface, ctx->ovs_idl) {
+        if (sset_contains(&tunnels, iface->name)) {
+                interface_set_bfd(
+                        iface, sset_contains(&bfd_ifaces, iface->name));
+         }
+    }
+
+    sset_destroy(&tunnels);
+    sset_destroy(&bfd_ifaces);
+    sset_destroy(&bfd_chassis);
+}
diff --git a/ovn/controller/bfd.h b/ovn/controller/bfd.h
new file mode 100644
index 0000000..91d0d91
--- /dev/null
+++ b/ovn/controller/bfd.h
@@ -0,0 +1,34 @@
+/* Copyright (c) 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVN_BFD_H
+#define OVN_BFD_H 1
+
+struct chassis_index;
+struct controller_ctx;
+struct hmap;
+struct ovsdb_idl;
+struct ovsrec_bridge;
+struct sbrec_chassis;
+struct sset;
+
+void bfd_register_ovs_idl(struct ovsdb_idl *);
+void bfd_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int,
+             const struct sbrec_chassis *chassis_rec,
+             struct hmap *local_datapaths, const struct chassis_index *);
+void  bfd_calculate_active_tunnels(const struct ovsrec_bridge *br_int,
+                                   struct sset *active_tunnels);
+
+#endif
diff --git a/ovn/controller/binding.c b/ovn/controller/binding.c
index cc4563d..4744cf0 100644
--- a/ovn/controller/binding.c
+++ b/ovn/controller/binding.c
@@ -60,6 +60,8 @@ binding_register_ovs_idl(struct ovsdb_idl *ovs_idl)
     ovsdb_idl_add_table(ovs_idl, &ovsrec_table_interface);
     ovsdb_idl_add_column(ovs_idl, &ovsrec_interface_col_name);
     ovsdb_idl_add_column(ovs_idl, &ovsrec_interface_col_external_ids);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_interface_col_bfd);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_interface_col_bfd_status);
     ovsdb_idl_add_column(ovs_idl, &ovsrec_interface_col_status);
 
     ovsdb_idl_add_table(ovs_idl, &ovsrec_table_qos);
@@ -149,6 +151,12 @@ add_local_datapath__(const struct ldatapath_index *ldatapaths,
                 if (peer && peer->datapath) {
                     add_local_datapath__(ldatapaths, lports, peer->datapath,
                                          false, depth + 1, local_datapaths);
+                    ld->n_peer_dps++;
+                    ld->peer_dps = xrealloc(
+                            ld->peer_dps,
+                            ld->n_peer_dps * sizeof *ld->peer_dps);
+                    ld->peer_dps[ld->n_peer_dps - 1] = ldatapath_lookup_by_key(
+                        ldatapaths, peer->datapath->tunnel_key);
                 }
             }
         }
@@ -359,6 +367,8 @@ static void
 consider_local_datapath(struct controller_ctx *ctx,
                         const struct ldatapath_index *ldatapaths,
                         const struct lport_index *lports,
+                        const struct chassis_index *chassis_index,
+                        struct sset *active_tunnels,
                         const struct sbrec_chassis *chassis_rec,
                         const struct sbrec_port_binding *binding_rec,
                         struct hmap *qos_map,
@@ -368,6 +378,7 @@ consider_local_datapath(struct controller_ctx *ctx,
 {
     const struct ovsrec_interface *iface_rec
         = shash_find_data(lport_to_iface, binding_rec->logical_port);
+    struct ovs_list *gateway_chassis = NULL;
 
     bool our_chassis = false;
     if (iface_rec
@@ -396,16 +407,28 @@ consider_local_datapath(struct controller_ctx *ctx,
                                false, local_datapaths);
         }
     } else if (!strcmp(binding_rec->type, "chassisredirect")) {
-        if (gateway_chassis_in_pb_contains(binding_rec, chassis_rec)) {
+        gateway_chassis = gateway_chassis_get_ordered(binding_rec,
+                                                       chassis_index);
+        if (gateway_chassis &&
+            gateway_chassis_contains(gateway_chassis, chassis_rec)) {
+            struct gateway_chassis *gwc;
+            LIST_FOR_EACH (gwc, node, gateway_chassis) {
+                if (!gwc->db->chassis) {
+                    continue;
+                }
+                if (!strcmp(gwc->db->chassis->name, chassis_rec->name)) {
+                    /* sb_rec_port_binding->chassis should reflect master */
+                    our_chassis = true;
+                    break;
+                }
+                if (sset_contains(active_tunnels, gwc->db->chassis->name)) {
+                    break;
+                }
+            }
             add_local_datapath(ldatapaths, lports, binding_rec->datapath,
                                false, local_datapaths);
-            /* XXX this should only be set to true if our chassis
-             * (chassis_rec) is the master for this chassisredirect port
-             * but for now we'll bind it only when not bound, this is
-             * handled in subsequent patches */
-            our_chassis = !binding_rec->chassis ||
-                chassis_rec == binding_rec->chassis;
         }
+        gateway_chassis_destroy(gateway_chassis);
     } else if (!strcmp(binding_rec->type, "l3gateway")) {
         const char *chassis_id = smap_get(&binding_rec->options,
                                           "l3gateway-chassis");
@@ -475,8 +498,10 @@ void
 binding_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int,
             const struct sbrec_chassis *chassis_rec,
             const struct ldatapath_index *ldatapaths,
-            const struct lport_index *lports, struct hmap *local_datapaths,
-            struct sset *local_lports)
+            const struct lport_index *lports,
+            const struct chassis_index *chassis_index,
+            struct sset *active_tunnels,
+            struct hmap *local_datapaths, struct sset *local_lports)
 {
     if (!chassis_rec) {
         return;
@@ -497,8 +522,8 @@ binding_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int,
      * chassis and update the binding accordingly.  This includes both
      * directly connected logical ports and children of those ports. */
     SBREC_PORT_BINDING_FOR_EACH(binding_rec, ctx->ovnsb_idl) {
-        consider_local_datapath(ctx, ldatapaths, lports,
-                                chassis_rec, binding_rec,
+        consider_local_datapath(ctx, ldatapaths, lports, chassis_index,
+                                active_tunnels, chassis_rec, binding_rec,
                                 sset_is_empty(&egress_ifaces) ? NULL :
                                 &qos_map, local_datapaths, &lport_to_iface,
                                 local_lports);
diff --git a/ovn/controller/binding.h b/ovn/controller/binding.h
index 136b3a7..9195061 100644
--- a/ovn/controller/binding.h
+++ b/ovn/controller/binding.h
@@ -32,7 +32,8 @@ struct sset;
 void binding_register_ovs_idl(struct ovsdb_idl *);
 void binding_run(struct controller_ctx *, const struct ovsrec_bridge *br_int,
                  const struct sbrec_chassis *, const struct ldatapath_index *,
-                 const struct lport_index *, struct hmap *local_datapaths,
+                 const struct lport_index *, const struct chassis_index *,
+                 struct sset *active_tunnels, struct hmap *local_datapaths,
                  struct sset *all_lports);
 bool binding_cleanup(struct controller_ctx *, const struct sbrec_chassis *);
 
diff --git a/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c
index 9fceeca..9715371 100644
--- a/ovn/controller/ovn-controller.c
+++ b/ovn/controller/ovn-controller.c
@@ -23,6 +23,7 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "bfd.h"
 #include "binding.h"
 #include "chassis.h"
 #include "command-line.h"
@@ -502,6 +503,8 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
     ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_bridges);
     ovsdb_idl_add_table(ovs_idl, &ovsrec_table_interface);
     ovsdb_idl_add_column(ovs_idl, &ovsrec_interface_col_name);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_interface_col_bfd);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_interface_col_bfd_status);
     ovsdb_idl_add_column(ovs_idl, &ovsrec_interface_col_type);
     ovsdb_idl_add_column(ovs_idl, &ovsrec_interface_col_options);
     ovsdb_idl_add_column(ovs_idl, &ovsrec_interface_col_ofport);
@@ -523,6 +526,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
     chassis_register_ovs_idl(ovs_idl);
     encaps_register_ovs_idl(ovs_idl);
     binding_register_ovs_idl(ovs_idl);
+    bfd_register_ovs_idl(ovs_idl);
     physical_register_ovs_idl(ovs_idl);
 }
 
@@ -621,6 +625,7 @@ main(int argc, char *argv[])
          * l2gateway ports for which options:l2gateway-chassis designates the
          * local hypervisor, and localnet ports. */
         struct sset local_lports = SSET_INITIALIZER(&local_lports);
+        struct sset active_tunnels = SSET_INITIALIZER(&active_tunnels);
 
         const struct ovsrec_bridge *br_int = get_br_int(&ctx);
         const char *chassis_id = get_chassis_id(ctx.ovs_idl);
@@ -639,8 +644,10 @@ main(int argc, char *argv[])
         if (chassis_id) {
             chassis = chassis_run(&ctx, chassis_id, br_int);
             encaps_run(&ctx, br_int, chassis_id);
+            bfd_calculate_active_tunnels(br_int, &active_tunnels);
             binding_run(&ctx, br_int, chassis, &ldatapaths, &lports,
-                        &local_datapaths, &local_lports);
+                        &chassis_index, &active_tunnels, &local_datapaths,
+                        &local_lports);
         }
 
         if (br_int && chassis) {
@@ -664,6 +671,10 @@ main(int argc, char *argv[])
                               &local_datapaths, &group_table,
                               &addr_sets, &flow_table);
 
+                    if (chassis_id) {
+                        bfd_run(&ctx, br_int, chassis, &local_datapaths,
+                                &chassis_index);
+                    }
                     physical_run(&ctx, mff_ovn_geneve,
                                  br_int, chassis, &ct_zones, &lports,
                                  &flow_table, &local_datapaths, &local_lports,
@@ -717,9 +728,11 @@ main(int argc, char *argv[])
         chassis_index_destroy(&chassis_index);
 
         sset_destroy(&local_lports);
+        sset_destroy(&active_tunnels);
 
         struct local_datapath *cur_node, *next_node;
         HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node, &local_datapaths) {
+            free(cur_node->peer_dps);
             hmap_remove(&local_datapaths, &cur_node->hmap_node);
             free(cur_node);
         }
diff --git a/ovn/controller/ovn-controller.h b/ovn/controller/ovn-controller.h
index 4bc0467..35a9e21 100644
--- a/ovn/controller/ovn-controller.h
+++ b/ovn/controller/ovn-controller.h
@@ -23,6 +23,8 @@
 /* Linux supports a maximum of 64K zones, which seems like a fine default. */
 #define MAX_CT_ZONES 65535
 
+struct sset;
+
 struct controller_ctx {
     struct ovsdb_idl *ovnsb_idl;
     struct ovsdb_idl_txn *ovnsb_idl_txn;
@@ -66,6 +68,8 @@ struct local_datapath {
     /* True if this datapath contains an l3gateway port located on this
      * hypervisor. */
     bool has_local_l3gateway;
+    const struct ldatapath **peer_dps;
+    size_t n_peer_dps;
 };
 
 struct local_datapath *get_local_datapath(const struct hmap *,
diff --git a/tests/ovn.at b/tests/ovn.at
index 0cbbc27..e4dd090 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -7787,6 +7787,22 @@ echo "------ Port_Binding chassisredirect -------"
 ovn-sbctl find Port_Binding type=chassisredirect
 echo "-------------------------------------------"
 
+for chassis in gw1 gw2 hv1 hv2; do
+    as $chassis
+    echo "------ $chassis dump ----------"
+    ovs-ofctl show br-int
+    ovs-ofctl dump-flows br-int
+    echo ""
+    echo "BFD (from $chassis):"
+    # dump BFD config and status to the other chassis
+    for chassis2 in gw1 gw2 hv1 hv2; do
+        if [[ "$chassis" != "$chassis2" ]]; then
+            echo " -> $chassis2:"
+            echo "   $(ovs-vsctl --bare --columns bfd,bfd_status find Interface name=ovn-$chassis2-0)"
+        fi
+    done
+    echo "--------------------------"
+done
 
 hv1_gw1_ofport=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw1-0)
 hv1_gw2_ofport=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw2-0)
@@ -7828,6 +7844,52 @@ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=32 | grep active_backup | gre
 ])
 
 
+# check BFD enablement on tunnel ports from gw1 #########
+as gw1
+for chassis in gw2 hv1 hv2; do
+    echo "checking gw1 -> $chassis"
+    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
+             [[enable=true
+]])
+done
+
+
+# check BFD enablement on tunnel ports from gw2 ##########
+as gw2
+for chassis in gw1 hv1 hv2; do
+    echo "checking gw2 -> $chassis"
+    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
+             [[enable=true
+]])
+done
+
+# check BFD enablement on tunnel ports from hv1 ###########
+as hv1
+for chassis in gw1 gw2; do
+    echo "checking hv1 -> $chassis"
+    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
+             [[enable=true
+]])
+done
+# make sure BFD is not enabled to hv2, we don't need it
+AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv2-0],[0],
+         [[enable=false
+]])
+
+
+# check BFD enablement on tunnel ports from hv2 ##########
+as hv2
+for chassis in gw1 gw2; do
+    echo "checking hv2 -> $chassis"
+    AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-$chassis-0],[0],
+             [[enable=true
+]])
+done
+# make sure BFD is not enabled to hv1, we don't need it
+AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-0],[0],
+         [[enable=false
+]])
+
 OVN_CLEANUP([gw1],[gw2],[hv1],[hv2])
 
 AT_CLEANUP
