@@ -96,6 +96,8 @@ struct collector_set_ids;
OVNACT(LOOKUP_ND_IP, ovnact_lookup_mac_bind_ip) \
OVNACT(PUT_DHCPV4_OPTS, ovnact_put_opts) \
OVNACT(PUT_DHCPV6_OPTS, ovnact_put_opts) \
+ OVNACT(DHCPV4_RELAY_REQ_CHK, ovnact_dhcp_relay) \
+ OVNACT(DHCPV4_RELAY_RESP_CHK, ovnact_dhcp_relay) \
OVNACT(SET_QUEUE, ovnact_set_queue) \
OVNACT(DNS_LOOKUP, ovnact_result) \
OVNACT(LOG, ovnact_log) \
@@ -396,6 +398,15 @@ struct ovnact_put_opts {
size_t n_options;
};
+/* OVNACT_DHCP_RELAY. */
+struct ovnact_dhcp_relay {
+ struct ovnact ovnact;
+ int family;
+ struct expr_field dst; /* 1-bit destination field. */
+ ovs_be32 relay_ipv4;
+ ovs_be32 server_ipv4;
+};
+
/* Valid arguments to SET_QUEUE action.
*
* QDISC_MIN_QUEUE_ID is the default queue, so user-defined queues should
@@ -772,6 +783,22 @@ enum action_opcode {
/* multicast group split buffer action. */
ACTION_OPCODE_MG_SPLIT_BUF,
+
+ /* "dhcp_relay_req_chk(relay_ip, server_ip)".
+ *
+ * Arguments follow the action_header, in this format:
+ * - The 32-bit DHCP relay IP.
+ * - The 32-bit DHCP server IP.
+ */
+ ACTION_OPCODE_DHCP_RELAY_REQ_CHK,
+
+ /* "dhcp_relay_resp_chk(relay_ip, server_ip)".
+ *
+ * Arguments follow the action_header, in this format:
+ * - The 32-bit DHCP relay IP.
+ * - The 32-bit DHCP server IP.
+ */
+ ACTION_OPCODE_DHCP_RELAY_RESP_CHK,
};
/* Header. */
@@ -2706,6 +2706,149 @@ ovnact_controller_event_free(struct ovnact_controller_event *event)
free_gen_options(event->options, event->n_options);
}
+static void
+format_DHCPV4_RELAY_REQ_CHK(const struct ovnact_dhcp_relay *dhcp_relay,
+ struct ds *s)
+{
+ expr_field_format(&dhcp_relay->dst, s);
+ ds_put_format(s, " = dhcp_relay_req_chk("IP_FMT", "IP_FMT");",
+ IP_ARGS(dhcp_relay->relay_ipv4),
+ IP_ARGS(dhcp_relay->server_ipv4));
+}
+
+static void
+parse_dhcp_relay_req_chk(struct action_context *ctx,
+ const struct expr_field *dst,
+ struct ovnact_dhcp_relay *dhcp_relay)
+{
+ /* Skip dhcp_relay_req_chk( */
+ lexer_force_match(ctx->lexer, LEX_T_LPAREN);
+
+ /* Validate that the destination is a 1-bit, modifiable field. */
+ char *error = expr_type_check(dst, 1, true, ctx->scope);
+ if (error) {
+ lexer_error(ctx->lexer, "%s", error);
+ free(error);
+ return;
+ }
+ dhcp_relay->dst = *dst;
+
+ /* Parse relay ip and server ip. */
+ if (ctx->lexer->token.format == LEX_F_IPV4) {
+ dhcp_relay->family = AF_INET;
+ dhcp_relay->relay_ipv4 = ctx->lexer->token.value.ipv4;
+ lexer_get(ctx->lexer);
+ lexer_match(ctx->lexer, LEX_T_COMMA);
+ if (ctx->lexer->token.format == LEX_F_IPV4) {
+ dhcp_relay->family = AF_INET;
+ dhcp_relay->server_ipv4 = ctx->lexer->token.value.ipv4;
+ lexer_get(ctx->lexer);
+ } else {
+ lexer_syntax_error(ctx->lexer, "expecting IPv4 dhcp server ip");
+ return;
+ }
+ } else {
+ lexer_syntax_error(ctx->lexer, "expecting IPv4 dhcp relay "
+ "and server ips");
+ return;
+ }
+ lexer_force_match(ctx->lexer, LEX_T_RPAREN);
+}
+
+static void
+encode_DHCPV4_RELAY_REQ_CHK(const struct ovnact_dhcp_relay *dhcp_relay,
+ const struct ovnact_encode_params *ep,
+ struct ofpbuf *ofpacts)
+{
+ struct mf_subfield dst = expr_resolve_field(&dhcp_relay->dst);
+ size_t oc_offset = encode_start_controller_op(
+ ACTION_OPCODE_DHCP_RELAY_REQ_CHK,
+ true, ep->ctrl_meter_id,
+ ofpacts);
+ nx_put_header(ofpacts, dst.field->id, OFP15_VERSION, false);
+ ovs_be32 ofs = htonl(dst.ofs);
+ ofpbuf_put(ofpacts, &ofs, sizeof ofs);
+ ofpbuf_put(ofpacts, &dhcp_relay->relay_ipv4,
+ sizeof(dhcp_relay->relay_ipv4));
+ ofpbuf_put(ofpacts, &dhcp_relay->server_ipv4,
+ sizeof(dhcp_relay->server_ipv4));
+ encode_finish_controller_op(oc_offset, ofpacts);
+}
+
+static void
+format_DHCPV4_RELAY_RESP_CHK(const struct ovnact_dhcp_relay *dhcp_relay,
+ struct ds *s)
+{
+ expr_field_format(&dhcp_relay->dst, s);
+ ds_put_format(s, " = dhcp_relay_resp_chk("IP_FMT", "IP_FMT");",
+ IP_ARGS(dhcp_relay->relay_ipv4),
+ IP_ARGS(dhcp_relay->server_ipv4));
+}
+
+static void
+parse_dhcp_relay_resp_chk(struct action_context *ctx,
+ const struct expr_field *dst,
+ struct ovnact_dhcp_relay *dhcp_relay)
+{
+ /* Skip dhcp_relay_resp_chk( */
+ lexer_force_match(ctx->lexer, LEX_T_LPAREN);
+
+ /* Validate that the destination is a 1-bit, modifiable field. */
+ char *error = expr_type_check(dst, 1, true, ctx->scope);
+ if (error) {
+ lexer_error(ctx->lexer, "%s", error);
+ free(error);
+ return;
+ }
+ dhcp_relay->dst = *dst;
+
+ /* Parse relay ip and server ip. */
+ if (ctx->lexer->token.format == LEX_F_IPV4) {
+ dhcp_relay->family = AF_INET;
+ dhcp_relay->relay_ipv4 = ctx->lexer->token.value.ipv4;
+ lexer_get(ctx->lexer);
+ lexer_match(ctx->lexer, LEX_T_COMMA);
+ if (ctx->lexer->token.format == LEX_F_IPV4) {
+ dhcp_relay->family = AF_INET;
+ dhcp_relay->server_ipv4 = ctx->lexer->token.value.ipv4;
+ lexer_get(ctx->lexer);
+ } else {
+ lexer_syntax_error(ctx->lexer, "expecting IPv4 dhcp server ip");
+ return;
+ }
+ } else {
+ lexer_syntax_error(ctx->lexer, "expecting IPv4 dhcp relay and "
+ "server ips");
+ return;
+ }
+ lexer_force_match(ctx->lexer, LEX_T_RPAREN);
+}
+
+static void
+encode_DHCPV4_RELAY_RESP_CHK(const struct ovnact_dhcp_relay *dhcp_relay,
+ const struct ovnact_encode_params *ep,
+ struct ofpbuf *ofpacts)
+{
+ struct mf_subfield dst = expr_resolve_field(&dhcp_relay->dst);
+ size_t oc_offset = encode_start_controller_op(
+ ACTION_OPCODE_DHCP_RELAY_RESP_CHK,
+ true, ep->ctrl_meter_id,
+ ofpacts);
+ nx_put_header(ofpacts, dst.field->id, OFP15_VERSION, false);
+ ovs_be32 ofs = htonl(dst.ofs);
+ ofpbuf_put(ofpacts, &ofs, sizeof ofs);
+ ofpbuf_put(ofpacts, &dhcp_relay->relay_ipv4,
+ sizeof(dhcp_relay->relay_ipv4));
+ ofpbuf_put(ofpacts, &dhcp_relay->server_ipv4,
+ sizeof(dhcp_relay->server_ipv4));
+ encode_finish_controller_op(oc_offset, ofpacts);
+}
+
+static void 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,
@@ -5406,6 +5549,12 @@ parse_set_action(struct action_context *ctx)
lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
parse_chk_lb_aff(ctx, &lhs,
ovnact_put_CHK_LB_AFF(ctx->ovnacts));
+ } else if (lexer_match_id(ctx->lexer, "dhcp_relay_req_chk")) {
+ parse_dhcp_relay_req_chk(ctx, &lhs,
+ ovnact_put_DHCPV4_RELAY_REQ_CHK(ctx->ovnacts));
+ } else if (lexer_match_id(ctx->lexer, "dhcp_relay_resp_chk")) {
+ parse_dhcp_relay_resp_chk(ctx, &lhs,
+ ovnact_put_DHCPV4_RELAY_RESP_CHK(ctx->ovnacts));
} else {
parse_assignment_action(ctx, false, &lhs);
}
@@ -2329,6 +2329,63 @@ execute_put_dhcp_opts(const struct ovnact_put_opts *pdo,
execute_put_opts(pdo, name, uflow, super);
}
+static void
+execute_dhcpv4_relay_req_chk(const struct ovnact_dhcp_relay *dr,
+ struct flow *uflow, struct ovs_list *super)
+{
+ ovntrace_node_append(
+ super, OVNTRACE_NODE_ERROR,
+ "/* We assume that this packet is DHCPDISCOVER or DHCPREQUEST. */");
+
+ struct ds s = DS_EMPTY_INITIALIZER;
+ struct mf_subfield dst = expr_resolve_field(&dr->dst);
+ if (!mf_is_register(dst.field->id)) {
+ /* Format assignment. */
+ ds_clear(&s);
+ expr_field_format(&dr->dst, &s);
+ ovntrace_node_append(super, OVNTRACE_NODE_MODIFY,
+ "%s = 1", ds_cstr(&s));
+ }
+ ds_destroy(&s);
+
+ struct mf_subfield sf = expr_resolve_field(&dr->dst);
+ union mf_subvalue sv = { .u8_val = 1 };
+ mf_write_subfield_flow(&sf, &sv, uflow);
+}
+
+static void
+execute_dhcpv4_relay_resp_chk(const struct ovnact_dhcp_relay *dr,
+ struct flow *uflow, struct ovs_list *super)
+{
+ ovntrace_node_append(
+ super, OVNTRACE_NODE_ERROR,
+ "/* We assume that this packet is DHCPOFFER or DHCPACK and "
+ "DHCP broadcast flag is set. Dest IP is set to broadcast. "
+ "Dest MAC is set to broadcast but in real network this is unicast "
+ "which is extracted from DHCP header. */");
+
+ /* Assume DHCP broadcast flag is set */
+ uflow->nw_dst = htonl(0xFFFFFFFF);
+ /* Dest MAC is set to broadcast but in real network this is unicast */
+ struct eth_addr bcast_mac = { .ea = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
+ uflow->dl_dst = bcast_mac;
+
+ struct ds s = DS_EMPTY_INITIALIZER;
+ struct mf_subfield dst = expr_resolve_field(&dr->dst);
+ if (!mf_is_register(dst.field->id)) {
+ /* Format assignment. */
+ ds_clear(&s);
+ expr_field_format(&dr->dst, &s);
+ ovntrace_node_append(super, OVNTRACE_NODE_MODIFY,
+ "%s = 1", ds_cstr(&s));
+ }
+ ds_destroy(&s);
+
+ struct mf_subfield sf = expr_resolve_field(&dr->dst);
+ union mf_subvalue sv = { .u8_val = 1 };
+ mf_write_subfield_flow(&sf, &sv, uflow);
+}
+
static void
execute_put_nd_ra_opts(const struct ovnact_put_opts *pdo,
const char *name, struct flow *uflow,
@@ -3216,6 +3273,16 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
"put_dhcpv6_opts", uflow, super);
break;
+ case OVNACT_DHCPV4_RELAY_REQ_CHK:
+ execute_dhcpv4_relay_req_chk(ovnact_get_DHCPV4_RELAY_REQ_CHK(a),
+ uflow, super);
+ break;
+
+ case OVNACT_DHCPV4_RELAY_RESP_CHK:
+ execute_dhcpv4_relay_resp_chk(ovnact_get_DHCPV4_RELAY_RESP_CHK(a),
+ uflow, super);
+ break;
+
case OVNACT_PUT_ND_RA_OPTS:
execute_put_nd_ra_opts(ovnact_get_PUT_DHCPV6_OPTS(a),
"put_nd_ra_opts", uflow, super);
NEW OVN ACTIONS --------------- 1. dhcp_relay_req_chk(<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_chk(<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. Signed-off-by: Naveen Yerramneni <naveen.yerramneni@nutanix.com> --- include/ovn/actions.h | 27 ++++++++ lib/actions.c | 149 ++++++++++++++++++++++++++++++++++++++++++ utilities/ovn-trace.c | 67 +++++++++++++++++++ 3 files changed, 243 insertions(+)