To support native dhcp in ovn - A new column 'dhcp-options' is added in 'Logical_Switch' north db. - A logical flow is added for each logical port to handle dhcp packets if the CMS has defined dhcp options in this column.
Eg. action=(dhcp_offer(offerip = 10.0.0.2, router = 10.0.0.1, server_id = 10.0.0.2, mtu = 1300, lease_time = 3600, netmask = 255.255.255.0); eth.dst = eth.src; eth.src = 00:00:00:00:00:03; ip4.dst = 10.0.0.2; ip4.src = 10.0.0.2; udp.src = 67; udp.dst = 68; outport = inport; inport = ""; /* Allow sending out inport. */ output;) - ovn-controller will convert this logical flow to a packet-in OF flow with 'pause' flag set. The dhcp options defined in 'dhcp_offer' action are stored in the 'userdata' - When the ovn-controller receives the packet-in, it would frame a new dhcp packet with the dhcp options set in the 'userdata' and resume the packet. TODO: Test cases and updating the necessary documentation. Signed-off-by: Numan Siddique <nusid...@redhat.com> --- lib/dhcp.h | 13 +++ ovn/controller/lflow.c | 55 ++++++++++ ovn/controller/pinctrl.c | 102 +++++++++++++++++- ovn/lib/actions.c | 150 ++++++++++++++++++++++++++- ovn/lib/actions.h | 8 ++ ovn/lib/expr.c | 75 +++++++------- ovn/lib/expr.h | 58 +++++++++++ ovn/northd/ovn-northd.8.xml | 79 +++++++++++--- ovn/northd/ovn-northd.c | 244 +++++++++++++++++++++++++++++++++++++++++--- ovn/ovn-nb.ovsschema | 7 +- ovn/ovn-nb.xml | 183 +++++++++++++++++++++++++++++++++ ovn/ovn-sb.xml | 29 ++++++ ovn/utilities/ovn-nbctl.c | 51 +++++++++ 13 files changed, 985 insertions(+), 69 deletions(-) diff --git a/lib/dhcp.h b/lib/dhcp.h index 6f97298..c9537e7 100644 --- a/lib/dhcp.h +++ b/lib/dhcp.h @@ -25,6 +25,19 @@ #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_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) + #define DHCP_HEADER_LEN 236 struct dhcp_header { uint8_t op; /* DHCP_BOOTREQUEST or DHCP_BOOTREPLY. */ diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c index 96b7c66..cfd3328 100644 --- a/ovn/controller/lflow.c +++ b/ovn/controller/lflow.c @@ -28,6 +28,7 @@ #include "packets.h" #include "simap.h" + VLOG_DEFINE_THIS_MODULE(lflow); /* Symbol table. */ @@ -35,6 +36,9 @@ VLOG_DEFINE_THIS_MODULE(lflow); /* Contains "struct expr_symbol"s for fields supported by OVN lflows. */ static struct shash symtab; +/* Contains "struct expr_symbol_dhcp_opts"s for dhcp options */ +static struct shash dhcp_opt_symtab; + static void add_logical_register(struct shash *symtab, enum mf_field_id id) { @@ -156,6 +160,54 @@ lflow_init(void) expr_symtab_add_predicate(&symtab, "sctp", "ip.proto == 132"); expr_symtab_add_field(&symtab, "sctp.src", MFF_SCTP_SRC, "sctp", false); expr_symtab_add_field(&symtab, "sctp.dst", MFF_SCTP_DST, "sctp", false); + + /* dhcp options */ + shash_init(&dhcp_opt_symtab); + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "offerip", 0, + DHCP_OPT_TYPE_IP4); + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "netmask", 1, + DHCP_OPT_TYPE_IP4); + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "router", 3, + DHCP_OPT_TYPE_IP4); + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "dns_server", 6, + DHCP_OPT_TYPE_IP4); + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "log_server", 7, + DHCP_OPT_TYPE_IP4); + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "lpr_server", 9, + DHCP_OPT_TYPE_IP4); + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "swap_server", 16, + DHCP_OPT_TYPE_IP4); + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "policy_filter", 21, + DHCP_OPT_TYPE_IP4); + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "router_solicitation", 32, + DHCP_OPT_TYPE_IP4); + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "nis_server", 41, + DHCP_OPT_TYPE_IP4); + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "ntp_server", 42, + DHCP_OPT_TYPE_IP4); + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "tftp_server", 66, + DHCP_OPT_TYPE_IP4); + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "server_id", 54, + DHCP_OPT_TYPE_IP4); + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "classless_static_route", 121, + DHCP_OPT_TYPE_STATIC_ROUTES); + + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "ip_forward_enable", 19, + DHCP_OPT_TYPE_BOOL); + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "router_discovery", 31, + DHCP_OPT_TYPE_BOOL); + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "ethernet_encap", 36, + DHCP_OPT_TYPE_BOOL); + + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "default_ttl", 23, + DHCP_OPT_TYPE_UINT8); + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "tcp_ttl", 37, + DHCP_OPT_TYPE_UINT8); + + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "mtu", 26, + DHCP_OPT_TYPE_UINT16); + dhcp_opt_expr_symtab_add_field(&dhcp_opt_symtab, "lease_time", 51, + DHCP_OPT_TYPE_UINT32); } struct lookup_port_aux { @@ -277,6 +329,7 @@ add_logical_flows(struct controller_ctx *ctx, const struct lport_index *lports, }; struct action_params ap = { .symtab = &symtab, + .dhcp_opt_symtab = &dhcp_opt_symtab, .lookup_port = lookup_port_cb, .aux = &aux, .ct_zones = ct_zones, @@ -441,4 +494,6 @@ lflow_destroy(void) { expr_symtab_destroy(&symtab); shash_destroy(&symtab); + dhcp_opt_expr_symtab_destroy(&dhcp_opt_symtab); + shash_destroy(&dhcp_opt_symtab); } diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c index cbac50e..2a9931b 100644 --- a/ovn/controller/pinctrl.c +++ b/ovn/controller/pinctrl.c @@ -17,10 +17,13 @@ #include "pinctrl.h" + #include "coverage.h" +#include "csum.h" #include "dirs.h" #include "dp-packet.h" #include "flow.h" +#include "lib/dhcp.h" #include "lport.h" #include "openvswitch/ofp-actions.h" #include "openvswitch/ofp-msgs.h" @@ -192,13 +195,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 +326,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 +339,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..50d0ff6 100644 --- a/ovn/lib/actions.c +++ b/ovn/lib/actions.c @@ -26,8 +26,11 @@ #include "openvswitch/dynamic-string.h" #include "openvswitch/ofp-actions.h" #include "openvswitch/ofpbuf.h" +#include "shash.h" #include "simap.h" +#include "openvswitch/vlog.h" +VLOG_DEFINE_THIS_MODULE(ovn_actions); /* Context maintained during actions_parse(). */ struct action_context { const struct action_params *ap; /* Parameters. */ @@ -186,13 +189,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 +217,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 +251,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 +268,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 expr_symbol_dhcp_opts *symbol = shash_find_data( + ctx->ap->dhcp_opt_symtab, ctx->lexer->token.s); + + if (!symbol) { + 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, ctx->ap->dhcp_opt_symtab, &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 (symbol->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] = symbol->opt_code; + + + switch(symbol->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 +617,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..782c5ba 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; + /* A table of "struct dhcp_opt_expr_symbol"s to support 'dhcp_offer' */ + const struct shash *dhcp_opt_symtab; + /* 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/expr.c b/ovn/lib/expr.c index 05dd498..fe3fd1e 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) { @@ -2929,3 +2895,42 @@ 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); +} + +struct expr_symbol_dhcp_opts * +dhcp_opt_expr_symtab_add_field(struct shash *symtab, const char *opt_name, + uint8_t opt_code, enum dhcp_opt_type type) +{ + struct expr_symbol_dhcp_opts *symbol = xzalloc(sizeof *symbol); + symbol->opt_name = xstrdup(opt_name); + symbol->opt_code = opt_code; + symbol->opt_type = type; + shash_add_assert(symtab, symbol->opt_name, symbol); + + return symbol; +} + +void +dhcp_opt_expr_symtab_destroy(struct shash *symtab) +{ + struct shash_node *node, *next; + + SHASH_FOR_EACH_SAFE (node, next, symtab) { + struct expr_symbol_dhcp_opts *symbol = node->data; + + shash_delete(symtab, node); + free(symbol->opt_name); + free(symbol); + } +} diff --git a/ovn/lib/expr.h b/ovn/lib/expr.h index 1327789..71f85b0 100644 --- a/ovn/lib/expr.h +++ b/ovn/lib/expr.h @@ -391,4 +391,62 @@ 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); + +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 expr_symbol_dhcp_opts { + char *opt_name; + uint8_t opt_code; + uint8_t opt_type; +}; + +struct expr_symbol_dhcp_opts *dhcp_opt_expr_symtab_add_field( + struct shash *symtab, const char *opt_name, uint8_t opt_code, + enum dhcp_opt_type type); + +void dhcp_opt_expr_symtab_destroy(struct shash *symtab); #endif /* ovn/expr.h */ diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml index da776e1..f0d05ee 100644 --- a/ovn/northd/ovn-northd.8.xml +++ b/ovn/northd/ovn-northd.8.xml @@ -223,17 +223,57 @@ </li> </ul> - <h3>Ingress Table 3: <code>from-lport</code> Pre-ACLs</h3> + <h3>Ingress Table 3: DHCP</h3> <p> - Ingress table 3 prepares flows for possible stateful ACL processing + Ingress table 3 implements DHCP responder for the logical ports + configured with IPv4 address(es) and DHCP options. + <ul> + <li> + <p> + A priority-100 logical flow is added for these logical ports + which match dhcp packets (udp.src = 68 and udp.dst = 67) + and replies with the DHCP REQUEST/ACK packet with the configured + DHCP options. + </p> + + <pre> +dhcp_offer(DHCP OPTIONS) +eth.dst = eth.src; +eth.src = <var>E</var>; +ip4.dst = <var>O</var>; +ip4.src = <var>S</var>; +udp.src = 67; +udp.dst = 68; +outport = <var>P</var>; +inport = ""; /* Allow sending out inport. */ +output; + </pre> + + <p> + Where <var>E</var> is the server mac address and <var>S</var> is the + server IPv4 address defined in the DHCP options and <var>O</var> is + the IPv4 address defined in the logical port's addresses column. + </p> + </li> + + <li> + A priority-0 flow that matches all packets to advances to table 4. + </li> + </ul> + </p> + + <h3>Ingress Table 4: <code>from-lport</code> Pre-ACLs</h3> + + <p> + Ingress table 4 prepares flows for possible stateful ACL processing in table 4. It contains a priority-0 flow that simply moves traffic to table 4. If stateful ACLs are used in the logical datapath, a priority-100 flow is added that sends IP packets to the connection tracker before advancing to table 4. </p> - <h3>Ingress table 4: <code>from-lport</code> ACLs</h3> + <h3>Ingress table 5: <code>from-lport</code> ACLs</h3> <p> Logical flows in this table closely reproduce those in the @@ -249,7 +289,7 @@ </p> <p> - Ingress table 4 also contains a priority 0 flow with action + Ingress table 5 also contains a priority 0 flow with action <code>next;</code>, so that ACLs allow packets by default. If the logical datapath has a statetful ACL, the following flows will also be added: @@ -281,7 +321,7 @@ </li> </ul> - <h3>Ingress Table 5: ARP responder</h3> + <h3>Ingress Table 6: ARP responder</h3> <p> This table implements ARP responder for known IPs. It contains these @@ -326,7 +366,7 @@ output; </li> </ul> - <h3>Ingress Table 6: Destination Lookup</h3> + <h3>Ingress Table 7: Destination Lookup</h3> <p> This table implements switching behavior. It contains these logical @@ -357,20 +397,35 @@ output; </li> </ul> - <h3>Egress Table 0: <code>to-lport</code> Pre-ACLs</h3> + <h3>Egress Table 0: DHCP</h3> + <p> + <ul> + <li> + This table adds a priority-100 flow to skip the ACL stages + for the DHCP REQUEST/ACK packets from the Ingress Table 3 DHCP + responder. + </li> + + <li> + A priority-0 flow that matches all packets to advances to table 4. + </li> + </ul> + </p> + + <h3>Egress Table 1: <code>to-lport</code> Pre-ACLs</h3> <p> - This is similar to ingress table 3 except for <code>to-lport</code> + This is similar to ingress table 4 except for <code>to-lport</code> traffic. </p> - <h3>Egress Table 1: <code>to-lport</code> ACLs</h3> + <h3>Egress Table 2: <code>to-lport</code> ACLs</h3> <p> - This is similar to ingress table 4 except for <code>to-lport</code> ACLs. + This is similar to ingress table 5 except for <code>to-lport</code> ACLs. </p> - <h3>Egress Table 2: Egress Port Security - IP</h3> + <h3>Egress Table 3: Egress Port Security - IP</h3> <p> This is similar to the ingress port security logic in table 1 except @@ -379,7 +434,7 @@ output; <code>eth.src</code>, <code>ip4.src</code> and <code>ip6.src</code> </p> - <h3>Egress Table 3: Egress Port Security - L2</h3> + <h3>Egress Table 4: Egress Port Security - L2</h3> <p> This is similar to the ingress port security logic in ingress table 0, diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index 260c02f..21b435e 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -32,6 +32,7 @@ #include "packets.h" #include "poll-loop.h" #include "smap.h" +#include "sset.h" #include "stream.h" #include "stream-ssl.h" #include "unixctl.h" @@ -90,16 +91,18 @@ enum ovn_stage { PIPELINE_STAGE(SWITCH, IN, PORT_SEC_L2, 0, "ls_in_port_sec_l2") \ PIPELINE_STAGE(SWITCH, IN, PORT_SEC_IP, 1, "ls_in_port_sec_ip") \ PIPELINE_STAGE(SWITCH, IN, PORT_SEC_ND, 2, "ls_in_port_sec_nd") \ - PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 3, "ls_in_pre_acl") \ - PIPELINE_STAGE(SWITCH, IN, ACL, 4, "ls_in_acl") \ - PIPELINE_STAGE(SWITCH, IN, ARP_RSP, 5, "ls_in_arp_rsp") \ - PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 6, "ls_in_l2_lkup") \ + PIPELINE_STAGE(SWITCH, IN, DHCP, 3, "ls_in_dhcp") \ + PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 4, "ls_in_pre_acl") \ + PIPELINE_STAGE(SWITCH, IN, ACL, 5, "ls_in_acl") \ + PIPELINE_STAGE(SWITCH, IN, ARP_RSP, 6, "ls_in_arp_rsp") \ + PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 7, "ls_in_l2_lkup") \ \ /* Logical switch egress stages. */ \ - PIPELINE_STAGE(SWITCH, OUT, PRE_ACL, 0, "ls_out_pre_acl") \ - PIPELINE_STAGE(SWITCH, OUT, ACL, 1, "ls_out_acl") \ - PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_IP, 2, "ls_out_port_sec_ip") \ - PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2, 3, "ls_out_port_sec_l2") \ + PIPELINE_STAGE(SWITCH, OUT, DHCP, 0, "ls_out_dhcp") \ + PIPELINE_STAGE(SWITCH, OUT, PRE_ACL, 1, "ls_out_pre_acl") \ + PIPELINE_STAGE(SWITCH, OUT, ACL, 2, "ls_out_acl") \ + PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_IP, 3, "ls_out_port_sec_ip") \ + PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2, 4, "ls_out_port_sec_l2") \ \ /* Logical router ingress stages. */ \ PIPELINE_STAGE(ROUTER, IN, ADMISSION, 0, "lr_in_admission") \ @@ -1358,6 +1361,130 @@ has_stateful_acl(struct ovn_datapath *od) return false; } +static bool +extract_dhcp_options_from_json(struct json *json, struct smap *dhcp_options) +{ + const struct shash_node *node; + /* Calling 'smap_from_json()' asserts if 'json' values are not string + * and we don't want to assert here. + * */ + SHASH_FOR_EACH (node, json_object(json)) { + const struct json *value = node->data; + if (value->type == JSON_STRING) { + smap_add(dhcp_options, node->name, json_string(value)); + } + else { + return false; + } + } + return true; +} + +static bool +build_dhcp_action(struct ovn_port *op, ovs_be32 offer_ip, struct ds *action) +{ + uint8_t options_bmap = 0; + struct smap_node *node; + char *server_ip= NULL; + char *server_mac = NULL; + bool retval = false; + bool dhcp_disabled = smap_get_bool(&op->nbs->options, + "dhcp_disabled", false); + if (dhcp_disabled) { + /* CMS has disabled native dhcp for this lport */ + return false; + } + + struct smap dhcp_options = SMAP_INITIALIZER(&dhcp_options); + struct json *json_dhcp_options = NULL; + SMAP_FOR_EACH(node, &op->od->nbs->dhcp_options) { + ovs_be32 host_ip, mask; + char *error = ip_parse_masked(node->key, &host_ip, &mask); + if (error) { + free(error); + continue; + } + + if ((offer_ip ^ host_ip) & mask) { + continue; + } + + json_dhcp_options = json_from_string(node->value); + if (json_dhcp_options->type != JSON_OBJECT) { + json_destroy(json_dhcp_options); + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_WARN_RL(&rl, "dhcp options for cidr [%s] are not in proper" + " json string format for lswitch - %s : %s", + node->key, op->od->nbs->name, node->value); + return false; + } + break; + } + + if (!json_dhcp_options || !extract_dhcp_options_from_json( + json_dhcp_options, &dhcp_options)) { + goto exit; + } + + struct smap_node *snode; + SMAP_FOR_EACH(snode, &dhcp_options) { + if(!strcmp(snode->key, "router")) { + options_bmap |= 0x1; + } else if (!strcmp(snode->key, "server_id")) { + options_bmap |= 0x2; + server_ip = snode->value; + } else if (!strcmp(snode->key, "server_mac")) { + options_bmap |= 0x4; + server_mac = xstrdup(snode->value); + } else if (!strcmp(snode->key, "lease_time")) { + options_bmap |= 0x8; + } else if (!strcmp(snode->key, "netmask")) { + options_bmap |= 0x10; + } + } + + if (!(options_bmap & 0x08)) { + goto exit; + } + + if (!(options_bmap & 0x10)) { + smap_add(&dhcp_options, "netmask", "255.255.255.0"); + } + + /* server_mac is not dhcp option, delete it from the smap */ + smap_remove(&dhcp_options, "server_mac"); + + SMAP_FOR_EACH(node, &op->nbs->options) { + if(!strncmp(node->key, "dhcp_opt_", 9)) { + smap_replace(&dhcp_options, &node->key[9], node->value); + } + } + + ds_put_format(action, "dhcp_offer(offerip = "IP_FMT", ", + IP_ARGS(offer_ip)); + SMAP_FOR_EACH(node, &dhcp_options) { + ds_put_format(action, "%s = %s, ", node->key, node->value); + } + + ds_chomp(action, ' '); + ds_chomp(action, ','); + + ds_put_format(action, "); eth.dst = eth.src; eth.src = %s; ip4.dst = "IP_FMT"; " + "ip4.src = %s; udp.src = 67; udp.dst = 68; ", + server_mac, IP_ARGS(offer_ip), server_ip); + ds_put_cstr(action, "outport = inport; inport = \"\"; " + "/* Allow sending out inport. */ output;"); + + retval = true; +exit: + free(server_mac); + smap_destroy(&dhcp_options); + if (json_dhcp_options) { + json_destroy(json_dhcp_options); + } + return retval; +} + static void build_acls(struct ovn_datapath *od, struct hmap *lflows, struct hmap *ports) { @@ -1579,8 +1706,8 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } - /* Ingress table 1 and 2: Port security - IP and ND, by default goto next. - * (priority 0)*/ + /* Ingress table 1 and 2: Port security - IP and ND, tabl3 - DCHP + * by default goto next. (priority 0)*/ HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbs) { continue; @@ -1588,9 +1715,54 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_ND, 0, "1", "next;"); ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_IP, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP, 0, "1", "next;"); } - /* Ingress table 3: ARP responder, skip requests coming from localnet ports. + /* Logical switch ingress table 3: DHCP priority 50. */ + HMAP_FOR_EACH (op, key_node, ports) { + if (!op->nbs) { + continue; + } + + if (!lport_is_enabled(op->nbs)) { + /* Drop packets from disabled logical ports (since logical flow + * tables are default-drop). */ + continue; + } + + for (size_t i = 0; i < op->nbs->n_addresses; i++) { + struct lport_addresses laddrs; + if (!extract_lport_addresses(op->nbs->addresses[i], &laddrs, + false)) { + continue; + } + + if (!laddrs.n_ipv4_addrs) { + continue; + } + + for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) { + struct ds action = DS_EMPTY_INITIALIZER; + if (build_dhcp_action(op, laddrs.ipv4_addrs[j].addr, &action)) { + struct ds match = DS_EMPTY_INITIALIZER; + ds_put_format( + &match, "inport == %s && eth.src == "ETH_ADDR_FMT + " && ip4.src == 0.0.0.0 && " + "ip4.dst == 255.255.255.255 && udp.src == 68 && " + "udp.dst == 67", op->json_key, + ETH_ADDR_ARGS(laddrs.ea)); + + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP, 50, + ds_cstr(&match), ds_cstr(&action)); + ds_destroy(&match); + ds_destroy(&action); + break; + } + } + } + } + + /* Ingress table 6: ARP responder, skip requests coming from localnet ports. * (priority 100). */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { @@ -1605,7 +1777,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } - /* Ingress table 5: ARP responder, reply for known IPs. + /* Ingress table 6: ARP responder, reply for known IPs. * (priority 50). */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { @@ -1655,7 +1827,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } - /* Ingress table 5: ARP responder, by default goto next. + /* Ingress table 6: ARP responder, by default goto next. * (priority 0)*/ HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbs) { @@ -1665,7 +1837,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_RSP, 0, "1", "next;"); } - /* Ingress table 6: Destination lookup, broadcast and multicast handling + /* Ingress table 7: Destination lookup, broadcast and multicast handling * (priority 100). */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { @@ -1685,7 +1857,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, "outport = \""MC_FLOOD"\"; output;"); } - /* Ingress table 6: Destination lookup, unicast handling (priority 50), */ + /* Ingress table 7: Destination lookup, unicast handling (priority 50), */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { continue; @@ -1722,7 +1894,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } - /* Ingress table 6: Destination lookup for unknown MACs (priority 0). */ + /* Ingress table 7: Destination lookup for unknown MACs (priority 0). */ HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbs) { continue; @@ -1734,6 +1906,46 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } + /* Egress table 0: dhcp response - Priority 100 flow to skip ACL + * stages for dhcp response from ovn-controller. + * Priority 0 flow to advance to the next stage + */ + HMAP_FOR_EACH (od, key_node, datapaths) { + if (!od->nbs) { + continue; + } + + ovn_lflow_add(lflows, od, S_SWITCH_OUT_DHCP, 0, "1", "next;"); + if (!od->nbs->n_ports) { + continue; + } + + struct smap_node *node; + SMAP_FOR_EACH(node, &od->nbs->dhcp_options) { + struct json *json = json_from_string(node->value); + if (json->type != JSON_OBJECT) { + json_destroy(json); + continue; + } + + struct smap dhcp_options = SMAP_INITIALIZER(&dhcp_options); + extract_dhcp_options_from_json(json, &dhcp_options); + json_destroy(json); + + const char *server_id = smap_get(&dhcp_options, "server_id"); + const char *server_mac = smap_get(&dhcp_options, "server_mac"); + if (server_id && server_mac) { + struct ds match = DS_EMPTY_INITIALIZER; + ds_put_format(&match, "eth.src == %s && ip4.src == %s &&" + " udp && udp.src == 67 && udp.dst == 68", server_mac, + server_id); + ovn_lflow_add(lflows, od, S_SWITCH_OUT_DHCP, 100, ds_cstr(&match), + "/* Skip ACL stages for dhcp response */ next(3);"); + } + smap_destroy(&dhcp_options); + } + } + /* Egress table 2: Egress port security - IP (priority 0) * port security L2 - multicast/broadcast (priority * 100). */ diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema index 40a7a97..2088fd5 100644 --- a/ovn/ovn-nb.ovsschema +++ b/ovn/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "2.0.2", - "cksum": "4289495412 4436", + "version": "2.0.3", + "cksum": "776261543 4598", "tables": { "Logical_Switch": { "columns": { @@ -16,6 +16,9 @@ "refType": "strong"}, "min": 0, "max": "unlimited"}}, + "dhcp_options": { + "type": {"key": "string", "value": "string", + "min": 0, "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml index e65bc3a..7305398 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -78,6 +78,167 @@ See <em>External IDs</em> at the beginning of this document. </column> </group> + + <group title="Dhcp options"> + <column name="dhcp_options"> + <p> + OVN implements a native DHCP support which caters to the common + use case of providing an IP address to a booting instance by providing + stateless replies to DHCP requests based on statically configured + address mappings. To do this it allows a short list of DHCP options to be + configured and applied at each compute host running ovn-controller. + + This column is a key/value pair with key being the <code>cidr</code> + of the subnet belonging to this logical switch and value being the + <code>DHCP options</code> defined as a valid JSON string with + key/value pairs as DHCP options. All the values should be strings. + + <p> + Examples: + </p> + + <dl> + <dt><code>key: '10.0.0.0/24'</code></dt> + <dd> + This indicates that the DHCP options defined in the value should be used + for DHCP requests from the logical ports having the IPv4 addresses from + this subnet. + </dd> + + <dt><code>value: </code></dt> + <dd> + <p> + "{"server_id": "10.0.0.1", "lease_time": "43200", + "mtu": "1450", "netmask": "255.255.255.0", + "router": "10.0.0.1", "server_mac": "fa:16:3e:3e:b8:8a"}" + </p> + </dd> + </dl> + </p> + + <p> + <code>Supported DHCP options</code> + </p> + + <p> + Below are the supported DHCP options whose values are IPv4 address + or addresses. If the value has more than one IPv4 address, then it + should be enclosed within '{}' braces. + </p> + + <dl> + <dt><code>netmask</code></dt> + + <dt><code>router</code></dt> + + <dt><code>dns_server</code></dt> + + <dt><code>log_server</code></dt> + + <dt><code>lpr_server</code></dt> + + <dt><code>swap_server</code></dt> + + <dt><code>policy_filter</code></dt> + + <dt><code>router_solicitation</code></dt> + + <dt><code>nis_server</code></dt> + + <dt><code>ntp_server</code></dt> + + <dt><code>tftp_server</code></dt> + + <dt><code>server_id</code></dt> + + <dt><code>router_solicitation</code></dt> + + <dt><code>classless_static_route</code></dt> + <dd> + This option can contain one or more static routes, each of which + consists of a destination descriptor and the IP address of the router + that should be used to reach that destination. Please see RFC 3442 + for more details. + </dd> + </dl> + + <p> + <p> + <code>Examples</code> + </p> + + <dl> + <dt><code>"router": "10.0.0.1"</code></dt> + + <dt><code>"dns_server": "{7.7.7.7,8.8.8.8}"</code></dt> + + <dt><code>"classless_static_route": "{40.0.0.0/24,10.0.0.2, 0.0.0.0/0,10.0.0.1}"</code></dt> + <dd> + The route <code>0.0.0.0/0,10.0.0.1</code> indicates the default gateway. + </dd> + </dl> + </p> + + <p> + Below are the other supported DHCP options + </p> + + <dl> + <dt><code>ip_forward_enable</code></dt> + <dd> + <p> + type: <code>bool</code> + </p> + + <p> + Example: <code>"ip_forward_enable": "1"</code> + </p> + </dd> + + <dt><code>router_discovery</code></dt> + <dd> + type: <code>bool</code> + </dd> + + <dt><code>ethernet_encap</code></dt> + <dd> + type: <code>bool</code> + </dd> + + <dt><code>default_ttl</code></dt> + <dd> + <p> + type: <code>uint8</code> + </p> + <p> + Example: <code>"default_ttl": "100"</code> + </p> + </dd> + + <dt><code>tcp_ttl</code></dt> + <dd> + type: <code>uint8</code> + </dd> + + <dt><code>mtu</code></dt> + <dd> + <p> + type: <code>uint16</code> + </p> + + <p> + Example: <code>"mtu": "1450"</code> + </p> + </dd> + + <dt><code>lease_time</code></dt> + <dd> + type: <code>uint32</code> + </dd> + </dl> + </column> + + </group> </table> <table name="Logical_Port" title="L2 logical switch port"> @@ -212,6 +373,28 @@ interface, in kb. </column> </group> + + <group title="DHCP Options"> + <p> + These options apply to logical ports with <ref column="type"/> having + (empty string) + </p> + + <column name="options" key="dhcp_opt_OPTION_NAME"> + Each logical port can override the DHCP options defined in the + <ref column="dhcp_options"/> of <ref table="Logical_Switch"/> + by defining them in this column with the prefix "dhcp_opt_". + Please see the <ref column="dhcp_options"/> of + <ref table="Logical_Switch"/> for supported DHCP options. + + Example: key="dhcp_opt_mtu", value="1300" + </column> + + <column name="options" key="dhcp_disabled"> + If this is defined, <code>ovn-northd</code> will disable the native + DHCP responder for the logical port. + </column> + </group> </group> <group title="Containers"> diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml index efd2f9a..86e593a 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> diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c index bdad368..5bff15e 100644 --- a/ovn/utilities/ovn-nbctl.c +++ b/ovn/utilities/ovn-nbctl.c @@ -478,6 +478,52 @@ nbctl_lswitch_list(struct ctl_context *ctx) smap_destroy(&lswitches); free(nodes); } + +static void +nbctl_lswitch_set_dhcp_options(struct ctl_context *ctx) +{ + const char *id = ctx->argv[1]; + const struct nbrec_logical_switch *lswitch; + size_t i; + struct smap dhcp_options = SMAP_INITIALIZER(&dhcp_options); + + lswitch = lswitch_by_name_or_uuid(ctx, id); + if (!lswitch) { + return; + } + + for (i = 2; i < ctx->argc; i++) { + char *key, *value; + value = xstrdup(ctx->argv[i]); + key = strsep(&value, "="); + if (value) { + smap_add(&dhcp_options, key, value); + } + free(key); + } + + nbrec_logical_switch_set_dhcp_options(lswitch, &dhcp_options); + + smap_destroy(&dhcp_options); +} + +static void +nbctl_lswitch_get_dhcp_options(struct ctl_context *ctx) +{ + const char *id = ctx->argv[1]; + const struct nbrec_logical_switch *lswitch; + struct smap_node *node; + + lswitch = lswitch_by_name_or_uuid(ctx, id); + if (!lswitch) { + return; + } + + SMAP_FOR_EACH(node, &lswitch->dhcp_options) { + ds_put_format(&ctx->output, "%s=%s\n", node->key, node->value); + } +} + static const struct nbrec_logical_port * lport_by_name_or_uuid(struct ctl_context *ctx, const char *id) @@ -1311,6 +1357,11 @@ static const struct ctl_command_syntax nbctl_commands[] = { { "lswitch-del", 1, 1, "LSWITCH", NULL, nbctl_lswitch_del, NULL, "", RW }, { "lswitch-list", 0, 0, "", NULL, nbctl_lswitch_list, NULL, "", RO }, + { "lswitch-set-dhcp-options", 1, INT_MAX, + "LSWITCH KEY=VALUE [KEY=VALUE]...", NULL, nbctl_lswitch_set_dhcp_options, + NULL, "", RW }, + { "lswitch-get-dhcp-options", 1, 1, "LSWITCH", NULL, + nbctl_lswitch_get_dhcp_options, NULL, "", RO }, /* acl commands. */ { "acl-add", 5, 5, "LSWITCH DIRECTION PRIORITY MATCH ACTION", NULL, -- 2.5.5 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev