diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index 98b29de9f..f70d08796 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -1909,6 +1909,514 @@ is_dhcp_flags_broadcast(ovs_be16 flags)
     return flags & htons(DHCP_BROADCAST_FLAG);
 }
 
+static const char *dhcp_msg_str[] = {
+[0] = "INVALID",
+[DHCP_MSG_DISCOVER] = "DISCOVER",
+[DHCP_MSG_OFFER] = "OFFER",
+[DHCP_MSG_REQUEST] = "REQUEST",
+[OVN_DHCP_MSG_DECLINE] = "DECLINE",
+[DHCP_MSG_ACK] = "ACK",
+[DHCP_MSG_NAK] = "NAK",
+[OVN_DHCP_MSG_RELEASE] = "RELEASE",
+[OVN_DHCP_MSG_INFORM] = "INFORM"
+};
+
+static bool
+dhcp_relay_is_msg_type_supported(uint8_t msg_type)
+{
+    return (msg_type >= DHCP_MSG_DISCOVER && msg_type <= OVN_DHCP_MSG_RELEASE);
+}
+
+static const char *dhcp_msg_str_get(uint8_t msg_type)
+{
+    if (!dhcp_relay_is_msg_type_supported(msg_type)) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "Unknown DHCP msg type: %u", msg_type);
+        return "UNKNOWN";
+    }
+    return dhcp_msg_str[msg_type];
+}
+
+static const struct dhcp_header *
+dhcp_get_hdr_from_pkt(struct dp_packet *pkt_in, const char **in_dhcp_pptr,
+                  const char *end)
+{
+    /* Validate the DHCP request packet.
+     * Format of the DHCP packet is
+     * -----------------------------------------------------------------------
+     *| UDP HEADER | DHCP HEADER | 4 Byte DHCP Cookie | DHCP OPTIONS(var len) |
+     * -----------------------------------------------------------------------
+     */
+
+    *in_dhcp_pptr = dp_packet_get_udp_payload(pkt_in);
+    if (*in_dhcp_pptr == NULL) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "DHCP: Invalid or incomplete DHCP packet received");
+        return NULL;
+    }
+
+    const struct dhcp_header *dhcp_hdr
+        = (const struct dhcp_header *) *in_dhcp_pptr;
+    (*in_dhcp_pptr) += sizeof *dhcp_hdr;
+    if (*in_dhcp_pptr > end) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "DHCP: Invalid or incomplete DHCP packet received, "
+                     "bad data length");
+        return NULL;
+    }
+
+    if (dhcp_hdr->htype != 0x1) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "DHCP: Packet is recieved with "
+                "unsupported hardware type");
+        return NULL;
+    }
+
+    if (dhcp_hdr->hlen != 0x6) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "DHCP: Packet is recieved with "
+                "unsupported hardware length");
+        return NULL;
+    }
+
+    /* DHCP options follow the DHCP header. The first 4 bytes of the DHCP
+     * options is the DHCP magic cookie followed by the actual DHCP options.
+     */
+    ovs_be32 magic_cookie = htonl(DHCP_MAGIC_COOKIE);
+    if ((*in_dhcp_pptr) + sizeof magic_cookie > end ||
+        get_unaligned_be32((const void *) (*in_dhcp_pptr)) != magic_cookie) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "DHCP: Magic cookie not present in the DHCP packet");
+        return NULL;
+    }
+
+    (*in_dhcp_pptr) += sizeof magic_cookie;
+
+    return dhcp_hdr;
+}
+
+static void
+dhcp_parse_options(const char **in_dhcp_pptr, const char *end,
+          const uint8_t **dhcp_msg_type_pptr, ovs_be32 *request_ip_ptr,
+          bool *ipxe_req_ptr, ovs_be32 *server_id_ptr,
+          ovs_be32 *netmask_ptr, ovs_be32 *router_ip_ptr)
+{
+    while ((*in_dhcp_pptr) < end) {
+        const struct dhcp_opt_header *in_dhcp_opt =
+            (const struct dhcp_opt_header *) *in_dhcp_pptr;
+        if (in_dhcp_opt->code == DHCP_OPT_END) {
+            break;
+        }
+        if (in_dhcp_opt->code == DHCP_OPT_PAD) {
+            (*in_dhcp_pptr) += 1;
+            continue;
+        }
+        (*in_dhcp_pptr) += sizeof *in_dhcp_opt;
+        if ((*in_dhcp_pptr) > end) {
+            break;
+        }
+        (*in_dhcp_pptr) += in_dhcp_opt->len;
+        if ((*in_dhcp_pptr) > end) {
+            break;
+        }
+
+        switch (in_dhcp_opt->code) {
+        case DHCP_OPT_MSG_TYPE:
+            if (dhcp_msg_type_pptr && in_dhcp_opt->len == 1) {
+                *dhcp_msg_type_pptr = DHCP_OPT_PAYLOAD(in_dhcp_opt);
+            }
+            break;
+        case DHCP_OPT_REQ_IP:
+            if (request_ip_ptr && in_dhcp_opt->len == 4) {
+                *request_ip_ptr = get_unaligned_be32(
+                                  DHCP_OPT_PAYLOAD(in_dhcp_opt));
+            }
+            break;
+        case OVN_DHCP_OPT_CODE_SERVER_ID:
+            if (server_id_ptr && in_dhcp_opt->len == 4) {
+                *server_id_ptr = get_unaligned_be32(
+                                  DHCP_OPT_PAYLOAD(in_dhcp_opt));
+            }
+            break;
+        case OVN_DHCP_OPT_CODE_NETMASK:
+            if (netmask_ptr && in_dhcp_opt->len == 4) {
+                *netmask_ptr = get_unaligned_be32(
+                                  DHCP_OPT_PAYLOAD(in_dhcp_opt));
+            }
+            break;
+        case OVN_DHCP_OPT_CODE_ROUTER_IP:
+            if (router_ip_ptr && in_dhcp_opt->len == 4) {
+                *router_ip_ptr = get_unaligned_be32(
+                                  DHCP_OPT_PAYLOAD(in_dhcp_opt));
+            }
+            break;
+        case DHCP_OPT_ETHERBOOT:
+            if (ipxe_req_ptr) {
+                *ipxe_req_ptr = true;
+            }
+            break;
+        default:
+            break;
+        }
+    }
+}
+
+/* Called with in the pinctrl_handler thread context. */
+static void
+pinctrl_handle_dhcp_relay_req_chk(
+    struct rconn *swconn,
+    struct dp_packet *pkt_in, struct ofputil_packet_in *pin,
+    struct ofpbuf *userdata,
+    struct ofpbuf *continuation)
+{
+    enum ofp_version version = rconn_get_version(swconn);
+    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
+    struct dp_packet *pkt_out_ptr = NULL;
+    uint32_t success = 0;
+
+    /* Parse result field. */
+    const struct mf_field *f;
+    enum ofperr ofperr = nx_pull_header(userdata, NULL, &f, NULL);
+    if (ofperr) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ_CHK: bad result OXM (%s)",
+                        ofperr_to_string(ofperr));
+        goto exit;
+    }
+    ovs_be32 *ofsp = ofpbuf_try_pull(userdata, sizeof *ofsp);
+    /* Check that the result is valid and writable. */
+    struct mf_subfield dst = { .field = f, .ofs = ntohl(*ofsp), .n_bits = 1 };
+    ofperr = mf_check_dst(&dst, NULL);
+    if (ofperr) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ_CHK: bad result bit (%s)",
+                        ofperr_to_string(ofperr));
+        goto exit;
+    }
+
+    /* Parse relay IP and server IP. */
+    ovs_be32 *relay_ip = ofpbuf_try_pull(userdata, sizeof *relay_ip);
+    ovs_be32 *server_ip = ofpbuf_try_pull(userdata, sizeof *server_ip);
+    if (!relay_ip || !server_ip) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ_CHK: relay ip or server ip "
+                  "not present in the userdata");
+        goto exit;
+    }
+
+    size_t in_l4_size = dp_packet_l4_size(pkt_in);
+    const char *end = (char *) dp_packet_l4(pkt_in) + in_l4_size;
+    const char *in_dhcp_ptr = NULL;
+    const struct dhcp_header *in_dhcp_data
+                          = dhcp_get_hdr_from_pkt(pkt_in, &in_dhcp_ptr, end);
+
+    if (!in_dhcp_data) {
+        goto exit;
+    }
+    ovs_assert(in_dhcp_ptr);
+
+    if (in_dhcp_data->op != DHCP_OP_REQUEST) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ_CHK: invalid opcode in the "
+                "DHCP packet: %d", in_dhcp_data->op);
+        goto exit;
+    }
+
+    if (in_dhcp_data->giaddr) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ_CHK: giaddr is already set");
+        goto exit;
+    }
+
+    const uint8_t *in_dhcp_msg_type = NULL;
+    ovs_be32 request_ip = in_dhcp_data->ciaddr;
+
+    dhcp_parse_options(&in_dhcp_ptr, end,
+          &in_dhcp_msg_type, &request_ip, NULL, NULL, NULL, NULL);
+
+    /* Check whether the DHCP Message Type (opt 53) is present or not */
+    if (!in_dhcp_msg_type) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ_CHK: missing message type");
+        goto exit;
+    }
+
+    /* Relay the DHCP request packet */
+    uint16_t new_l4_size = in_l4_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);
+    dp_packet_clear(&pkt_out);
+    dp_packet_prealloc_tailroom(&pkt_out, new_packet_size);
+    pkt_out_ptr = &pkt_out;
+
+    /* Copy the L2 and L3 headers from the pkt_in as they would remain same*/
+    dp_packet_put(
+        &pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), pkt_in->l4_ofs);
+
+    pkt_out.l2_5_ofs = pkt_in->l2_5_ofs;
+    pkt_out.l2_pad_size = pkt_in->l2_pad_size;
+    pkt_out.l3_ofs = pkt_in->l3_ofs;
+    pkt_out.l4_ofs = pkt_in->l4_ofs;
+
+    struct udp_header *udp = dp_packet_put(
+        &pkt_out, dp_packet_pull(pkt_in, UDP_HEADER_LEN), UDP_HEADER_LEN);
+
+    struct dhcp_header *dhcp_data = dp_packet_put(&pkt_out,
+        dp_packet_pull(pkt_in, new_l4_size - UDP_HEADER_LEN),
+        new_l4_size - UDP_HEADER_LEN);
+
+    uint8_t hops = dhcp_data->hops;
+    dhcp_data->hops = hops + 1;
+    if (udp->udp_csum) {
+        udp->udp_csum = recalc_csum16(udp->udp_csum,
+            (ovs_be16) (hops << 8), (ovs_be16) (dhcp_data->hops << 8));
+    }
+
+    dhcp_data->giaddr = *relay_ip;
+    if (udp->udp_csum) {
+        udp->udp_csum = recalc_csum32(udp->udp_csum,
+            0, dhcp_data->giaddr);
+    }
+    pin->packet = dp_packet_data(&pkt_out);
+    pin->packet_len = dp_packet_size(&pkt_out);
+
+    /* Log the DHCP message. */
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 40);
+    VLOG_INFO_RL(&rl, "DHCP_RELAY_REQ_CHK:: MSG_TYPE:%s MAC:"ETH_ADDR_FMT
+                " XID:%u"
+                " REQ_IP:"IP_FMT
+                " GIADDR:"IP_FMT
+                " SERVER_ADDR:"IP_FMT,
+                dhcp_msg_str_get(*in_dhcp_msg_type),
+                ETH_ADDR_BYTES_ARGS(dhcp_data->chaddr), ntohl(dhcp_data->xid),
+                IP_ARGS(request_ip), IP_ARGS(dhcp_data->giaddr),
+                IP_ARGS(*server_ip));
+    success = 1;
+exit:
+    if (!ofperr) {
+        union mf_subvalue sv;
+        sv.u8_val = success;
+        mf_write_subfield(&dst, &sv, &pin->flow_metadata);
+    }
+    queue_msg(swconn, ofputil_encode_resume(pin, continuation, proto));
+    if (pkt_out_ptr) {
+        dp_packet_uninit(pkt_out_ptr);
+    }
+}
+
+/* Called with in the pinctrl_handler thread context. */
+static void
+pinctrl_handle_dhcp_relay_resp_chk(
+    struct rconn *swconn,
+    struct dp_packet *pkt_in, struct ofputil_packet_in *pin,
+    struct ofpbuf *userdata,
+    struct ofpbuf *continuation)
+{
+    enum ofp_version version = rconn_get_version(swconn);
+    enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
+    struct dp_packet *pkt_out_ptr = NULL;
+    uint32_t success = 0;
+
+    /* Parse result field. */
+    const struct mf_field *f;
+    enum ofperr ofperr = nx_pull_header(userdata, NULL, &f, NULL);
+    if (ofperr) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP_CHK: bad result OXM (%s)",
+                        ofperr_to_string(ofperr));
+        goto exit;
+    }
+    ovs_be32 *ofsp = ofpbuf_try_pull(userdata, sizeof *ofsp);
+    /* Check that the result is valid and writable. */
+    struct mf_subfield dst = { .field = f, .ofs = ntohl(*ofsp), .n_bits = 1 };
+    ofperr = mf_check_dst(&dst, NULL);
+    if (ofperr) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP_CHK: bad result bit (%s)",
+                        ofperr_to_string(ofperr));
+        goto exit;
+    }
+
+    /* Parse relay IP and server IP. */
+    ovs_be32 *relay_ip = ofpbuf_try_pull(userdata, sizeof *relay_ip);
+    ovs_be32 *server_ip = ofpbuf_try_pull(userdata, sizeof *server_ip);
+    if (!relay_ip || !server_ip) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP_CHK: relay ip or server ip "
+                "not present in the userdata");
+        goto exit;
+    }
+
+    size_t in_l4_size = dp_packet_l4_size(pkt_in);
+    const char *end = (char *) dp_packet_l4(pkt_in) + in_l4_size;
+    const char *in_dhcp_ptr = NULL;
+    const struct dhcp_header *in_dhcp_data
+                          = dhcp_get_hdr_from_pkt(pkt_in, &in_dhcp_ptr, end);
+
+    if (!in_dhcp_data) {
+        goto exit;
+    }
+    ovs_assert(in_dhcp_ptr);
+
+    if (in_dhcp_data->op != DHCP_OP_REPLY) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP_CHK: invalid opcode "
+                "in the packet: %d", in_dhcp_data->op);
+        goto exit;
+    }
+
+    if (!in_dhcp_data->giaddr) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP_CHK: giaddr is "
+                    "not set in request");
+        goto exit;
+    }
+
+    ovs_be32 giaddr = in_dhcp_data->giaddr;
+    ovs_be32 yiaddr = in_dhcp_data->yiaddr;
+    ovs_be32 server_id = 0, netmask = 0, router_ip = 0;
+    const uint8_t *in_dhcp_msg_type = NULL;
+
+    dhcp_parse_options(&in_dhcp_ptr, end,
+          &in_dhcp_msg_type, NULL, NULL, &server_id, &netmask, &router_ip);
+
+    /* Check whether the DHCP Message Type (opt 53) is present or not */
+    if (!in_dhcp_msg_type) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: missing message type");
+        goto exit;
+    }
+
+    if (!server_id) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: missing server identifier");
+        goto exit;
+    }
+
+    if (server_id != *server_ip) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: server identifier mismatch");
+        goto exit;
+    }
+
+    if (giaddr != *relay_ip) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: giaddr mismatch");
+        goto exit;
+    }
+
+    if (*in_dhcp_msg_type == DHCP_MSG_OFFER ||
+        *in_dhcp_msg_type == DHCP_MSG_ACK) {
+        if ((yiaddr & netmask) != (giaddr & netmask)) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+            VLOG_INFO_RL(&rl, "DHCP_RELAY_RESP_CHK::"
+                     " Allocated ip adress and giaddr are not in same subnet."
+                     " MSG_TYPE:%s MAC:"ETH_ADDR_FMT
+                     " XID:%u"
+                     " YIADDR:"IP_FMT
+                     " GIADDR:"IP_FMT
+                     " SERVER_ADDR:"IP_FMT,
+                     dhcp_msg_str_get(*in_dhcp_msg_type),
+                     ETH_ADDR_BYTES_ARGS(in_dhcp_data->chaddr),
+                     ntohl(in_dhcp_data->xid),
+                     IP_ARGS(yiaddr),
+                     IP_ARGS(giaddr), IP_ARGS(server_id));
+            goto exit;
+        }
+
+        if (router_ip && router_ip != giaddr) {
+            /* Log the default gateway mismatch and
+             * continue with rest of the processing */
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+            VLOG_INFO_RL(&rl, "DHCP_RELAY_RESP_CHK::"
+                     " Router ip adress and giaddr are not same."
+                     " MSG_TYPE:%s MAC:"ETH_ADDR_FMT
+                     " XID:%u"
+                     " YIADDR:"IP_FMT
+                     " GIADDR:"IP_FMT
+                     " SERVER_ADDR:"IP_FMT,
+                     dhcp_msg_str_get(*in_dhcp_msg_type),
+                     ETH_ADDR_BYTES_ARGS(in_dhcp_data->chaddr),
+                     ntohl(in_dhcp_data->xid),
+                     IP_ARGS(yiaddr),
+                     IP_ARGS(giaddr), IP_ARGS(server_id));
+        }
+    }
+
+    /* Update destination MAC & IP so that the packet is forward to the
+     * right destination node.
+     */
+    uint16_t new_l4_size = in_l4_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);
+    dp_packet_clear(&pkt_out);
+    dp_packet_prealloc_tailroom(&pkt_out, new_packet_size);
+    pkt_out_ptr = &pkt_out;
+
+    /* Copy the L2 and L3 headers from the pkt_in as they would remain same*/
+    struct eth_header *eth = dp_packet_put(
+        &pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), pkt_in->l4_ofs);
+
+    pkt_out.l2_5_ofs = pkt_in->l2_5_ofs;
+    pkt_out.l2_pad_size = pkt_in->l2_pad_size;
+    pkt_out.l3_ofs = pkt_in->l3_ofs;
+    pkt_out.l4_ofs = pkt_in->l4_ofs;
+
+    struct udp_header *udp = dp_packet_put(
+        &pkt_out, dp_packet_pull(pkt_in, UDP_HEADER_LEN), UDP_HEADER_LEN);
+
+    struct dhcp_header *dhcp_data = dp_packet_put(
+        &pkt_out, dp_packet_pull(pkt_in, new_l4_size - UDP_HEADER_LEN),
+        new_l4_size - UDP_HEADER_LEN);
+    memcpy(&eth->eth_dst, dhcp_data->chaddr, sizeof(eth->eth_dst));
+
+    /* Send a broadcast IP frame when BROADCAST flag is set. */
+    struct ip_header *out_ip = dp_packet_l3(&pkt_out);
+    ovs_be32 ip_dst;
+    ovs_be32 ip_dst_orig = get_16aligned_be32(&out_ip->ip_dst);
+    if (!is_dhcp_flags_broadcast(dhcp_data->flags)) {
+        ip_dst = dhcp_data->yiaddr;
+    } else {
+        ip_dst = htonl(0xffffffff);
+    }
+    put_16aligned_be32(&out_ip->ip_dst, ip_dst);
+    out_ip->ip_csum = recalc_csum32(out_ip->ip_csum,
+              ip_dst_orig, ip_dst);
+    if (udp->udp_csum) {
+        udp->udp_csum = recalc_csum32(udp->udp_csum,
+            ip_dst_orig, ip_dst);
+    }
+    pin->packet = dp_packet_data(&pkt_out);
+    pin->packet_len = dp_packet_size(&pkt_out);
+
+    /* Log the DHCP message. */
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 40);
+    VLOG_INFO_RL(&rl, "DHCP_RELAY_RESP_CHK:: MSG_TYPE:%s MAC:"ETH_ADDR_FMT
+             " XID:%u"
+             " YIADDR:"IP_FMT
+             " GIADDR:"IP_FMT
+             " SERVER_ADDR:"IP_FMT,
+             dhcp_msg_str_get(*in_dhcp_msg_type),
+             ETH_ADDR_BYTES_ARGS(dhcp_data->chaddr), ntohl(dhcp_data->xid),
+             IP_ARGS(yiaddr),
+             IP_ARGS(giaddr), IP_ARGS(server_id));
+    success = 1;
+exit:
+    if (!ofperr) {
+        union mf_subvalue sv;
+        sv.u8_val = success;
+        mf_write_subfield(&dst, &sv, &pin->flow_metadata);
+    }
+    queue_msg(swconn, ofputil_encode_resume(pin, continuation, proto));
+    if (pkt_out_ptr) {
+        dp_packet_uninit(pkt_out_ptr);
+    }
+}
+
 /* Called with in the pinctrl_handler thread context. */
 static void
 pinctrl_handle_put_dhcp_opts(
@@ -1956,30 +2464,16 @@ pinctrl_handle_put_dhcp_opts(
         goto exit;
     }
 
-    /* Validate the DHCP request packet.
-     * Format of the DHCP packet is
-     * ------------------------------------------------------------------------
-     *| UDP HEADER  | DHCP HEADER  | 4 Byte DHCP Cookie | DHCP OPTIONS(var len)|
-     * ------------------------------------------------------------------------
-     */
-
     const char *end = (char *)dp_packet_l4(pkt_in) + dp_packet_l4_size(pkt_in);
-    const char *in_dhcp_ptr = dp_packet_get_udp_payload(pkt_in);
-    if (!in_dhcp_ptr) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "Invalid or incomplete DHCP packet received");
-        goto exit;
-    }
-
+    const char *in_dhcp_ptr = NULL;
     const struct dhcp_header *in_dhcp_data
