diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index dcacbb1ff..8d0c6b9fa 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -96,6 +96,8 @@ struct collector_set_ids;
     OVNACT(LOOKUP_ND_IP,      ovnact_lookup_mac_bind_ip) \
     OVNACT(PUT_DHCPV4_OPTS,   ovnact_put_opts)        \
     OVNACT(PUT_DHCPV6_OPTS,   ovnact_put_opts)        \
+    OVNACT(DHCPV4_RELAY_REQ_CHK,  ovnact_dhcp_relay)      \
+    OVNACT(DHCPV4_RELAY_RESP_CHK, ovnact_dhcp_relay)      \
     OVNACT(SET_QUEUE,         ovnact_set_queue)       \
     OVNACT(DNS_LOOKUP,        ovnact_result)          \
     OVNACT(LOG,               ovnact_log)             \
@@ -396,6 +398,15 @@ struct ovnact_put_opts {
     size_t n_options;
 };
 
+/* OVNACT_DHCP_RELAY. */
+struct ovnact_dhcp_relay {
+    struct ovnact ovnact;
+    int family;
+    struct expr_field dst;      /* 1-bit destination field. */
+    ovs_be32 relay_ipv4;
+    ovs_be32 server_ipv4;
+};
+
 /* Valid arguments to SET_QUEUE action.
  *
  * QDISC_MIN_QUEUE_ID is the default queue, so user-defined queues should
@@ -772,6 +783,22 @@ enum action_opcode {
 
     /* multicast group split buffer action. */
     ACTION_OPCODE_MG_SPLIT_BUF,
+
+    /* "dhcp_relay_req_chk(relay_ip, server_ip)".
+     *
+     * Arguments follow the action_header, in this format:
+     *   - The 32-bit DHCP relay IP.
+     *   - The 32-bit DHCP server IP.
+     */
+    ACTION_OPCODE_DHCP_RELAY_REQ_CHK,
+
+    /* "dhcp_relay_resp_chk(relay_ip, server_ip)".
+     *
+     * Arguments follow the action_header, in this format:
+     *   - The 32-bit DHCP relay IP.
+     *   - The 32-bit DHCP server IP.
+     */
+    ACTION_OPCODE_DHCP_RELAY_RESP_CHK,
 };
 
 /* Header. */
diff --git a/lib/actions.c b/lib/actions.c
index 71615fc53..d4f4ec2d0 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -2706,6 +2706,149 @@ ovnact_controller_event_free(struct ovnact_controller_event *event)
     free_gen_options(event->options, event->n_options);
 }
 
