On Fri, Sep 8, 2023 at 12:04 AM Naveen Yerramneni
<naveen.yerramn...@nutanix.com> wrote:
>
> Hi Team,
>
> Could someone please look into this ?
>
> Thanks,
> Naveen
>
> > On 23-Aug-2023, at 8:06 AM, Naveen Yerramneni 
> > <naveen.yerramn...@nutanix.com> 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>
> >

Hi Naveen,

Thanks for the RFC patch.  This is not a full review.  I have a few questions

1.  Is there an RFC for DHCP proxy ?  If so, can you please share and
link it in the patch documentation ?
     If not, can you share any documentation/standard for DHCP proxy ?

2.  Can you please provide a few examples on how a logical port is
created ?  What address would be set for the logical port ?
     And once a VM gets IP using dhcp proxy,  is this IP address
stored in OVN Northbound db Logical_Switch_Port ?
     How does OVN learn about this mac-ip binding for a VM and forward
the packet later for any E-W or N-S traffic ?

3.  Is it possible to handle all this DHCP proxy in the logical switch
pipeline itself ?  In a typical deployment where DHCP proxy is used,
    Who does the DHCP proxy ? Is it the router ?


Thanks
Numan


> > 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 <naveen.yerramn...@nutanix.com>
> > Co-authored-by: Huzaifa Calcuttawala <huzaif...@nutanix.com>
> > Signed-off-by: Huzaifa Calcuttawala <huzaif...@nutanix.com>
> > CC: Mary Manohar <mary.mano...@nutanix.com>
> > CC: Abhiram Sangana <sangana.abhi...@nutanix.com>
> > ---
> > 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
> d...@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to