diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index f697dff39..ab2f3856c 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)             \
@@ -389,6 +391,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
@@ -765,6 +776,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 361d55009..6cd60366a 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -1869,6 +1869,8 @@ is_paused_nested_action(enum action_opcode opcode)
     case ACTION_OPCODE_BFD_MSG:
     case ACTION_OPCODE_ACTIVATION_STRATEGY_RARP:
     case ACTION_OPCODE_MG_SPLIT_BUF:
+    case ACTION_OPCODE_DHCP_RELAY_REQ_CHK:
+    case ACTION_OPCODE_DHCP_RELAY_RESP_CHK:
     default:
         return false;
     }
@@ -2610,6 +2612,114 @@ ovnact_controller_event_free(struct ovnact_controller_event *event)
     free_gen_options(event->options, event->n_options);
 }
 
+static void
+format_dhcpv4_relay_chk(const char *name,
+                        const struct ovnact_dhcp_relay *dhcp_relay,
+                        struct ds *s)
+{
+    expr_field_format(&dhcp_relay->dst, s);
+    ds_put_format(s, " = %s("IP_FMT", "IP_FMT");",
+                  name,
+                  IP_ARGS(dhcp_relay->relay_ipv4),
+                  IP_ARGS(dhcp_relay->server_ipv4));
+}
+
+static void
+parse_dhcp_relay_chk(struct action_context *ctx,
+                     const struct expr_field *dst,
+                     struct ovnact_dhcp_relay *dhcp_relay)
+{
+    /* Skip dhcp_relay_req_chk/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_chk(const struct ovnact_dhcp_relay *dhcp_relay,
+                        const struct ovnact_encode_params *ep,
+                        struct ofpbuf *ofpacts,
+                        enum action_opcode dhcp_relay_opcode)
+{
+    struct mf_subfield dst = expr_resolve_field(&dhcp_relay->dst);
+    size_t oc_offset = encode_start_controller_op(dhcp_relay_opcode, 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_REQ_CHK(const struct ovnact_dhcp_relay *dhcp_relay,
+                            struct ds *s)
+{
+    format_dhcpv4_relay_chk("dhcp_relay_req_chk",dhcp_relay, s);
+}
+
+static void
+encode_DHCPV4_RELAY_REQ_CHK(const struct ovnact_dhcp_relay *dhcp_relay,
+                            const struct ovnact_encode_params *ep,
+                            struct ofpbuf *ofpacts)
+{
+    encode_dhcpv4_relay_chk(dhcp_relay, ep, ofpacts,
+                            ACTION_OPCODE_DHCP_RELAY_REQ_CHK);
+}
+
+static void
+format_DHCPV4_RELAY_RESP_CHK(const struct ovnact_dhcp_relay *dhcp_relay,
+                             struct ds *s)
+{
+    format_dhcpv4_relay_chk("dhcp_relay_resp_chk",dhcp_relay, s);
+}
+
+static void
+encode_DHCPV4_RELAY_RESP_CHK(const struct ovnact_dhcp_relay *dhcp_relay,
+                             const struct ovnact_encode_params *ep,
+                             struct ofpbuf *ofpacts)
+{
+    encode_dhcpv4_relay_chk(dhcp_relay, ep, ofpacts,
+                            ACTION_OPCODE_DHCP_RELAY_RESP_CHK);
+}
+
+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,
@@ -5312,6 +5422,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_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_chk(ctx, &lhs,
+                ovnact_put_DHCPV4_RELAY_RESP_CHK(ctx->ovnacts));
         } else {
             parse_assignment_action(ctx, false, &lhs);
         }
diff --git a/ovn-sb.xml b/ovn-sb.xml
index 4c26c6714..81ed824d0 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -2801,6 +2801,68 @@ tcp.flags = RST;
             use statistics of MAC cache.
           </p>
         </dd>
+
+        <dt><code><var>R</var> = dhcp_relay_req_chk(<var>relay-ip</var>,
+                                      <var>server-ip</var>);</code></dt>
+        <dd>
+          <p>
+            <b>Parameters</b>: Logical Router Port IP <var>relay-ip</var>, DHCP
+            Server IP <var>server-ip</var>.
+          </p>
+
+          <p>
+            <b>Result</b>: stored to a 1-bit subfield <var>R</var>.
+          </p>
+
+          <p>
+            This action executes on the source node on which the DHCP request
+            (DHCPDISCOVER or DHCPREQUEST) originated.
+          </p>
+
+          <p>
+            When this action applied successfully on the DHCP request packet,
+            it updates GIADDR in the DHCP packet with <var>relay-ip</var> and
+            stores 1 in R.
+          </p>
+
+          <p>
+            When this action failed to apply on the packet, it leaves the
+            packet unchanged and stores 0 in R.
+          </p>
+        </dd>
+
+        <dt><code><var>R</var> = dhcp_relay_resp_chk(<var>relay-ip</var>,
+                                      <var>server-ip</var>);</code></dt>
+        <dd>
+          <p>
+            <b>Parameters</b>: Logical Router Port IP <var>relay-ip</var>, DHCP
+            Server IP <var>server-ip</var>.
+          </p>
+
+          <p>
+            <b>Result</b>: stored to a 1-bit subfield <var>R</var>.
+          </p>
+
+          <p>
+            This action executes on the first node (Redirect Chassis node)
+            which processes the DHCP response(DHCPOFFER, DHCPACK) from the DHCP
+            server.
+          </p>
+
+          <p>
+            When this action applied successfully on the DHCP response packet,
+            it updates the destination MAC and destination IP in the packet and
+            stores 1 in R.
+            <var>relay-ip</var> and <var>server-ip</var> are used to validate
+            GIADDR and SERVER-ID in the DHCP response packet.
+          </p>
+
+          <p>
+            When this action failed to apply on the packet, it leaves the
+            packet unchanged and stores 0 in R.
+          </p>
+        </dd>
+
       </dl>
     </column>
 
diff --git a/tests/ovn.at b/tests/ovn.at
index dc6aafd53..1ad4159cf 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -1653,6 +1653,40 @@ reg1[[0]] = put_dhcp_opts(offerip=1.2.3.4, domain_name=1.2.3.4);
 reg1[[0]] = put_dhcp_opts(offerip=1.2.3.4, domain_search_list=1.2.3.4);
     DHCPv4 option domain_search_list requires string value.
 
+#dhcp_relay_req_chk
+reg9[[7]] = dhcp_relay_req_chk(192.168.1.1, 172.16.1.1);
+    encodes as controller(userdata=00.00.00.1c.00.00.00.00.80.01.08.08.00.00.00.07.c0.a8.01.01.ac.10.01.01,pause)
+
+reg9[[7]] = dhcp_relay_req_chk(192.168.1.1,172.16.1.1);
+    formats as reg9[[7]] = dhcp_relay_req_chk(192.168.1.1, 172.16.1.1);
+    encodes as controller(userdata=00.00.00.1c.00.00.00.00.80.01.08.08.00.00.00.07.c0.a8.01.01.ac.10.01.01,pause)
+
+reg9[[7..8]] = dhcp_relay_req_chk(192.168.1.1, 172.16.1.1);
+    Cannot use 2-bit field reg9[[7..8]] where 1-bit field is required.
+
+reg9[[7]] = dhcp_relay_req_chk("192.168.1.1", "172.16.1.1");
+    Syntax error at `"192.168.1.1"' expecting IPv4 dhcp relay and server ips.
+
+reg9[[7]] = dhcp_relay_req_chk(192.168.1, 172.16.1.1);
+    Invalid numeric constant.
+
+#dhcp_relay_resp_chk
+reg9[[8]] = dhcp_relay_resp_chk(192.168.1.1, 172.16.1.1);
+    encodes as controller(userdata=00.00.00.1d.00.00.00.00.80.01.08.08.00.00.00.08.c0.a8.01.01.ac.10.01.01,pause)
+
+reg9[[8]] = dhcp_relay_resp_chk(192.168.1.1,172.16.1.1);
+    formats as reg9[[8]] = dhcp_relay_resp_chk(192.168.1.1, 172.16.1.1);
+    encodes as controller(userdata=00.00.00.1d.00.00.00.00.80.01.08.08.00.00.00.08.c0.a8.01.01.ac.10.01.01,pause)
+
+reg9[[7..8]] = dhcp_relay_resp_chk(192.168.1.1, 172.16.1.1);
+    Cannot use 2-bit field reg9[[7..8]] where 1-bit field is required.
+
+reg9[[8]] = dhcp_relay_resp_chk("192.168.1.1", "172.16.1.1");
+    Syntax error at `"192.168.1.1"' expecting IPv4 dhcp relay and server ips.
+
+reg9[[8]] = dhcp_relay_resp_chk(192.168.1, 172.16.1.1);
+    Invalid numeric constant.
+
 # nd_ns
 nd_ns { nd.target = xxreg0; output; };
     encodes as controller(userdata=00.00.00.09.00.00.00.00.00.1c.00.18.00.80.00.00.00.00.00.00.00.01.de.10.80.00.3e.10.00.00.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.OFTABLE_SAVE_INPORT_HEX.00.00.00,pause)
diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
index ee086a7ae..0103253e1 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,
@@ -3215,6 +3272,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);
