Thanks Ansis for the review. Just FYI - I just sent v5 patchset yesterday - https://patchwork.ozlabs.org/patch/622521/ - https://patchwork.ozlabs.org/patch/622522/
Please see the comments inline. On Tue, May 17, 2016 at 12:15 AM, Ansis Atteka <ansisatt...@gmail.com> wrote: > On 6 May 2016 at 07:46, Numan Siddique <nusid...@redhat.com> wrote: > > This patch adds a new OVN action 'dhcp_offer' to support native > > DHCP in OVN. > > > > 'dhcp_offer' takes the DHCP options as input params. > > Eg. dhcp_offer(offerip = 10.0.0.4, router = 10.0.0.1, > > netmask = 255.255.255.0, lease_time = 3600,....) > > > > 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 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. > > > > Eg. dhcp_offer(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 support 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 <nusid...@redhat.com> > > Thanks for working on this. I did not try to apply your patch yet so I > might have missed something while reviewing. Also, I was trying to > recollect some DHCP behavioral quirks that I may or may not recollect > correctly after my experience with DHCP. So feel free to ask me to > collaborate on things that I mentioned here but don't make sense to > you. > > --- > > lib/dhcp.h | 13 ++++ > > ovn/controller/lflow.c | 11 ++++ > > ovn/controller/pinctrl.c | 103 +++++++++++++++++++++++++++++++- > > ovn/lib/actions.c | 151 > ++++++++++++++++++++++++++++++++++++++++++++++- > > ovn/lib/actions.h | 8 +++ > > ovn/lib/automake.mk | 3 +- > > ovn/lib/expr.c | 48 ++++----------- > > ovn/lib/expr.h | 37 ++++++++++++ > > ovn/lib/ovn-dhcp.h | 121 +++++++++++++++++++++++++++++++++++++ > > ovn/ovn-sb.ovsschema | 13 +++- > > ovn/ovn-sb.xml | 44 ++++++++++++++ > > tests/automake.mk | 1 + > > tests/ovn.at | 39 ++++++++++++ > > tests/test-ovn-dhcp.c | 117 ++++++++++++++++++++++++++++++++++++ > > 14 files changed, 667 insertions(+), 42 deletions(-) > > create mode 100644 ovn/lib/ovn-dhcp.h > > create mode 100644 tests/test-ovn-dhcp.c > > > > diff --git a/lib/dhcp.h b/lib/dhcp.h > > index 6f97298..271e0a5 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 > I don't see code where you would send DHCP NACK message. The typical > use case for DHCPNACK is if DHCP server configuration changed at the > serverside and DHCP client state machine should immediately move to > INIT state see [ > http://www.tcpipguide.com/free/t_DHCPGeneralOperationandClientFiniteStateMachine.htm > ] > > This implementation of DHCP is not a full fledged implementation, but rather to support common openstack usecases. Please see the commit message of patch 2 - https://patchwork.ozlabs.org/patch/622522/ > > + > > +#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 96b7c66..0299d49 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. */ > > @@ -277,6 +285,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, > > @@ -360,6 +369,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 289a995..489ca21 100644 > > --- a/ovn/controller/pinctrl.c > > +++ b/ovn/controller/pinctrl.c > > @@ -17,7 +17,9 @@ > > > > #include "pinctrl.h" > > > > + > > #include "coverage.h" > > +#include "csum.h" > > #include "dirs.h" > > #include "dp-packet.h" > > #include "flow.h" > > @@ -27,6 +29,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" > > @@ -192,13 +196,106 @@ exit: > > } > > > > static void > > +pinctl_handle_dhcp_offer(struct dp_packet *pkt, 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); > > + > > + ovs_be32 *offer_ip = ofpbuf_pull(userdata, sizeof *offer_ip); > > + const uint8_t *payload = dp_packet_get_udp_payload(pkt); > > + payload = (payload + sizeof (struct dhcp_header) + > sizeof(uint32_t)); > > + > > + if (payload[0] != DHCP_OPT_MSG_TYPE && (payload[2] != > DHCP_MSG_DISCOVER || > > + payload[2] != > DHCP_MSG_REQUEST)) { > > + /* If its not a valid dhcp packet resume the packet without any > > + * changes */ > > + goto exit; > > + } > > + > > + uint8_t msg_type; > > + if (payload[2] == DHCP_MSG_DISCOVER) { > > + msg_type = DHCP_MSG_OFFER; > > + } else { > > + msg_type = DHCP_MSG_ACK; > > + } > > + > > + /* 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_udp_len = UDP_HEADER_LEN + DHCP_HEADER_LEN + \ > > + userdata->size + 16; > > + size_t new_packet_len = ETH_HEADER_LEN + IP_HEADER_LEN + > new_udp_len; > > + > > + struct dp_packet packet; > > + dp_packet_init(&packet, new_packet_len); > > + dp_packet_clear(&packet); > > + dp_packet_prealloc_tailroom(&packet, new_packet_len); > > + > > + dp_packet_put(&packet, dp_packet_pull(pkt, ETH_HEADER_LEN), > > + ETH_HEADER_LEN); > > + struct ip_header *ip = dp_packet_put( > > + &packet, dp_packet_pull(pkt, IP_HEADER_LEN), IP_HEADER_LEN); > > + > > + struct udp_header *udp = dp_packet_put( > > + &packet, dp_packet_pull(pkt, UDP_HEADER_LEN), UDP_HEADER_LEN); > > + > > + struct dhcp_header *dhcp_data = dp_packet_put( > > + &packet, dp_packet_pull(pkt, DHCP_HEADER_LEN), DHCP_HEADER_LEN); > > + dhcp_data->op = DHCP_OP_REPLY; > > + dhcp_data->yiaddr = *offer_ip; > > + > > + ovs_be32 magic_cookie = htonl(DHCP_MAGIC_COOKIE); > > + dp_packet_put(&packet, &magic_cookie, sizeof(ovs_be32)); > > + > > + uint8_t *dhcp_options = dp_packet_put_zeros(&packet, userdata->size > + 12); > > + > > + /* Dhcp option - type */ > > + dhcp_options[0] = (uint8_t) DHCP_OPT_MSG_TYPE; > > + dhcp_options[1] = (uint8_t) 1; > > + dhcp_options[2] = msg_type; > > + dhcp_options += 3; > > + > > + memcpy(dhcp_options, userdata->data, userdata->size); > > + dhcp_options += userdata->size; > > + > > + /* Padding */ > > + dhcp_options += 4; > > + > > + /* End */ > > + dhcp_options[0] = DHCP_OPT_END; > > + > > + udp->udp_len = htons(new_udp_len); > > + ip->ip_tos = 0; > > + ip->ip_tot_len = htons(IP_HEADER_LEN + new_udp_len); > > + udp->udp_csum = 0; > > + ip->ip_csum = 0; > > + ip->ip_csum = csum(ip, sizeof *ip); > > + > > + pin->packet = dp_packet_data(&packet); > > + pin->packet_len = dp_packet_size(&packet); > > + > > +exit: > > + queue_msg(ofputil_encode_resume(pin, continuation, proto)); > > + dp_packet_uninit(&packet); > > +} > > + > > +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)); > > @@ -230,6 +327,9 @@ process_packet_in(const struct ofp_header *msg) > > pinctrl_handle_put_arp(&pin.flow_metadata.flow, &headers); > > break; > > > > + case ACTION_OPCODE_DHCP_OFFER: > > + pinctl_handle_dhcp_offer(&packet, &pin, &userdata, > &continuation); > > + break; > > default: > > VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32, > > ntohl(ah->opcode)); > > @@ -240,6 +340,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) { > > diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c > > index 5f0bf19..6081cae 100644 > > --- a/ovn/lib/actions.c > > +++ b/ovn/lib/actions.c > > @@ -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); > > @@ -261,6 +269,141 @@ parse_arp_action(struct action_context *ctx) > > } > > > > static bool > > +parse_dhcp_option(struct action_context *ctx, struct ofpbuf *dhcp_opts) > > +{ > > + 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 (!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_push(dhcp_opts, &cs.values[0].value.ipv4, > sizeof(ovs_be32)); > > + goto exit; > > + } > > + > > + uint8_t *opt_header = ofpbuf_put_uninit(dhcp_opts, 2); > > + opt_header[0] = dhcp_opt->code; > > + > > + > > + switch(dhcp_opt->type) { > > + case DHCP_OPT_TYPE_BOOL: > > + case DHCP_OPT_TYPE_UINT8: > > + opt_header[1] = 1; > > + uint8_t val = cs.values[0].value.u8[127]; > > + ofpbuf_put(dhcp_opts, &val, 1); > > + break; > > + > > + case DHCP_OPT_TYPE_UINT16: > > + opt_header[1] = 2; > > + ofpbuf_put(dhcp_opts, &cs.values[0].value.be16[63], 2); > > + break; > > + > > + case DHCP_OPT_TYPE_UINT32: > > + opt_header[1] = 4; > > + ofpbuf_put(dhcp_opts, &cs.values[0].value.be32[31], 4); > > + break; > > + > > + case DHCP_OPT_TYPE_IP4: > > + opt_header[1] = 0; > > + for (size_t i = 0; i < cs.n_values; i++) { > > + ofpbuf_put(dhcp_opts, &cs.values[i].value.ipv4, > sizeof(ovs_be32)); > > + opt_header[1] += sizeof(ovs_be32); > > + } > > + break; > > + > > + case 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; > > + 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(dhcp_opts, &plen, 1); > > + ofpbuf_put(dhcp_opts, &cs.values[i].value.ipv4, plen / 8); > > + ofpbuf_put(dhcp_opts, &cs.values[i + 1].value.ipv4, > > + sizeof(ovs_be32)); > > + opt_header[1] += (1 + (plen / 8) + sizeof(ovs_be32)) ; > > + } > > + break; > > + } > > + > > + case DHCP_OPT_TYPE_STR: > > + opt_header[1] = strlen(cs.values[0].string); > > + ofpbuf_put(dhcp_opts, cs.values[0].string, opt_header[1]); > > + break; > > + } > > + > > +exit: > > + expr_constant_set_destroy(&cs); > > + return true; > > +} > > + > > +static void > > +parse_dhcp_offer_action(struct action_context *ctx) > > +{ > > + if (!lexer_match(ctx->lexer, LEX_T_LPAREN)) { > > + action_syntax_error(ctx, "expecting `('"); > > + return; > > + } > > + > > + uint64_t dhcp_opts_stub[1024 / 8]; > > + struct ofpbuf dhcp_opts = OFPBUF_STUB_INITIALIZER(dhcp_opts_stub); > > + > > + /* Parse inner actions. */ > > + while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) { > > + if (!parse_dhcp_option(ctx, &dhcp_opts)) { > > + return; > > + } > > + } > > + > > + /* controller. */ > > + size_t oc_offset = start_controller_op(ctx->ofpacts, > > + ACTION_OPCODE_DHCP_OFFER, > true); > > + ofpbuf_put(ctx->ofpacts, dhcp_opts.data, dhcp_opts.size); > > + finish_controller_op(ctx->ofpacts, oc_offset); > > + > > + /* Free memory. */ > > + ofpbuf_uninit(&dhcp_opts); > > +} > > + > > +static bool > > action_force_match(struct action_context *ctx, enum lex_type t) > > { > > if (lexer_match(ctx->lexer, t)) { > > @@ -475,6 +618,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, "dhcp_offer")) { > > + parse_dhcp_offer_action(ctx); > > } else { > > action_syntax_error(ctx, "expecting action"); > > } > > diff --git a/ovn/lib/actions.h b/ovn/lib/actions.h > > index 29af06f..6c74878 100644 > > --- a/ovn/lib/actions.h > > +++ b/ovn/lib/actions.h > > @@ -44,6 +44,11 @@ enum action_opcode { > > * MFF_ETH_SRC = mac > > */ > > ACTION_OPCODE_PUT_ARP, > > + > > + /* "dhcp_offer(...dhcp actions ...}". > > + * > > + */ > > + ACTION_OPCODE_DHCP_OFFER, > > }; > > > > /* Header. */ > > @@ -58,6 +63,9 @@ struct action_params { > > * expr_parse()). */ > > const struct shash *symtab; > > > > + /* hmap of 'struct dhcp_opts_map' to support 'dhcp_offer' */ > > + 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 9e09352..7294742 100644 > > --- a/ovn/lib/automake.mk > > +++ b/ovn/lib/automake.mk > > @@ -10,7 +10,8 @@ ovn_lib_libovn_la_SOURCES = \ > > ovn/lib/expr.h \ > > ovn/lib/lex.c \ > > ovn/lib/lex.h \ > > - ovn/lib/logical-fields.h > > + ovn/lib/logical-fields.h \ > > + ovn/lib/ovn-dhcp.h > > nodist_ovn_lib_libovn_la_SOURCES = \ > > ovn/lib/ovn-nb-idl.c \ > > ovn/lib/ovn-nb-idl.h \ > > diff --git a/ovn/lib/expr.c b/ovn/lib/expr.c > > index f274ab4..05a5c8e 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 {}. > */ > > -}; > > - > > /* 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); > > +} > > diff --git a/ovn/lib/expr.h b/ovn/lib/expr.h > > index 1327789..99e7aaf 100644 > > --- a/ovn/lib/expr.h > > +++ b/ovn/lib/expr.h > > @@ -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 */ > > diff --git a/ovn/lib/ovn-dhcp.h b/ovn/lib/ovn-dhcp.h > > new file mode 100644 > > index 0000000..fae944f > > --- /dev/null > > +++ b/ovn/lib/ovn-dhcp.h > > @@ -0,0 +1,121 @@ > > +/* > > + * 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" > > + > > +enum dhcp_opt_type { > > + DHCP_OPT_TYPE_BOOL, > > + DHCP_OPT_TYPE_UINT8, > > + DHCP_OPT_TYPE_UINT16, > > + DHCP_OPT_TYPE_UINT32, > > + DHCP_OPT_TYPE_IP4, > > + DHCP_OPT_TYPE_STATIC_ROUTES, > > + DHCP_OPT_TYPE_STR > > +}; > > + > > +struct dhcp_opts_map { > > + struct hmap_node hmap_node; > > + char *name; > > + size_t code; > > + size_t type; > > +}; > > + > > +#define DHCP_OPTION(NAME, CODE, TYPE) \ > > + {.name = NAME, .code = CODE, .type = TYPE} > > + > > +#define OFFERIP DHCP_OPTION("offerip", 0, > DHCP_OPT_TYPE_IP4) > > +#define DHCP_OPT_NETMASK DHCP_OPTION("netmask", 1, > DHCP_OPT_TYPE_IP4) > > +#define DHCP_OPT_ROUTER DHCP_OPTION("router", 3, DHCP_OPT_TYPE_IP4) > > +#define DHCP_OPT_DNS_SERVER DHCP_OPTION("dns_server", 6, > DHCP_OPT_TYPE_IP4) > > +#define DHCP_OPT_LOG_SERVER DHCP_OPTION("log_server", 7, > DHCP_OPT_TYPE_IP4) > > +#define DHCP_OPT_LPR_SERVER DHCP_OPTION("lpr_server", 9, > DHCP_OPT_TYPE_IP4) > > +#define DHCP_OPT_SWAP_SERVER DHCP_OPTION("swap_server", 16, > DHCP_OPT_TYPE_IP4) > > + > > +#define DHCP_OPT_POLICY_FILTER \ > > + DHCP_OPTION("policy_filter", 21, DHCP_OPT_TYPE_IP4) > > + > > +#define DHCP_OPT_ROUTER_SOLICITATION \ > > + DHCP_OPTION("router_solicitation", 32, DHCP_OPT_TYPE_IP4) > > + > > +#define DHCP_OPT_NIS_SERVER DHCP_OPTION("nis_server", 41, > DHCP_OPT_TYPE_IP4) > > +#define DHCP_OPT_NTP_SERVER DHCP_OPTION("ntp_server", 42, > DHCP_OPT_TYPE_IP4) > > +#define DHCP_OPT_SERVER_ID DHCP_OPTION("server_id", 54, > DHCP_OPT_TYPE_IP4) > > +#define DHCP_OPT_TFTP_SERVER DHCP_OPTION("tftp_server", 66, > DHCP_OPT_TYPE_IP4) > > AFAIK DHCP option tftp_server (66) is tricky. Allegedly there are > clients who use it. However, the KVM PXE implementation asks for this > option via DHCP Parameters list option, but still unconditionally ends > up using SIADDR field to locate tftp server with initial PXE boot > image. If tftp_server happens to be an IP address, then I think you > may want to populate it in SIADDR field as well. > > > > + > > +#define DHCP_OPT_CLASSLESS_STATIC_ROUTE \ > > + DHCP_OPTION("classless_static_route", 121, > DHCP_OPT_TYPE_STATIC_ROUTES) > > I think Linux dhclient is using option 121, but microsoft dhcp client > is using option 249 for classless static routes See > https://ercpe.de/blog/pushing-static-routes-with-isc-dhcp-server for > more details. > > The list of supported DHCP options are stored in a new table "DHCP_Options" in SB DB. Please see the v5 of the patchset. If we need to support option 249 or any other option, we just need to update the 'ovn-northd' to add the new option in the table. I think we can add new options if there is a requirement. I have no idea if Windows clients are used or not. If you think this needs to be supported please let me know, I can spin another patchset. > I think ideally DHCP server should honor option 54 (the DHCP options > client understands) and then server should return either 121 or 249. > > The DHCP options to reply is defined by the CMS (cloud management system). So I think CMS can decide to put option 121 or 249 by adding these options in the NB DB table "Subnet", "dhcp_options" column. > > > > + > > +#define DHCP_OPT_IP_FORWARD_ENABLE \ > > + DHCP_OPTION("ip_forward_enable", 19, DHCP_OPT_TYPE_BOOL) > > +#define DHCP_OPT_ROUTER_DISCOVERY \ > > + DHCP_OPTION("router_discovery", 31, DHCP_OPT_TYPE_BOOL) > > +#define DHCP_OPT_ETHERNET_ENCAP \ > > + DHCP_OPTION("ethernet_encap", 36, DHCP_OPT_TYPE_BOOL) > > + > > +#define DHCP_OPT_DEFAULT_TTL \ > > + DHCP_OPTION("default_ttl", 23, DHCP_OPT_TYPE_UINT8) > > + > > +#define DHCP_OPT_TCP_TTL DHCP_OPTION("tcp_ttl", 37, > DHCP_OPT_TYPE_UINT8) > > +#define DHCP_OPT_MTU DHCP_OPTION("mtu", 26, DHCP_OPT_TYPE_UINT16) > > +#define DHCP_OPT_LEASE_TIME \ > > + DHCP_OPTION("lease_time", 51, DHCP_OPT_TYPE_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, > size_t type) > > +{ > > + struct dhcp_opts_map *dhcp_opt = xzalloc(sizeof *dhcp_opt); > > + dhcp_opt->name = xstrdup(opt_name); > > + dhcp_opt->code = code; > > + dhcp_opt->type = 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); > > + } > > + hmap_destroy(dhcp_opts); > > +} > > + > > +#endif /* OVN_DHCP_H */ > > diff --git a/ovn/ovn-sb.ovsschema b/ovn/ovn-sb.ovsschema > > index 06e8a07..1b20d45 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": "1002497001 5942", > > "tables": { > > "Chassis": { > > "columns": { > > @@ -110,4 +110,13 @@ > > "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": "integer", > > + "minInteger": 0, "maxInteger": 6}}}}, > > "isRoot": true}}} > > diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml > > index efd2f9a..d699c2e 100644 > > --- a/ovn/ovn-sb.xml > > +++ b/ovn/ovn-sb.xml > > @@ -1022,6 +1022,35 @@ > > > > <p><b>Example:</b> <code>put_arp(inport, arp.spa, > arp.sha);</code></p> > > </dd> > > + > > + <dt> > > + <code>dhcp_offer(<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>: DHCP option name <var>Dn</var>, DHCP > option > > + value <var>Vn</var>. > > + </p> > > + > > + <p> > > + Valid only in the ingress pipeline. > > + </p> > > + > > + <p> > > + DHCP options and values defined in the parameters are added > in > > + the 'DHCP options' field of the incoming DHCP > DISCOVER/REQUEST > > + packet and resumed in the pipeline. > > + </p> > > + > > + <p> > > + <b>Example:</b> > > + <code> > > + dhcp_offer(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> > > @@ -1511,4 +1540,19 @@ 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"> > > + > > + <column name="name"> > > + Name of the DHCP option > > + </column> > > + > > + <column name="code"> > > + DHCP option code > > + </column> > > + > > + <column name="type"> > > + Data type of the DHCP option code > > + </column> > > + </table> > > </database> > > diff --git a/tests/automake.mk b/tests/automake.mk > > index a5c6074..15fcb50 100644 > > --- a/tests/automake.mk > > +++ b/tests/automake.mk > > @@ -324,6 +324,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 \ > > diff --git a/tests/ovn.at b/tests/ovn.at > > index b9d7ada..c439947 100644 > > --- a/tests/ovn.at > > +++ b/tests/ovn.at > > @@ -2545,3 +2545,42 @@ OVS_APP_EXIT_AND_WAIT([ovsdb-server]) > > > > AT_CLEANUP > > > > +AT_SETUP([ovn -- dhcp_offer 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-dhcp-offer-action $dhcp_options > $expected_dhcp_opts], > > + [0], [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-dhcp-offer-action $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-dhcp-offer-action $dhcp_options > $expected_dhcp_opts], > > + [1], [ignore]) > > + > > +AT_CLEANUP > > diff --git a/tests/test-ovn-dhcp.c b/tests/test-ovn-dhcp.c > > new file mode 100644 > > index 0000000..60726d9 > > --- /dev/null > > +++ b/tests/test-ovn-dhcp.c > > @@ -0,0 +1,117 @@ > > +/* 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" > > + > > + > > +static void > > +test_dhcp_offer_action(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) > > +{ > > + if (argc != 3) { > > + printf("Usage: %s 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, DHCP_OPT_TYPE_IP4); > > + dhcp_opt_add(&dhcp_opts, "netmask", 1, DHCP_OPT_TYPE_IP4); > > + dhcp_opt_add(&dhcp_opts, "router", 3, DHCP_OPT_TYPE_IP4); > > DHCP option 3 (Routers) is a list of IP address a (See setion 3.5 of > RFC2132), but DHCP options 1 (subnet mask) is a single IP address (see > 3.3 of RFC2132). However, here you are using the same type for all of > these options - DHCP_OPT_TYPE_IP4. Shouldn't you break down > DHCP_OPT_TYPE_IP4 option into two types - one that represents a single > IP address and another that represents a list of IP addresses? > > > I think it should be fine. CMS has to define it properly. If CMS want to include a list of IP addresses for example "dns_nameserver", it should define as "dns_nameserver={7.7.7.7, 8.8.8.8}". If it wants to include only one ip, it should define as "dns_nameserver=7.7.7.7". This has been documented in the next patch which adds logical flows in ovn-northd. > > + dhcp_opt_add(&dhcp_opts, "dns_server", 6, DHCP_OPT_TYPE_IP4); > > + dhcp_opt_add(&dhcp_opts, "log_server", 7, DHCP_OPT_TYPE_IP4); > > + dhcp_opt_add(&dhcp_opts, "lpr_server", 9, DHCP_OPT_TYPE_IP4); > > + dhcp_opt_add(&dhcp_opts, "swap_server", 16, DHCP_OPT_TYPE_IP4); > > + dhcp_opt_add(&dhcp_opts, "policy_filter", 21, DHCP_OPT_TYPE_IP4); > > + dhcp_opt_add(&dhcp_opts, "router_solicitation", 32, > DHCP_OPT_TYPE_IP4); > > + dhcp_opt_add(&dhcp_opts, "nis_server", 41, DHCP_OPT_TYPE_IP4); > > + dhcp_opt_add(&dhcp_opts, "ntp_server", 42, DHCP_OPT_TYPE_IP4); > > + dhcp_opt_add(&dhcp_opts, "server_id", 54, DHCP_OPT_TYPE_IP4); > > + dhcp_opt_add(&dhcp_opts, "tftp_server", 66, DHCP_OPT_TYPE_IP4); > > + dhcp_opt_add(&dhcp_opts, "classless_static_route", 121, > > + DHCP_OPT_TYPE_STATIC_ROUTES); > > + dhcp_opt_add(&dhcp_opts, "ip_forward_enable", 19, > DHCP_OPT_TYPE_BOOL); > > + dhcp_opt_add(&dhcp_opts, "router_discovery", 31, > DHCP_OPT_TYPE_BOOL); > > + dhcp_opt_add(&dhcp_opts, "ethernet_encap", 36, DHCP_OPT_TYPE_BOOL); > > + dhcp_opt_add(&dhcp_opts, "default_ttl", 23, DHCP_OPT_TYPE_UINT8); > > + dhcp_opt_add(&dhcp_opts, "tcp_ttl", 37, DHCP_OPT_TYPE_UINT8); > > + dhcp_opt_add(&dhcp_opts, "mtu", 26, DHCP_OPT_TYPE_UINT16); > > + dhcp_opt_add(&dhcp_opts, "lease_time", 51, DHCP_OPT_TYPE_UINT32); > > Should option 58 (renewal time) and 59 (rebinding time) be in this list as > well? > > If they are absent, then it seems that dhclient3 implementation > derives renewal time and rebinding time from lease_time option by > setting both of them to a "randomized" value that is lower so that all > DHCP clients would not try to renew their leases at the same time. I > don't know what other DHCP client implementations do in such scenarios > where 58 and 59 are absent. But the worst case scenario is that out > there could be some implementations that set option 58, 59 to the > same value as 51 and then there is a chance that client may unassign > IP address from interface and proceed with renewal. > > This can also be easily supported by adding these options in the "DHCP_Options" SB DB table. Also CMS should add these options in the "Subnet" NB DB table. Please let me know if this needs to be supported in this patchset so that I can spin another one. > IIRC then ISC dhcpd server always returns all three options (e.g. 58, > 59, 51) to client. However, server is the one generating 58 and 59 to > randomized lower value. > > > > > > > > + > > + struct action_params ap = { > > + .dhcp_opts = &dhcp_opts, > > + }; > > + > > + > > + char *actions = xasprintf("dhcp_offer(%s);", argv[1]); > > + 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. dhcp_offer parse action failed : " > > + " ofpact_controller not configured"); > > + } > > + > > + struct ofpact_controller *oc = ofpbuf_pull(&ofpacts, sizeof *oc); > > + if (!oc->pause) { > > + ovs_fatal(1, "Error. dhcp_offer 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_DHCP_OFFER)) { > > + ovs_fatal(1, "Error. dhcp_offer parse action failed : > dhcp_offer " > > + "action header flag not set"); > > + } > > + > > + 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[2], NULL)[0] != '\0') { > > + ovs_fatal(1, "Error. Invalid expected dhcp opts"); > > + } > > + > > + if (oc->userdata_len != (expected_dhcp_opts.size + sizeof *ah)) { > > + ovs_fatal(1, "Error. dhcp_offer parse action failed : userdata > length" > > + " mismatch. Expected - %lu : Actual - %u", > > + 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. dhcp_offer parse action failed : dhcp opts > are" > > + " not as expected"); > > + } > > + > > + exit(0); > > +} > > + > > +OVSTEST_REGISTER("test-ovn-dhcp-offer-action", test_dhcp_offer_action); > > -- > > 2.5.5 > > > > > > _______________________________________________ > > dev mailing list > > dev@openvswitch.org > > http://openvswitch.org/mailman/listinfo/dev > _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev