Hi Team,

Could someone please look into this ?

Thanks,
Naveen

> On 23-Aug-2023, at 8:06 AM, Naveen Yerramneni <[email protected]> 
> wrote:
> 
> This patch contains changes to enable DHCP Proxy Agent support for overlay 
> subnets.
> 
> NOTE:
> -----
>  - This patch is not complete, sending this to get the initial feedback about 
> the approach taken.
> 
> USE CASE:
> ----------
>  - Enable IP address assignment for overlay subnets from the centralized DHCP 
> server present in the underlay network.
> 
> 
> PREREQUISITES
> --------------
>  - Logical Router Port IP should be assigned (statically) from the same 
> overlay subnet which is managed by DHCP server.
>  - LRP IP is used for GIADRR field when proxying the DHCP packets and also 
> same IP needs to be configured as default gateway for the overlay subnet.
>  - Overlay subnets managed by external DHCP server are expected to be 
> directly reachable from the underlay network.
> 
> EXPECTED PACKET FLOW:
> ----------------------
> Following is the expected packet flow inorder to support DHCP Proxy 
> functionality in OVN.
>  1. VM originates DHCP discovery (broadcast).
>  2. DHCP Proxy (running on the OVN) receives the broadcast and forwards the 
> packet to the DHCP server by converting it to unicast. While forwarding the 
> packet, it updates the GIADDR in DHCP header to its
>     interface IP on which DHCP packet is received.
>  3. DHCP server uses GIADDR field to decide the IP address pool from which IP 
> has to be assigned and DHCP offer is sent to the same IP (GIADDR).
>  4. DHCP proxy resets the GIADDR field when forwarding the offer to the 
> client and it also replaces DHCP server ID option (54) with its IP address.
>  5. VM/Host sends DHCP request (broadcast) packet.
>  6. DHCP Proxy (running on the OVN) receives the broadcast and forwards the 
> packet to the DHCP server by converting it to unicast. While forwarding the 
> packet, it updates the GIADDR in DHCP header to its
>     interface IP on which DHCP packet is received and also replaces the DHCP 
> server ID option (54) with DHCP server IP address.
>  7. DHCP Server sends the ACK packet.
>  8. DHCP Proxy resets the GIADDR field when forwarding the ACK to the client 
> and it also replaces DHCP server ID option (54) with its IP address.
>  9. By setting DHCP server ID option (54) to its IP address, DHCP Proxy 
> completely hides the DHCP server from the end DHCP clients and all the future 
> renew/release packets come to DHCP proxy.
> 
> OVN DHCP PROXY PACKET FLOW:
> ----------------------------
> To add DHCP Proxy support on OVN, we need to replicate all the behavior 
> described above using distributed logical switch and logical router.
> At a highlevel, packet flow is distributed among Logical Switch and Logical 
> Router on source node (where VM is deployed) and redirect chassis(RC) node.
>  1. Request packet gets processed on the source node. Required info is 
> extracted from the DHCP payload and stored in the in-memory hash map (key: 
> MAC + DHCP_xid) of the OVN controller.
>  2. Response packet is first processed on RC node (which first receives the 
> packet from underlay network). RC node forwards the packet to the right node 
> by filling in the dest MAC and IP.
>  3. Response packet is processed on the source node which originated the DHCP 
> request. Hash map lookup is done to find the associative DHCP session and 
> stateful checks are performed to validate the response.
> 
> OVN Packet flow with DHCP Proxy is explained below.
>  1. VM sends the DHCP discover packet (broadcast).
>  2. Logical switch converts the packet to L2 unicast by setting the 
> destination MAC to LRP's MAC
>  3. Logical Router receives the packet and redirects it to the OVN controller.
>  4. OVN controller updates the required information in the DHCP payload 
> (GIADDR, SERVER ID) after doing the required checks. It stores required 
> information from the packet to do some stateful checks on
>     response packets and reinjects the packet back to the datapath provided 
> if all sanity checks are passed.
>  5. Logical Router converts the packet to L3 unicast and forwards it to the 
> server. This packet gets routed like any other packet (via RC node).
>  6. Server replies with DHCP offer.
>  7. RC node processes the DHCP offer and forwards it to the OVN controller.
>  8. OVN controller updates the destination MAC (available in DHCP header) and 
> destination IP (available in DHCP header) and reinjects the packet to 
> datapath.
>  9. Logical router outputs  the packet to the logical switch.
>  10.Logical switch processes the packet and redirects it to the OVN 
> controller.
>  11.OVN controller does the required checks on the packet, updates DHCP 
> payload (GIADDR, Server ID)and reinjects the packet back to the datapath if 
> all stateful checks on the packet are passed.
>  12.Logical switch outputs the packet to VM by updating the required fields 
> in IP and UDP header.
>  13. Similar steps are performed for further packets.
> 
> NEW OVN ACTIONS
> ---------------
> 
>  1. dhcp_relay_req(<proxy-ip>, <server-ip>)
>      - This action executes on the source node on which the DHCP request 
> originated.
>      - This action proxies the DHCP request coming from client to the server 
> and stores required info about the DHCP txn in the memory. Proxy-ip is used 
> to update GIADDR in the DHCP header.
>  2. dhcp_relay_resp_fwd()
>      - This action executes on the first node (RC node) which processes the 
> DHCP response from the server.
>      - This action updates  the destination MAC and destination IP so that 
> the response can be forwarded to the appropriate node from which request was 
> originated.
>  3. dhcp_relay_resp(<proxy-ip>, <server-ip>)
>      - This action executes on the node where the VM (to which DHCP packet is 
> destined) is running.
>      - This action does all the stateful packet checks and updates all the 
> required info in the DHCP payload.
>      - Proxy-ip, server-ip are used to update/validate GIADDR and SERVER ID 
> in the DHCP payload.
> 
> FLOWS
> -----
> Following are the new flows required to support DHCP functionality. Following 
> flows enable DHCP Proxy support for one VM port.
> For each DHCP subnet, four flows are requried to enable the DHCP proxy.
> For each VM port, two flows are requried to enable the DHCP proxy support in 
> the given subnet.
> 
>  1. table=27(ls_in_l2_lkup      ), priority=100  , match=(inport == <vm_port> 
> && eth.src == <vm_mac> && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && 
> udp.src == 68 && udp.dst == 67), 
> action=(eth.dst=<lrp_mac>;outport=<lrp-port>;next;/* DHCP_PROXY_REQ */)
>  2. table=9 (ls_out_dhcp_proxy_resp), priority=100  , match=(inport == 
> <lrp_port> && eth.src == <lrp_mac> && outport == <vm_port> &&ip4.src == 
> <dhcp_server_ip> && udp.src == 67 && udp.dst == 67), 
> action=(dhcp_proxy_resp(<lrp_ip>,<dhcp_server_ip>);ip4.src=<lrp_ip>;udp.dst=68;next;/*
>  DHCP_PROXY_RESP */)
>  3. table=3 (lr_in_ip_input     ), priority=110  , match=(inport == 
> <lrp_port> && ip4.dst == <lrp_ip> && udp.src == 68 && udp.dst == 67), 
> action=(dhcp_proxy_req(<lrp_ip>,<dhcp_server_ip>);ip4.src=<lrp_ip>;ip4.dst=<dhcp_server_ip>;udp.src=67;next;
>  /* DHCP_PROXY_REQ */)
>  4. table=3 (lr_in_ip_input     ), priority=110  , match=(inport == 
> <lrp_port> && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 
> 68 && udp.dst == 67), 
> action=(dhcp_proxy_req(<lrp_ip>,<dhcp_server_ip>);ip4.src=<lrp_ip>;ip4.dst=<dhcp_server_ip>;udp.src=67;next;
>  /* DHCP_PROXY_REQ */)
>  5. table=3 (lr_in_ip_input     ), priority=110  , match=(ip4.src == 
> <dhcp_server_ip> && ip4.dst ==<lrp_ip> && udp.src == 67 && udp.dst == 67), 
> action=(next;/* DHCP_PROXY_RESP */)
>  6. table=17(lr_in_dhcp_proxy_resp_fwd), priority=110  , match=(ip4.src == 
> <dhcp_server_ip> && ip4.dst == <lrp_ip> && udp.src == 67 && udp.dst == 67), 
> action=(dhcp_proxy_resp_fwd();outport=<lrp_port>;output; /* DHCP_PROXY_RESP 
> */)
> 
> NEW PIPELINE STAGES
> -------------------
> Following are new stages are added for DHCP proxy feature. Some of the flows 
> are fitted into the existing pipeline tages.
>  1. lr_in_dhcp_proxy_resp_fwd
>      - Forward teh DHCP response to the appropriate node
>  2. ls_out_dhcp_proxy_resp
>      - Process DHCP response on the source which originated the request.
> 
> NB SCHEMA CHANGES
> ----------------
>  1. New DHCP_Proxy table
>      "DHCP_Proxy": {
>            "columns": {
>        "name": {"type": "string"},
>                "servers": {"type": {"key": "string",
>                                       "min": 0,
>                                       "max": 1}},
>                "external_ids": {
>                    "type": {"key": "string", "value": "string",
>                            "min": 0, "max": "unlimited"}}},
>            "isRoot": true},
>  2. New column to Logical_Router_Port table
>      "dhcp_proxy": {"type": {"key": {"type": "uuid",
>                            "refTable": "DHCP_Proxy",
>                            "refType": "weak"},
>                            "min": 0,
>                            "max": 1}},
>  3. New column to Logical_Switch_table
>      "dhcp_proxy_port": {"type": {"key": {"type": "uuid",
>                                    "refTable": "Logical_Router_Port",
>                                    "refType": "weak"},
>                                     "min": 0,
>                                     "max": 1}}},
> Commands to enable the feature.
>  - ovn-nbctl create DHCP_Proxy servers=<ip>
>  - ovn-nbctl set Logical_Router_port <lrp_uuid> dhcp_proxy=<dhcp_proxy_uuid>
>  - ovn-nbctl set Logical_Switch <ls_uuid> dhcp_proxy_port=<lrp_uuid>
> 
> POST RFC REVIEW
> ----------------
>  1. Address review comments/suggestions
>  2. Address TODOs and pending changes
>  3. Unit tests
>  4. Complete testing
>  5. IP Discovery support in future
> 
> Signed-off-by: naveen.yerramneni <[email protected]>
> Co-authored-by: Huzaifa Calcuttawala <[email protected]>
> Signed-off-by: Huzaifa Calcuttawala <[email protected]>
> CC: Mary Manohar <[email protected]>
> CC: Abhiram Sangana <[email protected]>
> ---
> controller/pinctrl.c  | 766 ++++++++++++++++++++++++++++++++++++++++++
> include/ovn/actions.h |  33 ++
> lib/actions.c         | 143 ++++++++
> lib/ovn-l7.h          |   1 +
> northd/northd.c       | 218 +++++++++++-
> ovn-nb.ovsschema      |  23 +-
> ovn-nb.xml            |  27 ++
> tests/ovn-northd.at   |  32 +-
> tests/ovn.at          |  12 +-
> utilities/ovn-trace.c |  12 +
> 10 files changed, 1237 insertions(+), 30 deletions(-)
> 
> diff --git a/controller/pinctrl.c b/controller/pinctrl.c
> index bed90fe0b..11f5839e3 100644
> --- a/controller/pinctrl.c
> +++ b/controller/pinctrl.c
> @@ -377,6 +377,9 @@ static void wait_put_fdbs(struct ovsdb_idl_txn 
> *ovnsb_idl_txn);
> static void pinctrl_handle_put_fdb(const struct flow *md,
>                                    const struct flow *headers)
>                                    OVS_REQUIRES(pinctrl_mutex);
> +static struct dhcp_proxy_info *dhcp_proxy_get(void);
> +static void dhcp_proxy_init(struct dhcp_proxy_info *dhcp_proxy);
> +    
> 
> COVERAGE_DEFINE(pinctrl_drop_put_mac_binding);
> COVERAGE_DEFINE(pinctrl_drop_buffered_packets_map);
> @@ -547,6 +550,7 @@ pinctrl_init(void)
>     init_svc_monitors();
>     bfd_monitor_init();
>     init_fdb_entries();
> +    dhcp_proxy_init(dhcp_proxy_get());
>     pinctrl.br_int_name = NULL;
>     pinctrl.mac_binding_can_timestamp = false;
>     pinctrl_handler_seq = seq_create();
> @@ -1883,6 +1887,753 @@ is_dhcp_flags_broadcast(ovs_be16 flags)
>     return flags & htons(DHCP_BROADCAST_FLAG);
> }
> 
> +
> +enum dhcp_proxy_txn_state
> +{
> +    DHCP_PROXY_TXN_STATE_INIT=0,
> +    DHCP_PROXY_TXN_STATE_DISCOVER,
> +    DHCP_PROXY_TXN_STATE_OFFER,
> +    DHCP_PROXY_TXN_STATE_REQUEST,
> +    DHCP_PROXY_TXN_STATE_ASSIGNED,
> +    DHCP_PROXY_TXN_STATE_INVALID,
> +    DHCP_PROXY_TXN_STATE_MAX=DHCP_PROXY_TXN_STATE_INVALID
> +};
> +
> +struct dhcp_proxy_node
> +{
> +    struct hmap_node hmap_node;
> +    struct eth_addr client_mac;
> +    uint64_t epoch_ms;
> +    uint64_t lease_time_ms;
> +    enum dhcp_proxy_txn_state txn_state;
> +    ovs_be32 txn_id;
> +    ovs_be32 client_ip;
> +    ovs_be32 proxy_ip;
> +    ovs_be32 server_ip;
> +}; 
> +
> +#define DHCP_MSG_TYPE_MAX (OVN_DHCP_MSG_RELEASE+1)
> +struct dhcp_proxy_info
> +{
> +    struct hmap cache_hmap;
> +    uint32_t secret;
> +    enum dhcp_proxy_txn_state 
> sm[DHCP_PROXY_TXN_STATE_MAX][DHCP_MSG_TYPE_MAX];
> +};
> +
> +static struct dhcp_proxy_info g_dhcp_proxy;
> +
> +static struct dhcp_proxy_info*
> +dhcp_proxy_get(void)
> +{
> +    return &g_dhcp_proxy;
> +}
> +
> +static void
> +dhcp_proxy_init(struct dhcp_proxy_info *dhcp_proxy)
> +{
> +    hmap_init(&dhcp_proxy->cache_hmap);
> +    dhcp_proxy->secret = random_uint32();
> +    
> +    for (int i=DHCP_PROXY_TXN_STATE_INIT; i<DHCP_PROXY_TXN_STATE_MAX; i++)
> +    {
> +        for (int j=0; j<DHCP_MSG_TYPE_MAX; j++)
> +        {
> +            dhcp_proxy->sm[i][j] = DHCP_PROXY_TXN_STATE_INVALID;
> +        } 
> +    }
> +
> +    //TODO: Validate state machine for all possible cases
> +    enum dhcp_proxy_txn_state *dhcp_sm_state =
> +                                  dhcp_proxy->sm[DHCP_PROXY_TXN_STATE_INIT];
> +    dhcp_sm_state[DHCP_MSG_DISCOVER] = DHCP_PROXY_TXN_STATE_DISCOVER;
> +    dhcp_sm_state[DHCP_MSG_OFFER] = DHCP_PROXY_TXN_STATE_INIT;
> +    dhcp_sm_state[DHCP_MSG_REQUEST] = DHCP_PROXY_TXN_STATE_REQUEST;
> +    dhcp_sm_state[OVN_DHCP_MSG_DECLINE] = DHCP_PROXY_TXN_STATE_INIT;
> +    dhcp_sm_state[DHCP_MSG_ACK] = DHCP_PROXY_TXN_STATE_INIT;
> +    dhcp_sm_state[DHCP_MSG_NAK] = DHCP_PROXY_TXN_STATE_INIT;
> +    dhcp_sm_state[OVN_DHCP_MSG_RELEASE] = DHCP_PROXY_TXN_STATE_INIT;
> +
> +    dhcp_sm_state = dhcp_proxy->sm[DHCP_PROXY_TXN_STATE_DISCOVER];
> +    dhcp_sm_state[DHCP_MSG_DISCOVER] = DHCP_PROXY_TXN_STATE_DISCOVER;
> +    dhcp_sm_state[DHCP_MSG_OFFER] = DHCP_PROXY_TXN_STATE_OFFER;
> +
> +    dhcp_sm_state = dhcp_proxy->sm[DHCP_PROXY_TXN_STATE_OFFER];
> +    dhcp_sm_state[DHCP_MSG_DISCOVER] = DHCP_PROXY_TXN_STATE_DISCOVER;
> +    dhcp_sm_state[DHCP_MSG_REQUEST] = DHCP_PROXY_TXN_STATE_REQUEST;
> +    dhcp_sm_state[OVN_DHCP_MSG_DECLINE] = DHCP_PROXY_TXN_STATE_INIT;
> +    dhcp_sm_state[DHCP_MSG_OFFER] = DHCP_PROXY_TXN_STATE_OFFER;
> +
> +    dhcp_sm_state = dhcp_proxy->sm[DHCP_PROXY_TXN_STATE_REQUEST];
> +    dhcp_sm_state[DHCP_MSG_DISCOVER] = DHCP_PROXY_TXN_STATE_DISCOVER;
> +    dhcp_sm_state[DHCP_MSG_REQUEST] = DHCP_PROXY_TXN_STATE_REQUEST;
> +    dhcp_sm_state[DHCP_MSG_OFFER] = DHCP_PROXY_TXN_STATE_OFFER;
> +    dhcp_sm_state[DHCP_MSG_ACK] = DHCP_PROXY_TXN_STATE_ASSIGNED;
> +    dhcp_sm_state[DHCP_MSG_NAK] = DHCP_PROXY_TXN_STATE_INIT;
> +
> +    dhcp_sm_state = dhcp_proxy->sm[DHCP_PROXY_TXN_STATE_ASSIGNED];
> +    dhcp_sm_state[DHCP_MSG_DISCOVER] = DHCP_PROXY_TXN_STATE_DISCOVER;
> +    dhcp_sm_state[DHCP_MSG_REQUEST] = DHCP_PROXY_TXN_STATE_REQUEST;
> +    dhcp_sm_state[OVN_DHCP_MSG_RELEASE] = DHCP_PROXY_TXN_STATE_INIT;
> +}
> +
> +static bool
> +dhcp_proxy_is_msg_type_supported(uint8_t msg_type)
> +{
> +    return (msg_type >= DHCP_MSG_DISCOVER && msg_type <= 
> OVN_DHCP_MSG_RELEASE);
> +}
> +
> +static bool
> +dhcp_proxy_is_txn_state_valid(enum dhcp_proxy_txn_state state)
> +{
> +    return (state >= DHCP_PROXY_TXN_STATE_INIT
> +            && state < DHCP_PROXY_TXN_STATE_INVALID);
> +}
> +
> +static enum dhcp_proxy_txn_state
> +dhcp_proxy_next_txn_state(enum dhcp_proxy_txn_state sm[][DHCP_MSG_TYPE_MAX],
> +        enum dhcp_proxy_txn_state curr_state, uint8_t msg_type)
> +{
> +    if (!(dhcp_proxy_is_msg_type_supported(msg_type)
> +          && dhcp_proxy_is_txn_state_valid(curr_state)))
> +    {
> +        return DHCP_PROXY_TXN_STATE_INVALID;
> +    }
> +    return sm[curr_state][msg_type]; 
> +}
> +
> +static uint64_t
> +dhcp_proxy_hash(struct eth_addr ea, uint32_t basis)
> +{
> +    return hash_uint64_basis(eth_addr_to_uint64(ea), basis);
> +}
> +
> +static void
> +dhcp_proxy_insert(struct dhcp_proxy_info *dhcp_proxy,
> +                  struct dhcp_proxy_node *node, uint64_t hash)
> +{
> +    hmap_insert(&dhcp_proxy->cache_hmap, &node->hmap_node, hash); 
> +}
> +
> +static struct dhcp_proxy_node*
> +dhcp_proxy_lookup(struct dhcp_proxy_info *dhcp_proxy, struct eth_addr ea)
> +{
> +    struct dhcp_proxy_node *node;
> +    HMAP_FOR_EACH_WITH_HASH(node, hmap_node,
> +            dhcp_proxy_hash(ea, dhcp_proxy->secret), &dhcp_proxy->cache_hmap)
> +    {
> +        if (eth_addr_equals(node->client_mac, ea))
> +        {
> +            return node;
> +        }
> +    }
> +    return NULL;
> +}
> +
> +static struct dhcp_proxy_node*
> +dhcp_proxy_lookup_match_xid(struct dhcp_proxy_info *dhcp_proxy, struct 
> eth_addr ea,
> +                  uint32_t dhcp_txn_id)
> +{
> +    struct dhcp_proxy_node *node = dhcp_proxy_lookup(dhcp_proxy, ea);
> +    if (node && (node->txn_id == dhcp_txn_id))
> +    {
> +        return node;
> +    }
> +    return NULL;
> +}
> +
> +//TODO: yet to implement when and how nodes are deleted 
> +static void
> +dhcp_proxy_delete_node(struct dhcp_proxy_info *dhcp_proxy OVS_UNUSED,
> +                      struct eth_addr ea OVS_UNUSED)
> +{
> +}
> +
> +//time_wall_msec();
> +
> +/* Called with in the pinctrl_handler thread context. */
> +static void
> +pinctrl_handle_dhcp_proxy_req(
> +    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;
> +
> +    /* Parse relay IP and server IP. */
> +    ovs_be32 *proxy_ip = ofpbuf_try_pull(userdata, sizeof *proxy_ip);
> +    ovs_be32 *server_ip = ofpbuf_try_pull(userdata, sizeof *server_ip);
> +    if (!proxy_ip || !server_ip) {
> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> +        VLOG_WARN_RL(&rl, "DHCP_PROXY_REQ: proxy ip or server ip not present 
> in the userdata");
> +        return;
> +    }
> +
> +    /* Validate the DHCP request packet.
> +     * Format of the DHCP packet is
> +     * 
> ------------------------------------------------------------------------
> +     *| UDP HEADER  | DHCP HEADER  | 4 Byte DHCP Cookie | DHCP OPTIONS(var 
> len)|
> +     * 
> ------------------------------------------------------------------------
> +     */
> +
> +    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 = 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, "DHCP_PROXY_REQ: invalid or incomplete DHCP packet 
> received");
> +        return;
> +    }
> +
> +    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, "DHCP_PROXY_REQ: invalid or incomplete DHCP packet 
> received, "
> +                     "bad data length");
> +        return;
> +    }
> +    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_PROXY_REQ: invalid opcode in the DHCP 
> packet: %d",
> +                     in_dhcp_data->op);
> +        return;
> +    }
> +
> +    /* 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_PROXY_REQ: magic cookie not present in the 
> packet");
> +        return;
> +    }
> +
> +    if (in_dhcp_data->giaddr) {
> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> +        VLOG_WARN_RL(&rl, "DHCP_PROXY_REQ: giaddr is already set");
> +        return;
> +    }
> +    
> +    if (in_dhcp_data->htype != 0x1) {
> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> +        VLOG_WARN_RL(&rl, "DHCP_PROXY_REQ: packet is recieved with 
> unsupported hardware type");
> +        return;
> +    }
> +
> +    ovs_be32 *server_id_ptr = NULL;
> +    const uint8_t *in_dhcp_msg_type = NULL;
> +
> +    in_dhcp_ptr += sizeof magic_cookie;
> +    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 OVN_DHCP_OPT_CODE_SERVER_ID: //Server Identifier
> +            if (in_dhcp_opt->len == 4) {
> +                server_id_ptr = DHCP_OPT_PAYLOAD(in_dhcp_opt);
> +            }
> +            break;
> +        default:
> +            break;
> +        }
> +    }
> +
> +    /* 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_PROXY_REQ: missing message type");
> +        return;
> +    }
> +    
> +    struct eth_addr ea;
> +    struct dhcp_proxy_node *node;
> +    struct dhcp_proxy_info *dhcp_proxy = dhcp_proxy_get();
> +    memcpy(ea.ea, in_dhcp_data->chaddr, sizeof(ea));
> +
> +    if((node = dhcp_proxy_lookup_match_xid(dhcp_proxy, ea, 
> in_dhcp_data->xid))
> +                != NULL) {
> +        enum dhcp_proxy_txn_state nxt_state = dhcp_proxy_next_txn_state(
> +                                              dhcp_proxy->sm,
> +                                              node->txn_state,
> +                                              *in_dhcp_msg_type);
> +        if (nxt_state < DHCP_PROXY_TXN_STATE_INVALID) {
> +            if (server_id_ptr && *server_id_ptr != *proxy_ip) {
> +                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 
> 5);
> +                VLOG_WARN_RL(&rl, "DHCP_PROXY_REQ: server identifier 
> mismatch");
> +                return;
> +            }
> +            node->txn_state = nxt_state;
> +            node->epoch_ms = time_wall_msec();
> +        }
> +        else {
> +            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> +            VLOG_WARN_RL(&rl, "DHCP_PROXY_REQ: invalid state transistion"
> +                              " for existing session");
> +            return;
> +        }
> +    }
> +    else {
> +        enum dhcp_proxy_txn_state nxt_state = dhcp_proxy_next_txn_state(
> +                                              dhcp_proxy->sm,
> +                                              DHCP_PROXY_TXN_STATE_INIT,
> +                                              *in_dhcp_msg_type);
> +        if (nxt_state < DHCP_PROXY_TXN_STATE_INVALID) {
> +            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> +            VLOG_WARN_RL(&rl, "DHCP_PROXY_REQ: New session");
> +            node = xmalloc(sizeof(*node));
> +            node->client_mac = ea;
> +            node->client_ip = in_dhcp_data->ciaddr;
> +            node->txn_id = in_dhcp_data->xid;
> +            node->txn_state = nxt_state;
> +            node->epoch_ms = time_wall_msec();
> +            node->server_ip = *server_ip;
> +            node->proxy_ip = *proxy_ip;
> +            dhcp_proxy_insert(dhcp_proxy, node,
> +                    dhcp_proxy_hash(ea, dhcp_proxy->secret));
> +        }
> +        else {
> +            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> +            VLOG_WARN_RL(&rl, "DHCP_PROXY_REQ: invalid state transistion"
> +                              " for new session");
> +            return;
> +        }
> +    } 
> +   
> +    /* Proxy the DHCP request packet */
> + 
> +    /* Replace Server Identifier Option */
> +    if (server_id_ptr) {
> +        put_unaligned_be32(server_id_ptr, *server_ip);
> +    }
> +
> +    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 ip_header *out_ip = dp_packet_l3(&pkt_out);
> +
> +    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);
> +    dhcp_data->giaddr = *proxy_ip;
> +    //TODO: incremental checkcum
> +    if (udp->udp_csum) {
> +        udp->udp_csum = 0;
> +        uint32_t p_csum = packet_csum_pseudoheader(out_ip);
> +        udp->udp_csum = csum_finish(csum_continue(p_csum, udp, new_l4_size));
> +    }
> +    pin->packet = dp_packet_data(&pkt_out);
> +    pin->packet_len = dp_packet_size(&pkt_out);
> +
> +    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_proxy_resp(
> +    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;
> +
> +    /* Parse relay IP and server IP. */
> +    ovs_be32 *proxy_ip = ofpbuf_try_pull(userdata, sizeof *proxy_ip);
> +    ovs_be32 *server_ip = ofpbuf_try_pull(userdata, sizeof *server_ip);
> +    if (!proxy_ip || !server_ip) {
> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> +        VLOG_WARN_RL(&rl, "DHCP_PROXY_RESP: proxy ip or server ip not 
> present in the userdata");
> +        return;
> +    }
> +
> +    /* Validate the DHCP request packet.
> +     * Format of the DHCP packet is
> +     * 
> ------------------------------------------------------------------------
> +     *| UDP HEADER  | DHCP HEADER  | 4 Byte DHCP Cookie | DHCP OPTIONS(var 
> len)|
> +     * 
> ------------------------------------------------------------------------
> +     */
> +
> +    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 = 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, "DHCP_PROXY_RESP: invalid or incomplete packet 
> received");
> +        return;
> +    }
> +
> +    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, "DHCP_PROXY_RESP: invalid or incomplete packet 
> received, "
> +                     "bad data length");
> +        return;
> +    }
> +    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_PROXY_RESP:: invalid opcode in the packet: 
> %d",
> +                     in_dhcp_data->op);
> +        return;
> +    }
> +
> +    /* 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_PROXY_RESP:: magic cookie not present in the 
> packet");
> +        return;
> +    }
> +
> +    if (!in_dhcp_data->giaddr) {
> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> +        VLOG_WARN_RL(&rl, "DHCP_PROXY_RESP: giaddr is not set in response");
> +        return;
> +    }
> +    
> +    ovs_be32 *server_id_ptr = NULL;
> +    ovs_be32 lease_time = 0;
> +    ovs_be32 giaddr = in_dhcp_data->giaddr;
> +    const uint8_t *in_dhcp_msg_type = NULL;
> +    
> +    in_dhcp_ptr += sizeof magic_cookie;
> +    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 OVN_DHCP_OPT_CODE_SERVER_ID: //Server Identifier
> +            if (in_dhcp_opt->len == 4) {
> +                server_id_ptr = DHCP_OPT_PAYLOAD(in_dhcp_opt);
> +            }
> +            break;
> +        case OVN_DHCP_OPT_CODE_LEASE_TIME:
> +            if (in_dhcp_opt->len == 4) {
> +                lease_time = 
> get_unaligned_be32(DHCP_OPT_PAYLOAD(in_dhcp_opt));
> +            }
> +            break;
> +        default:
> +            break;
> +        }
> +    }
> +
> +    /* 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_PROXY_RESP: missing message type");
> +        return;
> +    }
> +
> +    if (!server_id_ptr) {
> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> +        VLOG_WARN_RL(&rl, "DHCP_PROXY_RESP: missing server identifier");
> +        return;
> +    }
> +
> +    if (*server_id_ptr != *server_ip) {
> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> +        VLOG_WARN_RL(&rl, "DHCP_PROXY_RESP: server identifier mismatch");
> +        return;
> +    }
> +
> +    if (giaddr != *proxy_ip) {
> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> +        VLOG_WARN_RL(&rl, "DHCP_PROXY_RESP: giaddr mismatch");
> +        return;
> +    }
> +    
> +    struct eth_addr ea;
> +    struct dhcp_proxy_node *node;
> +    struct dhcp_proxy_info *dhcp_proxy = dhcp_proxy_get();
> +    memcpy(ea.ea, in_dhcp_data->chaddr, sizeof(ea));
> +
> +    if((node = dhcp_proxy_lookup_match_xid(dhcp_proxy, ea, 
> in_dhcp_data->xid))
> +                != NULL) {
> +        enum dhcp_proxy_txn_state nxt_state = dhcp_proxy_next_txn_state(
> +                                              dhcp_proxy->sm,
> +                                              node->txn_state,
> +                                              *in_dhcp_msg_type);
> +        if (nxt_state < DHCP_PROXY_TXN_STATE_INVALID) {
> +            //TODO: Check IP assigned by server is falling in giaddr subnet
> +            if (nxt_state == DHCP_PROXY_TXN_STATE_ASSIGNED) {
> +                node->client_ip = in_dhcp_data->yiaddr;
> +            }
> +            node->txn_state = nxt_state;
> +            node->epoch_ms = time_wall_msec();
> +            node->lease_time_ms = node->epoch_ms + ntohl(lease_time)*60*1000;
> +        }
> +        else {
> +            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> +            VLOG_WARN_RL(&rl, "DHCP_PROXY_RESP: invalid state transistion"
> +                              " for existing session");
> +            return;
> +        }
> +    }
> +    else {
> +          static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> +          VLOG_WARN_RL(&rl, "DHCP_PROXY_RESP: transaction not found");
> +          return;
> +    } 
> +
> +    /* Proxy the DHCP response packet */
> + 
> +    /* Replace Server Identifier Option */
> +    if (server_id_ptr) {
> +        put_unaligned_be32(server_id_ptr, *proxy_ip);
> +    }
> +
> +    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);
> +    dhcp_data->giaddr = htonl(0x0);
> +    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_orig, ip_dst;
> +    if (!is_dhcp_flags_broadcast(dhcp_data->flags)) {
> +        ip_dst = dhcp_data->yiaddr;
> +    } else {
> +        ip_dst = htonl(0xffffffff);
> +    }
> +    ip_dst_orig = get_16aligned_be32(&out_ip->ip_dst);
> +    if (ip_dst_orig != ip_dst)
> +    {
> +        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);
> +        }
> +    }
> +    //TODO: incremental checkcum
> +    if (udp->udp_csum) {
> +        udp->udp_csum = 0;
> +        uint32_t p_csum = packet_csum_pseudoheader(out_ip);
> +        udp->udp_csum = csum_finish(csum_continue(p_csum, udp, new_l4_size));
> +    }
> +
> +    pin->packet = dp_packet_data(&pkt_out);
> +    pin->packet_len = dp_packet_size(&pkt_out);
> +
> +    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_proxy_resp_fwd(
> +    struct rconn *swconn,
> +    struct dp_packet *pkt_in, struct ofputil_packet_in *pin,
> +    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;
> +
> +    /* Validate the DHCP request packet.
> +     * Format of the DHCP packet is
> +     * 
> ------------------------------------------------------------------------
> +     *| UDP HEADER  | DHCP HEADER  | 4 Byte DHCP Cookie | DHCP OPTIONS(var 
> len)|
> +     * 
> ------------------------------------------------------------------------
> +     */
> +
> +    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 = 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, "DHCP_PROXY_RESP_FWD: invalid or incomplete packet 
> received");
> +        return;
> +    }
> +
> +    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, "DHCP_PROXY_RESP_FWD: invalid or incomplete packet 
> received, "
> +                     "bad data length");
> +        return;
> +    }
> +    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_PROXY_RESP_FWD: invalid opcode in the 
> packet: %d",
> +                     in_dhcp_data->op);
> +        return;
> +    }
> +
> +    /* 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_PROXY_RESP_FWD: magic cookie not present in 
> the packet");
> +        return;
> +    }
> +
> +    if (!in_dhcp_data->giaddr) {
> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> +        VLOG_WARN_RL(&rl, "DHCP_PROXY_RESP_FWD: giaddr is not set in 
> request");
> +        return;
> +    }
> +
> +    /* 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);
> +
> +    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(
> @@ -3105,6 +3856,21 @@ process_packet_in(struct rconn *swconn, const struct 
> ofp_header *msg)
>         ovs_mutex_unlock(&pinctrl_mutex);
>         break;
> 
> +    case ACTION_OPCODE_DHCP_PROXY_REQ:
> +        pinctrl_handle_dhcp_proxy_req(swconn, &packet, &pin,
> +                                     &userdata, &continuation);
> +        break;
> +
> +    case ACTION_OPCODE_DHCP_PROXY_RESP:
> +        pinctrl_handle_dhcp_proxy_resp(swconn, &packet, &pin,
> +                                     &userdata, &continuation);
> +        break;
> +
> +    case ACTION_OPCODE_DHCP_PROXY_RESP_FWD:
> +        pinctrl_handle_dhcp_proxy_resp_fwd(swconn, &packet, &pin,
> +                                     &continuation);
> +        break;
> +
>     case ACTION_OPCODE_PUT_DHCP_OPTS:
>         pinctrl_handle_put_dhcp_opts(swconn, &packet, &pin, &headers,
>                                      &userdata, &continuation);
> diff --git a/include/ovn/actions.h b/include/ovn/actions.h
> index 23a919049..97b074b3e 100644
> --- a/include/ovn/actions.h
> +++ b/include/ovn/actions.h
> @@ -95,6 +95,9 @@ 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_PROXY_REQ,  ovnact_dhcp_proxy)      \
> +    OVNACT(DHCPV4_PROXY_RESP, ovnact_dhcp_proxy)      \
> +    OVNACT(DHCPV4_PROXY_RESP_FWD, ovnact_dhcp_proxy)      \
>     OVNACT(SET_QUEUE,         ovnact_set_queue)       \
>     OVNACT(DNS_LOOKUP,        ovnact_result)          \
>     OVNACT(LOG,               ovnact_log)             \
> @@ -386,6 +389,14 @@ struct ovnact_put_opts {
>     size_t n_options;
> };
> 
> +/* OVNACT_DHCP_PROXY. */
> +struct ovnact_dhcp_proxy {
> +    struct ovnact ovnact;
> +    int family;
> +    ovs_be32 proxy_ipv4;
> +    ovs_be32 server_ipv4;
> +};
> +
> /* Valid arguments to SET_QUEUE action.
>  *
>  * QDISC_MIN_QUEUE_ID is the default queue, so user-defined queues should
> @@ -746,6 +757,28 @@ enum action_opcode {
> 
>     /* activation_strategy_rarp() */
>     ACTION_OPCODE_ACTIVATION_STRATEGY_RARP,
> +
> +    /* "result = dhcp_proxy_req(proxy_ip, server_ip)".
> +     *
> +     * Arguments follow the action_header, in this format:
> +     *   - The 32-bit DHCP proxy IP.
> +     *   - The 32-bit DHCP server IP.
> +     */
> +    ACTION_OPCODE_DHCP_PROXY_REQ,
> +
> +    /* "result = dhcp_proxy_resp(proxy_ip, server_ip)".
> +     *
> +     * Arguments follow the action_header, in this format:
> +     *   - The 32-bit DHCP proxy IP.
> +     */
> +    ACTION_OPCODE_DHCP_PROXY_RESP,
> +
> +    /* "result = dhcp_proxy_resp_fwd()".
> +     *
> +     * Arguments follow the action_header, in this format:
> +     *   - The 32-bit DHCP proxy IP.
> +     */
> +    ACTION_OPCODE_DHCP_PROXY_RESP_FWD,
> };
> 
> /* Header. */
> diff --git a/lib/actions.c b/lib/actions.c
> index f89ccc6bf..402d6cbde 100644
> --- a/lib/actions.c
> +++ b/lib/actions.c
> @@ -2629,6 +2629,143 @@ ovnact_controller_event_free(struct 
> ovnact_controller_event *event)
>     free_gen_options(event->options, event->n_options);
> }
> 
> +static void
> +format_DHCPV4_PROXY_REQ(const struct ovnact_dhcp_proxy *dhcp_proxy, struct 
> ds *s)
> +{
> +    ds_put_format(s, "dhcp_proxy_req("IP_FMT","IP_FMT");",
> +                  IP_ARGS(dhcp_proxy->proxy_ipv4),
> +                  IP_ARGS(dhcp_proxy->server_ipv4));
> +}
> +
> +static void
> +parse_dhcp_proxy_req(struct action_context *ctx,
> +               struct ovnact_dhcp_proxy *dhcp_proxy)
> +{
> +    //lexer_get(ctx->lexer); /* Skip dhcp_proxy_req. */
> +    lexer_force_match(ctx->lexer, LEX_T_LPAREN);
> +    
> +    /* Parse proxy ip and server ip. */
> +    if (ctx->lexer->token.format == LEX_F_IPV4) {
> +        dhcp_proxy->family = AF_INET;
> +        dhcp_proxy->proxy_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_proxy->family = AF_INET;
> +            dhcp_proxy->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 proxy and 
> server ips");
> +          return;
> +    }
> +    lexer_force_match(ctx->lexer, LEX_T_RPAREN);
> +}
> +
> +static void
> +encode_DHCPV4_PROXY_REQ(const struct ovnact_dhcp_proxy *dhcp_proxy,
> +                    const struct ovnact_encode_params *ep,
> +                    struct ofpbuf *ofpacts)
> +{
> +    size_t oc_offset = 
> encode_start_controller_op(ACTION_OPCODE_DHCP_PROXY_REQ,
> +                                                  true, ep->ctrl_meter_id,
> +                                                  ofpacts);
> +    ofpbuf_put(ofpacts, &dhcp_proxy->proxy_ipv4, 
> sizeof(dhcp_proxy->proxy_ipv4));
> +    ofpbuf_put(ofpacts, &dhcp_proxy->server_ipv4, 
> sizeof(dhcp_proxy->server_ipv4));
> +    encode_finish_controller_op(oc_offset, ofpacts);
> +}
> +
> +static void
> +format_DHCPV4_PROXY_RESP(const struct ovnact_dhcp_proxy *dhcp_proxy, struct 
> ds *s)
> +{
> +    ds_put_format(s, "dhcp_proxy_resp("IP_FMT");",
> +                  IP_ARGS(dhcp_proxy->proxy_ipv4));
> +}
> +
> +static void
> +parse_dhcp_proxy_resp(struct action_context *ctx,
> +               struct ovnact_dhcp_proxy *dhcp_proxy)
> +{
> +    //lexer_get(ctx->lexer); /* Skip dhcp_proxy_resp. */
> +    lexer_force_match(ctx->lexer, LEX_T_LPAREN);
> +    
> +    /* Parse proxy ip and server ip. */
> +    if (ctx->lexer->token.format == LEX_F_IPV4) {
> +        dhcp_proxy->family = AF_INET;
> +        dhcp_proxy->proxy_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_proxy->family = AF_INET;
> +            dhcp_proxy->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 proxy and 
> server ips");
> +          return;
> +    }
> +    lexer_force_match(ctx->lexer, LEX_T_RPAREN);
> +}
> +
> +static void
> +encode_DHCPV4_PROXY_RESP(const struct ovnact_dhcp_proxy *dhcp_proxy,
> +                    const struct ovnact_encode_params *ep,
> +                    struct ofpbuf *ofpacts)
> +{
> +    size_t oc_offset = 
> encode_start_controller_op(ACTION_OPCODE_DHCP_PROXY_RESP,
> +                                                  true, ep->ctrl_meter_id,
> +                                                  ofpacts);
> +    ofpbuf_put(ofpacts, &dhcp_proxy->proxy_ipv4, 
> sizeof(dhcp_proxy->proxy_ipv4));
> +    ofpbuf_put(ofpacts, &dhcp_proxy->server_ipv4, 
> sizeof(dhcp_proxy->server_ipv4));
> +    encode_finish_controller_op(oc_offset, ofpacts);
> +}
> +
> +static void
> +format_DHCPV4_PROXY_RESP_FWD(const struct ovnact_dhcp_proxy *dhcp_proxy, 
> struct ds *s)
> +{
> +    ds_put_format(s, "dhcp_proxy_resp_fwd("IP_FMT");",
> +                  IP_ARGS(dhcp_proxy->proxy_ipv4));
> +}
> +
> +static void
> +parse_dhcp_proxy_resp_fwd(struct action_context *ctx,
> +               struct ovnact_dhcp_proxy *dhcp_proxy OVS_UNUSED)
> +{
> +    //lexer_get(ctx->lexer); /* Skip dhcp_proxy_resp_fwd. */
> +    lexer_force_match(ctx->lexer, LEX_T_LPAREN);
> +    lexer_force_match(ctx->lexer, LEX_T_RPAREN);
> +}
> +
> +static void
> +encode_DHCPV4_PROXY_RESP_FWD(
> +                    const struct ovnact_dhcp_proxy *dhcp_proxy OVS_UNUSED,
> +                    const struct ovnact_encode_params *ep,
> +                    struct ofpbuf *ofpacts)
> +{
> +    size_t oc_offset = 
> encode_start_controller_op(ACTION_OPCODE_DHCP_PROXY_RESP_FWD,
> +                                                  true, ep->ctrl_meter_id,
> +                                                  ofpacts);
> +    encode_finish_controller_op(oc_offset, ofpacts);
> +}
> +
> +static void ovnact_dhcp_proxy_free(struct ovnact_dhcp_proxy *dhcp_proxy 
> 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,
> @@ -5435,6 +5572,12 @@ parse_action(struct action_context *ctx)
>         parse_commit_lb_aff(ctx, ovnact_put_COMMIT_LB_AFF(ctx->ovnacts));
>     } else if (lexer_match_id(ctx->lexer, "sample")) {
>         parse_sample(ctx);
> +    } else if (lexer_match_id(ctx->lexer, "dhcp_proxy_req")) {
> +        parse_dhcp_proxy_req(ctx, ovnact_put_DHCPV4_PROXY_REQ(ctx->ovnacts));
> +    } else if (lexer_match_id(ctx->lexer, "dhcp_proxy_resp")) {
> +        parse_dhcp_proxy_resp(ctx, 
> ovnact_put_DHCPV4_PROXY_RESP(ctx->ovnacts));
> +    } else if (lexer_match_id(ctx->lexer, "dhcp_proxy_resp_fwd")) {
> +        parse_dhcp_proxy_resp_fwd(ctx, 
> ovnact_put_DHCPV4_PROXY_RESP_FWD(ctx->ovnacts));
>     } else {
>         lexer_syntax_error(ctx->lexer, "expecting action");
>     }
> diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
> index 9dc331421..94c4955ff 100644
> --- a/lib/ovn-l7.h
> +++ b/lib/ovn-l7.h
> @@ -69,6 +69,7 @@ struct gen_opts_map {
>  */
> #define OVN_DHCP_OPT_CODE_NETMASK      1
> #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
> 
> diff --git a/northd/northd.c b/northd/northd.c
> index 9a12a94ae..7e2fa1d22 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -157,8 +157,9 @@ enum ovn_stage {
>     PIPELINE_STAGE(SWITCH, OUT, QOS_MARK,     6, "ls_out_qos_mark")       \
>     PIPELINE_STAGE(SWITCH, OUT, QOS_METER,    7, "ls_out_qos_meter")      \
>     PIPELINE_STAGE(SWITCH, OUT, STATEFUL,     8, "ls_out_stateful")       \
> -    PIPELINE_STAGE(SWITCH, OUT, CHECK_PORT_SEC,  9, "ls_out_check_port_sec") 
> \
> -    PIPELINE_STAGE(SWITCH, OUT, APPLY_PORT_SEC, 10, "ls_out_apply_port_sec") 
> \
> +    PIPELINE_STAGE(SWITCH, OUT, DHCP_PROXY_RESP, 9, 
> "ls_out_dhcp_proxy_resp")       \
> +    PIPELINE_STAGE(SWITCH, OUT, CHECK_PORT_SEC,  10, 
> "ls_out_check_port_sec") \
> +    PIPELINE_STAGE(SWITCH, OUT, APPLY_PORT_SEC, 11, "ls_out_apply_port_sec") 
> \
>                                                                       \
>     /* Logical router ingress stages. */                              \
>     PIPELINE_STAGE(ROUTER, IN,  ADMISSION,       0, "lr_in_admission")    \
> @@ -178,11 +179,12 @@ enum ovn_stage {
>     PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING_ECMP, 14, "lr_in_ip_routing_ecmp") 
> \
>     PIPELINE_STAGE(ROUTER, IN,  POLICY,          15, "lr_in_policy")          
> \
>     PIPELINE_STAGE(ROUTER, IN,  POLICY_ECMP,     16, "lr_in_policy_ecmp")     
> \
> -    PIPELINE_STAGE(ROUTER, IN,  ARP_RESOLVE,     17, "lr_in_arp_resolve")    
>  \
> -    PIPELINE_STAGE(ROUTER, IN,  CHK_PKT_LEN,     18, "lr_in_chk_pkt_len")    
>  \
> -    PIPELINE_STAGE(ROUTER, IN,  LARGER_PKTS,     19, "lr_in_larger_pkts")    
>  \
> -    PIPELINE_STAGE(ROUTER, IN,  GW_REDIRECT,     20, "lr_in_gw_redirect")    
>  \
> -    PIPELINE_STAGE(ROUTER, IN,  ARP_REQUEST,     21, "lr_in_arp_request")    
>  \
> +    PIPELINE_STAGE(ROUTER, IN,  DHCP_PROXY_RESP_FWD, 17, 
> "lr_in_dhcp_proxy_resp_fwd") \
> +    PIPELINE_STAGE(ROUTER, IN,  ARP_RESOLVE,     18, "lr_in_arp_resolve")    
>  \
> +    PIPELINE_STAGE(ROUTER, IN,  CHK_PKT_LEN,     19, "lr_in_chk_pkt_len")    
>  \
> +    PIPELINE_STAGE(ROUTER, IN,  LARGER_PKTS,     20, "lr_in_larger_pkts")    
>  \
> +    PIPELINE_STAGE(ROUTER, IN,  GW_REDIRECT,     21, "lr_in_gw_redirect")    
>  \
> +    PIPELINE_STAGE(ROUTER, IN,  ARP_REQUEST,     22, "lr_in_arp_request")    
>  \
>                                                                       \
>     /* Logical router egress stages. */                               \
>     PIPELINE_STAGE(ROUTER, OUT, CHECK_DNAT_LOCAL,   0,                       \
> @@ -9110,6 +9112,103 @@ build_dhcpv6_options_flows(struct ovn_port *op,
>     ds_destroy(&match);
> }
> 
> +static void
> +build_lswitch_dhcp_proxy_flows(struct ovn_port *op,
> +                           const struct hmap *lr_ports,
> +                           const struct hmap *lflows,
> +                           const struct shash *meter_groups OVS_UNUSED)
> +{
> +    if (op->nbrp || !op->nbsp) {
> +        return;
> +    }
> +    //consider only ports attached to VMs
> +    if (strcmp(op->nbsp->type, "")) {
> +        return;
> +    }
> +
> +    if (!op->od || !op->od->n_router_ports ||
> +        !op->od->nbs || !op->od->nbs->dhcp_proxy_port) {
> +        return;
> +    }
> +
> +    struct ds match = DS_EMPTY_INITIALIZER;
> +    struct ds action = DS_EMPTY_INITIALIZER;
> +    struct nbrec_logical_router_port *lrp = op->od->nbs->dhcp_proxy_port;
> +    struct ovn_port *rp = ovn_port_find(lr_ports, lrp->name);
> +
> +    if (!rp || !rp->nbrp || !rp->nbrp->dhcp_proxy) {
> +        return;
> +    }
> +
> +    struct ovn_port *sp = NULL;
> +    struct nbrec_dhcp_proxy *dhcp_proxy = rp->nbrp->dhcp_proxy;
> +
> +    for (int i=0; i<op->od->n_router_ports; i++) {
> +        struct ovn_port *sp_tmp = op->od->router_ports[i];
> +        if (sp_tmp->peer == rp) {
> +            sp = sp_tmp;
> +            break;
> +        }
> +    }
> +    if (!sp) {
> +      return;
> +    }
> +    
> +    char *server_ip_str = NULL;
> +    uint16_t port;
> +    int addr_family;
> +    struct in6_addr server_ip;
> + 
> +    if (!ip_address_and_port_from_lb_key(dhcp_proxy->servers, &server_ip_str,
> +                                         &server_ip, &port, &addr_family)) {
> +        return;
> +    }
> +
> +    if (server_ip_str == NULL) {
> +        return;
> +    }
> +
> +    ds_put_format(
> +        &match, "inport == %s && eth.src == %s && "
> +        "ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && "
> +        "udp.src == 68 && udp.dst == 67",
> +        op->json_key, op->lsp_addrs[0].ea_s);
> +    ds_put_format(&action,
> +                  "eth.dst=%s;outport=%s;next;/* DHCP_PROXY_REQ */",
> +                  rp->lrp_networks.ea_s,sp->json_key);
> +    ovn_lflow_add_with_hint__(lflows, op->od,
> +                              S_SWITCH_IN_L2_LKUP, 100,
> +                              ds_cstr(&match),
> +                              ds_cstr(&action),
> +                              op->key,
> +                              NULL,
> +                              &lrp->header_);
> +    ds_clear(&match);
> +    ds_clear(&action);
> +
> +    ds_put_format(
> +        &match, "inport == %s && eth.src == %s && outport == %s &&"
> +        "ip4.src == %s && "
> +        "udp.src == 67 && udp.dst == 67",
> +        sp->json_key, rp->lrp_networks.ea_s, op->json_key, server_ip_str);
> +    ds_put_format(&action,
> +                  "dhcp_proxy_resp(%s,%s);ip4.src=%s;udp.dst=68;next;"
> +                  "/* DHCP_PROXY_RESP */",
> +                  rp->lrp_networks.ipv4_addrs[0].addr_s,
> +                  server_ip_str,
> +                  rp->lrp_networks.ipv4_addrs[0].addr_s);
> +    ovn_lflow_add_with_hint__(lflows, op->od,
> +                              S_SWITCH_OUT_DHCP_PROXY_RESP, 100,
> +                              ds_cstr(&match),
> +                              ds_cstr(&action),
> +                              op->key,
> +                              NULL,
> +                              &lrp->header_);
> +    ds_clear(&match);
> +    ds_clear(&action);
> +    free(server_ip_str);
> +}
> +
> static void
> build_drop_arp_nd_flows_for_unbound_router_ports(struct ovn_port *op,
>                                                  const struct ovn_port *port,
> @@ -9672,6 +9771,13 @@ build_lswitch_dhcp_options_and_response(struct 
> ovn_port *op,
>         return;
>     }
> 
> +    if (op->od && op->od->nbs
> +        && op->od->nbs->dhcp_proxy_port) {
> +        /* Don't add the DHCP server flows if DHCP Proxy is enabled on the
> +         * logical switch. */            
> +        return;
> +    }
> +
>     bool is_external = lsp_is_external(op->nbsp);
>     if (is_external && (!op->od->n_localnet_ports ||
>                         !op->nbsp->ha_chassis_group)) {
> @@ -9719,6 +9825,7 @@ build_lswitch_dhcp_and_dns_defaults(struct ovn_datapath 
> *od,
>     ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 0, "1", "next;");
>     ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 0, "1", "next;");
>     ovn_lflow_add(lflows, od, S_SWITCH_IN_EXTERNAL_PORT, 0, "1", "next;");
> +    ovn_lflow_add(lflows, od, S_SWITCH_OUT_DHCP_PROXY_RESP, 0, "1", "next;");
> }
> 
> /* Logical switch ingress table 22 and 23: DNS lookup and response
> @@ -13918,6 +14025,100 @@ build_dhcpv6_reply_flows_for_lrouter_port(
>     }
> }
> 
> +static void
> +build_dhcp_proxy_flows_for_lrouter_port(
> +        struct ovn_port *op, struct hmap *lflows,
> +        struct ds *match)
> +{
> +    if (!op->nbrp || !op->nbrp->dhcp_proxy) {
> +        return;
> +    }
> +    struct nbrec_dhcp_proxy *dhcp_proxy = op->nbrp->dhcp_proxy;
> +    if (!dhcp_proxy->servers) {
> +        return;
> +    }
> +
> +    int addr_family;
> +    uint16_t port;
> +    char *server_ip_str = NULL;
> +    struct in6_addr server_ip;
> +    
> +    if (!ip_address_and_port_from_lb_key(dhcp_proxy->servers, &server_ip_str,
> +                                         &server_ip, &port, &addr_family)) {
> +        return;
> +    }
> +
> +    if (server_ip_str == NULL) {
> +        return;
> +    }
> +
> +    struct ds dhcp_action = DS_EMPTY_INITIALIZER; 
> +    ds_clear(match);
> +    ds_put_format(
> +        match, "inport == %s && "
> +        "ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && "
> +        "udp.src == 68 && udp.dst == 67",
> +        op->json_key);
> +    ds_put_format(&dhcp_action,
> +                "dhcp_proxy_req(%s,%s);"
> +                "ip4.src=%s;ip4.dst=%s;udp.src=67;next; /* DHCP_PROXY_REQ 
> */",
> +                op->lrp_networks.ipv4_addrs[0].addr_s, server_ip_str,
> +                op->lrp_networks.ipv4_addrs[0].addr_s, server_ip_str);
> +
> +    ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110,
> +                            ds_cstr(match), ds_cstr(&dhcp_action),
> +                            &op->nbrp->header_);
> +
> +    ds_clear(match);
> +    ds_clear(&dhcp_action);
> +
> +    ds_put_format(
> +        match, "inport == %s && "
> +        "ip4.dst == %s && "
> +        "udp.src == 68 && udp.dst == 67",
> +        op->json_key, op->lrp_networks.ipv4_addrs[0].addr_s);
> +
> +    ds_put_format(&dhcp_action,
> +                "dhcp_proxy_req(%s,%s);"
> +                "ip4.src=%s;ip4.dst=%s;udp.src=67;next; /* DHCP_PROXY_REQ 
> */",
> +                op->lrp_networks.ipv4_addrs[0].addr_s, server_ip_str,
> +                op->lrp_networks.ipv4_addrs[0].addr_s, server_ip_str);
> +    ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110,
> +                            ds_cstr(match), ds_cstr(&dhcp_action),
> +                            &op->nbrp->header_);
> +    ds_clear(match);
> +    ds_clear(&dhcp_action);
> +
> +    ds_put_format(
> +        match, "ip4.src == %s && ip4.dst == %s && "
> +        "udp.src == 67 && udp.dst == 67",
> +        server_ip_str, op->lrp_networks.ipv4_addrs[0].addr_s);
> +    ds_put_format(&dhcp_action, "next;/* DHCP_PROXY_RESP */");
> +    ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110,
> +                            ds_cstr(match), ds_cstr(&dhcp_action),
> +                            &op->nbrp->header_);
> +
> +    ds_clear(match);
> +    ds_clear(&dhcp_action);
> +
> +    ds_put_format(
> +        match, "ip4.src == %s && ip4.dst == %s && "
> +        "udp.src == 67 && udp.dst == 67",
> +        server_ip_str, op->lrp_networks.ipv4_addrs[0].addr_s);
> +    ds_put_format(&dhcp_action,
> +          "dhcp_proxy_resp_fwd();outport=%s;output; /* DHCP_PROXY_RESP */",
> +          op->json_key);
> +    ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_DHCP_PROXY_RESP_FWD,
> +                            110,
> +                            ds_cstr(match), ds_cstr(&dhcp_action),
> +                            &op->nbrp->header_);
> +
> +    ds_clear(match);
> +    ds_clear(&dhcp_action);
> +
> +    free(server_ip_str);
> +}
> +
> static void
> build_ipv6_input_flows_for_lrouter_port(
>         struct ovn_port *op, struct hmap *lflows,
> @@ -15134,6 +15335,7 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath 
> *od, struct hmap *lflows,
>     ovn_lflow_add(lflows, od, S_ROUTER_OUT_POST_SNAT, 0, "1", "next;");
>     ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;");
>     ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;");
> +    ovn_lflow_add(lflows, od, S_ROUTER_IN_DHCP_PROXY_RESP_FWD, 0, "1", 
> "next;");
> 
>     const char *ct_flag_reg = features->ct_no_masked_label
>                               ? "ct_mark"
> @@ -15614,6 +15816,7 @@ build_lswitch_and_lrouter_iterate_by_lsp(struct 
> ovn_port *op,
>     build_lswitch_dhcp_options_and_response(op, lflows, meter_groups);
>     build_lswitch_external_port(op, lflows);
>     build_lswitch_ip_unicast_lookup(op, lflows, actions, match);
> +    build_lswitch_dhcp_proxy_flows(op, lr_ports, lflows, meter_groups);
> 
>     /* Build Logical Router Flows. */
>     build_ip_routing_flows_for_router_type_lsp(op, lr_ports, lflows);
> @@ -15643,6 +15846,7 @@ build_lswitch_and_lrouter_iterate_by_lrp(struct 
> ovn_port *op,
>     build_egress_delivery_flows_for_lrouter_port(op, lsi->lflows, &lsi->match,
>                                                  &lsi->actions);
>     build_dhcpv6_reply_flows_for_lrouter_port(op, lsi->lflows, &lsi->match);
> +    build_dhcp_proxy_flows_for_lrouter_port(op, lsi->lflows, &lsi->match);
>     build_ipv6_input_flows_for_lrouter_port(op, lsi->lflows,
>                                             &lsi->match, &lsi->actions,
>                                             lsi->meter_groups);
> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
> index f8bac5302..d448e2bfe 100644
> --- a/ovn-nb.ovsschema
> +++ b/ovn-nb.ovsschema
> @@ -1,7 +1,7 @@
> {
>     "name": "OVN_Northbound",
>     "version": "7.0.4",
> -    "cksum": "1676649201 33795",
> +    "cksum": "2503700777 34818",
>     "tables": {
>         "NB_Global": {
>             "columns": {
> @@ -89,7 +89,12 @@
>                     "type": {"key": {"type": "uuid",
>                                      "refTable": "Forwarding_Group",
>                                      "refType": "strong"},
> -                                     "min": 0, "max": "unlimited"}}},
> +                                     "min": 0, "max": "unlimited"}},
> +                "dhcp_proxy_port": {"type": {"key": {"type": "uuid",
> +                                            "refTable": 
> "Logical_Router_Port",
> +                                            "refType": "weak"},
> +                                            "min": 0,
> +                                            "max": 1}}},
>             "isRoot": true},
>         "Logical_Switch_Port": {
>             "columns": {
> @@ -436,6 +441,11 @@
>                 "ipv6_prefix": {"type": {"key": "string",
>                                       "min": 0,
>                                       "max": "unlimited"}},
> +                "dhcp_proxy": {"type": {"key": {"type": "uuid",
> +                                            "refTable": "DHCP_Proxy",
> +                                            "refType": "weak"},
> +                                            "min": 0,
> +                                            "max": 1}},
>                 "external_ids": {
>                     "type": {"key": "string", "value": "string",
>                              "min": 0, "max": "unlimited"}}},
> @@ -526,6 +536,15 @@
>                     "type": {"key": "string", "value": "string",
>                              "min": 0, "max": "unlimited"}}},
>             "isRoot": true},
> +        "DHCP_Proxy": {
> +            "columns": {
> +                "servers": {"type": {"key": "string",
> +                                       "min": 0,
> +                                       "max": 1}},
> +                "external_ids": {
> +                    "type": {"key": "string", "value": "string",
> +                             "min": 0, "max": "unlimited"}}},
> +            "isRoot": true},
>         "Connection": {
>             "columns": {
>                 "target": {"type": "string"},
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index 4fbf4f7e5..00e4c41fc 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -572,6 +572,11 @@
>       Please see the <ref table="DNS"/> table.
>     </column>
> 
> +    <column name="dhcp_proxy_port">
> +      This column defines the <ref table="Logical_Router_Port"/> on which
> +      DHCP proxy is enabled.
> +    </column>
> +
>     <column name="forwarding_groups">
>       Groups a set of logical port endpoints for traffic going out of the
>       logical switch.
> @@ -2944,6 +2949,10 @@ or
>       port has all ingress and egress traffic dropped.
>     </column>
> 
> +    <column name="dhcp_proxy">
> +      This column is used to enabled DHCP Proxy. Please refer to <ref 
> table="DHCP_Proxy"/> table.
> +    </column>
> +
>     <group title="Distributed Gateway Ports">
>       <p>
>         Gateways, as documented under <code>Gateways</code> in the OVN
> @@ -4231,6 +4240,24 @@ or
>     </group>
>   </table>
> 
> +  <table name="DHCP_Proxy" title="DHCP Proxy">
> +    <p>
> +      OVN implements native DHCPv4 proxy support which caters to the common
> +      use case of proxying the DHCP requests to external DHCP server.
> +    </p>
> +
> +    <column name="servers">
> +      <p>
> +        The DHCPv4 server IP address.
> +      </p>
> +    </column>
> +    <group title="Common Columns">
> +      <column name="external_ids">
> +        See <em>External IDs</em> at the beginning of this document.
> +      </column>
> +    </group>
> +  </table>
> +
>   <table name="Connection" title="OVSDB client connections.">
>     <p>
>       Configuration for a database connection to an Open vSwitch database
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index d5be3be75..ace0c5c34 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -8206,6 +8206,8 @@ check ovn-nbctl --wait=sb ls-add sw0
> 
> ovn-sbctl dump-flows sw0 > sw0flows
> AT_CAPTURE_FILE([sw0flows])
> +echo "FLOWS"
> +cat sw0flows | grep -e port_sec -e ls_in_l2_lkup -e ls_in_l2_unknown | sort 
> | sed 's/table=../table=??/'
> 
> AT_CHECK([cat sw0flows | grep -e port_sec -e ls_in_l2_lkup -e 
> ls_in_l2_unknown | \
> sort | sed 's/table=../table=??/' ], [0], [dnl
> @@ -8214,6 +8216,8 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
>   table=??(ls_in_check_port_sec), priority=50   , match=(1), 
> action=(reg0[[15]] = check_in_port_sec(); next;)
>   table=??(ls_in_apply_port_sec), priority=0    , match=(1), action=(next;)
>   table=??(ls_in_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), 
> action=(drop;)
> +  table=??(ls_out_check_port_sec), priority=0    , match=(1), 
> action=(reg0[[15]] = check_out_port_sec(); next;)
> +  table=??(ls_out_check_port_sec), priority=100  , match=(eth.mcast), 
> action=(reg0[[15]] = 0; next;)
>   table=??(ls_out_apply_port_sec), priority=0    , match=(1), action=(output;)
>   table=??(ls_out_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), 
> action=(drop;)
>   table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = 
> get_fdb(eth.dst); next;)
> @@ -8221,8 +8225,6 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
>   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), 
> action=(outport = "_MC_flood"; output;)
>   table=??(ls_in_l2_unknown   ), priority=0    , match=(1), action=(output;)
>   table=??(ls_in_l2_unknown   ), priority=50   , match=(outport == "none"), 
> action=(drop;)
> -  table=??(ls_out_check_port_sec), priority=0    , match=(1), 
> action=(reg0[[15]] = check_out_port_sec(); next;)
> -  table=??(ls_out_check_port_sec), priority=100  , match=(eth.mcast), 
> action=(reg0[[15]] = 0; next;)
> ])
> 
> check ovn-nbctl lsp-add sw0 sw0p1 -- lsp-set-addresses sw0p1 
> "00:00:00:00:00:01"
> @@ -8239,6 +8241,8 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
>   table=??(ls_in_check_port_sec), priority=50   , match=(1), 
> action=(reg0[[15]] = check_in_port_sec(); next;)
>   table=??(ls_in_apply_port_sec), priority=0    , match=(1), action=(next;)
>   table=??(ls_in_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), 
> action=(drop;)
> +  table=??(ls_out_check_port_sec), priority=0    , match=(1), 
> action=(reg0[[15]] = check_out_port_sec(); next;)
> +  table=??(ls_out_check_port_sec), priority=100  , match=(eth.mcast), 
> action=(reg0[[15]] = 0; next;)
>   table=??(ls_out_apply_port_sec), priority=0    , match=(1), action=(output;)
>   table=??(ls_out_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), 
> action=(drop;)
>   table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = 
> get_fdb(eth.dst); next;)
> @@ -8248,8 +8252,6 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
>   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), 
> action=(outport = "_MC_flood"; output;)
>   table=??(ls_in_l2_unknown   ), priority=0    , match=(1), action=(output;)
>   table=??(ls_in_l2_unknown   ), priority=50   , match=(outport == "none"), 
> action=(drop;)
> -  table=??(ls_out_check_port_sec), priority=0    , match=(1), 
> action=(reg0[[15]] = check_out_port_sec(); next;)
> -  table=??(ls_out_check_port_sec), priority=100  , match=(eth.mcast), 
> action=(reg0[[15]] = 0; next;)
> ])
> 
> check ovn-nbctl lsp-set-port-security sw0p1 "00:00:00:00:00:01 10.0.0.3 
> 1000::3"
> @@ -8265,6 +8267,8 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
>   table=??(ls_in_check_port_sec), priority=50   , match=(1), 
> action=(reg0[[15]] = check_in_port_sec(); next;)
>   table=??(ls_in_apply_port_sec), priority=0    , match=(1), action=(next;)
>   table=??(ls_in_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), 
> action=(drop;)
> +  table=??(ls_out_check_port_sec), priority=0    , match=(1), 
> action=(reg0[[15]] = check_out_port_sec(); next;)
> +  table=??(ls_out_check_port_sec), priority=100  , match=(eth.mcast), 
> action=(reg0[[15]] = 0; next;)
>   table=??(ls_out_apply_port_sec), priority=0    , match=(1), action=(output;)
>   table=??(ls_out_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), 
> action=(drop;)
>   table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = 
> get_fdb(eth.dst); next;)
> @@ -8274,8 +8278,6 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
>   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), 
> action=(outport = "_MC_flood"; output;)
>   table=??(ls_in_l2_unknown   ), priority=0    , match=(1), action=(output;)
>   table=??(ls_in_l2_unknown   ), priority=50   , match=(outport == "none"), 
> action=(drop;)
> -  table=??(ls_out_check_port_sec), priority=0    , match=(1), 
> action=(reg0[[15]] = check_out_port_sec(); next;)
> -  table=??(ls_out_check_port_sec), priority=100  , match=(eth.mcast), 
> action=(reg0[[15]] = 0; next;)
> ])
> 
> # Disable sw0p1
> @@ -8292,6 +8294,8 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
>   table=??(ls_in_check_port_sec), priority=50   , match=(1), 
> action=(reg0[[15]] = check_in_port_sec(); next;)
>   table=??(ls_in_apply_port_sec), priority=0    , match=(1), action=(next;)
>   table=??(ls_in_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), 
> action=(drop;)
> +  table=??(ls_out_check_port_sec), priority=0    , match=(1), 
> action=(reg0[[15]] = check_out_port_sec(); next;)
> +  table=??(ls_out_check_port_sec), priority=100  , match=(eth.mcast), 
> action=(reg0[[15]] = 0; next;)
>   table=??(ls_out_apply_port_sec), priority=0    , match=(1), action=(output;)
>   table=??(ls_out_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), 
> action=(drop;)
>   table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = 
> get_fdb(eth.dst); next;)
> @@ -8302,8 +8306,6 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
>   table=??(ls_in_l2_unknown   ), priority=0    , match=(1), action=(output;)
>   table=??(ls_in_l2_unknown   ), priority=50   , match=(outport == "none"), 
> action=(drop;)
>   table=??(ls_in_l2_unknown   ), priority=50   , match=(outport == "sw0p1"), 
> action=(drop;)
> -  table=??(ls_out_check_port_sec), priority=0    , match=(1), 
> action=(reg0[[15]] = check_out_port_sec(); next;)
> -  table=??(ls_out_check_port_sec), priority=100  , match=(eth.mcast), 
> action=(reg0[[15]] = 0; next;)
> ])
> 
> check ovn-nbctl --wait=sb lsp-set-options sw0p2 qdisc_queue_id=10
> @@ -8319,6 +8321,8 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
>   table=??(ls_in_check_port_sec), priority=70   , match=(inport == "sw0p2"), 
> action=(set_queue(10); reg0[[15]] = check_in_port_sec(); next;)
>   table=??(ls_in_apply_port_sec), priority=0    , match=(1), action=(next;)
>   table=??(ls_in_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), 
> action=(drop;)
> +  table=??(ls_out_check_port_sec), priority=0    , match=(1), 
> action=(reg0[[15]] = check_out_port_sec(); next;)
> +  table=??(ls_out_check_port_sec), priority=100  , match=(eth.mcast), 
> action=(reg0[[15]] = 0; next;)
>   table=??(ls_out_apply_port_sec), priority=0    , match=(1), action=(output;)
>   table=??(ls_out_apply_port_sec), priority=110  , match=(outport == 
> "localnetport" && inport == "sw0p2"), action=(set_queue(10); output;)
>   table=??(ls_out_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), 
> action=(drop;)
> @@ -8330,8 +8334,6 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
>   table=??(ls_in_l2_unknown   ), priority=0    , match=(1), action=(output;)
>   table=??(ls_in_l2_unknown   ), priority=50   , match=(outport == "none"), 
> action=(drop;)
>   table=??(ls_in_l2_unknown   ), priority=50   , match=(outport == "sw0p1"), 
> action=(drop;)
> -  table=??(ls_out_check_port_sec), priority=0    , match=(1), 
> action=(reg0[[15]] = check_out_port_sec(); next;)
> -  table=??(ls_out_check_port_sec), priority=100  , match=(eth.mcast), 
> action=(reg0[[15]] = 0; next;)
> ])
> 
> check ovn-nbctl set logical_switch_port sw0p1 enabled=true
> @@ -8350,6 +8352,8 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
>   table=??(ls_in_check_port_sec), priority=70   , match=(inport == "sw0p2"), 
> action=(set_queue(10); reg0[[15]] = check_in_port_sec(); next;)
>   table=??(ls_in_apply_port_sec), priority=0    , match=(1), action=(next;)
>   table=??(ls_in_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), 
> action=(drop;)
> +  table=??(ls_out_check_port_sec), priority=0    , match=(1), 
> action=(reg0[[15]] = check_out_port_sec(); next;)
> +  table=??(ls_out_check_port_sec), priority=100  , match=(eth.mcast), 
> action=(reg0[[15]] = 0; next;)
>   table=??(ls_out_apply_port_sec), priority=0    , match=(1), action=(output;)
>   table=??(ls_out_apply_port_sec), priority=100  , match=(outport == 
> "localnetport"), action=(set_queue(10); output;)
>   table=??(ls_out_apply_port_sec), priority=110  , match=(outport == 
> "localnetport" && inport == "sw0p2"), action=(set_queue(10); output;)
> @@ -8361,8 +8365,6 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
>   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), 
> action=(outport = "_MC_flood"; output;)
>   table=??(ls_in_l2_unknown   ), priority=0    , match=(1), action=(output;)
>   table=??(ls_in_l2_unknown   ), priority=50   , match=(outport == "none"), 
> action=(drop;)
> -  table=??(ls_out_check_port_sec), priority=0    , match=(1), 
> action=(reg0[[15]] = check_out_port_sec(); next;)
> -  table=??(ls_out_check_port_sec), priority=100  , match=(eth.mcast), 
> action=(reg0[[15]] = 0; next;)
> ])
> 
> AT_CLEANUP
> @@ -8684,9 +8686,9 @@ ovn-nbctl --wait=sb set logical_router_port R1-PUB 
> options:redirect-type=bridged
> ovn-sbctl dump-flows R1 > R1flows
> AT_CAPTURE_FILE([R1flows])
> 
> -AT_CHECK([grep "lr_in_arp_resolve" R1flows | grep priority=90 | sort], [0], 
> [dnl
> -  table=17(lr_in_arp_resolve  ), priority=90   , match=(outport == "R1-PUB" 
> && ip4.src == 10.0.0.3 && is_chassis_resident("S0-P0")), 
> action=(get_arp(outport, reg0); next;)
> -  table=17(lr_in_arp_resolve  ), priority=90   , match=(outport == "R1-PUB" 
> && ip6.src == 1000::3 && is_chassis_resident("S0-P0")), 
> action=(get_nd(outport, xxreg0); next;)
> +AT_CHECK([grep "lr_in_arp_resolve" R1flows | grep priority=90 | sed 
> 's/table=../table=??/' | sort], [0], [dnl
> +  table=??(lr_in_arp_resolve  ), priority=90   , match=(outport == "R1-PUB" 
> && ip4.src == 10.0.0.3 && is_chassis_resident("S0-P0")), 
> action=(get_arp(outport, reg0); next;)
> +  table=??(lr_in_arp_resolve  ), priority=90   , match=(outport == "R1-PUB" 
> && ip6.src == 1000::3 && is_chassis_resident("S0-P0")), 
> action=(get_nd(outport, xxreg0); next;)
> ])
> 
> AT_CLEANUP
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 2cf5d5169..03e4d77e5 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -21871,7 +21871,7 @@ eth_dst=00000000ff01
> ip_src=$(ip_to_hex 10 0 0 10)
> ip_dst=$(ip_to_hex 172 168 0 101)
> send_icmp_packet 1 1 $eth_src $eth_dst $ip_src $ip_dst c4c9 
> 0000000000000000000000
> -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int metadata=0x$lr0_dp_key | awk 
> '/table=28, n_packets=1, n_bytes=45/{print $7" "$8}'],[0],[dnl
> +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int metadata=0x$lr0_dp_key | awk 
> '/table=29, n_packets=1, n_bytes=45/{print $7" "$8}'],[0],[dnl
> priority=80,ip,reg15=0x3,metadata=0x3,nw_src=10.0.0.10 actions=drop
> ])
> 
> @@ -28888,7 +28888,7 @@ AT_CHECK([
>         grep "priority=100" | \
>         grep -c 
> "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],.*exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_MARK\\[[16..31\\]]))"
> 
> -        grep table=25 hv${hv}flows | \
> +        grep table=26 hv${hv}flows | \
>         grep "priority=200" | \
>         grep -c 
> "move:NXM_NX_CT_LABEL\\[[\\]]->NXM_NX_XXREG1\\[[\\]],move:NXM_NX_XXREG1\\[[32..79\\]]->NXM_OF_ETH_DST"
>     done; :], [0], [dnl
> @@ -29013,7 +29013,7 @@ AT_CHECK([
>         grep "priority=100" | \
>         grep -c 
> "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],.*exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_MARK\\[[16..31\\]]))"
> 
> -        grep table=25 hv${hv}flows | \
> +        grep table=26 hv${hv}flows | \
>         grep "priority=200" | \
>         grep -c 
> "move:NXM_NX_CT_LABEL\\[[\\]]->NXM_NX_XXREG1\\[[\\]],move:NXM_NX_XXREG1\\[[32..79\\]]->NXM_OF_ETH_DST"
>     done; :], [0], [dnl
> @@ -29510,7 +29510,7 @@ if test X"$1" = X"DGP"; then
> else
>     prio=2
> fi
> -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=25, 
> n_packets=1,.* 
> priority=$prio,ip,$inport.*$outport.*metadata=0x${sw_key},nw_dst=10.0.1.1 
> actions=drop" -c], [0], [dnl
> +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=26, 
> n_packets=1,.* 
> priority=$prio,ip,$inport.*$outport.*metadata=0x${sw_key},nw_dst=10.0.1.1 
> actions=drop" -c], [0], [dnl
> 1
> ])
> 
> @@ -29529,13 +29529,13 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep 
> "actions=controller" | grep
> 
> if test X"$1" = X"DGP"; then
>     # The packet dst should be resolved once for E/W centralized NAT purpose.
> -    AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=25, 
> n_packets=1,.* priority=100,reg0=0xa000101,reg15=.*metadata=0x${sw_key} 
> actions=mod_dl_dst:00:00:00:00:01:01,resubmit" -c], [0], [dnl
> +    AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=26, 
> n_packets=1,.* priority=100,reg0=0xa000101,reg15=.*metadata=0x${sw_key} 
> actions=mod_dl_dst:00:00:00:00:01:01,resubmit" -c], [0], [dnl
> 1
> ])
> fi
> 
> # The packet should've been finally dropped in the lr_in_arp_resolve stage.
> -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=25, 
> n_packets=2,.* 
> priority=$prio,ip,$inport.*$outport.*metadata=0x${sw_key},nw_dst=10.0.1.1 
> actions=drop" -c], [0], [dnl
> +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=26, 
> n_packets=2,.* 
> priority=$prio,ip,$inport.*$outport.*metadata=0x${sw_key},nw_dst=10.0.1.1 
> actions=drop" -c], [0], [dnl
> 1
> ])
> OVN_CLEANUP([hv1])
> diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
> index fb54ed060..9a0cbeafe 100644
> --- a/utilities/ovn-trace.c
> +++ b/utilities/ovn-trace.c
> @@ -3205,6 +3205,18 @@ trace_actions(const struct ovnact *ovnacts, size_t 
> ovnacts_len,
>                                        super);
>             break;
> 
> +        case OVNACT_DHCPV4_PROXY_REQ:
> +            /* TODO. */
> +            break;
> +
> +        case OVNACT_DHCPV4_PROXY_RESP:
> +            /* TODO. */
> +            break;
> +
> +        case OVNACT_DHCPV4_PROXY_RESP_FWD:
> +            /* TODO. */
> +            break;
> +
>         case OVNACT_PUT_DHCPV4_OPTS:
>             execute_put_dhcp_opts(ovnact_get_PUT_DHCPV4_OPTS(a),
>                                   "put_dhcp_opts", uflow, super);
> -- 
> 2.36.6
> 

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to