diff --git a/include/ovn/expr.h b/include/ovn/expr.h
index 371ba20..d3749fa 100644
--- a/include/ovn/expr.h
+++ b/include/ovn/expr.h
@@ -292,6 +292,15 @@ enum expr_type {
     EXPR_T_AND,                 /* Logical AND of 2 or more subexpressions. */
     EXPR_T_OR,                  /* Logical OR of 2 or more subexpressions. */
     EXPR_T_BOOLEAN,             /* True or false constant. */
+    EXPR_T_CONDITION,           /* Conditional to be evaluated in the
+                                 * controller during expr_simplify(),
+                                 * prior to constructing OpenFlow matches. */
+};
+
+/* Expression condition type. */
+enum expr_cond_type {
+    EXPR_COND_CHASSIS_RESIDENT, /* Check if specified logical port name is
+                                 * resident on the controller chassis. */
 };
 
 /* Relational operator. */
@@ -349,6 +358,14 @@ struct expr {
 
         /* EXPR_T_BOOLEAN. */
         bool boolean;
+
+        /* EXPR_T_CONDITION. */
+        struct {
+            enum expr_cond_type type;
+            bool not;
+            /* XXX Should arguments for conditions be generic? */
+            char *string;
+        } cond;
     };
 };
 
@@ -375,7 +392,10 @@ void expr_destroy(struct expr *);
 
 struct expr *expr_annotate(struct expr *, const struct shash *symtab,
                            char **errorp);
-struct expr *expr_simplify(struct expr *);
+struct expr *expr_simplify(struct expr *,
+                           bool (*is_chassis_resident)(const void *c_aux,
+                                                       const char *port_name),
+                           const void *c_aux);
 struct expr *expr_normalize(struct expr *);
 
 bool expr_honors_invariants(const struct expr *);
diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
index 4e67365..a6aea48 100644
--- a/ovn/controller/lflow.c
+++ b/ovn/controller/lflow.c
@@ -64,6 +64,11 @@ struct lookup_port_aux {
     const struct sbrec_datapath_binding *dp;
 };
 