-        = (const struct dhcp_header *) in_dhcp_ptr;
-    in_dhcp_ptr += sizeof *in_dhcp_data;
-    if (in_dhcp_ptr > end) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "Invalid or incomplete DHCP packet received, "
-                     "bad data length");
+                          = dhcp_get_hdr_from_pkt(pkt_in, &in_dhcp_ptr, end);
+
+    if (!in_dhcp_data) {
         goto exit;
     }
+    ovs_assert(in_dhcp_ptr);
+
     if (in_dhcp_data->op != DHCP_OP_REQUEST) {
         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
         VLOG_WARN_RL(&rl, "Invalid opcode in the DHCP packet: %d",
@@ -1987,58 +2481,11 @@ pinctrl_handle_put_dhcp_opts(
         goto exit;
     }
 
-    /* DHCP options follow the DHCP header. The first 4 bytes of the DHCP
-     * options is the DHCP magic cookie followed by the actual DHCP options.
-     */
-    ovs_be32 magic_cookie = htonl(DHCP_MAGIC_COOKIE);
-    if (in_dhcp_ptr + sizeof magic_cookie > end ||
-        get_unaligned_be32((const void *) in_dhcp_ptr) != magic_cookie) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-        VLOG_WARN_RL(&rl, "DHCP magic cookie not present in the DHCP packet");
-        goto exit;
-    }
-    in_dhcp_ptr += sizeof magic_cookie;
-
     bool ipxe_req = false;
     const uint8_t *in_dhcp_msg_type = NULL;
     ovs_be32 request_ip = in_dhcp_data->ciaddr;
-    while (in_dhcp_ptr < end) {
-        const struct dhcp_opt_header *in_dhcp_opt =
-            (const struct dhcp_opt_header *)in_dhcp_ptr;
-        if (in_dhcp_opt->code == DHCP_OPT_END) {
-            break;
-        }
-        if (in_dhcp_opt->code == DHCP_OPT_PAD) {
-            in_dhcp_ptr += 1;
-            continue;
-        }
-        in_dhcp_ptr += sizeof *in_dhcp_opt;
-        if (in_dhcp_ptr > end) {
-            break;
-        }
-        in_dhcp_ptr += in_dhcp_opt->len;
-        if (in_dhcp_ptr > end) {
-            break;
-        }
-
-        switch (in_dhcp_opt->code) {
-        case DHCP_OPT_MSG_TYPE:
-            if (in_dhcp_opt->len == 1) {
-                in_dhcp_msg_type = DHCP_OPT_PAYLOAD(in_dhcp_opt);
-            }
-            break;
-        case DHCP_OPT_REQ_IP:
-            if (in_dhcp_opt->len == 4) {
-                request_ip = get_unaligned_be32(DHCP_OPT_PAYLOAD(in_dhcp_opt));
-            }
-            break;
-        case DHCP_OPT_ETHERBOOT:
-            ipxe_req = true;
-            break;
-        default:
-            break;
-        }
-    }
+    dhcp_parse_options(&in_dhcp_ptr, end,
+          &in_dhcp_msg_type, &request_ip, &ipxe_req, NULL, NULL, NULL);
 
     /* Check that the DHCP Message Type (opt 53) is present or not with
      * valid values - DHCP_MSG_DISCOVER or DHCP_MSG_REQUEST.
@@ -2245,6 +2692,7 @@ pinctrl_handle_put_dhcp_opts(
         dhcp_data->yiaddr = 0;
     }
 
+    ovs_be32 magic_cookie = htonl(DHCP_MAGIC_COOKIE);
     dp_packet_put(&pkt_out, &magic_cookie, sizeof(ovs_be32));
 
     uint16_t out_dhcp_opts_size = 12;
@@ -3215,6 +3663,16 @@ process_packet_in(struct rconn *swconn, const struct ofp_header *msg)
         ovs_mutex_unlock(&pinctrl_mutex);
         break;
 
+    case ACTION_OPCODE_DHCP_RELAY_REQ_CHK:
+        pinctrl_handle_dhcp_relay_req_chk(swconn, &packet, &pin,
+                                     &userdata, &continuation);
+        break;
+
+    case ACTION_OPCODE_DHCP_RELAY_RESP_CHK:
+        pinctrl_handle_dhcp_relay_resp_chk(swconn, &packet, &pin,
+                                     &userdata, &continuation);
+        break;
+
     case ACTION_OPCODE_PUT_DHCP_OPTS:
         pinctrl_handle_put_dhcp_opts(swconn, &packet, &pin, &headers,
                                      &userdata, &continuation);
diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
index ad514a922..5eeb7c1a0 100644
--- a/lib/ovn-l7.h
+++ b/lib/ovn-l7.h
@@ -68,7 +68,9 @@ struct gen_opts_map {
  * OVN_DHCP_OPT_CODE_<opt_name>.
  */
 #define OVN_DHCP_OPT_CODE_NETMASK      1
+#define OVN_DHCP_OPT_CODE_ROUTER_IP    3
 #define OVN_DHCP_OPT_CODE_LEASE_TIME   51
+#define OVN_DHCP_OPT_CODE_SERVER_ID    54
 #define OVN_DHCP_OPT_CODE_T1           58
 #define OVN_DHCP_OPT_CODE_T2           59
 
