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> --- 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 | 38 ++++++++++++ tests/test-ovn-dhcp.c | 117 ++++++++++++++++++++++++++++++++++++ 14 files changed, 666 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..cafe116 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 (uint32_t)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 ((uint8_t)1) +#define DHCP_OP_REPLY ((uint8_t)2) + +#define DHCP_MSG_DISCOVER ((uint8_t)1) +#define DHCP_MSG_OFFER ((uint8_t)2) +#define DHCP_MSG_REQUEST ((uint8_t)3) +#define DHCP_MSG_ACK ((uint8_t)5) + +#define DHCP_OPT_MSG_TYPE ((uint8_t)53) +#define DHCP_OPT_END ((uint8_t)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) + +#define DHCP_OPT_CLASSLESS_STATIC_ROUTE \ + DHCP_OPTION("classless_static_route", 121, DHCP_OPT_TYPE_STATIC_ROUTES) + +#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..8e44de0 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 0b77617..02dc99b 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 8c1f1be..971c8cd 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -2341,4 +2341,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_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); + + 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