+struct condition_aux {
+    const struct lport_index *lports;
+    const char *chassis_id;
+};
+
 static void consider_logical_flow(const struct lport_index *lports,
                                   const struct mcgroup_index *mcgroups,
                                   const struct sbrec_logical_flow *lflow,
@@ -71,6 +76,7 @@ static void consider_logical_flow(const struct lport_index *lports,
                                   const struct hmap *patched_datapaths,
                                   struct group_table *group_table,
                                   const struct simap *ct_zones,
+                                  const char *chassis_id,
                                   struct hmap *dhcp_opts_p,
                                   struct hmap *dhcpv6_opts_p,
                                   uint32_t *conj_id_ofs_p,
@@ -100,6 +106,20 @@ lookup_port_cb(const void *aux_, const char *port_name, unsigned int *portp)
 }
 
 static bool
+is_chassis_resident_cb(const void *c_aux_, const char *port_name)
+{
+    const struct condition_aux *c_aux = c_aux_;
+
+    const struct sbrec_port_binding *pb
+        = lport_lookup_by_name(c_aux->lports, port_name);
+    if (pb && pb->chassis && !strcmp(pb->chassis->name, c_aux->chassis_id)) {
+        return true;
+    }
+
+    return false;
+}
+
+static bool
 is_switch(const struct sbrec_datapath_binding *ldp)
 {
     return smap_get(&ldp->external_ids, "logical-switch") != NULL;
@@ -114,6 +134,7 @@ add_logical_flows(struct controller_ctx *ctx, const struct lport_index *lports,
                   const struct hmap *patched_datapaths,
                   struct group_table *group_table,
                   const struct simap *ct_zones,
+                  const char *chassis_id,
                   struct hmap *flow_table,
                   struct shash *expr_address_sets_p)
 {
@@ -138,6 +159,7 @@ add_logical_flows(struct controller_ctx *ctx, const struct lport_index *lports,
     SBREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->ovnsb_idl) {
         consider_logical_flow(lports, mcgroups, lflow, local_datapaths,
                               patched_datapaths, group_table, ct_zones,
+                              chassis_id,
                               &dhcp_opts, &dhcpv6_opts, &conj_id_ofs,
                               flow_table, expr_address_sets_p);
     }
@@ -154,6 +176,7 @@ consider_logical_flow(const struct lport_index *lports,
                       const struct hmap *patched_datapaths,
                       struct group_table *group_table,
                       const struct simap *ct_zones,
+                      const char *chassis_id,
                       struct hmap *dhcp_opts_p,
                       struct hmap *dhcpv6_opts_p,
                       uint32_t *conj_id_ofs_p,
@@ -267,6 +290,11 @@ consider_logical_flow(const struct lport_index *lports,
     struct hmap matches;
     struct expr *expr;
 
+    struct condition_aux cond_aux = {
+        .lports = lports,
+        .chassis_id = chassis_id
+    };
+
     expr = expr_parse_string(lflow->match, &symtab,
                              expr_address_sets_p, &error);
     if (!error) {
@@ -286,7 +314,7 @@ consider_logical_flow(const struct lport_index *lports,
         return;
     }
 
-    expr = expr_simplify(expr);
+    expr = expr_simplify(expr, is_chassis_resident_cb, &cond_aux);
     expr = expr_normalize(expr);
     uint32_t n_conjs = expr_to_matches(expr, lookup_port_cb, &aux,
                                        &matches);
@@ -413,14 +441,15 @@ lflow_run(struct controller_ctx *ctx, const struct lport_index *lports,
           const struct hmap *patched_datapaths,
           struct group_table *group_table,
           const struct simap *ct_zones,
+          const char *chassis_id,
           struct hmap *flow_table)
 {
     struct shash expr_address_sets = SHASH_INITIALIZER(&expr_address_sets);
 
     update_address_sets(ctx, &expr_address_sets);
     add_logical_flows(ctx, lports, mcgroups, local_datapaths,
-                      patched_datapaths, group_table, ct_zones, flow_table,
-                      &expr_address_sets);
+                      patched_datapaths, group_table, ct_zones,
+                      chassis_id, flow_table, &expr_address_sets);
     add_neighbor_flows(ctx, lports, flow_table);
 
     expr_macros_destroy(&expr_address_sets);
diff --git a/ovn/controller/lflow.h b/ovn/controller/lflow.h
index d3ca5d1..80924ea 100644
--- a/ovn/controller/lflow.h
+++ b/ovn/controller/lflow.h
@@ -67,6 +67,7 @@ void lflow_run(struct controller_ctx *, const struct lport_index *,
                const struct hmap *patched_datapaths,
                struct group_table *group_table,
                const struct simap *ct_zones,
+               const char *chassis_id,
                struct hmap *flow_table);
 void lflow_destroy(void);
 
diff --git a/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c
index 0fb4187..3305863 100644
--- a/ovn/controller/ovn-controller.c
+++ b/ovn/controller/ovn-controller.c
@@ -541,7 +541,7 @@ main(int argc, char *argv[])
                 struct hmap flow_table = HMAP_INITIALIZER(&flow_table);
                 lflow_run(&ctx, &lports, &mcgroups, &local_datapaths,
                           &patched_datapaths, &group_table, &ct_zones,
-                          &flow_table);
+                          chassis_id, &flow_table);
 
                 physical_run(&ctx, mff_ovn_geneve,
                              br_int, chassis_id,
diff --git a/ovn/lib/expr.c b/ovn/lib/expr.c
index 5399985..28fb9b0 100644
--- a/ovn/lib/expr.c
+++ b/ovn/lib/expr.c
@@ -233,6 +233,11 @@ expr_not(struct expr *expr)
     case EXPR_T_BOOLEAN:
         expr->boolean = !expr->boolean;
         break;
+
+    case EXPR_T_CONDITION:
+        expr->cond.not = !expr->cond.not;
+        break;
+
     default:
         OVS_NOT_REACHED();
     }
@@ -290,6 +295,9 @@ expr_fix(struct expr *expr)
     case EXPR_T_BOOLEAN:
         return expr;
 
+    case EXPR_T_CONDITION:
+        return expr;
+
     default:
         OVS_NOT_REACHED();
     }
@@ -394,6 +402,21 @@ expr_format_andor(const struct expr *e, const char *op, struct ds *s)
     }
 }
 
+static void
+expr_format_condition(const struct expr *e, struct ds *s)
+{
+    if (e->cond.not) {
+        ds_put_char(s, '!');
+    }
+    switch (e->cond.type) {
+    case EXPR_COND_CHASSIS_RESIDENT:
+        ds_put_format(s, "is_chassis_resident(");
+        json_string_escape(e->cond.string, s);
+        ds_put_char(s, ')');
+        break;
+    }
+}
+
 /* Appends a string form of 'e' to 's'.  The string form is acceptable for
  * parsing back into an equivalent expression. */
 void
@@ -415,6 +438,10 @@ expr_format(const struct expr *e, struct ds *s)
     case EXPR_T_BOOLEAN:
         ds_put_char(s, e->boolean ? '1' : '0');
         break;
+
+    case EXPR_T_CONDITION:
+        expr_format_condition(e, s);
+        break;
     }
 }
 
@@ -973,6 +1000,32 @@ expr_macros_destroy(struct shash *macros)
 }
 
 static struct expr *
+parse_chassis_resident(struct expr_context *ctx)
+{
+    if (!lexer_force_match(ctx->lexer, LEX_T_LPAREN)) {
+        return NULL;
+    }
+    if (ctx->lexer->token.type != LEX_T_STRING) {
+        lexer_syntax_error(ctx->lexer, "expecting string");
+        return NULL;
+    }
+
+    struct expr *e = xzalloc(sizeof *e);
+    e->type = EXPR_T_CONDITION;
+    e->cond.type = EXPR_COND_CHASSIS_RESIDENT;
+    e->cond.not = false;
+    e->cond.string = xstrdup(ctx->lexer->token.s);
+
+    lexer_get(ctx->lexer);
+    if (!lexer_force_match(ctx->lexer, LEX_T_RPAREN)) {
+        expr_destroy(e);
+        return NULL;
+    }
+
+    return e;
+}
+
+static struct expr *
 expr_parse_primary(struct expr_context *ctx, bool *atomic)
 {
     *atomic = false;
@@ -991,6 +1044,10 @@ expr_parse_primary(struct expr_context *ctx, bool *atomic)
         enum expr_relop r;
         struct expr_constant_set c;
 
+        if (lexer_match_id(ctx->lexer, "is_chassis_resident")) {
+            return parse_chassis_resident(ctx);
+        }
+
         if (!parse_field(ctx, &f)) {
             return NULL;
         }
@@ -1383,6 +1440,7 @@ expr_get_level(const struct expr *expr)
         return level;
 
     case EXPR_T_BOOLEAN:
+    case EXPR_T_CONDITION:
         return EXPR_L_BOOLEAN;
 
     default:
@@ -1465,6 +1523,14 @@ expr_clone_andor(struct expr *expr)
     return new;
 }
 
+static struct expr *
+expr_clone_condition(struct expr *expr)
+{
+    struct expr *new = xmemdup(expr, sizeof *expr);
+    new->cond.string = xstrdup(new->cond.string);
+    return new;
+}
+
 /* Returns a clone of 'expr'.  This is a "deep copy": neither the returned
  * expression nor any of its substructure will be shared with 'expr'. */
 struct expr *
@@ -1480,6 +1546,9 @@ expr_clone(struct expr *expr)
 
     case EXPR_T_BOOLEAN:
         return expr_create_boolean(expr->boolean);
+
+    case EXPR_T_CONDITION:
+        return expr_clone_condition(expr);
     }
     OVS_NOT_REACHED();
 }
@@ -1511,6 +1580,10 @@ expr_destroy(struct expr *expr)
 
     case EXPR_T_BOOLEAN:
         break;
+
+    case EXPR_T_CONDITION:
+        free(expr->cond.string);
+        break;
     }
     free(expr);
 }
@@ -1639,6 +1712,7 @@ expr_annotate__(struct expr *expr, const struct shash *symtab,
     }
 
     case EXPR_T_BOOLEAN:
+    case EXPR_T_CONDITION:
         *errorp = NULL;
         return expr;
 
@@ -1781,10 +1855,42 @@ expr_simplify_relational(struct expr *expr)
     return new ? new : expr_create_boolean(false);
 }
 
+/* Resolves condition and replaces the expression with a boolean. */
+static struct expr *
+expr_simplify_condition(struct expr *expr,
+                        bool (*is_chassis_resident)(const void *c_aux,
+                                                    const char *port_name),
+                        const void *c_aux)
+{
+    bool cond_initial_result;
+    switch (expr->cond.type) {
+    case EXPR_COND_CHASSIS_RESIDENT:
+        if (!is_chassis_resident || !c_aux) {
+            /* For trace and test cases where chassis is not considered. */
+            cond_initial_result = true;
+        } else {
+            cond_initial_result =
+                                 is_chassis_resident(c_aux, expr->cond.string);
+        }
+        break;
+
+    default:
+        OVS_NOT_REACHED();
+    }
+    if (expr->cond.not) {
+        cond_initial_result = !cond_initial_result;
+    }
+    expr_destroy(expr);
+    return expr_create_boolean(cond_initial_result);
+}
+
 /* Takes ownership of 'expr' and returns an equivalent expression whose
  * EXPR_T_CMP nodes use only tests for equality (EXPR_R_EQ). */
 struct expr *
-expr_simplify(struct expr *expr)
+expr_simplify(struct expr *expr,
+              bool (*is_chassis_resident)(const void *c_aux,
+                                          const char *port_name),
+              const void *c_aux)
 {
     struct expr *sub, *next;
 
@@ -1799,12 +1905,16 @@ expr_simplify(struct expr *expr)
     case EXPR_T_OR:
         LIST_FOR_EACH_SAFE (sub, next, node, &expr->andor) {
             ovs_list_remove(&sub->node);
-            expr_insert_andor(expr, next, expr_simplify(sub));
+            expr_insert_andor(expr, next,
+                              expr_simplify(sub, is_chassis_resident, c_aux));
         }
         return expr_fix(expr);
 
     case EXPR_T_BOOLEAN:
         return expr;
+
+    case EXPR_T_CONDITION:
+        return expr_simplify_condition(expr, is_chassis_resident, c_aux);
     }
     OVS_NOT_REACHED();
 }
@@ -1832,6 +1942,7 @@ expr_is_cmp(const struct expr *expr)
     }
 
     case EXPR_T_BOOLEAN:
+    case EXPR_T_CONDITION:
         return NULL;
 
     default:
@@ -1928,6 +2039,8 @@ crush_and_string(struct expr *expr, const struct expr_symbol *symbol)
             }
             free(new);
             break;
+        case EXPR_T_CONDITION:
+            OVS_NOT_REACHED();
         }
     }
 
@@ -2022,6 +2135,8 @@ crush_and_numeric(struct expr *expr, const struct expr_symbol *symbol)
             }
             expr_destroy(new);
             break;
+        case EXPR_T_CONDITION:
+            OVS_NOT_REACHED();
         }
     }
     if (ovs_list_is_empty(&expr->andor)) {
@@ -2234,6 +2349,10 @@ crush_cmps(struct expr *expr, const struct expr_symbol *symbol)
     case EXPR_T_BOOLEAN:
         return expr;
 
+    /* Should not hit expression type condition, since crush_cmps is only
+     * called during expr_normalize, after expr_simplify which resolves
+     * all conditions. */
+    case EXPR_T_CONDITION:
     default:
         OVS_NOT_REACHED();
     }
@@ -2441,8 +2560,13 @@ expr_normalize(struct expr *expr)
 
     case EXPR_T_BOOLEAN:
         return expr;
