diff --git a/include/ovn/actions.h b/include/ovn/actions.h index fb2d6a96762..f1f38c6ab3b 100644 --- a/include/ovn/actions.h +++ b/include/ovn/actions.h @@ -90,6 +90,15 @@ enum action_opcode { * MFF_ETH_SRC = mac */ ACTION_OPCODE_PUT_ND, + + /* "result = put_dhcpv6_opts(option, ...)". + * + * Arguments follow the action_header, in this format: + * - A 32-bit or 64-bit OXM header designating the result field. + * - A 32-bit integer specifying a bit offset within the result field. + * - Any number of DHCPv6 options. + */ + ACTION_OPCODE_PUT_DHCPV6_OPTS, }; /* Header. */ @@ -107,6 +116,9 @@ struct action_params { /* hmap of 'struct dhcp_opts_map' to support 'put_dhcp_opts' action */ const struct hmap *dhcp_opts; + /* hmap of 'struct dhcp_opts_map' to support 'put_dhcpv6_opts' action */ + const struct hmap *dhcpv6_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/controller/lflow.c b/ovn/controller/lflow.c index 9024cc00c99..f72a14fe3f8 100644 --- a/ovn/controller/lflow.c +++ b/ovn/controller/lflow.c @@ -218,6 +218,7 @@ static void consider_logical_flow(const struct lport_index *lports, struct group_table *group_table, const struct simap *ct_zones, struct hmap *dhcp_opts_p, + struct hmap *dhcpv6_opts_p, uint32_t *conj_id_ofs_p); static bool @@ -271,17 +272,25 @@ add_logical_flows(struct controller_ctx *ctx, const struct lport_index *lports, } struct hmap dhcp_opts = HMAP_INITIALIZER(&dhcp_opts); + struct hmap dhcpv6_opts = HMAP_INITIALIZER(&dhcpv6_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_dhcpv6_options *dhcpv6_opt_row; + SBREC_DHCPV6_OPTIONS_FOR_EACH(dhcpv6_opt_row, ctx->ovnsb_idl) { + dhcp_opt_add(&dhcpv6_opts, dhcpv6_opt_row->name, dhcpv6_opt_row->code, + dhcpv6_opt_row->type); + } + if (full_logical_flow_processing) { SBREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->ovnsb_idl) { consider_logical_flow(lports, mcgroups, lflow, local_datapaths, patched_datapaths, group_table, ct_zones, - &dhcp_opts, &conj_id_ofs); + &dhcp_opts, &dhcpv6_opts, &conj_id_ofs); } full_logical_flow_processing = false; } else { @@ -299,12 +308,13 @@ add_logical_flows(struct controller_ctx *ctx, const struct lport_index *lports, consider_logical_flow(lports, mcgroups, lflow, local_datapaths, patched_datapaths, group_table, ct_zones, - &dhcp_opts, &conj_id_ofs); + &dhcp_opts, &dhcpv6_opts, &conj_id_ofs); } } } dhcp_opts_destroy(&dhcp_opts); + dhcp_opts_destroy(&dhcpv6_opts); } static void @@ -316,6 +326,7 @@ consider_logical_flow(const struct lport_index *lports, struct group_table *group_table, const struct simap *ct_zones, struct hmap *dhcp_opts_p, + struct hmap *dhcpv6_opts_p, uint32_t *conj_id_ofs_p) { /* Determine translation of logical table IDs to physical table IDs. */ @@ -388,6 +399,7 @@ consider_logical_flow(const struct lport_index *lports, struct action_params ap = { .symtab = &symtab, .dhcp_opts = dhcp_opts_p, + .dhcpv6_opts = dhcpv6_opts_p, .lookup_port = lookup_port_cb, .aux = &aux, .ct_zones = ct_zones, diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c index ca49ca632c7..8a759cd81c6 100644 --- a/ovn/controller/pinctrl.c +++ b/ovn/controller/pinctrl.c @@ -37,6 +37,7 @@ #include "ovn-controller.h" #include "ovn/actions.h" #include "ovn/lib/logical-fields.h" +#include "ovn/lib/ovn-dhcp.h" #include "ovn/lib/ovn-util.h" #include "poll-loop.h" #include "rconn.h" @@ -366,6 +367,290 @@ pinctrl_handle_put_dhcp_opts( } } +static bool +compose_out_dhcpv6_opts(struct ofpbuf *userdata, + struct ofpbuf *out_dhcpv6_opts, ovs_be32 iaid) +{ + while (userdata->size) { + struct dhcp_opt6_header *userdata_opt = ofpbuf_try_pull( + userdata, sizeof *userdata_opt); + if (!userdata_opt) { + return false; + } + + uint8_t *userdata_opt_data = ofpbuf_try_pull(userdata, + userdata_opt->len); + if (!userdata_opt_data) { + return false; + } + + switch (userdata_opt->code) { + case DHCPV6_OPT_SERVER_ID_CODE: + { + /* The Server Identifier option carries a DUID + * identifying a server between a client and a server. + * See RFC 3315 Sec 9 and Sec 22.3. + * + * We use DUID Based on Link-layer Address [DUID-LL]. + */ + + struct dhcpv6_opt_server_id *opt_server_id = ofpbuf_put_zeros( + out_dhcpv6_opts, sizeof *opt_server_id); + + opt_server_id->opt.code = htons(DHCPV6_OPT_SERVER_ID_CODE); + opt_server_id->opt.len = htons(userdata_opt->len + 4); + opt_server_id->duid_type = htons(DHCPV6_DUID_LL); + opt_server_id->hw_type = htons(DHCPV6_HW_TYPE_ETH); + memcpy(&opt_server_id->mac, userdata_opt_data, + sizeof(struct eth_addr)); + break; + } + + case DHCPV6_OPT_IA_ADDR_CODE: + { + if (userdata_opt->len != sizeof(struct in6_addr)) { + return false; + } + + /* IA Address option is used to specify IPv6 addresses associated + * with an IA_NA or IA_TA. The IA Address option must be + * encapsulated in the Options field of an IA_NA or IA_TA option. + * + * We will encapsulate the IA Address within the IA_NA option. + * Please see RFC 3315 section 22.5 and 22.6 + */ + struct dhcpv6_opt_ia_na *opt_ia_na = ofpbuf_put_zeros( + out_dhcpv6_opts, sizeof *opt_ia_na); + opt_ia_na->opt.code = htons(DHCPV6_OPT_IA_NA_CODE); + /* IA_NA length (in bytes)- + * IAID - 4 + * T1 - 4 + * T2 - 4 + * IA Address - sizeof(struct dhcpv6_opt_ia_addr) + */ + opt_ia_na->opt.len = htons(12 + sizeof(struct dhcpv6_opt_ia_addr)); + opt_ia_na->iaid = iaid; + /* Set the lifetime of the address(es) to infinity */ + opt_ia_na->t1 = OVS_BE32_MAX; + opt_ia_na->t2 = OVS_BE32_MAX; + + struct dhcpv6_opt_ia_addr *opt_ia_addr = ofpbuf_put_zeros( + out_dhcpv6_opts, sizeof *opt_ia_addr); + opt_ia_addr->opt.code = htons(DHCPV6_OPT_IA_ADDR_CODE); + opt_ia_addr->opt.len = htons(userdata_opt->len + 8); + memcpy(opt_ia_addr->ipv6.s6_addr, userdata_opt_data, + userdata_opt->len); + opt_ia_addr->t1 = OVS_BE32_MAX; + opt_ia_addr->t2 = OVS_BE32_MAX; + break; + } + + case DHCPV6_OPT_DNS_SERVER_CODE: + { + struct dhcpv6_opt_header *opt_dns = ofpbuf_put_zeros( + out_dhcpv6_opts, sizeof *opt_dns); + opt_dns->code = htons(DHCPV6_OPT_DNS_SERVER_CODE); + opt_dns->len = htons(userdata_opt->len); + ofpbuf_put(out_dhcpv6_opts, userdata_opt_data, userdata_opt->len); + break; + } + + case DHCPV6_OPT_DOMAIN_SEARCH_CODE: + { + struct dhcpv6_opt_header *opt_dsl = ofpbuf_put_zeros( + out_dhcpv6_opts, sizeof *opt_dsl); + opt_dsl->code = htons(DHCPV6_OPT_DOMAIN_SEARCH_CODE); + opt_dsl->len = htons(userdata_opt->len + 2); + uint8_t *data = ofpbuf_put_zeros(out_dhcpv6_opts, + userdata_opt->len + 2); + *data = userdata_opt->len; + memcpy(data + 1, userdata_opt_data, userdata_opt->len); + break; + } + + default: + return false; + } + } + return true; +} + +static void +pinctrl_handle_put_dhcpv6_opts( + struct dp_packet *pkt_in, struct ofputil_packet_in *pin, + struct ofpbuf *userdata, struct ofpbuf *continuation OVS_UNUSED) +{ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + enum ofp_version version = rconn_get_version(swconn); + enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version); + struct dp_packet *pkt_out_ptr = NULL; + uint32_t success = 0; + + /* Parse result field. */ + const struct mf_field *f; + enum ofperr ofperr = nx_pull_header(userdata, &f, NULL); + if (ofperr) { + VLOG_WARN_RL(&rl, "bad result OXM (%s)", ofperr_to_string(ofperr)); + goto exit; + } + + /* Parse result offset. */ + ovs_be32 *ofsp = ofpbuf_try_pull(userdata, sizeof *ofsp); + if (!ofsp) { + VLOG_WARN_RL(&rl, "offset not present in the userdata"); + goto exit; + } + + /* Check that the result is valid and writable. */ + struct mf_subfield dst = { .field = f, .ofs = ntohl(*ofsp), .n_bits = 1 }; + ofperr = mf_check_dst(&dst, NULL); + if (ofperr) { + VLOG_WARN_RL(&rl, "bad result bit (%s)", ofperr_to_string(ofperr)); + goto exit; + } + + if (!userdata->size) { + VLOG_WARN_RL(&rl, "DHCPv6 options not present in the userdata"); + goto exit; + } + + struct udp_header *in_udp = dp_packet_l4(pkt_in); + const uint8_t *in_dhcpv6_data = dp_packet_get_udp_payload(pkt_in); + uint8_t out_dhcpv6_msg_type; + switch(*in_dhcpv6_data) { + case DHCPV6_MSG_TYPE_SOLICIT: + out_dhcpv6_msg_type = DHCPV6_MSG_TYPE_ADVT; + break; + + case DHCPV6_MSG_TYPE_REQUEST: + case DHCPV6_MSG_TYPE_CONFIRM: + case DHCPV6_MSG_TYPE_DECLINE: + out_dhcpv6_msg_type = DHCPV6_MSG_TYPE_REPLY; + break; + + default: + /* Invalid or unsupported DHCPv6 message type */ + goto exit; + } + + /* Skip 4 bytes (message type (1 byte) + transaction ID (3 bytes). */ + in_dhcpv6_data += 4; + /* We need to extract IAID from the IA-NA option of the client's DHCPv6 + * solicit/request/confirm packet and copy the same IAID in the Server's + * response. */ + ovs_be32 iaid = 0; + struct dhcpv6_opt_header const *in_opt_client_id = NULL; + size_t udp_len = ntohs(in_udp->udp_len); + size_t l4_len = dp_packet_l4_size(pkt_in); + uint8_t *end = (uint8_t *)in_udp + MIN(udp_len, l4_len); + while (in_dhcpv6_data < end) { + struct dhcpv6_opt_header const *in_opt = + (struct dhcpv6_opt_header *)in_dhcpv6_data; + switch(ntohs(in_opt->code)) { + case DHCPV6_OPT_IA_NA_CODE: + { + struct dhcpv6_opt_ia_na *opt_ia_na = ( + struct dhcpv6_opt_ia_na *)in_opt; + iaid = opt_ia_na->iaid; + break; + } + + case DHCPV6_OPT_CLIENT_ID_CODE: + in_opt_client_id = in_opt; + break; + + default: + break; + } + in_dhcpv6_data += sizeof *in_opt + ntohs(in_opt->len); + } + + if (!in_opt_client_id) { + VLOG_WARN_RL(&rl, "DHCPv6 option - Client id not present in the " + " DHCPv6 packet"); + goto exit; + } + + if (!iaid) { + VLOG_WARN_RL(&rl, "DHCPv6 option - IA NA not present in the " + " DHCPv6 packet"); + goto exit; + } + + uint64_t out_ofpacts_dhcpv6_opts_stub[256 / 8]; + struct ofpbuf out_dhcpv6_opts = + OFPBUF_STUB_INITIALIZER(out_ofpacts_dhcpv6_opts_stub); + + if (!compose_out_dhcpv6_opts(userdata, &out_dhcpv6_opts, iaid)) { + VLOG_WARN_RL(&rl, "Invalid userdata"); + goto exit; + } + + uint16_t new_l4_size + = (UDP_HEADER_LEN + 4 + sizeof *in_opt_client_id + + ntohs(in_opt_client_id->len) + out_dhcpv6_opts.size); + size_t new_packet_size = pkt_in->l4_ofs + new_l4_size; + + struct dp_packet pkt_out; + dp_packet_init(&pkt_out, new_packet_size); + dp_packet_clear(&pkt_out); + dp_packet_prealloc_tailroom(&pkt_out, new_packet_size); + pkt_out_ptr = &pkt_out; + + /* Copy L2 and L3 headers from pkt_in. */ + dp_packet_put(&pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), + pkt_in->l4_ofs); + + pkt_out.l2_5_ofs = pkt_in->l2_5_ofs; + pkt_out.l2_pad_size = pkt_in->l2_pad_size; + pkt_out.l3_ofs = pkt_in->l3_ofs; + pkt_out.l4_ofs = pkt_in->l4_ofs; + + /* Pull the DHCPv6 message type and transaction id from the pkt_in. + * Need to preserve the transaction id in the DHCPv6 reply packet. */ + struct udp_header *out_udp = dp_packet_put( + &pkt_out, dp_packet_pull(pkt_in, UDP_HEADER_LEN), UDP_HEADER_LEN); + uint8_t *out_dhcpv6 = dp_packet_put(&pkt_out, dp_packet_pull(pkt_in, 4), 4); + + /* Set the proper DHCPv6 message type. */ + *out_dhcpv6 = out_dhcpv6_msg_type; + + /* Copy the Client Identifier. */ + dp_packet_put(&pkt_out, in_opt_client_id, + sizeof *in_opt_client_id + ntohs(in_opt_client_id->len)); + + /* Copy the DHCPv6 Options. */ + dp_packet_put(&pkt_out, out_dhcpv6_opts.data, out_dhcpv6_opts.size); + out_udp->udp_len = htons(new_l4_size); + out_udp->udp_csum = 0; + + struct ovs_16aligned_ip6_hdr *out_ip6 = dp_packet_l3(&pkt_out); + out_ip6->ip6_ctlun.ip6_un1.ip6_un1_plen = out_udp->udp_len; + + uint32_t csum; + csum = packet_csum_pseudoheader6(dp_packet_l3(&pkt_out)); + csum = csum_continue(csum, out_udp, dp_packet_size(&pkt_out) - + ((const unsigned char *)out_udp - + (const unsigned char *)dp_packet_l2(&pkt_out))); + out_udp->udp_csum = csum_finish(csum); + if (!out_udp->udp_csum) { + out_udp->udp_csum = htons(0xffff); + } + + pin->packet = dp_packet_data(&pkt_out); + pin->packet_len = dp_packet_size(&pkt_out); + ofpbuf_uninit(&out_dhcpv6_opts); + success = 1; +exit: + if (!ofperr) { + union mf_subvalue sv; + sv.u8_val = success; + mf_write_subfield(&dst, &sv, &pin->flow_metadata); + } + queue_msg(ofputil_encode_resume(pin, continuation, proto)); + dp_packet_uninit(pkt_out_ptr); +} + static void process_packet_in(const struct ofp_header *msg) { @@ -421,6 +706,11 @@ process_packet_in(const struct ofp_header *msg) false); break; + case ACTION_OPCODE_PUT_DHCPV6_OPTS: + pinctrl_handle_put_dhcpv6_opts(&packet, &pin, &userdata, + &continuation); + break; + default: VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32, ntohl(ah->opcode)); diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c index ec82ec3829a..f61afc0aba6 100644 --- a/ovn/lib/actions.c +++ b/ovn/lib/actions.c @@ -47,6 +47,8 @@ struct action_context { static bool parse_action(struct action_context *); static void parse_put_dhcp_opts_action(struct action_context *, const struct expr_field *dst); +static void parse_put_dhcpv6_opts_action(struct action_context *ctx, + const struct expr_field *dst); static bool action_error_handle_common(struct action_context *ctx) @@ -132,6 +134,12 @@ parse_set_action(struct action_context *ctx) lexer_get(ctx->lexer); /* Skip put_dhcp_opts. */ lexer_get(ctx->lexer); /* Skip '('. */ parse_put_dhcp_opts_action(ctx, &dst); + } else if (ctx->lexer->token.type == LEX_T_ID + && !strcmp(ctx->lexer->token.s, "put_dhcpv6_opts") + && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) { + lexer_get(ctx->lexer); /* Skip put_dhcpv6_opts. */ + lexer_get(ctx->lexer); /* Skip '('. */ + parse_put_dhcpv6_opts_action(ctx, &dst); } else { error = expr_parse_assignment( ctx->lexer, &dst, ctx->ap->symtab, ctx->ap->lookup_port, @@ -628,6 +636,112 @@ parse_put_dhcp_opts_action(struct action_context *ctx, finish_controller_op(ctx->ofpacts, oc_offset); } +static void +parse_dhcpv6_opt(struct action_context *ctx, struct ofpbuf *ofpacts) +{ + if (ctx->lexer->token.type != LEX_T_ID) { + action_syntax_error(ctx, NULL); + return; + } + + const struct dhcp_opts_map *dhcp_opt = dhcp_opts_find( + ctx->ap->dhcpv6_opts, ctx->lexer->token.s); + + if (!dhcp_opt) { + action_syntax_error(ctx, "expecting DHCPv6 option name"); + return; + } + + lexer_get(ctx->lexer); + if (!action_force_match(ctx, LEX_T_EQUALS)) { + return; + } + + struct expr_constant_set cs; + memset(&cs, 0, sizeof(struct expr_constant_set)); + char *error = expr_parse_constant_set(ctx->lexer, NULL, &cs); + if (error) { + action_error(ctx, "%s", error); + free(error); + return; + } + + if (!strcmp(dhcp_opt->type, "str")) { + if (cs.type != EXPR_C_STRING) { + action_error(ctx, "DHCPv6 option %s requires string value.", + dhcp_opt->name); + return; + } + } else { + if (cs.type != EXPR_C_INTEGER) { + action_error(ctx, "DHCPv6 option %s requires numeric value.", + dhcp_opt->name); + return; + } + } + + if (!lexer_match(ctx->lexer, LEX_T_COMMA) && ( + ctx->lexer->token.type != LEX_T_RPAREN)) { + action_syntax_error(ctx, NULL); + return; + } + + struct dhcp_opt6_header *opt = ofpbuf_put_uninit(ofpacts, sizeof *opt); + opt->code = dhcp_opt->code; + + if (!strcmp(dhcp_opt->type, "ipv6")) { + opt->len = cs.n_values * sizeof(struct in6_addr); + for (size_t i = 0; i < cs.n_values; i++) { + ofpbuf_put(ofpacts, &cs.values[i].value.ipv6, + sizeof(struct in6_addr)); + } + } else if (!strcmp(dhcp_opt->type, "mac")) { + opt->len = sizeof(struct eth_addr); + ofpbuf_put(ofpacts, &cs.values[0].value.mac, opt->len); + } else if (!strcmp(dhcp_opt->type, "str")) { + opt->len = strlen(cs.values[0].string); + ofpbuf_put(ofpacts, cs.values[0].string, opt->len); + } + + expr_constant_set_destroy(&cs); + return; +} + +/* Parses the "put_dhcpv6_opts" action. The result should be stored into 'dst'. + * + * The caller has already consumed "put_dhcpv6_opts(", so this just parses the + * rest. */ +static void +parse_put_dhcpv6_opts_action(struct action_context *ctx, + const struct expr_field *dst) +{ + /* Validate that the destination is a 1-bit, modifiable field. */ + struct mf_subfield sf; + struct expr *prereqs; + char *error = expr_expand_field(ctx->lexer, ctx->ap->symtab, + dst, 1, true, &sf, &prereqs); + if (error) { + action_error(ctx, "%s", error); + free(error); + return; + } + ctx->prereqs = expr_combine(EXPR_T_AND, ctx->prereqs, prereqs); + + /* controller. */ + size_t oc_offset = start_controller_op( + ctx->ofpacts, ACTION_OPCODE_PUT_DHCPV6_OPTS, true); + nx_put_header(ctx->ofpacts, sf.field->id, OFP13_VERSION, false); + ovs_be32 ofs = htonl(sf.ofs); + ofpbuf_put(ctx->ofpacts, &ofs, sizeof ofs); + while (!lexer_match(ctx->lexer, LEX_T_RPAREN)) { + parse_dhcpv6_opt(ctx, ctx->ofpacts); + if (ctx->error) { + return; + } + } + finish_controller_op(ctx->ofpacts, oc_offset); +} + static bool action_parse_port(struct action_context *ctx, uint16_t *port) { diff --git a/ovn/lib/ovn-dhcp.h b/ovn/lib/ovn-dhcp.h index 6750f951385..65a066be029 100644 --- a/ovn/lib/ovn-dhcp.h +++ b/ovn/lib/ovn-dhcp.h @@ -108,4 +108,75 @@ dhcp_opts_destroy(struct hmap *dhcp_opts) hmap_destroy(dhcp_opts); } +struct dhcp_opt6_header { + uint16_t code; + uint16_t len; +}; + +/* Supported DHCPv6 Message Types */ +#define DHCPV6_MSG_TYPE_SOLICIT 1 +#define DHCPV6_MSG_TYPE_ADVT 2 +#define DHCPV6_MSG_TYPE_REQUEST 3 +#define DHCPV6_MSG_TYPE_CONFIRM 4 +#define DHCPV6_MSG_TYPE_REPLY 7 +#define DHCPV6_MSG_TYPE_DECLINE 9 + + +/* DHCPv6 Option codes */ +#define DHCPV6_OPT_CLIENT_ID_CODE 1 +#define DHCPV6_OPT_SERVER_ID_CODE 2 +#define DHCPV6_OPT_IA_NA_CODE 3 +#define DHCPV6_OPT_IA_ADDR_CODE 5 +#define DHCPV6_OPT_DNS_SERVER_CODE 23 +#define DHCPV6_OPT_DOMAIN_SEARCH_CODE 24 + +#define DHCPV6_OPT_SERVER_ID \ + DHCP_OPTION("server_id", DHCPV6_OPT_SERVER_ID_CODE, "mac") + +#define DHCPV6_OPT_IA_ADDR \ + DHCP_OPTION("ia_addr", DHCPV6_OPT_IA_ADDR_CODE, "ipv6") + +#define DHCPV6_OPT_DNS_SERVER \ + DHCP_OPTION("dns_server", DHCPV6_OPT_DNS_SERVER_CODE, "ipv6") + +#define DHCPV6_OPT_DOMAIN_SEARCH \ + DHCP_OPTION("domain_search", DHCPV6_OPT_DOMAIN_SEARCH_CODE, "str") + +OVS_PACKED( +struct dhcpv6_opt_header { + ovs_be16 code; + ovs_be16 len; +}); + +OVS_PACKED( +struct dhcpv6_opt_server_id { + struct dhcpv6_opt_header opt; + ovs_be16 duid_type; + ovs_be16 hw_type; + struct eth_addr mac; +}); + + +OVS_PACKED( +struct dhcpv6_opt_ia_addr { + struct dhcpv6_opt_header opt; + struct in6_addr ipv6; + ovs_be32 t1; + ovs_be32 t2; +}); + +OVS_PACKED( +struct dhcpv6_opt_ia_na { + struct dhcpv6_opt_header opt; + ovs_be32 iaid; + ovs_be32 t1; + ovs_be32 t2; +}); + +#define DHCPV6_DUID_LL 3 +#define DHCPV6_HW_TYPE_ETH 1 + +#define DHCPV6_OPT_PAYLOAD(opt) \ + (void *)((char *)opt + sizeof(struct dhcpv6_opt_header)) + #endif /* OVN_DHCP_H */ diff --git a/ovn/ovn-sb.ovsschema b/ovn/ovn-sb.ovsschema index b1737f56042..8604b4e1b55 100644 --- a/ovn/ovn-sb.ovsschema +++ b/ovn/ovn-sb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Southbound", - "version": "1.7.0", - "cksum": "3677179333 6917", + "version": "1.8.0", + "cksum": "59582657 7376", "tables": { "SB_Global": { "columns": { @@ -141,4 +141,15 @@ "type": "string", "enum": ["set", ["bool", "uint8", "uint16", "uint32", "ipv4", "static_routes", "str"]]}}}}, + "isRoot": true}, + "DHCPv6_Options": { + "columns": { + "name": {"type": "string"}, + "code": { + "type": {"key": {"type": "integer", + "minInteger": 0, "maxInteger": 254}}}, + "type": { + "type": {"key": { + "type": "string", + "enum": ["set", ["ipv6", "str", "mac"]]}}}}, "isRoot": true}}} diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml index 2ea3c4eb0c3..1314e9e86ac 100644 --- a/ovn/ovn-sb.xml +++ b/ovn/ovn-sb.xml @@ -1290,6 +1290,49 @@
+R = put_dhcpv6_opts(D1 = V1, D2 = V2, ..., Dn = Vn);
+ + Parameters: one or more DHCPv6 option/value pairs. +
+ ++ Result: stored to a 1-bit subfield R. +
+ ++ Valid only in the ingress pipeline. +
+ ++ When this action is applied to a DHCPv6 request packet, it changes + the packet into a DHCPv6 reply, replaces the options by those + specified as parameters, and stores 1 in R. +
+ ++ When this action is applied to a non-DHCPv6 packet or an invalid + DHCPv6 request packet , it leaves the packet unchanged and stores + 0 in R. +
+ ++ The contents of the table control the + DHCPv6 option names and values that this action supports. +
+ +
+ Example:
+
+ reg0[3] = put_dhcpv6_opts(ia_addr = aef0::4, server_id = 00:00:00:00:10:02,
+ dns_server={ae70::1,ae70::2});
+
+
ct_lb;
ct_lb(
ip[:
port]...);