@@ -1943,7 +1943,12 @@ parse_gen_opt(struct action_context *ctx, struct ovnact_gen_option *o,
return;
}
- if (!strcmp(o->option->type, "str")) {
+ if (!strcmp(o->option->type, "host_id")) {
+ return;
+ }
+
+ if (!strcmp(o->option->type, "str") ||
+ !strcmp(o->option->type, "domains")) {
if (o->value.type != EXPR_C_STRING) {
lexer_error(ctx->lexer, "%s option %s requires string value.",
opts_type, o->option->name);
@@ -2270,6 +2275,110 @@ encode_put_dhcpv4_option(const struct ovnact_gen_option *o,
} else if (!strcmp(o->option->type, "str")) {
opt_header[1] = strlen(c->string);
ofpbuf_put(ofpacts, c->string, opt_header[1]);
+ } else if (!strcmp(o->option->type, "host_id")) {
+ if (o->value.type == EXPR_C_STRING) {
+ opt_header[1] = strlen(c->string);
+ ofpbuf_put(ofpacts, c->string, opt_header[1]);
+ } else {
+ opt_header[1] = sizeof(ovs_be32);
+ ofpbuf_put(ofpacts, &c->value.ipv4, sizeof(ovs_be32));
+ }
+ } else if (!strcmp(o->option->type, "domains")) {
+ /* Please refer to RFC 1035, section 4.1.4 for the format of encoding
+ * domain names. Below is an example for encoding a search list
+ * consisting of the "abc.com" and "xyz.abc.com".
+ *
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * |119|14 | 3 |'a'|'b'|'c'| 3 |'c'|'o'|'m'| 0 |'x'|'y'|'z'|xC0|x00|
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ *
+ * The encoding of "abc.com" ends with 0 to mark the end of the
+ * domain name as required by RFC 1035.
+ *
+ * The encoding of "xyz" (for "xyz.abc.com") ends with the two-octet
+ * compression pointer C000 (hex), which points to offset 0 where
+ * another validly encoded domain name can be found to complete
+ * the name ("abc.com").
+ */
+ typedef struct namemap_node {
+ struct hmap_node node;
+ uint16_t offset;
+ char *name;
+ } namemap_node;
+ struct hmap namemap;
+ hmap_init(&namemap);
+ namemap_node *ref;
+ /* Encoding adds 2 bytes (one for length and one for delimiter) for
+ * every domain name that is unique. If all the domain names are unique
+ * (which probably never happens in real world), then encoded string
+ * could be longer than the original string. Just to be on the safer
+ * side, allocate the (approx.) worst case length here.
+ */
+ uint8_t *dns_encoded = xzalloc(2 * strlen(c->string));
+ uint8_t encode_offset = 0;
+ char *domain_list = xstrdup(c->string), *dom_ptr = NULL;
+ for (char *domain = strtok_r(domain_list, ",", &dom_ptr);
+ domain != NULL;
+ domain = strtok_r(NULL, ",", &dom_ptr)) {
+ if (strlen(domain) > DOMAIN_NAME_MAX_LEN) {
+ VLOG_WARN("Domain names longer than 255 characters are not"
+ "supported");
+ goto out;
+ }
+ char *label_ptr = NULL, *label;
+ for (label = strtok_r(domain, ".", &label_ptr);
+ label != NULL;
+ label = strtok_r(NULL, ".", &label_ptr)) {
+ ref = NULL;
+ /* Check if we have already encoded this label and
+ * fill in the reference, if yes. */
+ uint32_t label_hash = hash_string(label, 0);
+ HMAP_FOR_EACH_IN_BUCKET (ref, node, label_hash, &namemap) {
+ if (!strcmp(label, ref->name)) {
+ uint16_t temp = (0xc0 | htons(ref->offset));
+ memcpy(dns_encoded + encode_offset, &temp,
+ sizeof(temp));
+ encode_offset += sizeof(temp);
+ break;
+ }
+ }
+ /* Break, since we have already filled the offset for this
+ * domain. */
+ if (ref != NULL) {
+ break;
+ } else {
+ /* The label was not encoded before, encode it now and add
+ * the offset to the namemap map. */
+ ref = xzalloc(sizeof *ref);
+ ref->name = xstrdup(label);
+ ref->offset = encode_offset;
+ hmap_insert(&namemap, &ref->node, label_hash);
+
+ uint8_t len = strlen(label);
+ memcpy(dns_encoded + encode_offset, &len, sizeof(uint8_t));
+ encode_offset += sizeof(uint8_t);
+ memcpy(dns_encoded + encode_offset, label, len);
+ encode_offset += len;
+ }
+ }
+ /* Add the end marker (0 byte) to determine the end of the domain.*/
+ if (label == NULL) {
+ uint8_t end = 0;
+ memcpy(dns_encoded + encode_offset, &end, sizeof(uint8_t));
+ encode_offset += sizeof(uint8_t);
+ }
+ }
+ opt_header[1] = encode_offset;
+ ofpbuf_put(ofpacts, dns_encoded, encode_offset);
+
+ out:
+ HMAP_FOR_EACH_POP (ref, node, &namemap) {
+ free(ref->name);
+ free(ref);
+ }
+ hmap_destroy(&namemap);
+ free(domain_list);
+ free(dns_encoded);
}
}
@@ -34,6 +34,7 @@ struct gen_opts_map {
size_t code;
};
+#define DOMAIN_NAME_MAX_LEN 255
#define DHCP_BROADCAST_FLAG 0x8000
#define DHCP_OPTION(NAME, CODE, TYPE) \
@@ -57,7 +58,7 @@ struct gen_opts_map {
#define DHCP_OPT_NIS_SERVER DHCP_OPTION("nis_server", 41, "ipv4")
#define DHCP_OPT_NTP_SERVER DHCP_OPTION("ntp_server", 42, "ipv4")
#define DHCP_OPT_SERVER_ID DHCP_OPTION("server_id", 54, "ipv4")
-#define DHCP_OPT_TFTP_SERVER DHCP_OPTION("tftp_server", 66, "ipv4")
+#define DHCP_OPT_TFTP_SERVER DHCP_OPTION("tftp_server", 66, "host_id")
#define DHCP_OPT_CLASSLESS_STATIC_ROUTE \
DHCP_OPTION("classless_static_route", 121, "static_routes")
@@ -81,6 +82,8 @@ struct gen_opts_map {
#define DHCP_OPT_PATH_PREFIX DHCP_OPTION("path_prefix", 210, "str")
#define DHCP_OPT_TFTP_SERVER_ADDRESS \
DHCP_OPTION("tftp_server_address", 150, "ipv4")
+#define DHCP_OPT_DOMAIN_SEARCH_LIST \
+ DHCP_OPTION("domain_search_list", 119, "domains")
#define DHCP_OPT_ARP_CACHE_TIMEOUT \
DHCP_OPTION("arp_cache_timeout", 35, "uint32")
@@ -11358,6 +11358,7 @@ static struct gen_opts_map supported_dhcp_opts[] = {
DHCP_OPT_DOMAIN_NAME,
DHCP_OPT_ARP_CACHE_TIMEOUT,
DHCP_OPT_TCP_KEEPALIVE_INTERVAL,
+ DHCP_OPT_DOMAIN_SEARCH_LIST,
};
static struct gen_opts_map supported_dhcpv6_opts[] = {
@@ -11382,7 +11383,12 @@ check_and_add_supported_dhcp_opts_to_sb_db(struct northd_context *ctx)
struct gen_opts_map *dhcp_opt =
dhcp_opts_find(&dhcp_opts_to_add, opt_row->name);
if (dhcp_opt) {
- hmap_remove(&dhcp_opts_to_add, &dhcp_opt->hmap_node);
+ if (!strcmp(dhcp_opt->type, opt_row->type) &&
+ dhcp_opt->code == opt_row->code) {
+ hmap_remove(&dhcp_opts_to_add, &dhcp_opt->hmap_node);
+ } else {
+ sbrec_dhcp_options_delete(opt_row);
+ }
} else {
sbrec_dhcp_options_delete(opt_row);
}
@@ -214,7 +214,8 @@
"type": {"key": {
"type": "string",
"enum": ["set", ["bool", "uint8", "uint16", "uint32",
- "ipv4", "static_routes", "str"]]}}}},
+ "ipv4", "static_routes", "str",
+ "host_id", "domains"]]}}}},
"isRoot": true},
"DHCPv6_Options": {
"columns": {
@@ -3208,6 +3208,30 @@ tcp.flags = RST;
Example. "name=host_name", "code=12", "type=str".
</p>
</dd>
+
+ <dt><code>value: host_id</code></dt>
+ <dd>
+ <p>
+ This indicates that the value of the DHCP option is a host_id.
+ It can either be a host_name or an IP address.
+ </p>
+
+ <p>
+ Example. "name=tftp_server", "code=66", "type=host_id".
+ </p>
+ </dd>
+
+ <dt><code>value: domains</code></dt>
+ <dd>
+ <p>
+ This indicates that the value of the DHCP option is a domain name
+ or a comma separated list of domain names.
+ </p>
+
+ <p>
+ Example. "name=domain_search_list", "code=119", "type=domains".
+ </p>
+ </dd>
</dl>
</column>
</table>
@@ -1195,6 +1195,15 @@ reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,m
reg0[15] = put_dhcp_opts(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,router_discovery=0,tftp_server_address={10.0.0.4,10.0.0.5},arp_cache_timeout=10,tcp_keepalive_interval=10);
formats as reg0[15] = put_dhcp_opts(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, router_discovery = 0, tftp_server_address = {10.0.0.4, 10.0.0.5}, arp_cache_timeout = 10, tcp_keepalive_interval = 10);
encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.96.08.0a.00.00.04.0a.00.00.05.23.04.00.00.00.0a.26.04.00.00.00.0a,pause)
+reg0[15] = put_dhcp_opts(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,router_discovery=0,tftp_server=10.0.0.10);
+ formats as reg0[15] = put_dhcp_opts(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, router_discovery = 0, tftp_server = 10.0.0.10);
+ encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.42.04.0a.00.00.0a,pause)
+reg0[15] = put_dhcp_opts(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,router_discovery=0,tftp_server="tftp_server_test");
+ formats as reg0[15] = put_dhcp_opts(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, router_discovery = 0, tftp_server = "tftp_server_test");
+ encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.6f.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.ff.00.1a.02.05.78.13.01.01.17.01.79.06.08.08.08.08.08.07.07.07.07.79.14.18.1e.00.00.0a.00.00.04.10.28.00.0a.00.00.06.00.0a.00.00.01.24.01.01.1f.01.00.42.10.74.66.74.70.5f.73.65.72.76.65.72.5f.74.65.73.74,pause)
+reg2[5] = put_dhcp_opts(offerip=10.0.0.4,router=10.0.0.1,netmask=255.255.254.0,mtu=1400,domain_name="ovn.org",wpad="https://example.org",bootfile_name="https://127.0.0.1/boot.ipxe",path_prefix="/tftpboot",domain_search_list="ovn.org,abc.ovn.org");
+ formats as reg2[5] = put_dhcp_opts(offerip = 10.0.0.4, router = 10.0.0.1, netmask = 255.255.254.0, mtu = 1400, domain_name = "ovn.org", wpad = "https://example.org", bootfile_name = "https://127.0.0.1/boot.ipxe", path_prefix = "/tftpboot", domain_search_list = "ovn.org,abc.ovn.org");
+ encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.25.0a.00.00.04.03.04.0a.00.00.01.01.04.ff.ff.fe.00.1a.02.05.78.0f.07.6f.76.6e.2e.6f.72.67.fc.13.68.74.74.70.73.3a.2f.2f.65.78.61.6d.70.6c.65.2e.6f.72.67.43.1b.68.74.74.70.73.3a.2f.2f.31.32.37.2e.30.2e.30.2e.31.2f.62.6f.6f.74.2e.69.70.78.65.d2.09.2f.74.66.74.70.62.6f.6f.74.77.0f.03.6f.76.6e.03.6f.72.67.00.03.61.62.63.c0.00,pause)
reg1[0..1] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1);
Cannot use 2-bit field reg1[0..1] where 1-bit field is required.
@@ -174,7 +174,7 @@ create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts,
dhcp_opt_add(dhcp_opts, "nis_server", 41, "ipv4");
dhcp_opt_add(dhcp_opts, "ntp_server", 42, "ipv4");
dhcp_opt_add(dhcp_opts, "server_id", 54, "ipv4");
- dhcp_opt_add(dhcp_opts, "tftp_server", 66, "ipv4");
+ dhcp_opt_add(dhcp_opts, "tftp_server", 66, "host_id");
dhcp_opt_add(dhcp_opts, "classless_static_route", 121, "static_routes");
dhcp_opt_add(dhcp_opts, "ip_forward_enable", 19, "bool");
dhcp_opt_add(dhcp_opts, "router_discovery", 31, "bool");
@@ -189,6 +189,7 @@ create_gen_opts(struct hmap *dhcp_opts, struct hmap *dhcpv6_opts,
dhcp_opt_add(dhcp_opts, "tftp_server_address", 150, "ipv4");
dhcp_opt_add(dhcp_opts, "arp_cache_timeout", 35, "uint32");
dhcp_opt_add(dhcp_opts, "tcp_keepalive_interval", 38, "uint32");
+ dhcp_opt_add(dhcp_opts, "domain_search_list", 119, "domains");
/* DHCPv6 options. */
hmap_init(dhcpv6_opts);