On Fri, Nov 3, 2023 at 1:36 PM naveen.yerramneni
<[email protected]> wrote:
>
> This patch contains changes to enable DHCP Relay Agent support for
> overlay subnets.
>
> NOTE:
> -----
> - This patch has required changes to enable basic DHCP Relay
> functionality for overlay subnets. Sending this for review to get the initial
> feedback about the approach taken.
>
> POST RFC REVIEW
> ----------------
> 1. Address review comments/suggestions
> 2. Address TODOs
> 3. Add unit tests
> 4. Complete testing
>
> 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 relaying 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 rleay
> functionality in OVN.
> 1. DHCP client originates DHCP discovery (broadcast).
> 2. DHCP relay (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 relay agent forwards the offer to the client, it resets the
> GIADDR field when forwarding the offer to the client.
> 5. DHCP client sends DHCP request (broadcast) packet.
> 6. DHCP relay (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.
> 7. DHCP Server sends the ACK packet.
> 8. DHCP relay agent forwards the ACK packet to the client, it resets
> the GIADDR field when forwarding the ACK to the client.
> 9. All the future renew/release packets are directly exchanged between
> DHCP client and DHCP server.
>
> OVN DHCP RELAY PACKET FLOW:
> ----------------------------
> To add DHCP Relay support on OVN, we need to replicate all the behavior
> described above using distributed logical switch and logical router.
> At, 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 where VM is
> deployed and relays the packet to DHCP server.
> 2. Response packet is first processed on RC node (which first recieves
> the packet from underlay network). RC node forwards the packet to the right
> node by filling in the dest MAC and IP.
>
> OVN Packet flow with DHCP relay is explained below.
> 1. DHCP client (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(GIADDR) in the DHCP
> payload after doing the required checks. If any check fails, packet is
> dropped.
> 5. Logical Router converts the packet to L3 unicast and forwards it to
> the server. This packets 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 does sanity checks and updates the destination MAC
> (available in DHCP header), destination IP (available in DHCP header), resets
> GIADDR and reinjects the packet to datapath.
> If any check fails, packet is dropped.
> 9. Logical router updates the source IP and port and forwards the
> packet to logical switch.
> 10. Logical switch delivers the packet to the DHCP client.
> 11. Similar steps are performed for Request and Ack packets.
> 12. All the future renew/release packets are directly exchanged between
> DHCP client and DHCP server
>
> NEW OVN ACTIONS
> ---------------
>
> 1. dhcp_relay_req(<relay-ip>, <server-ip>)
> - This action executes on the source node on which the DHCP request
> originated.
> - This action relays the DHCP request coming from client to the
> server. Relay-ip is used to update GIADDR in the DHCP header.
> 2. dhcp_relay_resp_fwd(<relay-ip>, <server-ip>)
> - 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.
> - Relay-ip, server-ip are used to validate GIADDR and SERVER ID in
> the DHCP payload.
>
> FLOWS
> -----
> Following are the flows required for one overlay 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_RELAY_REQ */)
> 2. 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_relay_req(<lrp_ip>,<dhcp_server_ip>);ip4.src=<lrp_ip>;ip4.dst=<dhcp_server_ip>;udp.src=67;next;
> /* DHCP_RELAY_REQ */)
> 3. 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_RELAY_RESP */)
> 4. table=17(lr_in_dhcp_relay_resp_fwd), priority=110 , match=(ip4.src
> == <dhcp_server_ip> && ip4.dst == <lrp_ip> && udp.src == 67 && udp.dst ==
> 67),
> action=(dhcp_relay_resp_fwd();ip4.src=<lrp_ip>;udp.dst=68;outport=<lrp_port>;output;
> /* DHCP_RELAY_RESP */)
>
> NEW PIPELINE STAGES
> -------------------
> Following stage is added for DHCP relay feature. Some of the flows are
> fitted into the existing pipeline tages.
> 1. lr_in_dhcp_relay_resp_fwd
> - Forward teh DHCP response to the appropriate node
>
> NB SCHEMA CHANGES
> ----------------
> 1. New DHCP_Relay table
> "DHCP_Relay": {
> "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_relay": {"type": {"key": {"type": "uuid",
> "refTable": "DHCP_Relay",
> "refType": "weak"},
> "min": 0,
> "max": 1}},
> 3. New column to Logical_Switch_table
> "dhcp_relay_port": {"type": {"key": {"type": "uuid",
> "refTable": "Logical_Router_Port",
> "refType": "weak"},
> "min": 0,
> "max": 1}}},
> Commands to enable the feature:
> ------------------------------
> - ovn-nbctl create DHCP_Relay servers=<ip>
> - ovn-nbctl set Logical_Router_port <lrp_uuid>
> dhcp_relay=<dhcp_relay_uuid>
> - ovn-nbctl set Logical_Switch <ls_uuid> dhcp_relay_port=<lrp_uuid>
>
> Example:
> -------
> ovn-nbctl ls-add sw1
> ovn-nbctl lsp-add sw1 sw1-port1
> ovn-nbctl lsp-set-addresses sw1-port1 <MAC> #Only MAC address has to be
> specified when logical ports are created.
> ovn-nbctl lr-add lr1
> ovn-nbctl lrp-add lr1 lr1-port1 <MAC> <GATEWAY_IP/Prefix> #GATEWAY IP is
> set in GIADDR field when relaying the DHCP requests to server.
> ovn-nbctl lsp-add sw1 lr1-attachment
> ovn-nbctl lsp-set-type lr1-attachment router
> ovn-nbctl lsp-set-addresses lr1-attachment <MAC>
> ovn-nbctl lsp-set-options lr1-attachment router-port=lr1-port1
> ovn-nbctl create DHCP_Relay servers=<DHCP_SERVER_IP>
> ovn-nbctl set Logical_Router_port <lrp_uuid> dhcp_relay=<relay_uuid>
> ovn-nbctl set Logical_Switch <ls_uuid> dhcp_relay_port=<lrp_uuid>
>
> Limitations:
> ------------
> - All OVN features that needs IP address to be configured on logical
> port (like proxy arp, etc) will not be supported for overlay subnets on which
> DHCP relay is enabled.
>
> References:
> ----------
> - rfc1541, rfc1542, rfc2131
>
> 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]>
Hi Naveen,
I had a couple of questions in your first RFC patch. Can you please
answer those ? Please see below
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
> ---
> controller/pinctrl.c | 436 ++++++++++++++++++++++++++++++++++++++++++
> include/ovn/actions.h | 26 +++
> lib/actions.c | 114 +++++++++++
> lib/ovn-l7.h | 1 +
> northd/northd.c | 174 ++++++++++++++++-
> ovn-nb.ovsschema | 23 ++-
> ovn-nb.xml | 27 +++
> ovs | 2 +-
> tests/ovn-northd.at | 6 +-
> tests/ovn.at | 12 +-
> utilities/ovn-trace.c | 8 +
> 11 files changed, 812 insertions(+), 17 deletions(-)
> mode change 160000 => 120000 ovs
>
> diff --git a/controller/pinctrl.c b/controller/pinctrl.c
> index 3c1cecfde..ee68d0088 100644
> --- a/controller/pinctrl.c
> +++ b/controller/pinctrl.c
> @@ -383,6 +383,7 @@ static void pinctrl_handle_put_fdb(const struct flow *md,
> const struct flow *headers)
> OVS_REQUIRES(pinctrl_mutex);
>
> +
> COVERAGE_DEFINE(pinctrl_drop_put_mac_binding);
> COVERAGE_DEFINE(pinctrl_drop_buffered_packets_map);
> COVERAGE_DEFINE(pinctrl_drop_controller_event);
> @@ -1888,6 +1889,431 @@ is_dhcp_flags_broadcast(ovs_be16 flags)
> return flags & htons(DHCP_BROADCAST_FLAG);
> }
>
> +
> +static const char *dhcp_msg_str[] = {
> +[0] = "INVALID",
> +[DHCP_MSG_DISCOVER] = "DISCOVER",
> +[DHCP_MSG_OFFER] = "OFFER",
> +[DHCP_MSG_REQUEST] = "REQUEST",
> +[OVN_DHCP_MSG_DECLINE] = "DECLINE",
> +[DHCP_MSG_ACK] = "ACK",
> +[DHCP_MSG_NAK] = "NAK",
> +[OVN_DHCP_MSG_RELEASE] = "RELEASE",
> +[OVN_DHCP_MSG_INFORM] = "INFORM"
> +};
> +
> +static bool
> +dhcp_relay_is_msg_type_supported(uint8_t msg_type)
> +{
> + return (msg_type >= DHCP_MSG_DISCOVER && msg_type <=
> OVN_DHCP_MSG_RELEASE);
> +}
> +
> +static const char *dhcp_msg_str_get(uint8_t msg_type)
> +{
> + if (!dhcp_relay_is_msg_type_supported(msg_type)) {
> + return "INVALID";
> + }
> + return dhcp_msg_str[msg_type];
> +}
> +
> +/* Called with in the pinctrl_handler thread context. */
> +static void
> +pinctrl_handle_dhcp_relay_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 *relay_ip = ofpbuf_try_pull(userdata, sizeof *relay_ip);
> + ovs_be32 *server_ip = ofpbuf_try_pull(userdata, sizeof *server_ip);
> + if (!relay_ip || !server_ip) {
> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> + VLOG_WARN_RL(&rl, "DHCP_RELAY_REQ: relay 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_RELAY_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_RELAY_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_RELAY_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_RELAY_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_RELAY_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_RELAY_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;
> + ovs_be32 request_ip = in_dhcp_data->ciaddr;
> + while (in_dhcp_ptr < end) {
> + const struct dhcp_opt_header *in_dhcp_opt =
> + (const struct dhcp_opt_header *)in_dhcp_ptr;
> + if (in_dhcp_opt->code == DHCP_OPT_END) {
> + break;
> + }
> + if (in_dhcp_opt->code == DHCP_OPT_PAD) {
> + in_dhcp_ptr += 1;
> + continue;
> + }
> + in_dhcp_ptr += sizeof *in_dhcp_opt;
> + if (in_dhcp_ptr > end) {
> + break;
> + }
> + in_dhcp_ptr += in_dhcp_opt->len;
> + if (in_dhcp_ptr > end) {
> + break;
> + }
> +
> + switch (in_dhcp_opt->code) {
> + case DHCP_OPT_MSG_TYPE:
> + if (in_dhcp_opt->len == 1) {
> + in_dhcp_msg_type = DHCP_OPT_PAYLOAD(in_dhcp_opt);
> + }
> + break;
> + case DHCP_OPT_REQ_IP:
> + if (in_dhcp_opt->len == 4) {
> + request_ip =
> get_unaligned_be32(DHCP_OPT_PAYLOAD(in_dhcp_opt));
> + }
> + break;
> + case 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_RELAY_REQ: missing message type");
> + return;
> + }
> +
> + /* Relay the DHCP request packet */
> + uint16_t new_l4_size = in_l4_size;
> + size_t new_packet_size = pkt_in->l4_ofs + new_l4_size;
> +
> + struct dp_packet pkt_out;
> + dp_packet_init(&pkt_out, new_packet_size);
> + dp_packet_clear(&pkt_out);
> + dp_packet_prealloc_tailroom(&pkt_out, new_packet_size);
> + pkt_out_ptr = &pkt_out;
> +
> + /* Copy the L2 and L3 headers from the pkt_in as they would remain same*/
> + dp_packet_put(
> + &pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), pkt_in->l4_ofs);
> +
> + pkt_out.l2_5_ofs = pkt_in->l2_5_ofs;
> + pkt_out.l2_pad_size = pkt_in->l2_pad_size;
> + pkt_out.l3_ofs = pkt_in->l3_ofs;
> + pkt_out.l4_ofs = pkt_in->l4_ofs;
> +
> + struct 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 = *relay_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);
> +
> + /* Log the DHCP message. */
> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 40);
> + const struct eth_header *l2 = dp_packet_eth(&pkt_out);
> + VLOG_INFO_RL(&rl, "DHCP_RELAY_REQ:: MSG_TYPE:%s MAC:"ETH_ADDR_FMT
> + " XID:%u"
> + " REQ_IP:"IP_FMT
> + " GIADDR:"IP_FMT
> + " SERVER_ADDR:"IP_FMT,
> + dhcp_msg_str_get(*in_dhcp_msg_type),
> + ETH_ADDR_BYTES_ARGS(dhcp_data->chaddr),
> ntohl(dhcp_data->xid),
> + IP_ARGS(request_ip), IP_ARGS(dhcp_data->giaddr),
> + IP_ARGS(*server_ip));
> + queue_msg(swconn, ofputil_encode_resume(pin, continuation, proto));
> + if (pkt_out_ptr) {
> + dp_packet_uninit(pkt_out_ptr);
> + }
> +}
> +
> +/* Called with in the pinctrl_handler thread context. */
> +static void
> +pinctrl_handle_dhcp_relay_resp_fwd(
> + 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 *relay_ip = ofpbuf_try_pull(userdata, sizeof *relay_ip);
> + ovs_be32 *server_ip = ofpbuf_try_pull(userdata, sizeof *server_ip);
> + if (!relay_ip || !server_ip) {
> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: relay 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_RELAY_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_RELAY_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_RELAY_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_RELAY_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_RELAY_RESP_FWD: giaddr is not set in
> request");
> + return;
> + }
> + ovs_be32 giaddr = in_dhcp_data->giaddr;
> +
> + ovs_be32 *server_id_ptr = NULL;
> + ovs_be32 lease_time = 0;
> + 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_RELAY_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_RELAY_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_RELAY_RESP: server identifier mismatch");
> + return;
> + }
> +
> + if (giaddr != *relay_ip) {
> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
> + VLOG_WARN_RL(&rl, "DHCP_RELAY_RESP: giaddr mismatch");
> + 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_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);
> + }
> + /* Reset giaddr */
> + dhcp_data->giaddr = htonl(0x0);
> + if (udp->udp_csum)
> + {
> + udp->udp_csum = recalc_csum32(udp->udp_csum,
> + giaddr, 0);
> + }
> + pin->packet = dp_packet_data(&pkt_out);
> + pin->packet_len = dp_packet_size(&pkt_out);
> +
> + /* Log the DHCP message. */
> + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 40);
> + const struct eth_header *l2 = dp_packet_eth(&pkt_out);
> + VLOG_INFO_RL(&rl, "DHCP_RELAY_RESP_FWD:: MSG_TYPE:%s MAC:"ETH_ADDR_FMT
> + " XID:%u"
> + " YIADDR:"IP_FMT
> + " GIADDR:"IP_FMT
> + " SERVER_ADDR:"IP_FMT,
> + dhcp_msg_str_get(*in_dhcp_msg_type),
> + ETH_ADDR_BYTES_ARGS(dhcp_data->chaddr), ntohl(dhcp_data->xid),
> + IP_ARGS(dhcp_data->yiaddr),
> + IP_ARGS(giaddr), IP_ARGS(*server_id_ptr));
> + 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(
> @@ -3158,6 +3584,16 @@ process_packet_in(struct rconn *swconn, const struct
> ofp_header *msg)
> ovs_mutex_unlock(&pinctrl_mutex);
> break;
>
> + case ACTION_OPCODE_DHCP_RELAY_REQ:
> + pinctrl_handle_dhcp_relay_req(swconn, &packet, &pin,
> + &userdata, &continuation);
> + break;
> +
> + case ACTION_OPCODE_DHCP_RELAY_RESP_FWD:
> + pinctrl_handle_dhcp_relay_resp_fwd(swconn, &packet, &pin,
> + &userdata, &continuation);
> + break;
> +
> case ACTION_OPCODE_PUT_DHCP_OPTS:
> pinctrl_handle_put_dhcp_opts(swconn, &packet, &pin, &headers,
> &userdata, &continuation);
> diff --git a/include/ovn/actions.h b/include/ovn/actions.h
> index 04bb6ffd0..e97ae83b8 100644
> --- a/include/ovn/actions.h
> +++ b/include/ovn/actions.h
> @@ -95,6 +95,8 @@ struct collector_set_ids;
> OVNACT(LOOKUP_ND_IP, ovnact_lookup_mac_bind_ip) \
> OVNACT(PUT_DHCPV4_OPTS, ovnact_put_opts) \
> OVNACT(PUT_DHCPV6_OPTS, ovnact_put_opts) \
> + OVNACT(DHCPV4_RELAY_REQ, ovnact_dhcp_relay) \
> + OVNACT(DHCPV4_RELAY_RESP_FWD, ovnact_dhcp_relay) \
> OVNACT(SET_QUEUE, ovnact_set_queue) \
> OVNACT(DNS_LOOKUP, ovnact_result) \
> OVNACT(LOG, ovnact_log) \
> @@ -387,6 +389,14 @@ struct ovnact_put_opts {
> size_t n_options;
> };
>
> +/* OVNACT_DHCP_RELAY. */
> +struct ovnact_dhcp_relay {
> + struct ovnact ovnact;
> + int family;
> + ovs_be32 relay_ipv4;
> + ovs_be32 server_ipv4;
> +};
> +
> /* Valid arguments to SET_QUEUE action.
> *
> * QDISC_MIN_QUEUE_ID is the default queue, so user-defined queues should
> @@ -747,6 +757,22 @@ enum action_opcode {
>
> /* activation_strategy_rarp() */
> ACTION_OPCODE_ACTIVATION_STRATEGY_RARP,
> +
> + /* "dhcp_relay_req(relay_ip, server_ip)".
> + *
> + * Arguments follow the action_header, in this format:
> + * - The 32-bit DHCP relay IP.
> + * - The 32-bit DHCP server IP.
> + */
> + ACTION_OPCODE_DHCP_RELAY_REQ,
> +
> + /* "dhcp_relay_resp_fwd(relay_ip, server_ip)".
> + *
> + * Arguments follow the action_header, in this format:
> + * - The 32-bit DHCP relay IP.
> + * - The 32-bit DHCP server IP.
> + */
> + ACTION_OPCODE_DHCP_RELAY_RESP_FWD,
> };
>
> /* Header. */
> diff --git a/lib/actions.c b/lib/actions.c
> index b880927b6..4b63722c5 100644
> --- a/lib/actions.c
> +++ b/lib/actions.c
> @@ -2629,6 +2629,116 @@ ovnact_controller_event_free(struct
> ovnact_controller_event *event)
> free_gen_options(event->options, event->n_options);
> }
>
> +static void
> +format_DHCPV4_RELAY_REQ(const struct ovnact_dhcp_relay *dhcp_relay, struct
> ds *s)
> +{
> + ds_put_format(s, "dhcp_relay_req("IP_FMT","IP_FMT");",
> + IP_ARGS(dhcp_relay->relay_ipv4),
> + IP_ARGS(dhcp_relay->server_ipv4));
> +}
> +
> +static void
> +parse_dhcp_relay_req(struct action_context *ctx,
> + struct ovnact_dhcp_relay *dhcp_relay)
> +{
> + //lexer_get(ctx->lexer); /* Skip dhcp_relay_req. */
> + lexer_force_match(ctx->lexer, LEX_T_LPAREN);
> +
> + /* Parse relay ip and server ip. */
> + if (ctx->lexer->token.format == LEX_F_IPV4) {
> + dhcp_relay->family = AF_INET;
> + dhcp_relay->relay_ipv4 = ctx->lexer->token.value.ipv4;
> + lexer_get(ctx->lexer);
> + lexer_match(ctx->lexer, LEX_T_COMMA);
> + if (ctx->lexer->token.format == LEX_F_IPV4) {
> + dhcp_relay->family = AF_INET;
> + dhcp_relay->server_ipv4 = ctx->lexer->token.value.ipv4;
> + lexer_get(ctx->lexer);
> + }
> + else
> + {
> + lexer_syntax_error(ctx->lexer, "expecting IPv4 dhcp server ip");
> + return;
> + }
> + }
> + else
> + {
> + lexer_syntax_error(ctx->lexer, "expecting IPv4 dhcp relay and
> server ips");
> + return;
> + }
> + lexer_force_match(ctx->lexer, LEX_T_RPAREN);
> +}
> +
> +static void
> +encode_DHCPV4_RELAY_REQ(const struct ovnact_dhcp_relay *dhcp_relay,
> + const struct ovnact_encode_params *ep,
> + struct ofpbuf *ofpacts)
> +{
> + size_t oc_offset =
> encode_start_controller_op(ACTION_OPCODE_DHCP_RELAY_REQ,
> + true, ep->ctrl_meter_id,
> + ofpacts);
> + ofpbuf_put(ofpacts, &dhcp_relay->relay_ipv4,
> sizeof(dhcp_relay->relay_ipv4));
> + ofpbuf_put(ofpacts, &dhcp_relay->server_ipv4,
> sizeof(dhcp_relay->server_ipv4));
> + encode_finish_controller_op(oc_offset, ofpacts);
> +}
> +
> +static void
> +format_DHCPV4_RELAY_RESP_FWD(const struct ovnact_dhcp_relay *dhcp_relay,
> struct ds *s)
> +{
> + ds_put_format(s, "dhcp_relay_resp("IP_FMT","IP_FMT");",
> + IP_ARGS(dhcp_relay->relay_ipv4),
> + IP_ARGS(dhcp_relay->server_ipv4));
> +}
> +
> +static void
> +parse_dhcp_relay_resp_fwd(struct action_context *ctx,
> + struct ovnact_dhcp_relay *dhcp_relay)
> +{
> + //lexer_get(ctx->lexer); /* Skip dhcp_relay_resp. */
> + lexer_force_match(ctx->lexer, LEX_T_LPAREN);
> +
> + /* Parse relay ip and server ip. */
> + if (ctx->lexer->token.format == LEX_F_IPV4) {
> + dhcp_relay->family = AF_INET;
> + dhcp_relay->relay_ipv4 = ctx->lexer->token.value.ipv4;
> + lexer_get(ctx->lexer);
> + lexer_match(ctx->lexer, LEX_T_COMMA);
> + if (ctx->lexer->token.format == LEX_F_IPV4) {
> + dhcp_relay->family = AF_INET;
> + dhcp_relay->server_ipv4 = ctx->lexer->token.value.ipv4;
> + lexer_get(ctx->lexer);
> + }
> + else
> + {
> + lexer_syntax_error(ctx->lexer, "expecting IPv4 dhcp server ip");
> + return;
> + }
> + }
> + else
> + {
> + lexer_syntax_error(ctx->lexer, "expecting IPv4 dhcp relay and
> server ips");
> + return;
> + }
> + lexer_force_match(ctx->lexer, LEX_T_RPAREN);
> +}
> +
> +static void
> +encode_DHCPV4_RELAY_RESP_FWD(const struct ovnact_dhcp_relay *dhcp_relay,
> + const struct ovnact_encode_params *ep,
> + struct ofpbuf *ofpacts)
> +{
> + size_t oc_offset =
> encode_start_controller_op(ACTION_OPCODE_DHCP_RELAY_RESP_FWD,
> + true, ep->ctrl_meter_id,
> + ofpacts);
> + ofpbuf_put(ofpacts, &dhcp_relay->relay_ipv4,
> sizeof(dhcp_relay->relay_ipv4));
> + ofpbuf_put(ofpacts, &dhcp_relay->server_ipv4,
> sizeof(dhcp_relay->server_ipv4));
> + encode_finish_controller_op(oc_offset, ofpacts);
> +}
> +
> +static void ovnact_dhcp_relay_free(struct ovnact_dhcp_relay *dhcp_relay
> OVS_UNUSED)
> +{
> +}
> +
> static void
> parse_put_opts(struct action_context *ctx, const struct expr_field *dst,
> struct ovnact_put_opts *po, const struct hmap *gen_opts,
> @@ -5451,6 +5561,10 @@ parse_action(struct action_context *ctx)
> parse_sample(ctx);
> } else if (lexer_match_id(ctx->lexer, "mac_cache_use")) {
> ovnact_put_MAC_CACHE_USE(ctx->ovnacts);
> + } else if (lexer_match_id(ctx->lexer, "dhcp_relay_req")) {
> + parse_dhcp_relay_req(ctx, ovnact_put_DHCPV4_RELAY_REQ(ctx->ovnacts));
> + } else if (lexer_match_id(ctx->lexer, "dhcp_relay_resp_fwd")) {
> + parse_dhcp_relay_resp_fwd(ctx,
> ovnact_put_DHCPV4_RELAY_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 ad514a922..e08581123 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 f8b046d83..654c23da5 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -181,11 +181,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_RELAY_RESP_FWD, 17,
> "lr_in_dhcp_relay_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,
> \
> @@ -9626,6 +9627,80 @@ build_dhcpv6_options_flows(struct ovn_port *op,
> ds_destroy(&match);
> }
>
> +static void
> +build_lswitch_dhcp_relay_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_relay_port) {
> + return;
> + }
> +
> + struct ds match = DS_EMPTY_INITIALIZER;
> + struct ds action = DS_EMPTY_INITIALIZER;
> + struct nbrec_logical_router_port *lrp = op->od->nbs->dhcp_relay_port;
> + struct ovn_port *rp = ovn_port_find(lr_ports, lrp->name);
> +
> + if (!rp || !rp->nbrp || !rp->nbrp->dhcp_relay) {
> + return;
> + }
> +
> + struct ovn_port *sp = NULL;
> + struct nbrec_dhcp_relay *dhcp_relay = rp->nbrp->dhcp_relay;
> +
> + 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_relay->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_RELAY_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_);
> + free(server_ip_str);
> +}
> +
> static void
> build_drop_arp_nd_flows_for_unbound_router_ports(struct ovn_port *op,
> const struct ovn_port *port,
> @@ -10197,6 +10272,13 @@ build_lswitch_dhcp_options_and_response(struct
> ovn_port *op,
> return;
> }
>
> + if (op->od && op->od->nbs
> + && op->od->nbs->dhcp_relay_port) {
> + /* Don't add the DHCP server flows if DHCP Relay 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)) {
> @@ -14452,6 +14534,85 @@ build_dhcpv6_reply_flows_for_lrouter_port(
> }
> }
>
> +static void
> +build_dhcp_relay_flows_for_lrouter_port(
> + struct ovn_port *op, struct hmap *lflows,
> + struct ds *match)
> +{
> + if (!op->nbrp || !op->nbrp->dhcp_relay) {
> + return;
> + }
> + struct nbrec_dhcp_relay *dhcp_relay = op->nbrp->dhcp_relay;
> + if (!dhcp_relay->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_relay->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_relay_req(%s,%s);"
> + "ip4.src=%s;ip4.dst=%s;udp.src=67;next; /* DHCP_RELAY_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_RELAY_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_relay_resp_fwd(%s,%s);ip4.src=%s;udp.dst=68;"
> + "outport=%s;output; /* DHCP_RELAY_RESP */",
> + op->lrp_networks.ipv4_addrs[0].addr_s, server_ip_str,
> + op->lrp_networks.ipv4_addrs[0].addr_s, op->json_key);
> + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_DHCP_RELAY_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,
> @@ -15667,6 +15828,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_RELAY_RESP_FWD, 0, "1",
> "next;");
>
> const char *ct_flag_reg = features->ct_no_masked_label
> ? "ct_mark"
> @@ -16148,6 +16310,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_relay_flows(op, lr_ports, lflows, meter_groups);
>
> /* Build Logical Router Flows. */
> build_ip_routing_flows_for_router_type_lsp(op, lr_ports, lflows);
> @@ -16177,6 +16340,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_relay_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 e103360ec..7d7e680e0 100644
> --- a/ovn-nb.ovsschema
> +++ b/ovn-nb.ovsschema
> @@ -1,7 +1,7 @@
> {
> "name": "OVN_Northbound",
> "version": "7.1.0",
> - "cksum": "217362582 33949",
> + "cksum": "1797404008 34972",
> "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_relay_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_relay": {"type": {"key": {"type": "uuid",
> + "refTable": "DHCP_Relay",
> + "refType": "weak"},
> + "min": 0,
> + "max": 1}},
> "external_ids": {
> "type": {"key": "string", "value": "string",
> "min": 0, "max": "unlimited"}},
> @@ -529,6 +539,15 @@
> "type": {"key": "string", "value": "string",
> "min": 0, "max": "unlimited"}}},
> "isRoot": true},
> + "DHCP_Relay": {
> + "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 1de0c3041..ca3085e93 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -608,6 +608,11 @@
> Please see the <ref table="DNS"/> table.
> </column>
>
> + <column name="dhcp_relay_port">
> + This column defines the <ref table="Logical_Router_Port"/> on which
> + DHCP relay is enabled.
> + </column>
> +
> <column name="forwarding_groups">
> Groups a set of logical port endpoints for traffic going out of the
> logical switch.
> @@ -2980,6 +2985,10 @@ or
> port has all ingress and egress traffic dropped.
> </column>
>
> + <column name="dhcp_relay">
> + This column is used to enabled DHCP Relay. Please refer to <ref
> table="DHCP_Relay"/> table.
> + </column>
> +
> <group title="Distributed Gateway Ports">
> <p>
> Gateways, as documented under <code>Gateways</code> in the OVN
> @@ -4286,6 +4295,24 @@ or
> </group>
> </table>
>
> + <table name="DHCP_Relay" title="DHCP Relay">
> + <p>
> + OVN implements native DHCPv4 relay support which caters to the common
> + use case of relaying 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/ovs b/ovs
> deleted file mode 160000
> index 1d78a3f31..000000000
> --- a/ovs
> +++ /dev/null
> @@ -1 +0,0 @@
> -Subproject commit 1d78a3f3164a6bf651b34f52812f38655b28a9ce
> diff --git a/ovs b/ovs
> new file mode 120000
> index 000000000..7be8871aa
> --- /dev/null
> +++ b/ovs
> @@ -0,0 +1 @@
> +/home/naveen.yerramneni/development/ghub/ovs
> \ No newline at end of file
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index 196fe01fb..7f4ef6152 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -8774,9 +8774,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 637d92bed..2306d7e7d 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -21865,7 +21865,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_UNQUOTED([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_UNQUOTED([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=0x$lr0_public_dp_key,metadata=0x$lr0_dp_key,nw_src=10.0.0.10
> actions=drop
> ])
>
> @@ -28918,7 +28918,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
> @@ -29043,7 +29043,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
> @@ -29540,7 +29540,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
> ])
>
> @@ -29559,13 +29559,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 0b86eae7b..3253fc11f 100644
> --- a/utilities/ovn-trace.c
> +++ b/utilities/ovn-trace.c
> @@ -3205,6 +3205,14 @@ trace_actions(const struct ovnact *ovnacts, size_t
> ovnacts_len,
> super);
> break;
>
> + case OVNACT_DHCPV4_RELAY_REQ:
> + /* TODO. */
> + break;
> +
> + case OVNACT_DHCPV4_RELAY_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
>
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev