From 281977f72ede2f03b778896c58b1f1013ca6aee6 Mon Sep 17 00:00:00 2001 From: Numan Siddique Date: Wed, 27 Jul 2016 00:54:39 +0530 Subject: [PATCH] ovn-northd: Add logical flows to support native DHCPv4 OVN implements a native DHCPv4 support which caters to the common use case of providing an IP address to a booting instance by providing stateless replies to DHCPv4 requests based on statically configured address mappings. To do this it allows a short list of DHCPv4 options to be configured and applied at each compute host running ovn-controller. A new table 'DHCP_Options' is added in OVN NB DB to store the DHCP options. Logical ports refer to this table to configure the DHCPv4 options. For each logical port configured with DHCPv4 Options following flows are added - A logical flow which copies the DHCPv4 options to the DHCPv4 request packets using the 'put_dhcp_opts' action and advances the packet to the next stage. - A logical flow which implements the DHCP reponder by sending the DHCPv4 reply back to the inport once the 'put_dhcp_opts' action is applied. Signed-off-by: Numan Siddique Co-authored-by: Ben Pfaff Signed-off-by: Ben Pfaff Tested-by: Ramu Ramamurthy Acked-by: Ramu Ramamurthy --- ovn/northd/ovn-northd.8.xml | 91 ++++++++++- ovn/northd/ovn-northd.c | 245 ++++++++++++++++++++++++++++- ovn/ovn-nb.ovsschema | 20 ++- ovn/ovn-nb.xml | 232 ++++++++++++++++++++++++++++ ovn/utilities/ovn-nbctl.8.xml | 30 ++++ ovn/utilities/ovn-nbctl.c | 197 ++++++++++++++++++++++++ tests/ovn.at | 281 ++++++++++++++++++++++++++++++++++ 7 files changed, 1086 insertions(+), 10 deletions(-) diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml index ced2839d2..b95caef5d 100644 --- a/ovn/northd/ovn-northd.8.xml +++ b/ovn/northd/ovn-northd.8.xml @@ -457,7 +457,90 @@ output; -

Ingress Table 10: Destination Lookup

+

Ingress Table 10: DHCP option processing

+ +

+ This table adds the DHCPv4 options to a DHCPv4 packet from the + logical ports configured with IPv4 address(es) and DHCPv4 options. +

+ +
    +
  • +

    + A priority-100 logical flow is added for these logical ports + which matches the IPv4 packet with udp.src = 68 and + udp.dst = 67 and applies the action + put_dhcp_opts and advances the packet to the next table. +

    + +
    +reg0[3] = put_dhcp_opts(offer_ip = O, options...);
    +next;
    +        
    + +

    + For DHCPDISCOVER and DHCPREQUEST, this transforms the packet into a + DHCP reply, adds the DHCP offer IP O and options to the + packet, and stores 1 into reg0[3]. For other kinds of packets, it + just stores 0 into reg0[3]. Either way, it continues to the next + table. +

    + +
  • + +
  • + A priority-0 flow that matches all packets to advances to table 11. +
  • +
+ +

Ingress Table 11: DHCP responses

+ +

+ This table implements DHCP responder for the DHCP replies generated by + the previous table. +

+ +
    +
  • +

    + A priority 100 logical flow is added for the logical ports configured + with DHCPv4 options which matches IPv4 packets with udp.src == 68 + && udp.dst == 67 && reg0[3] == 1 and + responds back to the inport after applying these + actions. If reg0[3] is set to 1, it means that the + action put_dhcp_opts was successful. +

    + +
    +eth.dst = eth.src;
    +eth.src = E;
    +ip4.dst = O;
    +ip4.src = S;
    +udp.src = 67;
    +udp.dst = 68;
    +outport = P;
    +inport = ""; /* Allow sending out inport. */
    +output;
    +        
    + +

    + where E is the server MAC address and S is the + server IPv4 address defined in the DHCPv4 options and O is + the IPv4 address defined in the logical port's addresses column. +

    + +

    + (This terminates ingress packet processing; the packet does not go + to the next ingress table.) +

    +
  • + +
  • + A priority-0 flow that matches all packets to advances to table 12. +
  • +
+ +

Ingress Table 12: Destination Lookup

This table implements switching behavior. It contains these logical @@ -531,6 +614,12 @@ output; there are no rules added for load balancing new connections.

+

+ Also a priority 34000 logical flow is added for each logical port which + has DHCPv4 options defined to allow the DHCPv4 reply packet from the + Ingress Table 11: DHCP responses. +

+

Egress Table 6: Egress Port Security - IP

diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index 38a3d3083..aecc1d8f2 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -27,6 +27,7 @@ #include "openvswitch/hmap.h" #include "openvswitch/json.h" #include "ovn/lib/lex.h" +#include "ovn/lib/ovn-dhcp.h" #include "ovn/lib/ovn-nb-idl.h" #include "ovn/lib/ovn-sb-idl.h" #include "ovn/lib/ovn-util.h" @@ -99,7 +100,9 @@ enum ovn_stage { PIPELINE_STAGE(SWITCH, IN, LB, 7, "ls_in_lb") \ PIPELINE_STAGE(SWITCH, IN, STATEFUL, 8, "ls_in_stateful") \ PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 9, "ls_in_arp_rsp") \ - PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 10, "ls_in_l2_lkup") \ + PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 10, "ls_in_dhcp_options") \ + PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 11, "ls_in_dhcp_response") \ + PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 12, "ls_in_l2_lkup") \ \ /* Logical switch egress stages. */ \ PIPELINE_STAGE(SWITCH, OUT, PRE_LB, 0, "ls_out_pre_lb") \ @@ -140,6 +143,7 @@ enum ovn_stage { #define REGBIT_CONNTRACK_DEFRAG "reg0[0]" #define REGBIT_CONNTRACK_COMMIT "reg0[1]" #define REGBIT_CONNTRACK_NAT "reg0[2]" +#define REGBIT_DHCP_OPTS_RESULT "reg0[3]" /* Returns an "enum ovn_stage" built from the arguments. */ static enum ovn_stage @@ -1370,6 +1374,77 @@ lsp_is_up(const struct nbrec_logical_switch_port *lsp) return !lsp->up || *lsp->up; } +static bool +build_dhcpv4_action(struct ovn_port *op, ovs_be32 offer_ip, + struct ds *options_action, struct ds *response_action) +{ + if (!op->nbsp->dhcpv4_options) { + /* CMS has disabled native DHCPv4 for this lport. */ + return false; + } + + ovs_be32 host_ip, mask; + char *error = ip_parse_masked(op->nbsp->dhcpv4_options->cidr, &host_ip, + &mask); + if (error || ((offer_ip ^ host_ip) & mask)) { + /* Either + * - cidr defined is invalid or + * - the offer ip of the logical port doesn't belong to the cidr + * defined in the DHCPv4 options. + * */ + free(error); + return false; + } + + const char *server_ip = smap_get( + &op->nbsp->dhcpv4_options->options, "server_id"); + const char *server_mac = smap_get( + &op->nbsp->dhcpv4_options->options, "server_mac"); + const char *lease_time = smap_get( + &op->nbsp->dhcpv4_options->options, "lease_time"); + const char *router = smap_get( + &op->nbsp->dhcpv4_options->options, "router"); + + if (!(server_ip && server_mac && lease_time && router)) { + /* "server_id", "server_mac", "lease_time" and "router" should be + * present in the dhcp_options. */ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "Required DHCPv4 options not defined for lport - %s", + op->json_key); + return false; + } + + struct smap dhcpv4_options = SMAP_INITIALIZER(&dhcpv4_options); + smap_clone(&dhcpv4_options, &op->nbsp->dhcpv4_options->options); + + /* server_mac is not DHCPv4 option, delete it from the smap. */ + smap_remove(&dhcpv4_options, "server_mac"); + char *netmask = xasprintf(IP_FMT, IP_ARGS(mask)); + smap_add(&dhcpv4_options, "netmask", netmask); + free(netmask); + + ds_put_format(options_action, + REGBIT_DHCP_OPTS_RESULT" = put_dhcp_opts(offerip = " + IP_FMT", ", IP_ARGS(offer_ip)); + struct smap_node *node; + SMAP_FOR_EACH(node, &dhcpv4_options) { + ds_put_format(options_action, "%s = %s, ", node->key, node->value); + } + + ds_chomp(options_action, ' '); + ds_chomp(options_action, ','); + ds_put_cstr(options_action, "); next;"); + + ds_put_format(response_action, "eth.dst = eth.src; eth.src = %s; " + "ip4.dst = "IP_FMT"; ip4.src = %s; udp.src = 67; " + "udp.dst = 68; outport = inport; inport = \"\";" + " /* Allow sending out inport. */ output;", + server_mac, IP_ARGS(offer_ip), server_ip); + + smap_destroy(&dhcpv4_options); + return true; +} + static bool has_stateful_acl(struct ovn_datapath *od) { @@ -1774,6 +1849,35 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows) } } } + + /* Add 34000 priority flow to allow DHCP reply from ovn-controller to all + * logical ports of the datapath if the CMS has configured DHCPv4 options*/ + if (od->nbs && od->nbs->n_ports) { + for (size_t i = 0; i < od->nbs->n_ports; i++) { + if (od->nbs->ports[i]->dhcpv4_options) { + const char *server_id = smap_get( + &od->nbs->ports[i]->dhcpv4_options->options, "server_id"); + const char *server_mac = smap_get( + &od->nbs->ports[i]->dhcpv4_options->options, "server_mac"); + const char *lease_time = smap_get( + &od->nbs->ports[i]->dhcpv4_options->options, "lease_time"); + const char *router = smap_get( + &od->nbs->ports[i]->dhcpv4_options->options, "router"); + if (server_id && server_mac && lease_time && router) { + struct ds match = DS_EMPTY_INITIALIZER; + const char *actions = + has_stateful ? "ct_commit; next;" : "next;"; + ds_put_format(&match, "outport == \"%s\" && eth.src == %s " + "&& ip4.src == %s && udp && udp.src == 67 " + "&& udp.dst == 68", od->nbs->ports[i]->name, + server_mac, server_id); + ovn_lflow_add( + lflows, od, S_SWITCH_OUT_ACL, 34000, ds_cstr(&match), + actions); + } + } + } + } } static void @@ -1955,8 +2059,8 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_IP, 0, "1", "next;"); } - /* Ingress table 9: ARP responder, skip requests coming from localnet ports. - * (priority 100). */ + /* Ingress table 9: ARP/ND responder, skip requests coming from localnet + * ports. (priority 100). */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbsp) { continue; @@ -2052,7 +2156,69 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_ND_RSP, 0, "1", "next;"); } - /* Ingress table 10: Destination lookup, broadcast and multicast handling + /* Logical switch ingress table 10 and 11: DHCP options and response + * priority 100 flows. */ + HMAP_FOR_EACH (op, key_node, ports) { + if (!op->nbsp) { + continue; + } + + if (!lsp_is_enabled(op->nbsp) || !strcmp(op->nbsp->type, "router")) { + /* Don't add the DHCP flows if the port is not enabled or if the + * port is a router port. */ + continue; + } + + if (!op->nbsp->dhcpv4_options) { + /* CMS has disabled native DHCPv4 for this lport. */ + continue; + } + + for (size_t i = 0; i < op->n_lsp_addrs; i++) { + for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) { + struct ds options_action = DS_EMPTY_INITIALIZER; + struct ds response_action = DS_EMPTY_INITIALIZER; + if (build_dhcpv4_action( + op, op->lsp_addrs[i].ipv4_addrs[j].addr, + &options_action, &response_action)) { + struct ds match = DS_EMPTY_INITIALIZER; + ds_put_format( + &match, "inport == %s && eth.src == %s && " + "ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && " + "udp.src == 68 && udp.dst == 67", op->json_key, + op->lsp_addrs[i].ea_s); + + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_OPTIONS, + 100, ds_cstr(&match), + ds_cstr(&options_action)); + /* If REGBIT_DHCP_OPTS_RESULT is set, it means the + * put_dhcp_opts action is successful */ + ds_put_cstr(&match, " && "REGBIT_DHCP_OPTS_RESULT); + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_RESPONSE, + 100, ds_cstr(&match), + ds_cstr(&response_action)); + ds_destroy(&match); + ds_destroy(&options_action); + ds_destroy(&response_action); + break; + } + } + } + } + + /* Ingress table 10 and 11: DHCP options and response, by default goto next. + * (priority 0). */ + + HMAP_FOR_EACH (od, key_node, datapaths) { + if (!od->nbs) { + continue; + } + + ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_OPTIONS, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESPONSE, 0, "1", "next;"); + } + + /* Ingress table 12: Destination lookup, broadcast and multicast handling * (priority 100). */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbsp) { @@ -2072,7 +2238,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, "outport = \""MC_FLOOD"\"; output;"); } - /* Ingress table 10: Destination lookup, unicast handling (priority 50), */ + /* Ingress table 12: Destination lookup, unicast handling (priority 50), */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbsp) { continue; @@ -2105,7 +2271,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } - /* Ingress table 10: Destination lookup for unknown MACs (priority 0). */ + /* Ingress table 12: Destination lookup for unknown MACs (priority 0). */ HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbs) { continue; @@ -3092,6 +3258,66 @@ ovnsb_db_run(struct northd_context *ctx) } +static struct dhcp_opts_map supported_dhcp_opts[] = { + OFFERIP, + DHCP_OPT_NETMASK, + DHCP_OPT_ROUTER, + DHCP_OPT_DNS_SERVER, + DHCP_OPT_LOG_SERVER, + DHCP_OPT_LPR_SERVER, + DHCP_OPT_SWAP_SERVER, + DHCP_OPT_POLICY_FILTER, + DHCP_OPT_ROUTER_SOLICITATION, + DHCP_OPT_NIS_SERVER, + DHCP_OPT_NTP_SERVER, + DHCP_OPT_SERVER_ID, + DHCP_OPT_TFTP_SERVER, + DHCP_OPT_CLASSLESS_STATIC_ROUTE, + DHCP_OPT_MS_CLASSLESS_STATIC_ROUTE, + DHCP_OPT_IP_FORWARD_ENABLE, + DHCP_OPT_ROUTER_DISCOVERY, + DHCP_OPT_ETHERNET_ENCAP, + DHCP_OPT_DEFAULT_TTL, + DHCP_OPT_TCP_TTL, + DHCP_OPT_MTU, + DHCP_OPT_LEASE_TIME, + DHCP_OPT_T1, + DHCP_OPT_T2 +}; + +static void +check_and_add_supported_dhcp_opts_to_sb_db(struct northd_context *ctx) +{ + struct hmap dhcp_opts_to_add = HMAP_INITIALIZER(&dhcp_opts_to_add); + for (size_t i = 0; (i < sizeof(supported_dhcp_opts) / + sizeof(supported_dhcp_opts[0])); i++) { + hmap_insert(&dhcp_opts_to_add, &supported_dhcp_opts[i].hmap_node, + dhcp_opt_hash(supported_dhcp_opts[i].name)); + } + + const struct sbrec_dhcp_options *opt_row, *opt_row_next; + SBREC_DHCP_OPTIONS_FOR_EACH_SAFE(opt_row, opt_row_next, ctx->ovnsb_idl) { + struct dhcp_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); + } else { + sbrec_dhcp_options_delete(opt_row); + } + } + + struct dhcp_opts_map *opt; + HMAP_FOR_EACH (opt, hmap_node, &dhcp_opts_to_add) { + struct sbrec_dhcp_options *sbrec_dhcp_option = + sbrec_dhcp_options_insert(ctx->ovnsb_txn); + sbrec_dhcp_options_set_name(sbrec_dhcp_option, opt->name); + sbrec_dhcp_options_set_code(sbrec_dhcp_option, opt->code); + sbrec_dhcp_options_set_type(sbrec_dhcp_option, opt->type); + } + + hmap_destroy(&dhcp_opts_to_add); +} + static char *default_nb_db_; static const char * @@ -3260,6 +3486,10 @@ main(int argc, char *argv[]) add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_binding_col_options); add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_binding_col_mac); ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_port_binding_col_chassis); + ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_dhcp_options); + add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_code); + add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_type); + add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dhcp_options_col_name); ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_address_set); add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_name); @@ -3277,6 +3507,9 @@ main(int argc, char *argv[]) ovnnb_db_run(&ctx); ovnsb_db_run(&ctx); + if (ctx.ovnsb_txn) { + check_and_add_supported_dhcp_opts_to_sb_db(&ctx); + } unixctl_server_run(unixctl); unixctl_server_wait(unixctl); diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema index 460d5bdbf..3cf07c19c 100644 --- a/ovn/ovn-nb.ovsschema +++ b/ovn/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "5.0.0", - "cksum": "849073644 7576", + "version": "5.1.0", + "cksum": "2201958537 8295", "tables": { "Logical_Switch": { "columns": { @@ -48,6 +48,11 @@ "max": "unlimited"}}, "up": {"type": {"key": "boolean", "min": 0, "max": 1}}, "enabled": {"type": {"key": "boolean", "min": 0, "max": 1}}, + "dhcpv4_options": {"type": {"key": {"type": "uuid", + "refTable": "DHCP_Options", + "refType": "weak"}, + "min": 0, + "max": 1}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, @@ -149,6 +154,15 @@ "snat", "dnat_and_snat" ]]}}}}, - "isRoot": false} + "isRoot": false}, + "DHCP_Options": { + "columns": { + "cidr": {"type": "string"}, + "options": {"type": {"key": "string", "value": "string", + "min": 0, "max": "unlimited"}}, + "external_ids": { + "type": {"key": "string", "value": "string", + "min": 0, "max": "unlimited"}}}, + "isRoot": true} } } diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml index e571eeb6b..abd034045 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -509,6 +509,12 @@ + + This column defines the DHCPv4 Options to be included by the + ovn-controller when it replies to the DHCPv4 requests. + Please see the table. + + See External IDs at the beginning of this document. @@ -926,4 +932,230 @@ + +

