ovn-controller: Add 'put_dhcp_opts' action in ovn-controller
authorNuman Siddique <nusiddiq@redhat.com>
Wed, 15 Jun 2016 09:17:35 +0000 (14:47 +0530)
committerBen Pfaff <blp@ovn.org>
Thu, 23 Jun 2016 22:57:22 +0000 (15:57 -0700)
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 1-bit subfield.
If the packet is invalid, it resumes the packet without any modifying and
stores 0 in the 1-bit subfield.

Eg. reg0[0] = put_dhcp_opts(offerip = 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>
Co-authored-by: Ben Pfaff <blp@ovn.org>
Signed-off-by: Ben Pfaff <blp@ovn.org>
14 files changed:
include/openvswitch/meta-flow.h
lib/dhcp.h
ovn/controller/lflow.c
ovn/controller/pinctrl.c
ovn/lib/actions.c
ovn/lib/actions.h
ovn/lib/automake.mk
ovn/lib/expr.c
ovn/lib/expr.h
ovn/lib/ovn-dhcp.h [new file with mode: 0644]
ovn/ovn-sb.ovsschema
ovn/ovn-sb.xml
tests/ovn.at
tests/test-ovn.c

index e5f9962..84a0946 100644 (file)
@@ -1929,6 +1929,18 @@ union mf_subvalue {
     ovs_be64 be64[16];
 
     /* 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;
index 6f97298..271e0a5 100644 (file)
@@ -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 */
index efc427d..52e6131 100644 (file)
@@ -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
index 116397e..200505a 100644 (file)
 #include "pinctrl.h"
 
 #include "coverage.h"
+#include "csum.h"
 #include "dirs.h"
 #include "dp-packet.h"
 #include "flow.h"
 #include "lport.h"
+#include "nx-match.h"
 #include "ovn-controller.h"
 #include "lib/sset.h"
 #include "openvswitch/ofp-actions.h"
@@ -29,6 +31,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"
@@ -203,14 +207,192 @@ exit:
     ofpbuf_uninit(&ofpacts);
 }
 
+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;
+
+    /* Parse result field. */
+    const struct mf_field *f;
+    enum ofperr ofperr = nx_pull_header(userdata, &f, NULL);
+    if (ofperr) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "bad result OXM (%s)", ofperr_to_string(ofperr));
+        goto exit;
+    }
+
+    /* Parse result offset and offer IP. */
+    ovs_be32 *ofsp = ofpbuf_try_pull(userdata, sizeof *ofsp);
+    ovs_be32 *offer_ip = ofpbuf_try_pull(userdata, sizeof *offer_ip);
+    if (!ofsp || !offer_ip) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "offset or offer_ip not present in the userdata");
+        goto exit;
+    }
+
+    /* Check that the result is valid and writable. */
+    struct mf_subfield dst = { .field = f, .ofs = ntohl(*ofsp), .n_bits = 1 };
+    ofperr = mf_check_dst(&dst, NULL);
+    if (ofperr) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "bad result bit (%s)", ofperr_to_string(ofperr));
+        goto exit;
+    }
+
+    if (!userdata->size) {
+        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)) {
+        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))) {
+        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:
+    if (!ofperr) {
+        union mf_subvalue sv;
+        sv.u8_val = success;
+        mf_write_subfield(&dst, &sv, &pin->flow_metadata);
+    }
+    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,10 @@ 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));
index 6a79a5e..569970e 100644 (file)
 #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 "nx-match.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(). */
@@ -38,6 +42,8 @@ struct action_context {
 };
 
 static bool parse_action(struct action_context *);
+static void parse_put_dhcp_opts_action(struct action_context *,
+                                       const struct expr_field *dst);
 
 static bool
 action_error_handle_common(struct action_context *ctx)
@@ -102,7 +108,7 @@ action_syntax_error(struct action_context *ctx, const char *message, ...)
     ctx->error = ds_steal_cstr(&s);
 }
 
-/* Parses an assignment or exchange action. */
+/* Parses an assignment or exchange or put_dhcp_opts action. */
 static void
 parse_set_action(struct action_context *ctx)
 {
@@ -117,9 +123,17 @@ parse_set_action(struct action_context *ctx)
                                         ctx->ap->lookup_port, ctx->ap->aux,
                                         ctx->ofpacts, &prereqs);
         } else if (lexer_match(ctx->lexer, LEX_T_EQUALS)) {
-            error = expr_parse_assignment(
-                ctx->lexer, &dst, ctx->ap->symtab, ctx->ap->lookup_port,
-                ctx->ap->aux, ctx->ofpacts, &prereqs);
+            if (ctx->lexer->token.type == LEX_T_ID
+                && !strcmp(ctx->lexer->token.s, "put_dhcp_opts")
+                && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
+                lexer_get(ctx->lexer); /* Skip put_dhcp_opts. */
+                lexer_get(ctx->lexer); /* Skip '('. */
+                parse_put_dhcp_opts_action(ctx, &dst);
+            } else {
+                error = expr_parse_assignment(
+                    ctx->lexer, &dst, ctx->ap->symtab, ctx->ap->lookup_port,
+                    ctx->ap->aux, ctx->ofpacts, &prereqs);
+            }
         } else {
             action_syntax_error(ctx, "expecting `=' or `<->'");
         }
