From: Numan Siddique Date: Wed, 15 Jun 2016 09:17:35 +0000 (+0530) Subject: ovn-controller: Add 'put_dhcp_opts' action in ovn-controller X-Git-Url: http://git.cascardo.eti.br/?a=commitdiff_plain;h=42814145d70c77462ce28b38841cd160f0486776;p=cascardo%2Fovs.git ovn-controller: Add 'put_dhcp_opts' action in ovn-controller 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 Co-authored-by: Ben Pfaff Signed-off-by: Ben Pfaff --- diff --git a/include/openvswitch/meta-flow.h b/include/openvswitch/meta-flow.h index e5f996208..84a0946d1 100644 --- a/include/openvswitch/meta-flow.h +++ b/include/openvswitch/meta-flow.h @@ -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; diff --git a/lib/dhcp.h b/lib/dhcp.h index 6f97298ca..271e0a55b 100644 --- a/lib/dhcp.h +++ b/lib/dhcp.h @@ -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 */ diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c index efc427db7..52e613182 100644 --- a/ovn/controller/lflow.c +++ b/ovn/controller/lflow.c @@ -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 diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c index 116397e14..200505a66 100644 --- a/ovn/controller/pinctrl.c +++ b/ovn/controller/pinctrl.c @@ -18,10 +18,12 @@ #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)); diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c index 6a79a5e22..569970e2d 100644 --- a/ovn/lib/actions.c +++ b/ovn/lib/actions.c @@ -20,12 +20,16 @@ #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) { diff --git a/ovn/lib/actions.h b/ovn/lib/actions.h index 29af06f3b..f49e15ec6 100644 --- a/ovn/lib/actions.h +++ b/ovn/lib/actions.h @@ -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, diff --git a/ovn/lib/automake.mk b/ovn/lib/automake.mk index 2b178da38..4f8cd05b7 100644 --- a/ovn/lib/automake.mk +++ b/ovn/lib/automake.mk @@ -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 diff --git a/ovn/lib/expr.c b/ovn/lib/expr.c index 31b94d81c..91bff075d 100644 --- a/ovn/lib/expr.c +++ b/ovn/lib/expr.c @@ -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 {}. */ -}; - /* 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; +} diff --git a/ovn/lib/expr.h b/ovn/lib/expr.h index 351d0ff34..d6f848941 100644 --- a/ovn/lib/expr.h +++ b/ovn/lib/expr.h @@ -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 index 000000000..4da614bfc --- /dev/null +++ b/ovn/lib/ovn-dhcp.h @@ -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 */ diff --git a/ovn/ovn-sb.ovsschema b/ovn/ovn-sb.ovsschema index 06e8a072b..a1343c92d 100644 --- a/ovn/ovn-sb.ovsschema +++ b/ovn/ovn-sb.ovsschema @@ -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}}} diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml index e9353f336..f33037432 100644 --- a/ovn/ovn-sb.xml +++ b/ovn/ovn-sb.xml @@ -1063,6 +1063,51 @@

Example: put_arp(inport, arp.spa, arp.sha);

+ +
+ R = put_dhcp_opts(offerip = IP, D1 = V1, D2 = V2, ..., Dn = Vn); +
+ +
+

+ Parameters: one or more DHCP option/value pairs, the first + of which must set a value for the offered IP, offerip. +

+ +

+ Result: stored to a 1-bit subfield R. +

+ +

+ Valid only in the ingress pipeline. +

+ +

+ 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 R. +

+ +

+ 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 R. +

+ +

+ The contents of the table control the + DHCP option names and values that this action supports. +

+ +

+ Example: + + 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}); + +

+

@@ -1587,4 +1632,157 @@ tcp.flags = RST; The Ethernet address to which the IP is bound. + + +

+ Each row in this table stores the DHCP Options supported by native OVN + DHCP. ovn-northd populates this table with the supported + DHCP options. ovn-controller 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 "https://tools.ietf.org/html/rfc2132" + for the possible list of DHCP options that can be defined here. +

+ + +

+ Name of the DHCP option. +

+ +

+ Example. name="router" +

+
+ + +

+ DHCP option code for the DHCP option as defined in the RFC 2132. +

+ +

+ Example. code=3 +

+
+ + +

+ Data type of the DHCP option code. +

+ +
+
value: bool
+
+

+ This indicates that the value of the DHCP option is a bool. +

+ +

+ Example. "name=ip_forward_enable", "code=19", "type=bool". +

+ +

+ put_dhcp_opts(..., ip_forward_enable = 1,...) +

+
+ +
value: uint8
+
+

+ This indicates that the value of the DHCP option is an unsigned + int8 (8 bits) +

+ +

+ Example. "name=default_ttl", "code=23", "type=uint8". +

+ +

+ put_dhcp_opts(..., default_ttl = 50,...) +

+
+ +
value: uint16
+
+

+ This indicates that the value of the DHCP option is an unsigned + int16 (16 bits). +

+ +

+ Example. "name=mtu", "code=26", "type=uint16". +

+ +

+ put_dhcp_opts(..., mtu = 1450,...) +

+
+ +
value: uint32
+
+

+ This indicates that the value of the DHCP option is an unsigned + int32 (32 bits). +

+ +

+ Example. "name=lease_time", "code=51", "type=uint32". +

+ +

+ put_dhcp_opts(..., lease_time = 86400,...) +

+
+ +
value: ipv4
+
+

+ This indicates that the value of the DHCP option is an IPv4 + address or addresses. +

+ +

+ Example. "name=router", "code=3", "type=ipv4". +

+ +

+ put_dhcp_opts(..., router = 10.0.0.1,...) +

+ +

+ Example. "name=dns_server", "code=6", "type=ipv4". +

+ +

+ put_dhcp_opts(..., dns_server = {8.8.8.8 7.7.7.7},...) +

+
+ +
value: static_routes
+
+

+ This indicates that the value of the DHCP option contains a pair of + IPv4 route and next hop addresses. +

+ +

+ Example. "name=classless_static_route", "code=121", "type=static_routes". +

+ +

+ put_dhcp_opts(..., classless_static_route = {30.0.0.0/24,10.0.0.4,0.0.0.0/0,10.0.0.1}...) +

+
+ +
value: str
+
+

+ This indicates that the value of the DHCP option is a string. +

+ +

+ Example. "name=host_name", "code=12", "type=str". +

+
+
+
+
diff --git a/tests/ovn.at b/tests/ovn.at index beb1f6ee5..297070cb2 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -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]) diff --git a/tests/test-ovn.c b/tests/test-ovn.c index 051f3a792..18e5aca6e 100644 --- a/tests/test-ovn.c +++ b/tests/test-ovn.c @@ -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,