+ OVN implements a native DHCPv4 support which caters to the common + use case of providing an IPv4 address to a booting instance by + providing stateless replies to DHCPv4 requests based on statically + configured address mappings. To do this it allows a short list of + DHCPv4 options to be configured and applied at each compute host + running ovn-controller. +

+ + +

+ The DHCPv4 options will be included if the logical port has the IPv4 + address in this . +

+
+ + +

+ CMS should define the set of DHCPv4 options as key/value pairs in the + column of this table. For + ovn-controller to include these DHCPv4 options, the + of + should refer to an entry in this table. +

+ + +

+ The following options must be defined. +

+ + + The IP address for the DHCP server to use. This should be in the + subnet of the offered IP. This is also included in the DHCP offer as + option 54, ``server identifier.'' + + + + The Ethernet address for the DHCP server to use. + + + +

+ The IP address of a gateway for the client to use. This should be + in the subnet of the offered IP. The DHCPv4 option code for this + option is 3. +

+
+ + +

+ The offered lease time in seconds, +

+ +

+ The DHCPv4 option code for this option is 51. +

+
+
+ + +

+ Below are the supported DHCPv4 options whose values are an IPv4 + address, e.g. 192.168.1.1. Some options accept multiple + IPv4 addresses enclosed within curly braces, e.g. {192.168.1.2, + 192.168.1.3}. Please refer to RFC 2132 for more details on + DHCPv4 options and their codes. +

+ + +

+ The DHCPv4 option code for this option is 1. +

+
+ + +

+ The DHCPv4 option code for this option is 6. +

+
+ + +

+ The DHCPv4 option code for this option is 7. +

+
+ + +

+ The DHCPv4 option code for this option is 9. +

+
+ + +

+ The DHCPv4 option code for this option is 16. +

+
+ + +

+ The DHCPv4 option code for this option is 21. +

+
+ + +

+ The DHCPv4 option code for this option is 32. +

+
+ + +

+ The DHCPv4 option code for this option is 41. +

+
+ + +

+ The DHCPv4 option code for this option is 42. +

+
+ + +

+ The DHCPv4 option code for this option is 66. +

+
+ + +

+ The DHCPv4 option code for this option is 121. +

+ +

+ 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. +

+ +

+ Example: {30.0.0.0/24,10.0.0.10, 0.0.0.0/0,10.0.0.1} +

+
+ + +

+ The DHCPv4 option code for this option is 249. This option is + similar to classless_static_route supported by + Microsoft Windows DHCPv4 clients. +

+
+
+ + +

+ These options accept a Boolean value, expressed as 0 for + false or 1 for true. +

+ + +

+ The DHCPv4 option code for this option is 19. +

+
+ + +

+ The DHCPv4 option code for this option is 31. +

+
+ + +

+ The DHCPv4 option code for this option is 36. +

+
+
+ + +

+ These options accept a nonnegative integer value. +

+ + + The DHCPv4 option code for this option is 23. + + + + The DHCPv4 option code for this option is 37. + + + + The DHCPv4 option code for this option is 26. + + + + This specifies the time interval from address assignment until the + client begins trying to renew its address. The DHCPv4 option code + for this option is 58. + + + + This specifies the time interval from address assignment until the + client begins trying to rebind its address. The DHCPv4 option code + for this option is 59. + +
+
+ + + + See External IDs at the beginning of this document. + + +
diff --git a/ovn/utilities/ovn-nbctl.8.xml b/ovn/utilities/ovn-nbctl.8.xml index 6330aa180..9d58bc50e 100644 --- a/ovn/utilities/ovn-nbctl.8.xml +++ b/ovn/utilities/ovn-nbctl.8.xml @@ -391,6 +391,36 @@ +