@@ -200,13 +214,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);
@@ -226,7 +242,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);
 }
 
@@ -260,7 +276,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);
@@ -431,6 +449,180 @@ parse_put_arp_action(struct action_context *ctx)
     restore_args(ctx, args, ARRAY_SIZE(args));
 }
 
+static void
+parse_dhcp_opt(struct action_context *ctx, struct ofpbuf *ofpacts)
+{
+    if (ctx->lexer->token.type != LEX_T_ID) {
+        action_syntax_error(ctx, NULL);
+        return;
+    }
+    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 DHCP option name");
+        return;
+    }
+    lexer_get(ctx->lexer);
+
+    if (!action_force_match(ctx, LEX_T_EQUALS)) {
+        return;
+    }
+
+    struct expr_constant_set cs;
+    memset(&cs, 0, sizeof(struct expr_constant_set));
+    char *error = expr_parse_constant_set(ctx->lexer, NULL, &cs);
+    if (error) {
+        action_error(ctx, "%s", error);
+        free(error);
+        return;
+    }
+
+    if (!strcmp(dhcp_opt->type, "str")) {
+        if (cs.type != EXPR_C_STRING) {
+            action_error(ctx, "DHCP option %s requires string value.",
+                         dhcp_opt->name);
+            return;
+        }
+    } else {
+        if (cs.type != EXPR_C_INTEGER) {
+            action_error(ctx, "DHCP option %s requires numeric value.",
+                         dhcp_opt->name);
+            return;
+        }
+    }
+
+    if (!lexer_match(ctx->lexer, LEX_T_COMMA) && (
+        ctx->lexer->token.type != LEX_T_RPAREN)) {
+        action_syntax_error(ctx, NULL);
+        return;
+    }
+
+
+    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 = 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);
+}
+
+/* Parses the "put_dhcp_opts" action.  The result should be stored into 'dst'.
+ *
+ * The caller has already consumed "put_dhcp_opts(", so this just parses the
+ * rest. */
+static void
+parse_put_dhcp_opts_action(struct action_context *ctx,
+                           const struct expr_field *dst)
+{
+    /* Validate that the destination is a 1-bit, modifiable field. */
+    struct mf_subfield sf;
+    struct expr *prereqs;
+    char *error = expr_expand_field(ctx->lexer, ctx->ap->symtab,
+                                    dst, 1, true, &sf, &prereqs);
+    if (error) {
+        action_error(ctx, "%s", error);
+        free(error);
+        return;
+    }
+    ctx->prereqs = expr_combine(EXPR_T_AND, ctx->prereqs, prereqs);
+
+    /* Make sure the first option is "offer_ip" */
+    if (ctx->lexer->token.type != LEX_T_ID) {
+        action_syntax_error(ctx, NULL);
+        return;
+    }
+    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) {
+        action_syntax_error(ctx, "expecting offerip option");
+        return;
+    }
+
+    /* controller. */
+    size_t oc_offset = start_controller_op(ctx->ofpacts,
+                                           ACTION_OPCODE_PUT_DHCP_OPTS, true);
+    nx_put_header(ctx->ofpacts, sf.field->id, OFP13_VERSION, false);
+    ovs_be32 ofs = htonl(sf.ofs);
+    ofpbuf_put(ctx->ofpacts, &ofs, sizeof ofs);
+    while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
+        parse_dhcp_opt(ctx, ctx->ofpacts);
+        if (ctx->error) {
+            return;
+        }
+    }
+    finish_controller_op(ctx->ofpacts, oc_offset);
+}
+
 static void
 emit_ct(struct action_context *ctx, bool recirc_next, bool commit)
 {
index 29af06f..f49e15e 100644 (file)
@@ -44,6 +44,16 @@ enum action_opcode {
      *     MFF_ETH_SRC = mac
      */
     ACTION_OPCODE_PUT_ARP,
+
+    /* "result = put_dhcp_opts(offer_ip, option, ...)".
+     *
+     * Arguments follow the action_header, in this format:
+     *   - A 32-bit or 64-bit OXM header designating the result field.
+     *   - A 32-bit integer specifying a bit offset within the result field.
+     *   - The 32-bit DHCP offer IP.
+     *   - Any number of DHCP options.
+     */
+    ACTION_OPCODE_PUT_DHCP_OPTS,
 };
 
 /* Header. */
@@ -58,6 +68,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,
index 2b178da..4f8cd05 100644 (file)
@@ -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
index 31b94d8..91bff07 100644 (file)
@@ -405,39 +405,6 @@ expr_print(const struct expr *e)
 \f
 /* 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 {}. */
-};
-
 /* Context maintained during expr_parse(). */
 struct expr_context {
     struct lexer *lexer;        /* Lexer for pulling more tokens. */
@@ -448,7 +415,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
@@ -829,7 +795,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) {
@@ -2931,3 +2897,12 @@ exit:
     }
     return ctx.error;
 }
+
+char * OVS_WARN_UNUSED_RESULT
+expr_parse_constant_set(struct lexer *lexer, const struct shash *symtab,
+                        struct expr_constant_set *cs)
+{
+    struct expr_context ctx = { .lexer = lexer, .symtab = symtab };
+    parse_constant_set(&ctx, cs);
+    return ctx.error;
+}
index 351d0ff..d6f8489 100644 (file)
@@ -415,4 +415,42 @@ char *expr_expand_field(struct lexer *lexer, const struct shash *symtab,
                         struct mf_subfield *sf, struct expr **prereqsp)
     OVS_WARN_UNUSED_RESULT;
 
+/* 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 {}. */
+};
+
+char *expr_parse_constant_set(struct lexer *, const struct shash *symtab,
+                              struct expr_constant_set *cs)
+    OVS_WARN_UNUSED_RESULT;
+void expr_constant_set_destroy(struct expr_constant_set *cs);
+
 #endif /* ovn/expr.h */
diff --git a/ovn/lib/ovn-dhcp.h b/ovn/lib/ovn-dhcp.h
new file mode 100644 (file)
index 0000000..4da614b
--- /dev/null
@@ -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 */
index 06e8a07..a1343c9 100644 (file)
@@ -1,7 +1,7 @@
 {
     "name": "OVN_Southbound",
-    "version": "1.3.0",
-    "cksum": "654726257 5528",
+    "version": "1.4.0",
+    "cksum": "198773462 6073",
     "tables": {
         "Chassis": {
             "columns": {
                 "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}}}
index e9353f3..f330374 100644 (file)
 
           <p><b>Example:</b> <code>put_arp(inport, arp.spa, arp.sha);</code></p>
         </dd>
+
+        <dt>
+          <code><var>R</var> = put_dhcp_opts(<code>offerip</code> = <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>: one or more DHCP option/value pairs, the first
+            of which must set a value for the offered IP, <code>offerip</code>.
+          </p>
+
+          <p>
+            <b>Result</b>: stored to a 1-bit subfield <var>R</var>.
+          </p>
+
+          <p>
+            Valid only in the ingress pipeline.
+          </p>
+
+          <p>
+            When this action is applied to a DHCP request packet (DHCPDISCOVER
+            or DHCPREQUEST), it changes the packet into a DHCP reply (DHCPOFFER
+            or DHCPACK, respectively), replaces the options by those specified
+            as parameters, and stores 1 in <var>R</var>.
+          </p>
+
+          <p>
+            When this action is applied to a non-DHCP packet or a DHCP packet
+            that is not DHCPDISCOVER or DHCPREQUEST, it leaves the packet
+            unchanged and stores 0 in <var>R</var>.
+          </p>
+
+          <p>
+            The contents of the <ref table="DHCP_Option"/> table control the
+            DHCP option names and values that this action supports.
+          </p>
+
+          <p>
+            <b>Example:</b>
+            <code>
+              reg0[0] = put_dhcp_opts(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>
@@ -1587,4 +1632,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>
index beb1f6e..297070c 100644 (file)
@@ -542,6 +542,31 @@ get_arp(reg0, ip4.dst); => Cannot use numeric field reg0 where string field is r
 # put_arp
 put_arp(inport, arp.spa, arp.sha); => actions=push:NXM_NX_REG0[],push:NXM_OF_ETH_SRC[],push:NXM_NX_ARP_SHA[],push:NXM_OF_ARP_SPA[],pop:NXM_NX_REG0[],pop:NXM_OF_ETH_SRC[],controller(userdata=00.00.00.01.00.00.00.00),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_REG0[], prereqs=eth.type == 0x806 && eth.type == 0x806
 
+# put_dhcp_opts
+reg1[0] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1); => actions=controller(userdata=00.00.00.02.00.00.00.00.80.01.00.08.00.00.00.00.01.02.03.04.03.04.0a.00.00.01,pause), prereqs=1
+reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain="ovn.org"); => actions=controller(userdata=00.00.00.02.00.00.00.00.80.01.02.08.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67,pause), prereqs=1
+# offerip=10.0.0.4             --> 0a.00.00.04
+# router=10.0.0.1              --> 03.04.0a.00.00.01
+# netmask=255.255.255.0        --> 01.04.ff.ff.ff.00
+# mtu=1400                     --> 1a.02.05.78
+# ip_forward_enable-1          --> 13.01.01
+# default_ttl=121              --> 17.01.79
+# dns_server={8.8.8.8,7.7.7.7} --> 06.08.08.08.08.08.07.07.07.07
+# classless_static_route=      --> 79.14
+# {30.0.0.0/24,10.0.0.4        --> 18.1e.00.00.0a.00.00.04
+#  40.0.0.0/16,10.0.0.6        --> 10.28.00.0a.00.00.06
+#  0.0.0.0/0,10.0.0.1}         --> 00.0a.00.00.01
+# ethernet_encap=1             --> 24.01.01
+# router_discovery=0           --> 1f.01.00
+reg0[15] = put_dhcp_opts(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,router_discovery=0); => actions=controller(userdata=00.00.00.02.00.00.00.00.80.01.00.08.00.00.00.2f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00,pause), prereqs=1
+reg1[0..1] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1); => Cannot use 2-bit field reg1[0..1] where 1-bit field is required.
+reg1[0] = put_dhcp_opts(); => Syntax error at `)'.
+reg1[0] = put_dhcp_opts(x = 1.2.3.4, router = 10.0.0.1); => Syntax error at `x' expecting offerip option.
+reg1[0] = put_dhcp_opts(offerip=1.2.3.4, "hi"); => Syntax error at `"hi"'.
+reg1[0] = put_dhcp_opts(offerip=1.2.3.4, xyzzy); => Syntax error at `xyzzy' expecting DHCP option name.
+reg1[0] = put_dhcp_opts(offerip="xyzzy"); => DHCP option offerip requires numeric value.
+reg1[0] = put_dhcp_opts(offerip=1.2.3.4, domain=1.2.3.4); => DHCP option domain requires string value.
+
 # Contradictionary prerequisites (allowed but not useful):
 ip4.src = ip6.src[0..31]; => actions=move:NXM_NX_IPV6_SRC[0..31]->NXM_OF_IP_SRC[], prereqs=eth.type == 0x800 && eth.type == 0x86dd
 ip4.src <-> ip6.src[0..31]; => actions=push:NXM_NX_IPV6_SRC[0..31],push:NXM_OF_IP_SRC[],pop:NXM_NX_IPV6_SRC[0..31],pop:NXM_OF_IP_SRC[], prereqs=eth.type == 0x800 && eth.type == 0x86dd
@@ -2819,7 +2844,6 @@ OVS_APP_EXIT_AND_WAIT([ovsdb-server])
 
 AT_CLEANUP
 
-
 AT_SETUP([ovn -- 2 HVs, 2 LRs connected via LS, gateway router])
 AT_KEYWORDS([ovngatewayrouter])
 AT_SKIP_IF([test $HAVE_PYTHON = no])
index 051f3a7..18e5aca 100644 (file)
@@ -29,6 +29,7 @@
 #include "ovn/lib/actions.h"
 #include "ovn/lib/expr.h"
 #include "ovn/lib/lex.h"
+#include "ovn/lib/ovn-dhcp.h"
 #include "ovs-thread.h"
 #include "ovstest.h"
 #include "shash.h"
@@ -239,6 +240,34 @@ create_symtab(struct shash *symtab)
     expr_symtab_add_string(symtab, "big_string", MFF_XREG0, NULL);
 }
 
+static void
+create_dhcp_opts(struct hmap *dhcp_opts)
+{
+    hmap_init(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, "domain", 15, "str");
+    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");
+}
+
 static bool
 lookup_port_cb(const void *ports_, const char *port_name, unsigned int *portp)
 {
@@ -1221,10 +1250,12 @@ static void
 test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
 {
     struct shash symtab;
+    struct hmap dhcp_opts;
     struct simap ports, ct_zones;
     struct ds input;
 
     create_symtab(&symtab);
+    create_dhcp_opts(&dhcp_opts);
 
     simap_init(&ports);
     simap_put(&ports, "eth0", 5);
@@ -1242,6 +1273,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
 
         struct action_params ap = {
             .symtab = &symtab,
+            .dhcp_opts = &dhcp_opts,
             .lookup_port = lookup_port_cb,
             .aux = &ports,
             .ct_zones = &ct_zones,