@@ -338,6 +338,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
@@ -390,17 +391,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 {
@@ -422,12 +431,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
@@ -439,6 +449,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. */
@@ -511,6 +522,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,
@@ -37,6 +37,7 @@
#include "ovn-controller.h"
#include "ovn/lib/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"
@@ -365,6 +366,295 @@ exit:
}
}
+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 is used to carry a DUID
+ * identifying a server between a client and a server.
+ * See RFC 3315 Sec 9 and Sec 22.3
+ *
+ * We will 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 = htonl(UINT32_MAX);
+ opt_ia_na->t2 = htonl(UINT32_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 = htonl(UINT32_MAX);
+ opt_ia_addr->t2 = htonl(UINT32_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)
+{
+ enum ofp_version version = rconn_get_version(swconn);
+ enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
+ struct dp_packet *pkt_out_ptr = NULL;
+ uint32_t success = 0;
+
+ /* Parse result field. */
+ const struct mf_field *f;
+ enum ofperr ofperr = nx_pull_header(userdata, &f, NULL);
+ if (ofperr) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "bad result OXM (%s)", ofperr_to_string(ofperr));
+ goto exit;
+ }
+
+ /* Parse result offset. */
+ ovs_be32 *ofsp = ofpbuf_try_pull(userdata, sizeof *ofsp);
+ if (!ofsp) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ 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) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "bad result bit (%s)", ofperr_to_string(ofperr));
+ goto exit;
+ }
+
+ if (!userdata->size) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "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;
+ uint8_t *end = (uint8_t *)in_udp + ntohs(in_udp->udp_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) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_WARN_RL(&rl, "DHCPv6 option - Client id not present in the "
+ " DHCPv6 packet");
+ goto exit;
+ }
+
+ if (!iaid) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ 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)) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ 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 the L2 and L3 headers from the pkt_in as they would remain same*/
+ dp_packet_put(
+ &pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), pkt_in->l4_ofs);
+
+ pkt_out.l2_5_ofs = pkt_in->l2_5_ofs;
+ pkt_out.l2_pad_size = pkt_in->l2_pad_size;
+ pkt_out.l3_ofs = pkt_in->l3_ofs;
+ pkt_out.l4_ofs = pkt_in->l4_ofs;
+
+ /* 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));
+ if (pkt_out_ptr) {
+ dp_packet_uninit(pkt_out_ptr);
+ }
+}
+
static void
process_packet_in(const struct ofp_header *msg)
{
@@ -410,6 +700,11 @@ process_packet_in(const struct ofp_header *msg)
pinctrl_handle_put_dhcp_opts(&packet, &pin, &userdata, &continuation);
break;
+ case ACTION_OPCODE_PUT_DHCPV6_OPTS:
+ pinctrl_handle_put_dhcpv6_opts(&packet, &pin, &userdata,
+ &continuation);
+ break;
+
case ACTION_OPCODE_NA:
pinctrl_handle_na(&headers, &pin.flow_metadata, &userdata);
break;
@@ -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,
@@ -627,6 +635,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)
{
@@ -78,6 +78,15 @@ enum action_opcode {
* The actions, in OpenFlow 1.3 format, follow the action_header.
*/
ACTION_OPCODE_NA,
+
+ /* "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. */
@@ -95,6 +104,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,
@@ -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 */
@@ -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}}}
@@ -1229,6 +1229,49 @@
</p>
</dd>
+ <dt>
+ <code><var>R</var> = put_dhcpv6_opts(<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>: one or more DHCPv6 option/value pairs.
+ </p>
+
+ <p>
+ <b>Result</b>: stored to a 1-bit subfield <var>R</var>.
+ </p>
+
+ <p>
+ Valid only in the ingress pipeline.
+ </p>
+
+ <p>
+ 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 <var>R</var>.
+ </p>
+
+ <p>
+ 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 <var>R</var>.
+ </p>
+
+ <p>
+ The contents of the <ref table="DHCPv6_Options"/> table control the
+ DHCPv6 option names and values that this action supports.
+ </p>
+
+ <p>
+ <b>Example:</b>
+ <code>
+ reg0[3] = put_dhcpv6_opts(ia_addr = aef0::4, server_id = 00:00:00:00:10:02,
+ dns_server={ae70::1,ae70::2}....);
+ </code>
+ </p>
+ </dd>
+
<dt><code>ct_lb;</code></dt>
<dt><code>ct_lb(</code><var>ip</var>[<code>:</code><var>port</var>]...<code>);</code></dt>
<dd>
@@ -2013,4 +2056,90 @@ tcp.flags = RST;
</dl>
</column>
</table>
+
+ <table name="DHCPv6_Options" title="DHCPv6 Options supported by native OVN DHCPv6">
+ <p>
+ Each row in this table stores the DHCPv6 Options supported by native OVN
+ DHCPv6. <code>ovn-northd</code> populates this table with the supported
+ DHCPv6 options. <code>ovn-controller</code> looks up this table to get the
+ DHCPv6 codes of the DHCPv6 options defined in the "put_dhcpv6_opts"
+ action. Please refer to the RFC 3315 and RFC 3646 for the possible list
+ of DHCPv6 options that can be defined here.
+ </p>
+
+ <column name="name">
+ <p>
+ Name of the DHCPv6 option.
+ </p>
+
+ <p>
+ Example. name="ia_addr"
+ </p>
+ </column>
+
+ <column name="code">
+ <p>
+ DHCPv6 option code for the DHCPv6 option as defined in the appropriate
+ RFC.
+ </p>
+
+ <p>
+ Example. code=3
+ </p>
+ </column>
+
+ <column name="type">
+ <p>
+ Data type of the DHCPv6 option code.
+ </p>
+
+ <dl>
+ <dt><code>value: ipv6</code></dt>
+ <dd>
+ <p>
+ This indicates that the value of the DHCPv6 option is an IPv6
+ address(es).
+ </p>
+
+ <p>
+ Example. "name=ia_addr", "code=5", "type=ipv6".
+ </p>
+
+ <p>
+ put_dhcpv6_opts(..., ia_addr = ae70::4,...)
+ </p>
+ </dd>
+
+ <dt><code>value: str</code></dt>
+ <dd>
+ <p>
+ This indicates that the value of the DHCPv6 option is a string.
+ </p>
+
+ <p>
+ Example. "name=domain_search", "code=24", "type=str".
+ </p>
+
+ <p>
+ put_dhcpv6_opts(..., domain_search = ovn.domain,...)
+ </p>
+ </dd>
+
+ <dt><code>value: mac</code></dt>
+ <dd>
+ <p>
+ This indicates that the value of the DHCPv6 option is a MAC address.
+ </p>
+
+ <p>
+ Example. "name=server_id", "code=2", "type=mac".
+ </p>
+
+ <p>
+ put_dhcpv6_opts(..., server_id = 01:02:03:04L05:06,...)
+ </p>
+ </dd>
+ </dl>
+ </column>
+ </table>
</database>
@@ -652,6 +652,17 @@ reg1[0] = put_dhcp_opts(offerip=1.2.3.4, xyzzy); => Syntax error at `xyzzy' expe
reg1[0] = put_dhcp_opts(offerip="xyzzy"); => DHCP option offerip requires numeric value.
reg1[0] = put_dhcp_opts(offerip=1.2.3.4, domain=1.2.3.4); => DHCP option domain requires string value.
+# put_dhcpv6_opts
+reg1[0] = put_dhcpv6_opts(ia_addr = ae70::4, server_id = 00:00:00:00:10:02); => actions=controller(userdata=00.00.00.04.00.00.00.00.80.01.00.08.00.00.00.00.05.00.10.00.ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.04.02.00.06.00.00.00.00.00.10.02,pause), prereqs=1
+reg1[0] = put_dhcpv6_opts(); => actions=controller(userdata=00.00.00.04.00.00.00.00.80.01.00.08.00.00.00.00,pause), prereqs=1
+reg1[0] = put_dhcpv6_opts(dns_server={ae70::1,ae70::2}); => actions=controller(userdata=00.00.00.04.00.00.00.00.80.01.00.08.00.00.00.00.17.00.20.00.ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.01.ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.02,pause), prereqs=1
+reg1[0] = put_dhcpv6_opts(domain_search="ovn.org"); => actions=controller(userdata=00.00.00.04.00.00.00.00.80.01.00.08.00.00.00.00.18.00.07.00.6f.76.6e.2e.6f.72.67,pause), prereqs=1
+reg1[0] = put_dhcpv6_opts(x = 1.2.3.4); => Syntax error at `x' expecting DHCPv6 option name.
+reg1[0] = put_dhcpv6_opts(ia_addr=ae70::4, "hi"); => Syntax error at `"hi"'.
+reg1[0] = put_dhcpv6_opts(ia_addr=ae70::4, xyzzy); => Syntax error at `xyzzy' expecting DHCPv6 option name.
+reg1[0] = put_dhcpv6_opts(ia_addr="ae70::4"); => DHCPv6 option ia_addr requires numeric value.
+reg1[0] = put_dhcpv6_opts(ia_addr=ae70::4, domain_search=ae70::1); => DHCPv6 option domain_search requires string value.
+
# na
na { eth.src = 12:34:56:78:9a:bc; nd.tll = 12:34:56:78:9a:bc; outport = inport; inport = ""; /* Allow sending out inport. */ output; }; => actions=controller(userdata=00.00.00.03.00.00.00.00.00.19.00.10.80.00.08.06.12.34.56.78.9a.bc.00.00.00.19.00.10.80.00.42.06.12.34.56.78.9a.bc.00.00.ff.ff.00.18.00.00.23.20.00.06.00.20.00.00.00.00.00.01.1c.04.00.01.1e.04.00.19.00.10.00.01.1c.04.00.00.00.00.00.00.00.00.00.19.00.10.00.00.00.02.00.00.00.00.00.00.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00), prereqs=nd
@@ -244,7 +244,7 @@ create_symtab(struct shash *symtab)
}
static void
-create_dhcp_opts(struct hmap *dhcp_opts)
+create_dhcp_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts)
{
hmap_init(dhcp_opts);
dhcp_opt_add(dhcp_opts, "offerip", 0, "ipv4");
@@ -269,6 +269,13 @@ create_dhcp_opts(struct hmap *dhcp_opts)
dhcp_opt_add(dhcp_opts, "tcp_ttl", 37, "uint8");
dhcp_opt_add(dhcp_opts, "mtu", 26, "uint16");
dhcp_opt_add(dhcp_opts, "lease_time", 51, "uint32");
+
+ /* DHCPv6 options. */
+ hmap_init(dhcpv6_opts);
+ dhcp_opt_add(dhcpv6_opts, "server_id", 2, "mac");
+ dhcp_opt_add(dhcpv6_opts, "ia_addr", 5, "ipv6");
+ dhcp_opt_add(dhcpv6_opts, "dns_server", 23, "ipv6");
+ dhcp_opt_add(dhcpv6_opts, "domain_search", 24, "str");
}
static void
@@ -1278,11 +1285,12 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
{
struct shash symtab;
struct hmap dhcp_opts;
+ struct hmap dhcpv6_opts;
struct simap ports, ct_zones;
struct ds input;
create_symtab(&symtab);
- create_dhcp_opts(&dhcp_opts);
+ create_dhcp_opts(&dhcp_opts, &dhcpv6_opts);
/* Initialize group ids. */
struct group_table group_table;
@@ -1308,6 +1316,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
struct action_params ap = {
.symtab = &symtab,
.dhcp_opts = &dhcp_opts,
+ .dhcpv6_opts = &dhcpv6_opts,
.lookup_port = lookup_port_cb,
.aux = &ports,
.ct_zones = &ct_zones,
@@ -1348,6 +1357,8 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
simap_destroy(&ct_zones);
expr_symtab_destroy(&symtab);
shash_destroy(&symtab);
+ dhcp_opts_destroy(&dhcp_opts);
+ dhcp_opts_destroy(&dhcpv6_opts);
}
static unsigned int
This patch adds a new OVN action 'put_dhcpv6_opts' to support native DHCPv6 in OVN. ovn-controller parses this action and adds a NXT_PACKET_IN2 OF flow with 'pause' flag set and the DHCPv6 options stored in 'userdata' field. When the valid DHCPv6 packet is received by ovn-controller, it frames a new DHCPv6 reply packet with the DHCPv6 options present in the 'userdata' field and resumes the packet and stores 1 in the 1-bit subfield. If the packet is invalid, it resumes the packet without any modifying and stores 0 in the 1-bit subfield. Eg. reg0[3] = put_dhcpv6_opts(ia_addr = aef0::4, server_id = 00:00:00:00:10:02, dns_server = {ae70::1,ae70::2}....) A new 'DHCPv6_Options' table is added in SB DB which stores the supported DHCPv6 options with DHCPv6 code and type. ovn-northd is expected to popule this table. Upcoming patch will add logical flows using this action. Signed-off-by: Numan Siddique <nusiddiq@redhat.com> --- ovn/controller/lflow.c | 16 ++- ovn/controller/pinctrl.c | 295 +++++++++++++++++++++++++++++++++++++++++++++++ ovn/lib/actions.c | 114 ++++++++++++++++++ ovn/lib/actions.h | 12 ++ ovn/lib/ovn-dhcp.h | 71 ++++++++++++ ovn/ovn-sb.ovsschema | 15 ++- ovn/ovn-sb.xml | 129 +++++++++++++++++++++ tests/ovn.at | 11 ++ tests/test-ovn.c | 15 ++- 9 files changed, 672 insertions(+), 6 deletions(-)