+static void
+format_DHCPV4_RELAY_REQ_CHK(const struct ovnact_dhcp_relay *dhcp_relay,
+                struct ds *s)
+{
+    expr_field_format(&dhcp_relay->dst, s);
+    ds_put_format(s, " = dhcp_relay_req_chk("IP_FMT", "IP_FMT");",
+                  IP_ARGS(dhcp_relay->relay_ipv4),
+                  IP_ARGS(dhcp_relay->server_ipv4));
+}
+
+static void
+parse_dhcp_relay_req_chk(struct action_context *ctx,
+               const struct expr_field *dst,
+               struct ovnact_dhcp_relay *dhcp_relay)
+{
+    /* Skip dhcp_relay_req_chk( */
+    lexer_force_match(ctx->lexer, LEX_T_LPAREN);
+
+    /* Validate that the destination is a 1-bit, modifiable field. */
+    char *error = expr_type_check(dst, 1, true, ctx->scope);
+    if (error) {
+        lexer_error(ctx->lexer, "%s", error);
+        free(error);
+        return;
+    }
+    dhcp_relay->dst = *dst;
+
+    /* Parse relay ip and server ip. */
+    if (ctx->lexer->token.format == LEX_F_IPV4) {
+        dhcp_relay->family = AF_INET;
+        dhcp_relay->relay_ipv4 = ctx->lexer->token.value.ipv4;
+        lexer_get(ctx->lexer);
+        lexer_match(ctx->lexer, LEX_T_COMMA);
+        if (ctx->lexer->token.format == LEX_F_IPV4) {
+            dhcp_relay->family = AF_INET;
+            dhcp_relay->server_ipv4 = ctx->lexer->token.value.ipv4;
+            lexer_get(ctx->lexer);
+        } else {
+            lexer_syntax_error(ctx->lexer, "expecting IPv4 dhcp server ip");
+            return;
+        }
+    } else {
+          lexer_syntax_error(ctx->lexer, "expecting IPv4 dhcp relay "
+                          "and server ips");
+          return;
+    }
+    lexer_force_match(ctx->lexer, LEX_T_RPAREN);
+}
+
+static void
+encode_DHCPV4_RELAY_REQ_CHK(const struct ovnact_dhcp_relay *dhcp_relay,
+                    const struct ovnact_encode_params *ep,
+                    struct ofpbuf *ofpacts)
+{
+    struct mf_subfield dst = expr_resolve_field(&dhcp_relay->dst);
+    size_t oc_offset = encode_start_controller_op(
+                                            ACTION_OPCODE_DHCP_RELAY_REQ_CHK,
+                                            true, ep->ctrl_meter_id,
+                                            ofpacts);
+    nx_put_header(ofpacts, dst.field->id, OFP15_VERSION, false);
+    ovs_be32 ofs = htonl(dst.ofs);
+    ofpbuf_put(ofpacts, &ofs, sizeof ofs);
+    ofpbuf_put(ofpacts, &dhcp_relay->relay_ipv4,
+            sizeof(dhcp_relay->relay_ipv4));
+    ofpbuf_put(ofpacts, &dhcp_relay->server_ipv4,
+            sizeof(dhcp_relay->server_ipv4));
+    encode_finish_controller_op(oc_offset, ofpacts);
+}
+
+static void
+format_DHCPV4_RELAY_RESP_CHK(const struct ovnact_dhcp_relay *dhcp_relay,
+                    struct ds *s)
+{
+    expr_field_format(&dhcp_relay->dst, s);
+    ds_put_format(s, " = dhcp_relay_resp_chk("IP_FMT", "IP_FMT");",
+                  IP_ARGS(dhcp_relay->relay_ipv4),
+                  IP_ARGS(dhcp_relay->server_ipv4));
+}
+
+static void
+parse_dhcp_relay_resp_chk(struct action_context *ctx,
+               const struct expr_field *dst,
+               struct ovnact_dhcp_relay *dhcp_relay)
+{
+    /* Skip dhcp_relay_resp_chk( */
+    lexer_force_match(ctx->lexer, LEX_T_LPAREN);
+
+    /* Validate that the destination is a 1-bit, modifiable field. */
+    char *error = expr_type_check(dst, 1, true, ctx->scope);
+    if (error) {
+        lexer_error(ctx->lexer, "%s", error);
+        free(error);
+        return;
+    }
+    dhcp_relay->dst = *dst;
+
+    /* Parse relay ip and server ip. */
+    if (ctx->lexer->token.format == LEX_F_IPV4) {
+        dhcp_relay->family = AF_INET;
+        dhcp_relay->relay_ipv4 = ctx->lexer->token.value.ipv4;
+        lexer_get(ctx->lexer);
+        lexer_match(ctx->lexer, LEX_T_COMMA);
+        if (ctx->lexer->token.format == LEX_F_IPV4) {
+            dhcp_relay->family = AF_INET;
+            dhcp_relay->server_ipv4 = ctx->lexer->token.value.ipv4;
+            lexer_get(ctx->lexer);
+        } else {
+            lexer_syntax_error(ctx->lexer, "expecting IPv4 dhcp server ip");
+            return;
+        }
+    } else {
+          lexer_syntax_error(ctx->lexer, "expecting IPv4 dhcp relay and "
+                          "server ips");
+          return;
+    }
+    lexer_force_match(ctx->lexer, LEX_T_RPAREN);
+}
+
+static void
+encode_DHCPV4_RELAY_RESP_CHK(const struct ovnact_dhcp_relay *dhcp_relay,
+                    const struct ovnact_encode_params *ep,
+                    struct ofpbuf *ofpacts)
+{
+    struct mf_subfield dst = expr_resolve_field(&dhcp_relay->dst);
+    size_t oc_offset = encode_start_controller_op(
+                                ACTION_OPCODE_DHCP_RELAY_RESP_CHK,
+                                true, ep->ctrl_meter_id,
+                                ofpacts);
+    nx_put_header(ofpacts, dst.field->id, OFP15_VERSION, false);
+    ovs_be32 ofs = htonl(dst.ofs);
+    ofpbuf_put(ofpacts, &ofs, sizeof ofs);
+    ofpbuf_put(ofpacts, &dhcp_relay->relay_ipv4,
+                  sizeof(dhcp_relay->relay_ipv4));
+    ofpbuf_put(ofpacts, &dhcp_relay->server_ipv4,
+                  sizeof(dhcp_relay->server_ipv4));
+    encode_finish_controller_op(oc_offset, ofpacts);
+}
+
+static void ovnact_dhcp_relay_free(
+          struct ovnact_dhcp_relay *dhcp_relay OVS_UNUSED)
+{
+}
+
 static void
 parse_put_opts(struct action_context *ctx, const struct expr_field *dst,
                struct ovnact_put_opts *po, const struct hmap *gen_opts,
@@ -5406,6 +5549,12 @@ parse_set_action(struct action_context *ctx)
                    lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
             parse_chk_lb_aff(ctx, &lhs,
                     ovnact_put_CHK_LB_AFF(ctx->ovnacts));
+        } else if (lexer_match_id(ctx->lexer, "dhcp_relay_req_chk")) {
+            parse_dhcp_relay_req_chk(ctx, &lhs,
+                    ovnact_put_DHCPV4_RELAY_REQ_CHK(ctx->ovnacts));
+        } else if (lexer_match_id(ctx->lexer, "dhcp_relay_resp_chk")) {
+            parse_dhcp_relay_resp_chk(ctx, &lhs,
+                    ovnact_put_DHCPV4_RELAY_RESP_CHK(ctx->ovnacts));
         } else {
             parse_assignment_action(ctx, false, &lhs);
         }
diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
index e0f1c3ec9..dd7016fc3 100644
--- a/utilities/ovn-trace.c
+++ b/utilities/ovn-trace.c
@@ -2329,6 +2329,63 @@ execute_put_dhcp_opts(const struct ovnact_put_opts *pdo,
     execute_put_opts(pdo, name, uflow, super);
 }
 
+static void
+execute_dhcpv4_relay_req_chk(const struct ovnact_dhcp_relay *dr,
+                             struct flow *uflow, struct ovs_list *super)
+{
+    ovntrace_node_append(
+        super, OVNTRACE_NODE_ERROR,
+        "/* We assume that this packet is DHCPDISCOVER or DHCPREQUEST. */");
+
+    struct ds s = DS_EMPTY_INITIALIZER;
+    struct mf_subfield dst = expr_resolve_field(&dr->dst);
+    if (!mf_is_register(dst.field->id)) {
+        /* Format assignment. */
+        ds_clear(&s);
+        expr_field_format(&dr->dst, &s);
+        ovntrace_node_append(super, OVNTRACE_NODE_MODIFY,
+                             "%s = 1", ds_cstr(&s));
+    }
+    ds_destroy(&s);
+
+    struct mf_subfield sf = expr_resolve_field(&dr->dst);
+    union mf_subvalue sv = { .u8_val = 1 };
+    mf_write_subfield_flow(&sf, &sv, uflow);
+}
+
+static void
+execute_dhcpv4_relay_resp_chk(const struct ovnact_dhcp_relay *dr,
+                              struct flow *uflow, struct ovs_list *super)
+{
+    ovntrace_node_append(
+        super, OVNTRACE_NODE_ERROR,
+        "/* We assume that this packet is DHCPOFFER or DHCPACK and "
+            "DHCP broadcast flag is set. Dest IP is set to broadcast. "
+            "Dest MAC is set to broadcast but in real network this is unicast "
+            "which is extracted from DHCP header. */");
+
+    /* Assume DHCP broadcast flag is set */
+    uflow->nw_dst = htonl(0xFFFFFFFF);
+    /* Dest MAC is set to broadcast but in real network this is unicast */
+    struct eth_addr bcast_mac = { .ea = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
+    uflow->dl_dst = bcast_mac;
+
+    struct ds s = DS_EMPTY_INITIALIZER;
+    struct mf_subfield dst = expr_resolve_field(&dr->dst);
+    if (!mf_is_register(dst.field->id)) {
+        /* Format assignment. */
+        ds_clear(&s);
+        expr_field_format(&dr->dst, &s);
+        ovntrace_node_append(super, OVNTRACE_NODE_MODIFY,
+                             "%s = 1", ds_cstr(&s));
+    }
+    ds_destroy(&s);
+
+    struct mf_subfield sf = expr_resolve_field(&dr->dst);
+    union mf_subvalue sv = { .u8_val = 1 };
+    mf_write_subfield_flow(&sf, &sv, uflow);
+}
+
 static void
 execute_put_nd_ra_opts(const struct ovnact_put_opts *pdo,
                        const char *name, struct flow *uflow,
@@ -3216,6 +3273,16 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
                                   "put_dhcpv6_opts", uflow, super);
             break;
 
+        case OVNACT_DHCPV4_RELAY_REQ_CHK:
+            execute_dhcpv4_relay_req_chk(ovnact_get_DHCPV4_RELAY_REQ_CHK(a),
+                                    uflow, super);
+            break;
+
+        case OVNACT_DHCPV4_RELAY_RESP_CHK:
+            execute_dhcpv4_relay_resp_chk(ovnact_get_DHCPV4_RELAY_RESP_CHK(a),
+                                    uflow, super);
+            break;
+
         case OVNACT_PUT_ND_RA_OPTS:
             execute_put_nd_ra_opts(ovnact_get_PUT_DHCPV6_OPTS(a),
                                    "put_nd_ra_opts", uflow, super);