+
+    /* Should not hit expression type condition, since expr_normalize is
+     * only called after expr_simplify, which resolves all conditions. */
+    case EXPR_T_CONDITION:
+    default:
+        OVS_NOT_REACHED();
     }
-    OVS_NOT_REACHED();
 }
 
 /* Creates, initializes, and returns a new 'struct expr_match'.  If 'm' is
@@ -2609,6 +2733,8 @@ add_conjunction(const struct expr *and,
             break;
         case EXPR_T_AND:
         case EXPR_T_BOOLEAN:
+        case EXPR_T_CONDITION:
+        default:
             OVS_NOT_REACHED();
         }
     }
@@ -2740,6 +2866,12 @@ expr_to_matches(const struct expr *expr,
             /* No match. */
         }
         break;
+
+    /* Should not hit expression type condition, since expr_to_matches is
+     * only called after expr_simplify, which resolves all conditions. */
+    case EXPR_T_CONDITION:
+    default:
+        OVS_NOT_REACHED();
     }
     return n_conjs;
 }
@@ -2816,6 +2948,7 @@ expr_honors_invariants(const struct expr *expr)
         return true;
 
     case EXPR_T_BOOLEAN:
+    case EXPR_T_CONDITION:
         return true;
 
     default:
@@ -2864,6 +2997,9 @@ expr_is_normalized(const struct expr *expr)
     case EXPR_T_BOOLEAN:
         return true;
 
+    case EXPR_T_CONDITION:
+        return false;
+
     default:
         OVS_NOT_REACHED();
     }
@@ -2956,6 +3092,11 @@ expr_evaluate(const struct expr *e, const struct flow *uflow,
     case EXPR_T_BOOLEAN:
         return e->boolean;
 
+    case EXPR_T_CONDITION:
+        /* Assume tests calling expr_evaluate are not chassis specific, so
+         * is_chassis_resident evaluates as true. */
+        return (e->cond.not ? false : true);
+
     default:
         OVS_NOT_REACHED();
     }
@@ -3029,7 +3170,9 @@ expr_parse_microflow__(struct lexer *lexer,
     struct ds annotated = DS_EMPTY_INITIALIZER;
     expr_format(e, &annotated);
 
-    e = expr_simplify(e);
+    /* Assume tests calling expr_parse_microflow are not chassis specific, so
+     * is_chassis_resident need not be supplied and should return true. */
+    e = expr_simplify(e, NULL, NULL);
     e = expr_normalize(e);
 
     struct match m = MATCH_CATCHALL_INITIALIZER;
@@ -3065,6 +3208,9 @@ expr_parse_microflow__(struct lexer *lexer,
     }
         break;
 
+    /* Should not hit expression type condition, since
+     * expr_simplify was called above. */
+    case EXPR_T_CONDITION:
     default:
         OVS_NOT_REACHED();
     }
diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c
index 3d62cf8..ac6324c 100644
--- a/ovn/utilities/ovn-trace.c
+++ b/ovn/utilities/ovn-trace.c
@@ -628,7 +628,7 @@ read_flows(void)
             continue;
         }
         if (match) {
-            match = expr_simplify(match);
+            match = expr_simplify(match, NULL, NULL);
         }
 
         struct ovntrace_flow *flow = xzalloc(sizeof *flow);
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
index 2e82a6f..4db6649 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -246,7 +246,7 @@ test_parse_expr__(int steps)
         }
         if (!error) {
             if (steps > 1) {
-                expr = expr_simplify(expr);
+                expr = expr_simplify(expr, NULL, NULL);
             }
             if (steps > 2) {
                 expr = expr_normalize(expr);
@@ -837,7 +837,7 @@ test_tree_shape_exhaustively(struct expr *expr, struct shash *symtab,
                 exit(EXIT_FAILURE);
             }
         } else if (operation >= OP_SIMPLIFY) {
-            modified  = expr_simplify(expr_clone(expr));
+            modified  = expr_simplify(expr_clone(expr), NULL, NULL);
             ovs_assert(expr_honors_invariants(modified));
 
             if (operation >= OP_NORMALIZE) {