DHCP Options commands

+ +
+
dhcp-options-create cidr [key=value]
+
+ Creates a new DHCP Options entry in the DHCP_Options table + with the specified cidr and optional external-ids. +
+ +
dhcp-options-list
+
+ Lists the DHCP Options entries. +
+ +
dhcp-options-del dhcp-option
+
+ Deletes the DHCP Options entry referred by dhcp-option UUID. +
+ +
dhcp-options-set-options dhcp-option [key=value]...
+
+ Set the DHCP Options for the dhcp-option UUID. +
+ +
dhcp-options-get-options dhcp-option
+
+ Lists the DHCP Options for the dhcp-option UUID. +
+
+

Database Commands

These commands query and modify the contents of ovsdb tables. They are a slight abstraction of the ovsdb interface and diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c index d523eec83..d34a92ced 100644 --- a/ovn/utilities/ovn-nbctl.c +++ b/ovn/utilities/ovn-nbctl.c @@ -70,6 +70,8 @@ static void run_prerequisites(struct ctl_command[], size_t n_commands, struct ovsdb_idl *); static bool do_nbctl(const char *args, struct ctl_command *, size_t n, struct ovsdb_idl *); +static const struct nbrec_dhcp_options *dhcp_options_get( + struct ctl_context *ctx, const char *id, bool must_exist); int main(int argc, char *argv[]) @@ -334,6 +336,9 @@ Logical switch port commands:\n\ lsp-set-options PORT KEY=VALUE [KEY=VALUE]...\n\ set options related to the type of PORT\n\ lsp-get-options PORT get the type specific options for PORT\n\ + lsp-set-dhcpv4-options PORT [DHCP_OPTIONS_UUID]\n\ + set dhcpv4 options for PORT\n\ + lsp-get-dhcpv4-options PORT get the dhcpv4 options for PORT\n\ \n\ Logical router commands:\n\ lr-add [ROUTER] create a logical router named ROUTER\n\ @@ -358,6 +363,19 @@ Route commands:\n\ remove routes from ROUTER\n\ lr-route-list ROUTER print routes for ROUTER\n\ \n\ +\n\ +DHCP Options commands:\n\ + dhcp-options-create CIDR [EXTERNAL_IDS]\n\ + create a DHCP options row with CIDR\n\ + dhcp-options-del DHCP_OPTIONS_UUID\n\ + delete DHCP_OPTIONS_UUID\n\ + dhcp-options-list \n\ + lists the DHCP_Options rows\n\ + dhcp-options-set-options DHCP_OPTIONS_UUID KEY=VALUE [KEY=VALUE]...\n\ + set DHCP options to the DHCP_OPTIONS_UUID\n\ + dhcp-options-get-options DHCO_OPTIONS_UUID \n\ + displays the DHCP options of th DHCP_OPTIONS_UUID\n\ +\n\ %s\ \n\ Options:\n\ @@ -1027,6 +1045,45 @@ nbctl_lsp_get_options(struct ctl_context *ctx) } } +static void +nbctl_lsp_set_dhcpv4_options(struct ctl_context *ctx) +{ + const char *id = ctx->argv[1]; + const struct nbrec_logical_switch_port *lsp; + + lsp = lsp_by_name_or_uuid(ctx, id, true); + const struct nbrec_dhcp_options *dhcp_opt = NULL; + if (ctx->argc == 3 ) { + dhcp_opt = dhcp_options_get(ctx, ctx->argv[2], true); + } + + if (dhcp_opt) { + ovs_be32 ip; + unsigned int plen; + char *error = ip_parse_cidr(dhcp_opt->cidr, &ip, &plen); + if (error){ + free(error); + ctl_fatal("DHCP options cidr '%s' is not IPv4", dhcp_opt->cidr); + return; + } + } + nbrec_logical_switch_port_set_dhcpv4_options(lsp, dhcp_opt); +} + +static void +nbctl_lsp_get_dhcpv4_options(struct ctl_context *ctx) +{ + const char *id = ctx->argv[1]; + const struct nbrec_logical_switch_port *lsp; + + lsp = lsp_by_name_or_uuid(ctx, id, true); + if (lsp->dhcpv4_options) { + ds_put_format(&ctx->output, UUID_FMT " (%s)\n", + UUID_ARGS(&lsp->dhcpv4_options->header_.uuid), + lsp->dhcpv4_options->cidr); + } +} + enum { DIR_FROM_LPORT, DIR_TO_LPORT @@ -1283,6 +1340,126 @@ nbctl_lr_list(struct ctl_context *ctx) free(nodes); } +static const struct nbrec_dhcp_options * +dhcp_options_get(struct ctl_context *ctx, const char *id, bool must_exist) +{ + struct uuid dhcp_opts_uuid; + const struct nbrec_dhcp_options *dhcp_opts = NULL; + if (uuid_from_string(&dhcp_opts_uuid, id)) { + dhcp_opts = nbrec_dhcp_options_get_for_uuid(ctx->idl, &dhcp_opts_uuid); + } + + if (!dhcp_opts && must_exist) { + ctl_fatal("%s: dhcp options UUID not found", id); + } + return dhcp_opts; +} + +static void +nbctl_dhcp_options_create(struct ctl_context *ctx) +{ + /* Validate the cidr */ + ovs_be32 ip; + unsigned int plen; + char *error = ip_parse_cidr(ctx->argv[1], &ip, &plen); + if (error){ + /* check if its IPv6 cidr */ + free(error); + struct in6_addr ipv6; + error = ipv6_parse_cidr(ctx->argv[1], &ipv6, &plen); + if (error) { + free(error); + ctl_fatal("Invalid cidr format '%s'", ctx->argv[1]); + return; + } + } + + struct nbrec_dhcp_options *dhcp_opts = nbrec_dhcp_options_insert(ctx->txn); + nbrec_dhcp_options_set_cidr(dhcp_opts, ctx->argv[1]); + + struct smap ext_ids = SMAP_INITIALIZER(&ext_ids); + for (size_t i = 2; i < ctx->argc; i++) { + char *key, *value; + value = xstrdup(ctx->argv[i]); + key = strsep(&value, "="); + if (value) { + smap_add(&ext_ids, key, value); + } + free(key); + } + + nbrec_dhcp_options_set_external_ids(dhcp_opts, &ext_ids); + smap_destroy(&ext_ids); +} + +static void +nbctl_dhcp_options_set_options(struct ctl_context *ctx) +{ + const struct nbrec_dhcp_options *dhcp_opts = dhcp_options_get( + ctx, ctx->argv[1], true); + + struct smap dhcp_options = SMAP_INITIALIZER(&dhcp_options); + for (size_t 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_dhcp_options_set_options(dhcp_opts, &dhcp_options); + smap_destroy(&dhcp_options); +} + +static void +nbctl_dhcp_options_get_options(struct ctl_context *ctx) +{ + const struct nbrec_dhcp_options *dhcp_opts = dhcp_options_get( + ctx, ctx->argv[1], true); + + struct smap_node *node; + SMAP_FOR_EACH(node, &dhcp_opts->options) { + ds_put_format(&ctx->output, "%s=%s\n", node->key, node->value); + } +} + +static void +nbctl_dhcp_options_del(struct ctl_context *ctx) +{ + bool must_exist = !shash_find(&ctx->options, "--if-exists"); + const char *id = ctx->argv[1]; + const struct nbrec_dhcp_options *dhcp_opts; + + dhcp_opts = dhcp_options_get(ctx, id, must_exist); + if (!dhcp_opts) { + return; + } + + nbrec_dhcp_options_delete(dhcp_opts); +} + +static void +nbctl_dhcp_options_list(struct ctl_context *ctx) +{ + const struct nbrec_dhcp_options *dhcp_opts; + struct smap dhcp_options; + + smap_init(&dhcp_options); + NBREC_DHCP_OPTIONS_FOR_EACH(dhcp_opts, ctx->idl) { + smap_add_format(&dhcp_options, dhcp_opts->cidr, UUID_FMT, + UUID_ARGS(&dhcp_opts->header_.uuid)); + } + const struct smap_node **nodes = smap_sort(&dhcp_options); + for (size_t i = 0; i < smap_count(&dhcp_options); i++) { + const struct smap_node *node = nodes[i]; + ds_put_format(&ctx->output, "%s\n", node->value); + } + smap_destroy(&dhcp_options); + free(nodes); +} + /* The caller must free the returned string. */ static char * normalize_ipv4_prefix(ovs_be32 ipv4, unsigned int plen) @@ -1927,6 +2104,11 @@ static const struct ctl_table_class tables[] = { {{&nbrec_table_address_set, &nbrec_address_set_col_name, NULL}, {NULL, NULL, NULL}}}, + {&nbrec_table_dhcp_options, + {{&nbrec_table_dhcp_options, NULL, + NULL}, + {NULL, NULL, NULL}}}, + {NULL, {{NULL, NULL, NULL}, {NULL, NULL, NULL}}} }; @@ -2173,6 +2355,10 @@ static const struct ctl_command_syntax nbctl_commands[] = { nbctl_lsp_set_options, NULL, "", RW }, { "lsp-get-options", 1, 1, "PORT", NULL, nbctl_lsp_get_options, NULL, "", RO }, + { "lsp-set-dhcpv4-options", 1, 2, "PORT [DHCP_OPT_UUID]", NULL, + nbctl_lsp_set_dhcpv4_options, NULL, "", RW }, + { "lsp-get-dhcpv4-options", 1, 1, "PORT", NULL, + nbctl_lsp_get_dhcpv4_options, NULL, "", RO }, /* logical router commands. */ { "lr-add", 0, 1, "[ROUTER]", NULL, nbctl_lr_add, NULL, @@ -2199,6 +2385,17 @@ static const struct ctl_command_syntax nbctl_commands[] = { { "lr-route-list", 1, 1, "ROUTER", NULL, nbctl_lr_route_list, NULL, "", RO }, + /* DHCP_Options commands */ + {"dhcp-options-create", 1, INT_MAX, "CIDR [EXTERNAL:IDS]", NULL, + nbctl_dhcp_options_create, NULL, "", RW }, + {"dhcp-options-del", 1, 1, "DHCP_OPT_UUID", NULL, + nbctl_dhcp_options_del, NULL, "", RW}, + {"dhcp-options-list", 0, 0, "", NULL, nbctl_dhcp_options_list, NULL, "", RO}, + {"dhcp-options-set-options", 1, INT_MAX, "DHCP_OPT_UUID KEY=VALUE [KEY=VALUE]...", + NULL, nbctl_dhcp_options_set_options, NULL, "", RW }, + {"dhcp-options-get-options", 1, 1, "DHCP_OPT_UUID", NULL, + nbctl_dhcp_options_get_options, NULL, "", RO }, + {NULL, 0, 0, NULL, NULL, NULL, NULL, "", RO}, }; diff --git a/tests/ovn.at b/tests/ovn.at index 614a4bbf6..86efcf523 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -3067,6 +3067,287 @@ OVN_CLEANUP([hv1],[hv2]) AT_CLEANUP +AT_SETUP([ovn -- dhcpv4 : 1 HV, 2 LS, 2 LSPs/LS]) +AT_KEYWORDS([dhcpv4]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +ovn_start + +ovn-nbctl ls-add ls1 + +ovn-nbctl lsp-add ls1 ls1-lp1 \ +-- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4" + +ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4" + +ovn-nbctl lsp-add ls1 ls1-lp2 \ +-- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02 10.0.0.6 20.0.0.4" + +ovn-nbctl lsp-set-port-security ls1-lp2 "f0:00:00:00:00:02 10.0.0.6 20.0.0.4" + +ovn-nbctl ls-add ls2 +ovn-nbctl lsp-add ls2 ls2-lp1 \ +-- lsp-set-addresses ls2-lp1 "f0:00:00:00:00:03 30.0.0.6 40.0.0.4" +ovn-nbctl lsp-set-port-security ls2-lp1 "f0:00:00:00:00:03 30.0.0.6 40.0.0.4" +ovn-nbctl lsp-add ls2 ls2-lp2 \ +-- lsp-set-addresses ls2-lp2 "f0:00:00:00:00:04 30.0.0.7" +ovn-nbctl lsp-set-port-security ls2-lp2 "f0:00:00:00:00:04 30.0.0.7" + +ovn-nbctl -- --id=@d1 create DHCP_Options cidr=10.0.0.0/24 \ +options="\"server_id\"=\"10.0.0.1\" \"server_mac\"=\"ff:10:00:00:00:01\" \ +\"lease_time\"=\"3600\" \"router\"=\"10.0.0.1\"" \ +-- add Logical_Switch_Port ls1-lp1 dhcpv4_options @d1 \ +-- add Logical_Switch_Port ls1-lp2 dhcpv4_options @d1 + +ovn-nbctl -- --id=@d2 create DHCP_Options cidr=30.0.0.0/24 \ +options="\"server_id\"=\"30.0.0.1\" \"server_mac\"=\"ff:10:00:00:00:02\" \ +\"lease_time\"=\"3600\"" -- add Logical_Switch_Port ls2-lp2 dhcpv4_options @d2 + +net_add n1 +sim_add hv1 + +as hv1 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.1 +ovs-vsctl -- add-port br-int hv1-vif1 -- \ + set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \ + options:tx_pcap=hv1/vif1-tx.pcap \ + options:rxq_pcap=hv1/vif1-rx.pcap \ + ofport-request=1 + +ovs-vsctl -- add-port br-int hv1-vif2 -- \ + set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \ + options:tx_pcap=hv1/vif2-tx.pcap \ + options:rxq_pcap=hv1/vif2-rx.pcap \ + ofport-request=2 + +ovs-vsctl -- add-port br-int hv1-vif3 -- \ + set interface hv1-vif3 external-ids:iface-id=ls2-lp1 \ + options:tx_pcap=hv1/vif3-tx.pcap \ + options:rxq_pcap=hv1/vif3-rx.pcap \ + ofport-request=3 + +ovs-vsctl -- add-port br-int hv1-vif4 -- \ + set interface hv1-vif4 external-ids:iface-id=ls2-lp2 \ + options:tx_pcap=hv1/vif4-tx.pcap \ + options:rxq_pcap=hv1/vif4-rx.pcap \ + ofport-request=4 + +ovn_populate_arp + +sleep 2 + +as hv1 ovs-vsctl show + +trim_zeros() { + sed 's/\(00\)\{1,\}$//' +} + +# This shell function sends a DHCP request packet +# test_dhcp INPORT SRC_MAC DHCP_TYPE OFFER_IP ... +test_dhcp() { + local inport=$1 src_mac=$2 dhcp_type=$3 offer_ip=$4 + local request=ffffffffffff${src_mac}080045100110000000008011000000000000ffffffff + # udp header and dhcp header + request+=0044004300fc0000 + request+=010106006359aa760000000000000000000000000000000000000000${src_mac} + # client hardware padding + request+=00000000000000000000 + # server hostname + request+=0000000000000000000000000000000000000000000000000000000000000000 + request+=0000000000000000000000000000000000000000000000000000000000000000 + # boot file name + request+=0000000000000000000000000000000000000000000000000000000000000000 + request+=0000000000000000000000000000000000000000000000000000000000000000 + request+=0000000000000000000000000000000000000000000000000000000000000000 + request+=0000000000000000000000000000000000000000000000000000000000000000 + # dhcp magic cookie + request+=63825363 + # dhcp message type + request+=3501${dhcp_type}ff + + if test $offer_ip != 0; then + local srv_mac=$5 srv_ip=$6 expected_dhcp_opts=$7 + # total IP length will be the IP length of the request packet + # (which is 272 in our case) + 8 (padding bytes) + (expected_dhcp_opts / 2) + ip_len=`expr 280 + ${#expected_dhcp_opts} / 2` + udp_len=`expr $ip_len - 20` + printf -v ip_len "%x" $ip_len + printf -v udp_len "%x" $udp_len + # $ip_len var will be in 3 digits i.e 134. So adding a '0' before $ip_len + local reply=${src_mac}${srv_mac}080045100${ip_len}000000008011XXXX${srv_ip}${offer_ip} + # udp header and dhcp header. + # $udp_len var will be in 3 digits. So adding a '0' before $udp_len + reply+=004300440${udp_len}0000020106006359aa760000000000000000 + # your ip address + reply+=${offer_ip} + # next server ip address, relay agent ip address, client mac address + reply+=0000000000000000${src_mac} + # client hardware padding + reply+=00000000000000000000 + # server hostname + reply+=0000000000000000000000000000000000000000000000000000000000000000 + reply+=0000000000000000000000000000000000000000000000000000000000000000 + # boot file name + reply+=0000000000000000000000000000000000000000000000000000000000000000 + reply+=0000000000000000000000000000000000000000000000000000000000000000 + reply+=0000000000000000000000000000000000000000000000000000000000000000 + reply+=0000000000000000000000000000000000000000000000000000000000000000 + # dhcp magic cookie + reply+=63825363 + # dhcp message type + local dhcp_reply_type=02 + if test $dhcp_type = 03; then + dhcp_reply_type=05 + fi + reply+=3501${dhcp_reply_type}${expected_dhcp_opts}00000000ff00000000 + echo $reply >> $inport.expected + else + shift; shift; shift; shift; + for outport; do + echo $request | trim_zeros >> $outport.expected + done + fi + as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $request +} + +reset_pcap_file() { + local iface=$1 + local pcap_file=$2 + ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ +options:rxq_pcap=dummy-rx.pcap + rm -f ${pcap_file}*.pcap + ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ +options:rxq_pcap=${pcap_file}-rx.pcap +} + +ip_to_hex() { + printf "%02x%02x%02x%02x" "$@" +} + +AT_CAPTURE_FILE([ofctl_monitor0.log]) +as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \ +--pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log + +echo "---------NB dump-----" +ovn-nbctl show +echo "---------------------" +echo "---------SB dump-----" +ovn-sbctl list datapath_binding +echo "---------------------" +ovn-sbctl list logical_flow +echo "---------------------" + +echo "---------------------" +ovn-sbctl dump-flows +echo "---------------------" + +echo "------ hv1 dump ----------" +as hv1 ovs-ofctl dump-flows br-int + +# Send DHCPDISCOVER. +offer_ip=`ip_to_hex 10 0 0 4` +server_ip=`ip_to_hex 10 0 0 1` +expected_dhcp_opts=0104ffffff0003040a00000136040a000001330400000e10 +test_dhcp 1 f00000000001 01 $offer_ip ff1000000001 $server_ip $expected_dhcp_opts + +# NXT_RESUMEs should be 1. +OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets +cat 1.expected | cut -c -48 > expout +AT_CHECK([cat 1.packets | cut -c -48], [0], [expout]) +# Skipping the IPv4 checksum. +cat 1.expected | cut -c 53- > expout +AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout]) + +# ovs-ofctl also resumes the packets and this causes other ports to receive +# the DHCP request packet. So reset the pcap files so that its easier to test. +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +rm -f 1.expected +rm -f 2.expected + +# Send DHCPREQUEST. +offer_ip=`ip_to_hex 10 0 0 6` +server_ip=`ip_to_hex 10 0 0 1` +expected_dhcp_opts=0104ffffff0003040a00000136040a000001330400000e10 +test_dhcp 2 f00000000002 03 $offer_ip ff1000000001 $server_ip $expected_dhcp_opts + +# NXT_RESUMEs should be 2. +OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets +cat 2.expected | cut -c -48 > expout +AT_CHECK([cat 2.packets | cut -c -48], [0], [expout]) +# Skipping the IPv4 checksum. +cat 2.expected | cut -c 53- > expout +AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout]) + +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +rm -f 1.expected +rm -f 2.expected + +# Send Invalid DHCPv4 packet on ls1-lp2. It should be received by ovn-controller +# but should be resumed without the reply. +# ls1-lp1 (vif1-tx.pcap) should receive the DHCPv4 request packet twice, +# one from ovn-controller and the other from "ovs-ofctl resume." +offer_ip=0 +test_dhcp 2 f00000000002 08 $offer_ip 1 1 + +# NXT_RESUMEs should be 3. +OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +# vif1-tx.pcap should have received the DHCPv4 (invalid) request packet +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | trim_zeros > 1.packets +cat 1.expected > expout +AT_CHECK([cat 1.packets], [0], [expout]) + +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +rm -f 1.expected +rm -f 2.expected + +# Send DHCPv4 packet on ls2-lp1. It doesn't have any DHCPv4 options defined. +# ls2-lp2 (vif4-tx.pcap) should receive the DHCPv4 request packet once. + +test_dhcp 3 f00000000003 01 0 4 + +# Send DHCPv4 packet on ls2-lp2. "router" DHCPv4 option is not defined for +# this lport. +test_dhcp 4 f00000000004 01 0 3 + +# NXT_RESUMEs should be 3. +OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap | trim_zeros > 3.packets +cat 3.expected > expout +AT_CHECK([cat 3.packets], [0], [expout]) + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif4-tx.pcap | trim_zeros > 4.packets +cat 4.expected > expout +AT_CHECK([cat 4.packets], [0], [expout]) + +as hv1 +OVS_APP_EXIT_AND_WAIT([ovn-controller]) +OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +as ovn-sb +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +as ovn-nb +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +as northd +OVS_APP_EXIT_AND_WAIT([ovn-northd]) + +as main +OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +AT_CLEANUP + AT_SETUP([ovn -- 2 HVs, 2 LRs connected via LS, gateway router]) AT_KEYWORDS([ovngatewayrouter]) AT_SKIP_IF([test $HAVE_PYTHON = no]) -- 2.20.1