@@ -1930,6 +1930,18 @@ union mf_subvalue {
/* Convenient access to just least-significant bits in various forms. */
struct {
+ uint8_t dummy_u8[127];
+ uint8_t u8_val;
+ };
+ struct {
+ ovs_be16 dummy_be16[63];
+ ovs_be16 be16_int;
+ };
+ struct {
+ ovs_be32 dummy_be32[31];
+ ovs_be32 be32_int;
+ };
+ struct {
ovs_be64 dummy_integer[15];
ovs_be64 integer;
};
@@ -25,6 +25,8 @@
#define DHCP_SERVER_PORT 67 /* Port used by DHCP server. */
#define DHCP_CLIENT_PORT 68 /* Port used by DHCP client. */
+#define DHCP_MAGIC_COOKIE 0x63825363
+
#define DHCP_HEADER_LEN 236
struct dhcp_header {
uint8_t op; /* DHCP_BOOTREQUEST or DHCP_BOOTREPLY. */
@@ -45,4 +47,15 @@ struct dhcp_header {
};
BUILD_ASSERT_DECL(DHCP_HEADER_LEN == sizeof(struct dhcp_header));
+#define DHCP_OP_REQUEST 1
+#define DHCP_OP_REPLY 2
+
+#define DHCP_MSG_DISCOVER 1
+#define DHCP_MSG_OFFER 2
+#define DHCP_MSG_REQUEST 3
+#define DHCP_MSG_ACK 5
+
+#define DHCP_OPT_MSG_TYPE 53
+#define DHCP_OPT_END 255
+
#endif /* dhcp.h */
@@ -24,6 +24,7 @@
#include "ovn-controller.h"
#include "ovn/lib/actions.h"
#include "ovn/lib/expr.h"
+#include "ovn/lib/ovn-dhcp.h"
#include "ovn/lib/ovn-sb-idl.h"
#include "packets.h"
#include "simap.h"
@@ -203,6 +204,13 @@ add_logical_flows(struct controller_ctx *ctx, const struct lport_index *lports,
{
uint32_t conj_id_ofs = 1;
+ struct hmap dhcp_opts = HMAP_INITIALIZER(&dhcp_opts);
+ const struct sbrec_dhcp_options *dhcp_opt_row;
+ SBREC_DHCP_OPTIONS_FOR_EACH(dhcp_opt_row, ctx->ovnsb_idl) {
+ dhcp_opt_add(&dhcp_opts, dhcp_opt_row->name, dhcp_opt_row->code,
+ dhcp_opt_row->type);
+ }
+
const struct sbrec_logical_flow *lflow;
SBREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->ovnsb_idl) {
/* Determine translation of logical table IDs to physical table IDs. */
@@ -274,6 +282,7 @@ add_logical_flows(struct controller_ctx *ctx, const struct lport_index *lports,
};
struct action_params ap = {
.symtab = &symtab,
+ .dhcp_opts = &dhcp_opts,
.lookup_port = lookup_port_cb,
.aux = &aux,
.ct_zones = ct_zones,
@@ -357,6 +366,8 @@ add_logical_flows(struct controller_ctx *ctx, const struct lport_index *lports,
ofpbuf_uninit(&ofpacts);
conj_id_ofs += n_conjs;
}
+
+ dhcp_opts_destroy(&dhcp_opts);
}
static void
@@ -18,6 +18,7 @@
#include "pinctrl.h"
#include "coverage.h"
+#include "csum.h"
#include "dirs.h"
#include "dp-packet.h"
#include "flow.h"
@@ -29,6 +30,8 @@
#include "openvswitch/ofp-print.h"
#include "openvswitch/ofp-util.h"
#include "openvswitch/vlog.h"
+
+#include "lib/dhcp.h"
#include "ovn-controller.h"
#include "ovn/lib/actions.h"
#include "ovn/lib/logical-fields.h"
@@ -204,13 +207,192 @@ exit:
}
static void
+pinctrl_handle_put_dhcp_opts(
+ struct dp_packet *pkt_in, struct ofputil_packet_in *pin,
+ struct ofpbuf *userdata, struct ofpbuf *continuation)
+{
+ enum ofp_version version = rconn_get_version(swconn);
+ enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
+ struct dp_packet *pkt_out_ptr = NULL;
+ uint32_t success = 0;
+
+ /* Validate the userdata. Format of the userdata is
+ *
+ * ---------------------------------------------------
+ * | REG IDX (4 B) | OFFER IP | DHCP options |
+ * | (to store the result) | (4 B) | |
+ * ---------------------------------------------------
+ */
+ uint32_t *reg_idx = ofpbuf_try_pull(userdata, sizeof *reg_idx);
+ if (!reg_idx) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "reg_idx not present in the userdata");
+ goto exit;
+ }
+
+ ovs_be32 *offer_ip = ofpbuf_try_pull(userdata, sizeof *offer_ip);
+ if (!offer_ip) {
+ /* offer_ip is not present in userdata. Resume the packet without
+ * further processing. */
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "offer_ip not present in the userdata");
+ goto exit;
+ }
+
+ if (!userdata->size) {
+ /* No DHCP options present in the 'userdata'. Resume the packet
+ * without further processing */
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "DHCP options not present in the userdata");
+ goto exit;
+ }
+
+ /* Validate the DHCP request packet.
+ * Format of the DHCP packet is
+ * ------------------------------------------------------------------------
+ *| UDP HEADER | DHCP HEADER | 4 Byte DHCP Cookie | DHCP OPTIONS(var len)|
+ * ------------------------------------------------------------------------
+ */
+ if (dp_packet_l4_size(pkt_in) < (UDP_HEADER_LEN +
+ sizeof (struct dhcp_header) + sizeof(uint32_t) + 3)) {
+ /* Invalid or incomplete DHCP packet. Resume the packet without
+ * further processing. */
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "Invalid or incomplete DHCP packet recieved");
+ goto exit;
+ }
+
+ struct dhcp_header const *in_dhcp_data = dp_packet_get_udp_payload(pkt_in);
+ if (in_dhcp_data->op != DHCP_OP_REQUEST) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "Invalid opcode in the DHCP packet : %d",
+ in_dhcp_data->op);
+ goto exit;
+ }
+
+ /* DHCP options follow the DHCP header. The first 4 bytes of the DHCP
+ * options is the DHCP magic cookie followed by the actual DHCP options.
+ */
+ const uint8_t *in_dhcp_opt =
+ (const uint8_t *)dp_packet_get_udp_payload(pkt_in) +
+ sizeof (struct dhcp_header);
+
+ ovs_be32 magic_cookie = htonl(DHCP_MAGIC_COOKIE);
+ if (memcmp(in_dhcp_opt, &magic_cookie, sizeof(ovs_be32))) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "DHCP magic cookie not present in the DHCP packet");
+ goto exit;
+ }
+
+ in_dhcp_opt += 4;
+ /* Check that the DHCP Message Type (opt 53) is present or not with
+ * valid values - DHCP_MSG_DISCOVER or DHCP_MSG_REQUEST as the first
+ * DHCP option.
+ */
+ if (!(in_dhcp_opt[0] == DHCP_OPT_MSG_TYPE && in_dhcp_opt[1] == 1 && (
+ in_dhcp_opt[2] == DHCP_MSG_DISCOVER ||
+ in_dhcp_opt[2] == DHCP_MSG_REQUEST))) {
+ /* Invalid DHCP packet. Resume the packet without further
+ * processing. */
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "Invalid DHCP message type : opt code = %d,"
+ " opt value = %d", in_dhcp_opt[0], in_dhcp_opt[2]);
+ goto exit;
+ }
+
+ uint8_t msg_type;
+ if (in_dhcp_opt[2] == DHCP_MSG_DISCOVER) {
+ msg_type = DHCP_MSG_OFFER;
+ } else {
+ msg_type = DHCP_MSG_ACK;
+ }
+
+ /* Frame the DHCP reply packet
+ * Total DHCP options length will be options stored in the userdata +
+ * 16 bytes.
+ *
+ * --------------------------------------------------------------
+ *| 4 Bytes (dhcp cookie) | 3 Bytes (option type) | DHCP options |
+ * --------------------------------------------------------------
+ *| 4 Bytes padding | 1 Byte (option end 0xFF ) | 4 Bytes padding|
+ * --------------------------------------------------------------
+ */
+ uint16_t new_l4_size = UDP_HEADER_LEN + DHCP_HEADER_LEN + \
+ userdata->size + 16;
+ size_t new_packet_size = pkt_in->l4_ofs + new_l4_size;
+
+ struct dp_packet pkt_out;
+ dp_packet_init(&pkt_out, new_packet_size);
+ dp_packet_clear(&pkt_out);
+ dp_packet_prealloc_tailroom(&pkt_out, new_packet_size);
+ pkt_out_ptr = &pkt_out;
+
+ /* Copy the L2 and L3 headers from the pkt_in as they would remain same*/
+ dp_packet_put(
+ &pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), pkt_in->l4_ofs);
+
+ pkt_out.l2_5_ofs = pkt_in->l2_5_ofs;
+ pkt_out.l2_pad_size = pkt_in->l2_pad_size;
+ pkt_out.l3_ofs = pkt_in->l3_ofs;
+ pkt_out.l4_ofs = pkt_in->l4_ofs;
+
+ struct udp_header *udp = dp_packet_put(
+ &pkt_out, dp_packet_pull(pkt_in, UDP_HEADER_LEN), UDP_HEADER_LEN);
+
+ struct dhcp_header *dhcp_data = dp_packet_put(
+ &pkt_out, dp_packet_pull(pkt_in, DHCP_HEADER_LEN), DHCP_HEADER_LEN);
+ dhcp_data->op = DHCP_OP_REPLY;
+ dhcp_data->yiaddr = *offer_ip;
+ dp_packet_put(&pkt_out, &magic_cookie, sizeof(ovs_be32));
+
+ uint8_t *out_dhcp_opts = dp_packet_put_zeros(&pkt_out,
+ userdata->size + 12);
+ /* DHCP option - type */
+ out_dhcp_opts[0] = DHCP_OPT_MSG_TYPE;
+ out_dhcp_opts[1] = 1;
+ out_dhcp_opts[2] = msg_type;
+ out_dhcp_opts += 3;
+
+ memcpy(out_dhcp_opts, userdata->data, userdata->size);
+ out_dhcp_opts += userdata->size;
+ /* Padding */
+ out_dhcp_opts += 4;
+ /* End */
+ out_dhcp_opts[0] = DHCP_OPT_END;
+
+ udp->udp_len = htons(new_l4_size);
+
+ struct ip_header *out_ip = dp_packet_l3(&pkt_out);
+ out_ip->ip_tot_len = htons(pkt_out.l4_ofs - pkt_out.l3_ofs + new_l4_size);
+ udp->udp_csum = 0;
+ out_ip->ip_csum = 0;
+ out_ip->ip_csum = csum(out_ip, sizeof *out_ip);
+
+ pin->packet = dp_packet_data(&pkt_out);
+ pin->packet_len = dp_packet_size(&pkt_out);
+
+ success = 1;
+exit:
+ /* store the result in the regx */
+ if (reg_idx) {
+ match_set_reg(&pin->flow_metadata, *reg_idx, success);
+ }
+ queue_msg(ofputil_encode_resume(pin, continuation, proto));
+ if (pkt_out_ptr) {
+ dp_packet_uninit(pkt_out_ptr);
+ }
+}
+
+static void
process_packet_in(const struct ofp_header *msg)
{
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
struct ofputil_packet_in pin;
+ struct ofpbuf continuation;
enum ofperr error = ofputil_decode_packet_in(msg, true, &pin,
- NULL, NULL, NULL);
+ NULL, NULL, &continuation);
+
if (error) {
VLOG_WARN_RL(&rl, "error decoding packet-in: %s",
ofperr_to_string(error));
@@ -242,6 +424,9 @@ process_packet_in(const struct ofp_header *msg)
pinctrl_handle_put_arp(&pin.flow_metadata.flow, &headers);
break;
+ case ACTION_OPCODE_PUT_DHCP_OPTS:
+ pinctrl_handle_put_dhcp_opts(&packet, &pin, &userdata, &continuation);
+ break;
default:
VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32,
ntohl(ah->opcode));
@@ -252,6 +437,7 @@ process_packet_in(const struct ofp_header *msg)
static void
pinctrl_recv(const struct ofp_header *oh, enum ofptype type)
{
+
if (type == OFPTYPE_ECHO_REQUEST) {
queue_msg(make_echo_reply(oh));
} else if (type == OFPTYPE_GET_CONFIG_REPLY) {
@@ -20,14 +20,18 @@
#include "actions.h"
#include "byte-order.h"
#include "compiler.h"
+#include "ovn-dhcp.h"
#include "expr.h"
#include "lex.h"
#include "logical-fields.h"
#include "openvswitch/dynamic-string.h"
#include "openvswitch/ofp-actions.h"
#include "openvswitch/ofpbuf.h"
+#include "packets.h"
+#include "shash.h"
#include "simap.h"
+
/* Context maintained during actions_parse(). */
struct action_context {
const struct action_params *ap; /* Parameters. */
@@ -186,13 +190,15 @@ add_prerequisite(struct action_context *ctx, const char *prerequisite)
}
static size_t
-start_controller_op(struct ofpbuf *ofpacts, enum action_opcode opcode)
+start_controller_op(struct ofpbuf *ofpacts, enum action_opcode opcode,
+ bool pause)
{
size_t ofs = ofpacts->size;
struct ofpact_controller *oc = ofpact_put_CONTROLLER(ofpacts);
oc->max_len = UINT16_MAX;
oc->reason = OFPR_ACTION;
+ oc->pause = pause;
struct action_header ah = { .opcode = htonl(opcode) };
ofpbuf_put(ofpacts, &ah, sizeof ah);
@@ -212,7 +218,7 @@ finish_controller_op(struct ofpbuf *ofpacts, size_t ofs)
static void
put_controller_op(struct ofpbuf *ofpacts, enum action_opcode opcode)
{
- size_t ofs = start_controller_op(ofpacts, opcode);
+ size_t ofs = start_controller_op(ofpacts, opcode, false);
finish_controller_op(ofpacts, ofs);
}
@@ -246,7 +252,9 @@ parse_arp_action(struct action_context *ctx)
* converted to OpenFlow, as its userdata. ovn-controller will convert the
* packet to an ARP and then send the packet and actions back to the switch
* inside an OFPT_PACKET_OUT message. */
- size_t oc_offset = start_controller_op(ctx->ofpacts, ACTION_OPCODE_ARP);
+ /* controller. */
+ size_t oc_offset = start_controller_op(ctx->ofpacts, ACTION_OPCODE_ARP,
+ false);
ofpacts_put_openflow_actions(inner_ofpacts.data, inner_ofpacts.size,
ctx->ofpacts, OFP13_VERSION);
finish_controller_op(ctx->ofpacts, oc_offset);
@@ -413,6 +421,193 @@ parse_put_arp_action(struct action_context *ctx)
restore_args(ctx, args, ARRAY_SIZE(args));
}
+static bool
+parse_dhcp_opt(struct action_context *ctx, struct ofpbuf *ofpacts)
+{
+ if (ctx->lexer->token.type != LEX_T_ID) {
+ action_syntax_error(ctx, NULL);
+ return false;
+ }
+
+ enum lex_type lookahead = lexer_lookahead(ctx->lexer);
+ if (lookahead != LEX_T_EQUALS) {
+ action_syntax_error(ctx, NULL);
+ return false;
+ }
+
+ const struct dhcp_opts_map *dhcp_opt = dhcp_opts_find(
+ ctx->ap->dhcp_opts, ctx->lexer->token.s);
+
+ if (!dhcp_opt) {
+ action_syntax_error(ctx, "expecting valid dhcp option.");
+ return false;
+ }
+
+ lexer_get(ctx->lexer);
+ lexer_get(ctx->lexer);
+
+ struct expr_constant_set cs;
+ memset(&cs, 0, sizeof(struct expr_constant_set));
+ if (!expr_parse_constant_set(ctx->lexer, NULL, &cs)) {
+ action_syntax_error(ctx, "invalid dhcp option values");
+ return false;
+ }
+
+ if (!strcmp(dhcp_opt->type, "str")) {
+ if (cs.type != EXPR_C_STRING) {
+ return false;
+ }
+ } else {
+ if (cs.type != EXPR_C_INTEGER) {
+ return false;
+ }
+ }
+
+ if (!lexer_match(ctx->lexer, LEX_T_COMMA) && (
+ ctx->lexer->token.type != LEX_T_RPAREN)) {
+ action_syntax_error(ctx, NULL);
+ return false;
+ }
+
+
+ if (dhcp_opt->code == 0) {
+ /* offer-ip */
+ ofpbuf_put(ofpacts, &cs.values[0].value.ipv4, sizeof(ovs_be32));
+ goto exit;
+ }
+
+ uint8_t *opt_header = ofpbuf_put_uninit(ofpacts, 2);
+ opt_header[0] = dhcp_opt->code;
+
+ if (!strcmp(dhcp_opt->type, "bool") || !strcmp(dhcp_opt->type, "uint8")) {
+ opt_header[1] = 1;
+ ofpbuf_put(ofpacts, &cs.values[0].value.u8_val, 1);
+ } else if (!strcmp(dhcp_opt->type, "uint16")) {
+ opt_header[1] = 2;
+ ofpbuf_put(ofpacts, &cs.values[0].value.be16_int, 2);
+ } else if (!strcmp(dhcp_opt->type, "uint32")) {
+ opt_header[1] = 4;
+ ofpbuf_put(ofpacts, &cs.values[0].value.be32_int, 4);
+ } else if (!strcmp(dhcp_opt->type, "ipv4")) {
+ opt_header[1] = cs.n_values * sizeof(ovs_be32);
+ for (size_t i = 0; i < cs.n_values; i++) {
+ ofpbuf_put(ofpacts, &cs.values[i].value.ipv4, sizeof(ovs_be32));
+ }
+ } else if (!strcmp(dhcp_opt->type, "static_routes")) {
+ size_t no_of_routes = cs.n_values;
+ if (no_of_routes % 2) {
+ no_of_routes -= 1;
+ }
+ opt_header[1] = 0;
+
+ /* Calculating the length of this option first because when
+ * we call ofpbuf_put, it might reallocate the buffer if the
+ * tail room is short making "opt_header" pointer invalid.
+ * So running the for loop twice.
+ */
+ for (size_t i = 0; i < no_of_routes; i += 2) {
+ uint8_t plen = 32;
+ if (cs.values[i].masked) {
+ plen = (uint8_t) ip_count_cidr_bits(cs.values[i].mask.ipv4);
+ }
+ opt_header[1] += (1 + (plen / 8) + sizeof(ovs_be32)) ;
+ }
+
+ /* Copied from RFC 3442. Please refer to this RFC for the format of
+ * the classless static route option.
+ *
+ * The following table contains some examples of how various subnet
+ * number/mask combinations can be encoded:
+ *
+ * Subnet number Subnet mask Destination descriptor
+ * 0 0 0
+ * 10.0.0.0 255.0.0.0 8.10
+ * 10.0.0.0 255.255.255.0 24.10.0.0
+ * 10.17.0.0 255.255.0.0 16.10.17
+ * 10.27.129.0 255.255.255.0 24.10.27.129
+ * 10.229.0.128 255.255.255.128 25.10.229.0.128
+ * 10.198.122.47 255.255.255.255 32.10.198.122.47
+ */
+
+ for (size_t i = 0; i < no_of_routes; i += 2) {
+ uint8_t plen = 32;
+ if (cs.values[i].masked) {
+ plen = (uint8_t) ip_count_cidr_bits(cs.values[i].mask.ipv4);
+ }
+ ofpbuf_put(ofpacts, &plen, 1);
+ ofpbuf_put(ofpacts, &cs.values[i].value.ipv4, plen / 8);
+ ofpbuf_put(ofpacts, &cs.values[i + 1].value.ipv4,
+ sizeof(ovs_be32));
+ }
+ } else if (!strcmp(dhcp_opt->type, "str")) {
+ opt_header[1] = strlen(cs.values[0].string);
+ ofpbuf_put(ofpacts, cs.values[0].string, opt_header[1]);
+ }
+
+exit:
+ expr_constant_set_destroy(&cs);
+ return true;
+}
+
+/*
+ * Parses the "put_dhcp_opts" action.
+ * Format of the action is
+ * - put_dchp_opts(R, offer_ip = <offerip>, D1 = V1,..., Dn = Vn)
+ *
+ * Example:
+ * put_dhcp_opts(reg0, offer_ip = 10.0.0.4, router = 10.0.0.1,
+ * netmask = 255.255.255.0, lease_time = 3600,...)
+ *
+ * The parsed data is stored in the ofpact_controller->userdata.
+ *
+ * The format of the "userdata" is
+ * --------------------------------------------------------------------------
+ * | PUT_DHCP_OPTS OP CODE | REG IDX (4 B) | OFFER IP | DHCP options |
+ * | (8 B) |(to store the result)| (4 B) | |
+ * --------------------------------------------------------------------------
+ */
+static void
+parse_put_dhcp_opts_action(struct action_context *ctx)
+{
+ if (!action_force_match(ctx, LEX_T_LPAREN)) {
+ return;
+ }
+
+ const struct expr_symbol *symbol =
+ shash_find_data(ctx->ap->symtab, ctx->lexer->token.s);
+
+ /* Allowed registers to store the result are reg0 to reg2 */
+ if (!symbol || (
+ symbol->field->id < MFF_REG0 && symbol->field->id > MFF_REG2)) {
+ return;
+ }
+
+ lexer_get(ctx->lexer);
+ if (!action_force_match(ctx, LEX_T_COMMA)) {
+ return;
+ }
+
+ /* Make sure the first option is "offer_ip" */
+ const struct dhcp_opts_map *dhcp_opt = dhcp_opts_find(
+ ctx->ap->dhcp_opts, ctx->lexer->token.s);
+ if (!dhcp_opt || dhcp_opt->code != 0) {
+ return;
+ }
+
+ /* controller. */
+ size_t oc_offset = start_controller_op(ctx->ofpacts,
+ ACTION_OPCODE_PUT_DHCP_OPTS, true);
+ uint32_t *reg_idx = ofpbuf_put_uninit(ctx->ofpacts, sizeof(uint32_t));
+ *reg_idx = symbol->field->id - MFF_REG0;
+
+ while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
+ if (!parse_dhcp_opt(ctx, ctx->ofpacts)) {
+ return;
+ }
+ }
+ finish_controller_op(ctx->ofpacts, oc_offset);
+}
+
static void
emit_ct(struct action_context *ctx, bool recirc_next, bool commit)
{
@@ -475,6 +670,8 @@ parse_action(struct action_context *ctx)
parse_get_arp_action(ctx);
} else if (lexer_match_id(ctx->lexer, "put_arp")) {
parse_put_arp_action(ctx);
+ } else if (lexer_match_id(ctx->lexer, "put_dhcp_opts")) {
+ parse_put_dhcp_opts_action(ctx);
} else {
action_syntax_error(ctx, "expecting action");
}
@@ -44,6 +44,11 @@ enum action_opcode {
* MFF_ETH_SRC = mac
*/
ACTION_OPCODE_PUT_ARP,
+
+ /* "put_dhcp_opts(reg_idx, offer_ip,...dhcp actions ...)".
+ *
+ */
+ ACTION_OPCODE_PUT_DHCP_OPTS,
};
/* Header. */
@@ -58,6 +63,9 @@ struct action_params {
* expr_parse()). */
const struct shash *symtab;
+ /* hmap of 'struct dhcp_opts_map' to support 'put_dhcp_opts' action */
+ const struct hmap *dhcp_opts;
+
/* Looks up logical port 'port_name'. If found, stores its port number in
* '*portp' and returns true; otherwise, returns false. */
bool (*lookup_port)(const void *aux, const char *port_name,
@@ -10,6 +10,7 @@ ovn_lib_libovn_la_SOURCES = \
ovn/lib/expr.h \
ovn/lib/lex.c \
ovn/lib/lex.h \
+ ovn/lib/ovn-dhcp.h \
ovn/lib/ovn-util.c \
ovn/lib/ovn-util.h \
ovn/lib/logical-fields.h
@@ -405,39 +405,6 @@ expr_print(const struct expr *e)
/* Parsing. */
-/* Type of a "union expr_constant" or "struct expr_constant_set". */
-enum expr_constant_type {
- EXPR_C_INTEGER,
- EXPR_C_STRING
-};
-
-/* A string or integer constant (one must know which from context). */
-union expr_constant {
- /* Integer constant.
- *
- * The width of a constant isn't always clear, e.g. if you write "1",
- * there's no way to tell whether you mean for that to be a 1-bit constant
- * or a 128-bit constant or somewhere in between. */
- struct {
- union mf_subvalue value;
- union mf_subvalue mask; /* Only initialized if 'masked'. */
- bool masked;
-
- enum lex_format format; /* From the constant's lex_token. */
- };
-
- /* Null-terminated string constant. */
- char *string;
-};
-
-/* A collection of "union expr_constant"s of the same type. */
-struct expr_constant_set {
- union expr_constant *values; /* Constants. */
- size_t n_values; /* Number of constants. */
- enum expr_constant_type type; /* Type of the constants. */
- bool in_curlies; /* Whether the constants were in {}. */
-};
-
/* A reference to a symbol or a subfield of a symbol.
*
* For string fields, ofs and n_bits are 0. */
@@ -457,7 +424,6 @@ struct expr_context {
struct expr *expr_parse__(struct expr_context *);
static void expr_not(struct expr *);
-static void expr_constant_set_destroy(struct expr_constant_set *);
static bool parse_field(struct expr_context *, struct expr_field *);
static bool
@@ -838,7 +804,7 @@ parse_constant_set(struct expr_context *ctx, struct expr_constant_set *cs)
return ok;
}
-static void
+void
expr_constant_set_destroy(struct expr_constant_set *cs)
{
if (cs) {
@@ -2928,3 +2894,15 @@ exit:
}
return ctx.error;
}
+
+bool
+expr_parse_constant_set(struct lexer *lexer, const struct shash *symtab,
+ struct expr_constant_set *cs)
+{
+ struct expr_context ctx = {
+ .lexer = lexer,
+ .symtab = symtab,
+ };
+
+ return parse_constant_set(&ctx, cs);
+}
@@ -391,4 +391,41 @@ char *expr_parse_field(struct lexer *, int n_bits, bool rw,
const struct shash *symtab, struct mf_subfield *,
struct expr **prereqsp);
+/* Type of a "union expr_constant" or "struct expr_constant_set". */
+enum expr_constant_type {
+ EXPR_C_INTEGER,
+ EXPR_C_STRING
+};
+
+/* A string or integer constant (one must know which from context). */
+union expr_constant {
+ /* Integer constant.
+ *
+ * The width of a constant isn't always clear, e.g. if you write "1",
+ * there's no way to tell whether you mean for that to be a 1-bit constant
+ * or a 128-bit constant or somewhere in between. */
+ struct {
+ union mf_subvalue value;
+ union mf_subvalue mask; /* Only initialized if 'masked'. */
+ bool masked;
+
+ enum lex_format format; /* From the constant's lex_token. */
+ };
+
+ /* Null-terminated string constant. */
+ char *string;
+};
+
+/* A collection of "union expr_constant"s of the same type. */
+struct expr_constant_set {
+ union expr_constant *values; /* Constants. */
+ size_t n_values; /* Number of constants. */
+ enum expr_constant_type type; /* Type of the constants. */
+ bool in_curlies; /* Whether the constants were in {}. */
+};
+
+bool expr_parse_constant_set(struct lexer *, const struct shash *symtab,
+ struct expr_constant_set *cs);
+void expr_constant_set_destroy(struct expr_constant_set *cs);
+
#endif /* ovn/expr.h */
new file mode 100644
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVN_DHCP_H
+#define OVN_DHCP_H 1
+
+#include "hmap.h"
+#include "hash.h"
+
+struct dhcp_opts_map {
+ struct hmap_node hmap_node;
+ char *name;
+ char *type;
+ size_t code;
+};
+
+#define DHCP_OPTION(NAME, CODE, TYPE) \
+ {.name = NAME, .code = CODE, .type = TYPE}
+
+#define OFFERIP DHCP_OPTION("offerip", 0, "ipv4")
+#define DHCP_OPT_NETMASK DHCP_OPTION("netmask", 1, "ipv4")
+#define DHCP_OPT_ROUTER DHCP_OPTION("router", 3, "ipv4")
+#define DHCP_OPT_DNS_SERVER DHCP_OPTION("dns_server", 6, "ipv4")
+#define DHCP_OPT_LOG_SERVER DHCP_OPTION("log_server", 7, "ipv4")
+#define DHCP_OPT_LPR_SERVER DHCP_OPTION("lpr_server", 9, "ipv4")
+#define DHCP_OPT_SWAP_SERVER DHCP_OPTION("swap_server", 16, "ipv4")
+
+#define DHCP_OPT_POLICY_FILTER \
+ DHCP_OPTION("policy_filter", 21, "ipv4")
+
+#define DHCP_OPT_ROUTER_SOLICITATION \
+ DHCP_OPTION("router_solicitation", 32, "ipv4")
+
+#define DHCP_OPT_NIS_SERVER DHCP_OPTION("nis_server", 41, "ipv4")
+#define DHCP_OPT_NTP_SERVER DHCP_OPTION("ntp_server", 42, "ipv4")
+#define DHCP_OPT_SERVER_ID DHCP_OPTION("server_id", 54, "ipv4")
+#define DHCP_OPT_TFTP_SERVER DHCP_OPTION("tftp_server", 66, "ipv4")
+
+#define DHCP_OPT_CLASSLESS_STATIC_ROUTE \
+ DHCP_OPTION("classless_static_route", 121, "static_routes")
+#define DHCP_OPT_MS_CLASSLESS_STATIC_ROUTE \
+ DHCP_OPTION("ms_classless_static_route", 249, "static_routes")
+
+#define DHCP_OPT_IP_FORWARD_ENABLE DHCP_OPTION("ip_forward_enable", 19, "bool")
+#define DHCP_OPT_ROUTER_DISCOVERY DHCP_OPTION("router_discovery", 31, "bool")
+#define DHCP_OPT_ETHERNET_ENCAP DHCP_OPTION("ethernet_encap", 36, "bool")
+
+#define DHCP_OPT_DEFAULT_TTL DHCP_OPTION("default_ttl", 23, "uint8")
+
+#define DHCP_OPT_TCP_TTL DHCP_OPTION("tcp_ttl", 37, "uint8")
+#define DHCP_OPT_MTU DHCP_OPTION("mtu", 26, "uint16")
+#define DHCP_OPT_LEASE_TIME DHCP_OPTION("lease_time", 51, "uint32")
+#define DHCP_OPT_T1 DHCP_OPTION("T1", 58, "uint32")
+#define DHCP_OPT_T2 DHCP_OPTION("T2", 59, "uint32")
+
+static inline uint32_t
+dhcp_opt_hash(char *opt_name)
+{
+ return hash_string(opt_name, 0);
+}
+
+static inline struct dhcp_opts_map *
+dhcp_opts_find(const struct hmap *dhcp_opts, char *opt_name)
+{
+ struct dhcp_opts_map *dhcp_opt;
+ HMAP_FOR_EACH_WITH_HASH (dhcp_opt, hmap_node, dhcp_opt_hash(opt_name),
+ dhcp_opts) {
+ if (!strcmp(dhcp_opt->name, opt_name)) {
+ return dhcp_opt;
+ }
+ }
+
+ return NULL;
+}
+
+static inline void
+dhcp_opt_add(struct hmap *dhcp_opts, char *opt_name, size_t code, char *type)
+{
+ struct dhcp_opts_map *dhcp_opt = xzalloc(sizeof *dhcp_opt);
+ dhcp_opt->name = xstrdup(opt_name);
+ dhcp_opt->code = code;
+ dhcp_opt->type = xstrdup(type);
+ hmap_insert(dhcp_opts, &dhcp_opt->hmap_node, dhcp_opt_hash(opt_name));
+}
+
+static inline void
+dhcp_opts_destroy(struct hmap *dhcp_opts)
+{
+ struct dhcp_opts_map *dhcp_opt;
+ HMAP_FOR_EACH_POP(dhcp_opt, hmap_node, dhcp_opts) {
+ free(dhcp_opt->name);
+ free(dhcp_opt->type);
+ free(dhcp_opt);
+ }
+ hmap_destroy(dhcp_opts);
+}
+
+#endif /* OVN_DHCP_H */
@@ -1,7 +1,7 @@
{
"name": "OVN_Southbound",
- "version": "1.3.0",
- "cksum": "654726257 5528",
+ "version": "1.4.0",
+ "cksum": "198773462 6073",
"tables": {
"Chassis": {
"columns": {
@@ -110,4 +110,16 @@
"ip": {"type": "string"},
"mac": {"type": "string"}},
"indexes": [["logical_port", "ip"]],
+ "isRoot": true},
+ "DHCP_Options": {
+ "columns": {
+ "name": {"type": "string"},
+ "code": {
+ "type": {"key": {"type": "integer",
+ "minInteger": 0, "maxInteger": 254}}},
+ "type": {
+ "type": {"key": {
+ "type": "string",
+ "enum": ["set", ["bool", "uint8", "uint16", "uint32",
+ "ipv4", "static_routes", "str"]]}}}},
"isRoot": true}}}
@@ -1022,6 +1022,46 @@
<p><b>Example:</b> <code>put_arp(inport, arp.spa, arp.sha);</code></p>
</dd>
+
+ <dt>
+ <code>put_dhcp_opts(<var>R</var>, <var>offer_ip</var> = <var>IP</var>, <var>D1</var> = <var>V1</var>, <var>D2</var> = <var>V2</var>, ...,<var>Dn</var> = <var>Vn</var>);</code>
+ </dt>
+
+ <dd>
+ <p>
+ <b>Parameters</b>: Name of the OVS register <var>R</var>,
+ IPv4 address to be offered <var>IP</var>, DHCP option name <var>Dn</var>, DHCP option
+ value <var>Vn</var>.
+ </p>
+
+ <p>
+ Valid only in the ingress pipeline.
+ </p>
+
+ <p>
+ This action acts only on valid DHCP request packets (DHCPDISCOVER
+ and DHCPREQUEST) and adds the DHCP options (<var>Dn</var>) and
+ values (<var>Vn</var>) in the "DHCP options" field of the packet.
+ It also sets <code>0x1</code> to the OVS register <var>R</var>
+ indicating that the action was successful and resumes the pipeline.
+
+ If the packet is a non-DHCP packet or other type of DHCP packet
+ or an invalid DHCP packet, this action sets <code>0x0</code> to the
+ OVS register <var>R</var> indicating the action was unsuccessful
+ and resumes the pipeline.
+
+ <code>reg0</code>, <code>reg1</code> and <code>reg2</code>
+ are the allowed values for the OVS register <var>R</var>
+ </p>
+
+ <p>
+ <b>Example:</b>
+ <code>
+ put_dhcp_opts(reg0, offerip = 10.0.0.2, router = 10.0.0.1,
+ netmask = 255.255.255.0, dns_server = {8.8.8.8,7.7.7.7});
+ </code>
+ </p>
+ </dd>
</dl>
<p>
@@ -1544,4 +1584,157 @@ tcp.flags = RST;
The Ethernet address to which the IP is bound.
</column>
</table>
+
+ <table name="DHCP_Options" title="DHCP Options supported by native OVN DHCP">
+ <p>
+ Each row in this table stores the DHCP Options supported by native OVN
+ DHCP. <code>ovn-northd</code> populates this table with the supported
+ DHCP options. <code>ovn-controller</code> looks up this table to get the
+ DHCP codes of the DHCP options defined in the "put_dhcp_opts" action.
+ Please refer to the RFC 2132 <code>"https://tools.ietf.org/html/rfc2132"</code>
+ for the possible list of DHCP options that can be defined here.
+ </p>
+
+ <column name="name">
+ <p>
+ Name of the DHCP option.
+ </p>
+
+ <p>
+ Example. name="router"
+ </p>
+ </column>
+
+ <column name="code">
+ <p>
+ DHCP option code for the DHCP option as defined in the RFC 2132.
+ </p>
+
+ <p>
+ Example. code=3
+ </p>
+ </column>
+
+ <column name="type">
+ <p>
+ Data type of the DHCP option code.
+ </p>
+
+ <dl>
+ <dt><code>value: bool</code></dt>
+ <dd>
+ <p>
+ This indicates that the value of the DHCP option is a bool.
+ </p>
+
+ <p>
+ Example. "name=ip_forward_enable", "code=19", "type=bool".
+ </p>
+
+ <p>
+ put_dhcp_opts(..., ip_forward_enable = 1,...)
+ </p>
+ </dd>
+
+ <dt><code>value: uint8</code></dt>
+ <dd>
+ <p>
+ This indicates that the value of the DHCP option is an unsigned
+ int8 (8 bits)
+ </p>
+
+ <p>
+ Example. "name=default_ttl", "code=23", "type=uint8".
+ </p>
+
+ <p>
+ put_dhcp_opts(..., default_ttl = 50,...)
+ </p>
+ </dd>
+
+ <dt><code>value: uint16</code></dt>
+ <dd>
+ <p>
+ This indicates that the value of the DHCP option is an unsigned
+ int16 (16 bits).
+ </p>
+
+ <p>
+ Example. "name=mtu", "code=26", "type=uint16".
+ </p>
+
+ <p>
+ put_dhcp_opts(..., mtu = 1450,...)
+ </p>
+ </dd>
+
+ <dt><code>value: uint32</code></dt>
+ <dd>
+ <p>
+ This indicates that the value of the DHCP option is an unsigned
+ int32 (32 bits).
+ </p>
+
+ <p>
+ Example. "name=lease_time", "code=51", "type=uint32".
+ </p>
+
+ <p>
+ put_dhcp_opts(..., lease_time = 86400,...)
+ </p>
+ </dd>
+
+ <dt><code>value: ipv4</code></dt>
+ <dd>
+ <p>
+ This indicates that the value of the DHCP option is an IPv4
+ address or addresses.
+ </p>
+
+ <p>
+ Example. "name=router", "code=3", "type=ipv4".
+ </p>
+
+ <p>
+ put_dhcp_opts(..., router = 10.0.0.1,...)
+ </p>
+
+ <p>
+ Example. "name=dns_server", "code=6", "type=ipv4".
+ </p>
+
+ <p>
+ put_dhcp_opts(..., dns_server = {8.8.8.8 7.7.7.7},...)
+ </p>
+ </dd>
+
+ <dt><code>value: static_routes</code></dt>
+ <dd>
+ <p>
+ This indicates that the value of the DHCP option contains a pair of
+ IPv4 route and next hop addresses.
+ </p>
+
+ <p>
+ Example. "name=classless_static_route", "code=121", "type=static_routes".
+ </p>
+
+ <p>
+ put_dhcp_opts(..., classless_static_route = {30.0.0.0/24,10.0.0.4,0.0.0.0/0,10.0.0.1}...)
+ </p>
+ </dd>
+
+ <dt><code>value: str</code></dt>
+ <dd>
+ <p>
+ This indicates that the value of the DHCP option is a string.
+ </p>
+
+ <p>
+ Example. "name=host_name", "code=12", "type=str".
+ </p>
+ </dd>
+ </dl>
+ </column>
+ </table>
</database>
@@ -337,6 +337,7 @@ tests_ovstest_SOURCES = \
tests/test-odp.c \
tests/test-ofpbuf.c \
tests/test-ovn.c \
+ tests/test-ovn-dhcp.c \
tests/test-packets.c \
tests/test-random.c \
tests/test-rcu.c \
@@ -2871,6 +2871,57 @@ OVS_APP_EXIT_AND_WAIT([ovsdb-server])
AT_CLEANUP
+AT_SETUP([ovn -- put_dhcp_opts action])
+dhcp_options="offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400"
+echo "dhcp_options = $dhcp_options"
+
+expected_dhcp_opts="0a00000403040a0000010104fffffe001a020578"
+AT_CHECK([ovstest test-ovn-put-dhcp-opts-action reg0 $dhcp_options \
+$expected_dhcp_opts], [0], [ignore])
+
+AT_CHECK([ovstest test-ovn-put-dhcp-opts-action reg2 $dhcp_options \
+$expected_dhcp_opts], [0], [ignore])
+
+AT_CHECK([ovstest test-ovn-put-dhcp-opts-action reg3 $dhcp_options \
+$expected_dhcp_opts], [1], [ignore])
+
+AT_CHECK([ovstest test-ovn-put-dhcp-opts-action reg5 $dhcp_options \
+$expected_dhcp_opts], [1], [ignore])
+
+AT_CHECK([ovstest test-ovn-put-dhcp-opts-action foo $dhcp_options \
+$expected_dhcp_opts], [1], [ignore])
+
+dhcp_options="offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.255.0,mtu=1400,\
+ip_forward_enable=1,default_ttl=121,dns_server={8.8.8.8,7.7.7.7},\
+classless_static_route={30.0.0.0/24,10.0.0.4,40.0.0.0/16,10.0.0.6,\
+0.0.0.0/0,10.0.0.1},ethernet_encap=1"
+
+# offerip=10.0.0.4 --> 0a000004
+# router=10.0.0.1 --> 03040a000001
+# netmask=255.255.255.0 --> 0104ffffff00
+# mtu=1400 --> 1a020578
+# ip_forward_enable-1 --> 130101
+# default_ttl=121 --> 170179
+# dns_server={8.8.8.8,7.7.7.7} --> 06080808080807070707
+# classless_static_route= --> 7914
+# {30.0.0.0/24,10.0.0.4 --> 181e00000a000004
+# 40.0.0.0/16,10.0.0.6 --> 1028000a000006
+# 0.0.0.0/0,10.0.0.1} --> 000a000001
+# ethernet_encap=1 --> 240101
+# router_discovery=0 --> 1f0100
+
+expected_dhcp_opts="0a00000403040a0000010104ffffff001a020578130101170179\
+060808080808070707077914181e00000a0000041028000a000006000a000001240101"
+
+AT_CHECK([ovstest test-ovn-put-dhcp-opts-action reg1 $dhcp_options \
+$expected_dhcp_opts], [0], [ignore])
+
+dhcp_options="offerip=10.0.0.4,invalid_options=inv_value"
+expected_dhcp_opts="00"
+AT_CHECK([ovstest test-ovn-put-dhcp-opts-action reg0 $dhcp_options $expected_dhcp_opts],
+ [1], [ignore])
+
+AT_CLEANUP
AT_SETUP([ovn -- 2 HVs, 2 LRs connected via LS, gateway router])
AT_KEYWORDS([ovngatewayrouter])
new file mode 100644
@@ -0,0 +1,149 @@
+/* Copyright (c) 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <config.h>
+#include "command-line.h"
+#include "openvswitch/ofp-actions.h"
+#include "ovstest.h"
+#include "ovn/lib/actions.h"
+#include "ovn/lib/ovn-dhcp.h"
+#include "ovn/lib/expr.h"
+#include "ovn/lib/logical-fields.h"
+#include "shash.h"
+
+static void
+add_logical_register(struct shash *symtab, enum mf_field_id id)
+{
+ char name[8];
+
+ snprintf(name, sizeof name, "reg%d", id - MFF_REG0);
+ expr_symtab_add_field(symtab, name, id, NULL, false);
+}
+
+static void
+test_put_dhcp_opts_action(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ if (argc != 4) {
+ printf("Usage: %s reg_name dhcp-options expected-dhcp-opt-codes",
+ argv[0]);
+ exit(1);
+ }
+
+ struct hmap dhcp_opts = HMAP_INITIALIZER(&dhcp_opts);
+
+ dhcp_opt_add(&dhcp_opts, "offerip", 0, "ipv4");
+ dhcp_opt_add(&dhcp_opts, "netmask", 1, "ipv4");
+ dhcp_opt_add(&dhcp_opts, "router", 3, "ipv4");
+ dhcp_opt_add(&dhcp_opts, "dns_server", 6, "ipv4");
+ dhcp_opt_add(&dhcp_opts, "log_server", 7, "ipv4");
+ dhcp_opt_add(&dhcp_opts, "lpr_server", 9, "ipv4");
+ dhcp_opt_add(&dhcp_opts, "swap_server", 16, "ipv4");
+ dhcp_opt_add(&dhcp_opts, "policy_filter", 21, "ipv4");
+ dhcp_opt_add(&dhcp_opts, "router_solicitation", 32, "ipv4");
+ dhcp_opt_add(&dhcp_opts, "nis_server", 41, "ipv4");
+ dhcp_opt_add(&dhcp_opts, "ntp_server", 42, "ipv4");
+ dhcp_opt_add(&dhcp_opts, "server_id", 54, "ipv4");
+ dhcp_opt_add(&dhcp_opts, "tftp_server", 66, "ipv4");
+ dhcp_opt_add(&dhcp_opts, "classless_static_route", 121,
+ "static_routes");
+ dhcp_opt_add(&dhcp_opts, "ip_forward_enable", 19, "bool");
+ dhcp_opt_add(&dhcp_opts, "router_discovery", 31, "bool");
+ dhcp_opt_add(&dhcp_opts, "ethernet_encap", 36, "bool");
+ dhcp_opt_add(&dhcp_opts, "default_ttl", 23, "uint8");
+ dhcp_opt_add(&dhcp_opts, "tcp_ttl", 37, "uint8");
+ dhcp_opt_add(&dhcp_opts, "mtu", 26, "uint16");
+ dhcp_opt_add(&dhcp_opts, "lease_time", 51, "uint32");
+
+ struct shash symtab;
+ shash_init(&symtab);
+#define MFF_LOG_REG(ID) add_logical_register(&symtab, ID);
+ MFF_LOG_REGS;
+#undef MFF_LOG_REG
+
+ struct action_params ap = {
+ .symtab = &symtab,
+ .dhcp_opts = &dhcp_opts,
+ };
+
+
+ char *actions = xasprintf("put_dhcp_opts(%s, %s);", argv[1], argv[2]);
+ uint64_t ofpacts_stub[128 / 8];
+ struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(
+ ofpacts_stub);
+ struct expr *prereqs;
+ char *error;
+
+ error = actions_parse_string(actions, &ap, &ofpacts, &prereqs);
+ dhcp_opts_destroy(&dhcp_opts);
+ free(actions);
+ if (error) {
+ printf("actions_parse_string failed with error - %s\n", error);
+ free(error);
+ exit(1);
+ }
+
+ if (ofpacts.size < (sizeof(struct ofpact_controller) +
+ sizeof(struct action_header))) {
+ ovs_fatal(1, "Error. put_dhcp_opts parse action failed : "
+ " ofpact_controller not configured");
+ }
+
+ struct ofpact_controller *oc = ofpbuf_pull(&ofpacts, sizeof *oc);
+ if (!oc->pause) {
+ ovs_fatal(1, "Error. put_dhcp_opts parse action failed : pause flag "
+ " not set in ofpact_controller");
+ }
+ struct action_header *ah = ofpbuf_pull(&ofpacts, sizeof *ah);
+ if (ah->opcode != htonl(ACTION_OPCODE_PUT_DHCP_OPTS)) {
+ ovs_fatal(1, "Error. put_dhcp_opts parse action failed : put_dhcp_opts "
+ "action header flag not set");
+ }
+
+ uint32_t *reg_idx = ofpbuf_pull(&ofpacts, sizeof *reg_idx);
+ const struct mf_field *field = mf_from_name(argv[1]);
+ if (!field) {
+ ovs_fatal(1, "Error. Invalid register name : %s", argv[1]);
+ }
+
+ if (*reg_idx != (field->id - MFF_REG0)) {
+ ovs_fatal(1, "Error. put_dhcp_opts parse action failed : status register "
+ "id in userdata doesn't match. Expected - [%d] :"
+ " Actual - [%d]\n", field->id - MFF_REG0, *reg_idx);
+ }
+
+ uint64_t expected_dhcp_opts_stub[128 / 8];
+ struct ofpbuf expected_dhcp_opts = OFPBUF_STUB_INITIALIZER(
+ expected_dhcp_opts_stub);
+ if (ofpbuf_put_hex(&expected_dhcp_opts, argv[3], NULL)[0] != '\0') {
+ ovs_fatal(1, "Error. Invalid expected dhcp opts");
+ }
+
+ if (oc->userdata_len !=
+ (expected_dhcp_opts.size + sizeof *ah + sizeof(uint32_t))) {
+ ovs_fatal(1, "Error. put_dhcp_opts parse action failed : userdata length"
+ " mismatch. Expected - %"PRIuSIZE" : Actual - %"PRIu16"",
+ expected_dhcp_opts.size + sizeof *ah, oc->userdata_len);
+ }
+
+ if (memcmp(ofpacts.data, expected_dhcp_opts.data, expected_dhcp_opts.size)) {
+ ovs_fatal(1, "Error. put_dhcp_opts parse action failed : dhcp opts are"
+ " not as expected");
+ }
+
+ exit(0);
+}
+
+OVSTEST_REGISTER("test-ovn-put-dhcp-opts-action", test_put_dhcp_opts_action);
This patch adds a new OVN action 'put_dhcp_opts' to support native DHCP in OVN. ovn-controller parses this action and adds a NXT_PACKET_IN2 OF flow with 'pause' flag set and the DHCP options stored in 'userdata' field. When the valid DHCP packet is received by ovn-controller, it frames a new DHCP reply packet with the DHCP options present in the 'userdata' field and resumes the packet and stores 1 in the OVS register provded in the 'put_dhcp_opts' parameter. If the packet is invalid, it resumes the packet without any modifying and stores 0 in the OVS register. Eg. put_dhcp_opts(reg0, efferip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.255.0, lease_time = 3600,....) A new 'DHCP_Options' table is added in SB DB which stores the supported DHCP options with DHCP code and type. ovn-northd is expected to popule this table. The next patch will add logical flows with this action. Signed-Off-by: Numan Siddique <nusiddiq@redhat.com> --- include/openvswitch/meta-flow.h | 12 +++ lib/dhcp.h | 13 +++ ovn/controller/lflow.c | 11 +++ ovn/controller/pinctrl.c | 188 ++++++++++++++++++++++++++++++++++++- ovn/lib/actions.c | 203 +++++++++++++++++++++++++++++++++++++++- ovn/lib/actions.h | 8 ++ ovn/lib/automake.mk | 1 + ovn/lib/expr.c | 48 +++------- ovn/lib/expr.h | 37 ++++++++ ovn/lib/ovn-dhcp.h | 111 ++++++++++++++++++++++ ovn/ovn-sb.ovsschema | 16 +++- ovn/ovn-sb.xml | 193 ++++++++++++++++++++++++++++++++++++++ tests/automake.mk | 1 + tests/ovn.at | 51 ++++++++++ tests/test-ovn-dhcp.c | 149 +++++++++++++++++++++++++++++ 15 files changed, 1001 insertions(+), 41 deletions(-) create mode 100644 ovn/lib/ovn-dhcp.h create mode 100644 tests/test-ovn-dhcp.c