Numan, I tested this patchset along with the corresponding WIP ml2 native-dhcp openstack patch. It looks good to me,
On Tue, May 24, 2016 at 11:04 AM, Numan Siddique <nusid...@redhat.com> wrote: > OVN implements a native DHCP support which caters to the common > use case of providing an IP address to a booting instance by > providing stateless replies to DHCP requests based on statically > configured address mappings. To do this it allows a short list of > DHCP options to be configured and applied at each compute host > running ovn-controller. > > A new table 'Subnet' is added in OVN NB DB to store the DHCP options. > > For each logical port following flows are added if the CMS has defined > DHCP options in the 'Subnet' column > > - A logical flow which copies the DHCP options to the DHCP > 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 DHCP reply back to the inport once the 'put_dhcp_opts' action > is applied. > > Signed-Off-by: Numan Siddique <nusid...@redhat.com> > --- > ovn/northd/ovn-northd.8.xml | 89 +++++++++++- > ovn/northd/ovn-northd.c | 265 ++++++++++++++++++++++++++++++++++- > ovn/ovn-nb.ovsschema | 19 ++- > ovn/ovn-nb.xml | 314 > +++++++++++++++++++++++++++++++++++++++++- > ovn/utilities/ovn-nbctl.8.xml | 29 ++++ > ovn/utilities/ovn-nbctl.c | 196 ++++++++++++++++++++++++++ > tests/ovn.at | 250 +++++++++++++++++++++++++++++++++ > tests/test-ovn-dhcp.c | 135 ++++++++++++++++++ > 8 files changed, 1287 insertions(+), 10 deletions(-) > > diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml > index 970c352..9a4a46a 100644 > --- a/ovn/northd/ovn-northd.8.xml > +++ b/ovn/northd/ovn-northd.8.xml > @@ -326,7 +326,88 @@ output; > </li> > </ul> > > - <h3>Ingress Table 6: Destination Lookup</h3> > + <h3>Ingress Table 6: DHCP pause</h3> > + > + <p> > + This table adds the DHCP options to a DHCP packet from the > + logical ports configured with IPv4 address(es) and DHCP options. > + </p> > + > + <ul> > + <li> > + <p> > + A priority-100 logical flow is added for these logical ports > + which matches the packet with <code>udp.src</code> = 68 and > + <code>udp.dst</code> = 67 and applies the action > + <code>put_dhcp_opts</code> and advances the packet to the table 7. > + </p> > + > + <pre> > +put_dhcp_opts(<var>R</var>, offer_ip = <var>O</var>, DHCP_OPTIONS); > +next; > + </pre> > + > + <p> > + The action <code>put_dhcp_opts</code> adds the DHCP options to > + the packet and sets the <code>yiaddr</code> to the offer ip > + defined in <var>O</var> only if the packet is a > + DHCPDISCOVER/DHCPREQUEST and resumes the pipeline by setting > + <code>0x1</code> to the OVS register <var>R</var>. > + If the packet is non-DHCP packet or invalid DHCP packet > + it resumes the pipeline without any modifications. > + </p> > + > + </li> > + > + <li> > + A priority-0 flow that matches all packets to advances to table 7. > + </li> > + </ul> > + > + <h3>Ingress Table 7: DHCP resume</h3> > + > + <p> > + This table implements DHCP responder for the resumed DHCP packets > + from the previous table. > + </p> > + > + <ul> > + <li> > + <p> > + A priority 100 logical flow is added for the logical ports > configured > + with DHCP options which matches the packet with > + <code>udp.src</code> = 68, <code>udp.dst</code> = 67 and > + <code>reg0</code> = 0x1 and responds back to the > + <code>inport</code> after applying these actions. > + If <code>reg0</code> is set to 1, it means that the action > + <code>put_dhcp_opts</code> was successful. > + </p> > + > + <pre> > +eth.dst = eth.src; > +eth.src = <var>E</var>; > +ip4.dst = <var>O</var>; > +ip4.src = <var>S</var>; > +udp.src = 67; > +udp.dst = 68; > +outport = <var>P</var>; > +inport = ""; /* Allow sending out inport. */ > +output; > + </pre> > + > + <p> > + Where <var>E</var> is the server mac address and <var>S</var> is > the > + server IPv4 address defined in the DHCP options and <var>O</var> is > + the IPv4 address defined in the logical port's addresses column. > + </p> > + </li> > + > + <li> > + A priority-0 flow that matches all packets to advances to table 8. > + </li> > + </ul> > + > + <h3>Ingress Table 8: Destination Lookup</h3> > > <p> > This table implements switching behavior. It contains these logical > @@ -370,6 +451,12 @@ output; > This is similar to ingress table 4 except for <code>to-lport</code> > ACLs. > </p> > > + <p> > + Also a priority 34000 logical flow is added for each subnet of the > logical > + switch which has DHCP options defined to allow the DHCP reply packet > + from the <code>Ingress Table 7: DHCP resume</code>. > + </p> > + > <h3>Egress Table 2: Egress Port Security - IP</h3> > > <p> > diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c > index 44e9430..ff16622 100644 > --- a/ovn/northd/ovn-northd.c > +++ b/ovn/northd/ovn-northd.c > @@ -26,6 +26,7 @@ > #include "hash.h" > #include "hmap.h" > #include "json.h" > +#include "ovn/lib/ovn-dhcp.h" > #include "ovn/lib/lex.h" > #include "ovn/lib/ovn-nb-idl.h" > #include "ovn/lib/ovn-sb-idl.h" > @@ -33,6 +34,7 @@ > #include "packets.h" > #include "poll-loop.h" > #include "smap.h" > +#include "sset.h" > #include "stream.h" > #include "stream-ssl.h" > #include "unixctl.h" > @@ -94,7 +96,9 @@ enum ovn_stage { > PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 3, "ls_in_pre_acl") \ > PIPELINE_STAGE(SWITCH, IN, ACL, 4, "ls_in_acl") \ > PIPELINE_STAGE(SWITCH, IN, ARP_RSP, 5, "ls_in_arp_rsp") \ > - PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 6, "ls_in_l2_lkup") \ > + PIPELINE_STAGE(SWITCH, IN, DHCP_PAUSE, 6, "ls_in_dhcp_pause") \ > + PIPELINE_STAGE(SWITCH, IN, DHCP_RESUME, 7, "ls_in_dhcp_resume") > \ > + PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 8, "ls_in_l2_lkup") \ > \ > /* Logical switch egress stages. */ \ > PIPELINE_STAGE(SWITCH, OUT, PRE_ACL, 0, "ls_out_pre_acl") \ > @@ -1265,6 +1269,82 @@ has_stateful_acl(struct ovn_datapath *od) > return false; > } > > +static bool > +build_dhcp_action(struct ovn_port *op, ovs_be32 offer_ip, > + struct ds *pause_action, struct ds *resume_action) > +{ > + if(smap_get_bool(&op->nbs->options, "dhcp_disabled", false)) { > + /* CMS has disabled native dhcp for this lport */ > + return false; > + } > + > + struct nbrec_subnet *subnet = NULL; > + ovs_be32 host_ip, mask; > + for (size_t i = 0; i < op->od->nbs->n_subnets; i++) { > + char *error = ip_parse_masked(op->od->nbs->subnets[i]->cidr, > &host_ip, > + &mask); > + if (!error && !((offer_ip ^ host_ip) & mask)) { > + /* offerip belongs to this subnet */ > + subnet = op->od->nbs->subnets[i]; > + break; > + } > + free(error); > + } > + > + if (!(subnet && subnet->gateway_ip && subnet->enable_dhcp > + && subnet->ip_version == 4)) { > + return false; > + } > + > + > + const char *server_ip = smap_get(&subnet->dhcp_options, "server_id"); > + const char *server_mac = smap_get(&subnet->dhcp_options, "server_mac"); > + const char *lease_time = smap_get(&subnet->dhcp_options, "lease_time"); > + > + if (!(server_ip && server_mac && lease_time)) { > + /* "server_id", "server_mac" and "lease_time" should be present > + * in the dhcp_options. */ > + return false; > + } > + > + struct smap dhcp_options = SMAP_INITIALIZER(&dhcp_options); > + smap_clone(&dhcp_options, &subnet->dhcp_options); > + > + /* server_mac is not dhcp option, delete it from the smap */ > + smap_remove(&dhcp_options, "server_mac"); > + smap_add(&dhcp_options, "router", subnet->gateway_ip); > + char *netmask = xasprintf(IP_FMT, IP_ARGS(mask)); > + smap_add(&dhcp_options, "netmask", netmask); > + free(netmask); > + > + struct smap_node *node; > + /* override the dhcp options define in the lport options if any */ > + SMAP_FOR_EACH(node, &op->nbs->options) { > + if(!strncmp(node->key, "dhcp_opt_", 9)) { > + smap_replace(&dhcp_options, &node->key[9], node->value); > + } > + } > + > + ds_put_format(pause_action, "put_dhcp_opts(reg0, offerip = "IP_FMT", ", > + IP_ARGS(offer_ip)); > + SMAP_FOR_EACH(node, &dhcp_options) { > + ds_put_format(pause_action, "%s = %s, ", node->key, node->value); > + } > + > + ds_chomp(pause_action, ' '); > + ds_chomp(pause_action, ','); > + ds_put_cstr(pause_action, "); next;"); > + > + ds_put_format(resume_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(&dhcp_options); > + return true; > +} > + > static void > build_acls(struct ovn_datapath *od, struct hmap *lflows, struct hmap *ports) > { > @@ -1415,6 +1495,36 @@ build_acls(struct ovn_datapath *od, struct hmap > *lflows, struct hmap *ports) > acl->match, "drop;"); > } > } > + > + /* Add 34000 priority flow to allow DHCP reply from ovn-controller to all > + * logical ports of the datapath if the CMS has configured DHCP options*/ > + if (od->nbs && od->nbs->n_ports && od->nbs->n_subnets) { > + for (size_t i = 0; i < od->nbs->n_subnets; i++) { > + if (!(od->nbs->subnets[i]->gateway_ip && > + od->nbs->subnets[i]->enable_dhcp && > + od->nbs->subnets[i]->ip_version == 4)) { > + continue; > + } > + > + const char *server_id = smap_get( > + &od->nbs->subnets[i]->dhcp_options, "server_id"); > + const char *server_mac = smap_get( > + &od->nbs->subnets[i]->dhcp_options, "server_mac"); > + const char *lease_time = smap_get( > + &od->nbs->subnets[i]->dhcp_options, "lease_time"); > + if (server_id && server_mac && lease_time) { > + struct ds match = DS_EMPTY_INITIALIZER; > + const char *actions = has_stateful ? "ct_commit; next;" : > + "next;"; > + ds_put_format(&match, "eth.src == %s && ip4.src == %s &&" > + " udp && udp.src == 67 && udp.dst == 68", > + server_mac, server_id); > + ovn_lflow_add( > + lflows, od, S_SWITCH_OUT_ACL, 34000, ds_cstr(&match), > + actions); > + } > + } > + } > } > > static void > @@ -1497,7 +1607,7 @@ 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 3: ARP responder, skip requests coming from localnet > ports. > + /* Ingress table 5: ARP responder, skip requests coming from localnet > ports. > * (priority 100). */ > HMAP_FOR_EACH (op, key_node, ports) { > if (!op->nbs) { > @@ -1572,7 +1682,73 @@ build_lswitch_flows(struct hmap *datapaths, struct > hmap *ports, > ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_RSP, 0, "1", "next;"); > } > > - /* Ingress table 6: Destination lookup, broadcast and multicast handling > + /* Logical switch ingress table 6 and 7: DHCP pause and resume > + * priority 100 flows. */ > + HMAP_FOR_EACH (op, key_node, ports) { > + if (!op->nbs) { > + continue; > + } > + > + if (!lport_is_enabled(op->nbs) || !strcmp(op->nbs->type, "router")) { > + /* Don't add the DHCP flows if the port is not enabled or if the > + * port is a router port */ > + continue; > + } > + > + for (size_t i = 0; i < op->nbs->n_addresses; i++) { > + struct lport_addresses laddrs; > + if (!extract_lport_addresses(op->nbs->addresses[i], &laddrs, > + false)) { > + continue; > + } > + > + if (!laddrs.n_ipv4_addrs) { > + continue; > + } > + > + for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) { > + struct ds pause_action = DS_EMPTY_INITIALIZER; > + struct ds resume_action = DS_EMPTY_INITIALIZER; > + if (build_dhcp_action(op, laddrs.ipv4_addrs[j].addr, > + &pause_action, &resume_action)) { > + struct ds match = DS_EMPTY_INITIALIZER; > + ds_put_format( > + &match, "inport == %s && eth.src == "ETH_ADDR_FMT > + " && ip4.src == 0.0.0.0 && " > + "ip4.dst == 255.255.255.255 && udp.src == 68 && " > + "udp.dst == 67", op->json_key, > + ETH_ADDR_ARGS(laddrs.ea)); > + > + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_PAUSE, > 100, > + ds_cstr(&match), ds_cstr(&pause_action)); > + /* If reg0 is set to 1, it means the put_dhcp_opts action > + * is successful */ > + ds_put_cstr(&match, " && reg0 == 1"); > + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_DHCP_RESUME, > 100, > + ds_cstr(&match), ds_cstr(&resume_action)); > + ds_destroy(&match); > + ds_destroy(&pause_action); > + ds_destroy(&resume_action); > + break; > + } > + } > + free(laddrs.ipv4_addrs); > + } > + } > + > + /* Ingress table 6 and 7: DHCP pause and resume, 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_PAUSE, 0, "1", "next;"); > + ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESUME, 0, "1", "next;"); > + } > + > + /* Ingress table 8: Destination lookup, broadcast and multicast handling > * (priority 100). */ > HMAP_FOR_EACH (op, key_node, ports) { > if (!op->nbs) { > @@ -1592,7 +1768,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap > *ports, > "outport = \""MC_FLOOD"\"; output;"); > } > > - /* Ingress table 6: Destination lookup, unicast handling (priority 50), > */ > + /* Ingress table 8: Destination lookup, unicast handling (priority 50), > */ > HMAP_FOR_EACH (op, key_node, ports) { > if (!op->nbs) { > continue; > @@ -1629,7 +1805,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap > *ports, > } > } > > - /* Ingress table 6: Destination lookup for unknown MACs (priority 0). */ > + /* Ingress table 8: Destination lookup for unknown MACs (priority 0). */ > HMAP_FOR_EACH (od, key_node, datapaths) { > if (!od->nbs) { > continue; > @@ -2283,6 +2459,77 @@ 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) > +{ > + static bool nothing_to_add = false; > + > + if (nothing_to_add) { > + return; > + } > + > + 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); > + } > + } > + > + if (!dhcp_opts_to_add.n) { > + nothing_to_add = true; > + } > + > + struct dhcp_opts_map *opt; > + HMAP_FOR_EACH_POP(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 * > @@ -2451,6 +2698,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); > > /* Main loop. */ > exiting = false; > @@ -2464,7 +2715,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); > if (exiting) { > diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema > index 8163f6a..5edd032 100644 > --- a/ovn/ovn-nb.ovsschema > +++ b/ovn/ovn-nb.ovsschema > @@ -1,7 +1,7 @@ > { > "name": "OVN_Northbound", > - "version": "2.1.1", > - "cksum": "2615511875 5108", > + "version": "2.2.0", > + "cksum": "2898515457 5921", > "tables": { > "Logical_Switch": { > "columns": { > @@ -16,6 +16,11 @@ > "refType": "strong"}, > "min": 0, > "max": "unlimited"}}, > + "subnets": {"type": {"key": {"type": "uuid", > + "refTable": "Subnet", > + "refType": "strong"}, > + "min": 0, > + "max": "unlimited"}}, > "external_ids": { > "type": {"key": "string", "value": "string", > "min": 0, "max": "unlimited"}}}, > @@ -63,6 +68,16 @@ > "type": {"key": "string", "value": "string", > "min": 0, "max": "unlimited"}}}, > "isRoot": false}, > + "Subnet": { > + "columns": { > + "cidr": {"type": "string"}, > + "ip_version": {"type": {"key": {"type": "integer", > + "enum": ["set", [4, 6]]}}}, > + "gateway_ip": {"type": "string"}, > + "enable_dhcp": {"type": "boolean"}, > + "dhcp_options": {"type": {"key": "string", "value": "string", > + "min": 0, "max": "unlimited"}}}, > + "isRoot": false}, > "Logical_Router": { > "columns": { > "name": {"type": "string"}, > diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml > index c01455d..5bbe8c4 100644 > --- a/ovn/ovn-nb.xml > +++ b/ovn/ovn-nb.xml > @@ -73,11 +73,18 @@ > Access control rules that apply to packets within the logical switch. > </column> > > + <column name="subnets"> > + <p> > + Subnets configured to the logical switch. > + </p> > + </column> > + > <group title="Common Columns"> > <column name="external_ids"> > See <em>External IDs</em> at the beginning of this document. > </column> > </group> > + > </table> > > <table name="Logical_Port" title="L2 logical switch port"> > @@ -212,6 +219,28 @@ > interface, in kb. > </column> > </group> > + > + <group title="DHCP Options"> > + <p> > + These options apply to logical ports with <ref column="type"/> > having > + (empty string) > + </p> > + > + <column name="options" key="dhcp_opt_OPTION_NAME"> > + Each logical port can override the DHCP options defined in the > + <ref column="dhcp_options"/> of <ref table="Subnet"/> > + by defining them in this column with the prefix "dhcp_opt_". > + Please see the <ref column="dhcp_options"/> of > + <ref table="Subnet"/> for supported DHCP options. > + > + Example: key="dhcp_opt_mtu", value="1300" > + </column> > + > + <column name="options" key="dhcp_disabled"> > + If this is defined, <code>ovn-northd</code> will disable the native > + DHCP responder for the logical port. > + </column> > + </group> > </group> > > <group title="Containers"> > @@ -605,6 +634,289 @@ > </group> > </table> > > + <table name="Subnet" title="L2 logical switch subnet"> > + <p> > + A subnet within an L2 logical switch. This is an optional table. CMS > + can add the rows to this table to define the subnets belonging to > logical > + switch. > + </p> > + > + <column name="cidr"> > + <p> > + cidr of the subnet. > + </p> > + </column> > + > + <column name="ip_version"> > + <p> > + IP version of the subnet -4 or 6. > + </p> > + </column> > + > + <column name="gateway_ip"> > + <p> > + Gateway ip of the subnet. > + </p> > + </column> > + > + <column name="enable_dhcp"> > + <p> > + If set to true, native DHCP support will be enabled for all the > + logical ports having the IPv4 address from the subnet cidr. > + </p> > + </column> > + > + <group title="DHCP options"> > + <column name="dhcp_options"> > + <p> > + OVN implements a native DHCP support which caters to the common > + use case of providing an IP address to a booting instance by > + providing stateless replies to DHCP requests based on statically > + configured address mappings. To do this it allows a short list of > + DHCP options to be configured and applied at each compute host > + running ovn-controller. > + </p> > + > + <p> > + CMS should define the set of DHCP options as key/value pairs. > + The defined DHCP options will be include in the DHCP response to > the > + DHCP DISCOVER/REQUEST packet from the logical ports having the IPv4 > + addresses in the <ref column="cidr"/>. > + </p> > + </column> > + > + <group title="Supported DHCP options"> > + <p> > + Below are the supported DHCP options whose values are IPv4 address > + or addresses. If the value has more than one IPv4 address, then it > + should be enclosed within '{}' braces. Please refer to the > + RFC 2132 <code>"https://tools.ietf.org/html/rfc2132"</code> for > + more details on the DHCP options and their codes. > + </p> > + > + <column name="dhcp_options" key="netmask"> > + <p> > + The DHCP option code for this option is 1. > + </p> > + > + <p> > + Example. key="netmask", value="255.255.255.0" > + </p> > + </column> > + > + <column name="dhcp_options" key="router"> > + <p> > + The DHCP option code for this option is 3. > + </p> > + </column> > + > + <column name="dhcp_options" key="dns_server"> > + <p> > + The DHCP option code for this option is 6. > + </p> > + </column> > + > + <column name="dhcp_options" key="log_server"> > + <p> > + The DHCP option code for this option is 7. > + </p> > + </column> > + > + <column name="dhcp_options" key="lpr_server"> > + <p> > + The DHCP option code for this option is 9. > + </p> > + </column> > + > + <column name="dhcp_options" key="swap_server"> > + <p> > + The DHCP option code for this option is 16. > + </p> > + </column> > + > + <column name="dhcp_options" key="policy_filter"> > + <p> > + The DHCP option code for this option is 21. > + </p> > + </column> > + > + <column name="dhcp_options" key="router_solicitation"> > + <p> > + The DHCP option code for this option is 32. > + </p> > + </column> > + > + <column name="dhcp_options" key="nis_server"> > + <p> > + The DHCP option code for this option is 41. > + </p> > + </column> > + > + <column name="dhcp_options" key="ntp_server"> > + <p> > + The DHCP option code for this option is 42. > + </p> > + </column> > + > + <column name="dhcp_options" key="server_id"> > + <p> > + The DHCP option code for this option is 54. > + </p> > + </column> > + > + <column name="dhcp_options" key="tftp_server"> > + <p> > + The DHCP option code for this option is 66. > + </p> > + </column> > + > + <column name="dhcp_options" key="classless_static_route"> > + <p> > + The DHCP option code for this option is 121. > + </p> > + > + <p> > + 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. > + </p> > + > + <p> > + Example. > + key="classless_static_route" > + value="{30.0.0.0/24,10.0.0.10, 0.0.0.0/0,10.0.0.1}" > + </p> > + </column> > + > + <column name="dhcp_options" key="ms_classless_static_route"> > + <p> > + The DHCP option code for this option is 249. This option is > + similar to <code>classless_static_route</code> supported by > + Microsoft Windows DHCP clients. > + </p> > + </column> > + > + <column name="dhcp_options" key="server_mac"> > + <p> > + <code>eth.src</code> will be set to this value in the DHCP > + response packet. > + </p> > + </column> > + </group> > + > + <group title="Other supported DHCP options"> > + <column name="dhcp_options" key="ip_forward_enable"> > + <p> > + The DHCP option code for this option is 19. > + </p> > + > + <p> > + The value of this DHCP option is of type <code>bool</code>. > + > + Example. key="ip_forward_enable", value="1" > + </p> > + </column> > + > + <column name="dhcp_options" key="router_discovery"> > + <p> > + The DHCP option code for this option is 31. > + </p> > + > + <p> > + The value of this DHCP option is of type <code>bool</code>. > + </p> > + </column> > + > + <column name="dhcp_options" key="ethernet_encap"> > + <p> > + The DHCP option code for this option is 36. > + </p> > + > + <p> > + The value of this DHCP option is of type <code>bool</code>. > + </p> > + </column> > + > + <column name="dhcp_options" key="default_ttl"> > + <p> > + The DHCP option code for this option is 23. > + </p> > + > + <p> > + The value of this DHCP option is of type <code>uint8</code>. > + > + Example. key="default_ttl", value="128". > + </p> > + </column> > + > + <column name="dhcp_options" key="tcp_ttl"> > + <p> > + The DHCP option code for this option is 37. > + </p> > + > + <p> > + The value of this DHCP option is of type <code>uint8</code>. > + </p> > + </column> > + > + <column name="dhcp_options" key="mtu"> > + <p> > + The DHCP option code for this option is 26. > + </p> > + > + <p> > + The value of this DHCP option is of type <code>uint16</code>. > + </p> > + </column> > + > + <column name="dhcp_options" key="lease_time"> > + <p> > + The DHCP option code for this option is 51. > + </p> > + > + <p> > + The value of this DHCP option is of type <code>uint32</code>. > + > + Example. key="lease_time", value="42000" > + </p> > + </column> > + > + <column name="dhcp_options" key="T1"> > + <p> > + The DHCP option code for this option is 58. > + </p> > + > + <p> > + The value of this DHCP option is of type <code>uint32</code>. > + > + Example. key="T1", value="30000" > + </p> > + </column> > + > + <column name="dhcp_options" key="T2"> > + <p> > + The DHCP option code for this option is 59. > + </p> > + > + <p> > + The value of this DHCP option is of type <code>uint32</code>. > + > + Example. key="T2", value="40000" > + </p> > + </column> > + </group> > + > + <group title="Mandatory DHCP options"> > + <p> > + DHCP options <code>"server_id"</code>, <code>"server_mac"</code> > + and <code>"lease_time"</code> are mandatory options which CMS > should > + define for <code>OVN</code> to support native DHCP. > + </p> > + </group> > + </group> > + </table> > + > <table name="Logical_Router" title="L3 logical router"> > <p> > Each row represents one L3 logical router. > @@ -637,7 +949,7 @@ > column is set to <code>false</code>, the router is disabled. A > disabled > router has all ingress and egress traffic dropped. > </column> > - > + > <group title="Common Columns"> > <column name="external_ids"> > See <em>External IDs</em> at the beginning of this document. > diff --git a/ovn/utilities/ovn-nbctl.8.xml b/ovn/utilities/ovn-nbctl.8.xml > index 8375ab7..408badb 100644 > --- a/ovn/utilities/ovn-nbctl.8.xml > +++ b/ovn/utilities/ovn-nbctl.8.xml > @@ -64,6 +64,35 @@ > </dd> > </dl> > > + <h1>Subnet Commands</h1> > + > + <dl> > + <dt><code>subnet-add</code> <var>lswitch</var> <var>cidr</var> > <var>gateway_ip</var> [<var>enable_dhcp</var>]</dt> > + <dd> > + Adds a Subnet to the <var>lswitch</var> > + </dd> > + > + <dt><code>subnet-del</code> <var>subnet</var></dt> > + <dd> > + Deletes the subnet referred by <var>subnet</var> UUID. > + </dd> > + > + <dt><code>subnet-list</code> <var>lswitch</var></dt> > + <dd> > + Lists all the subnets belonging to the <var>lswitch</var> > + </dd> > + > + <dt><code>subnet-set-dhcp-options</code> <var>subnet</var> > [<var>key=value</var>]...</dt> > + <dd> > + Sets the DHCP options for the <var>subnet</var> > + </dd> > + > + <dt><code>subnet-get-dhcp-options</code> <var>subnet</var></dt> > + <dd> > + Lists the DHCP options of the <var>subnet</var> > + </dd> > + </dl> > + > <h1>ACL Commands</h1> > <dl> > <dt>[<code>--log</code>] <code>acl-add</code> <var>lswitch</var> > <var>direction</var> <var>priority</var> <var>match</var> > <var>action</var></dt> > diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c > index d267829..0887961 100644 > --- a/ovn/utilities/ovn-nbctl.c > +++ b/ovn/utilities/ovn-nbctl.c > @@ -333,6 +333,18 @@ Logical port commands:\n\ > Set options related to the type of LPORT\n\ > lport-get-options LPORT Get the type specific options for LPORT\n\ > \n\ > +Subnet commands:\n\ > + subnet-add LSWITCH CIDR GATEWAYIP [ENABLE_DHCP]\n\ > + add a subnet to LSWITCH\n\ > + subnet-del SUBNET_UUID\n\ > + remove subnet from its attached LSWITCH\n\ > + subnet-list LSWITCH\n\ > + list subnets attached to LSWITCH\n\ > + subnet-set-dhcp-options SUBNET_UUID KEY=VALUE [KEY=VALUE]...\n\ > + Set DHCP options for the SUBNET\n\ > + subnet-get-dhcp-options SUBNET_UUID\n\ > + Get the DHCP options for the SUBNET\n\ > +\n\ > %s\ > \n\ > Options:\n\ > @@ -502,6 +514,7 @@ nbctl_lswitch_list(struct ctl_context *ctx) > smap_destroy(&lswitches); > free(nodes); > } > + > > static const struct nbrec_logical_port * > lport_by_name_or_uuid(struct ctl_context *ctx, const char *id, > @@ -922,6 +935,175 @@ nbctl_lport_get_options(struct ctl_context *ctx) > } > } > > +static void > +nbctl_subnet_add(struct ctl_context *ctx) > +{ > + const struct nbrec_logical_switch *lswitch; > + lswitch = lswitch_by_name_or_uuid(ctx, ctx->argv[1], true); > + > + /* Validate the cidr */ > + int64_t ip_version = 4; > + ovs_be32 ip; > + unsigned int plen; > + char *error = ip_parse_cidr(ctx->argv[2], &ip, &plen); > + if (error){ > + /* check if its IPv6 cidr */ > + free(error); > + struct in6_addr ipv6; > + error = ipv6_parse_cidr(ctx->argv[2], &ipv6, &plen); > + if (error) { > + free(error); > + VLOG_WARN("Invalid cidr format '%s'", ctx->argv[2]); > + return; > + } > + ip_version = 6; > + } > + > + /* Validate the gateway ip format */ > + bool valid_gatewap_ip = false; > + if (ip_version == 4) { > + valid_gatewap_ip = ip_parse(ctx->argv[3], &ip); > + } > + else { > + struct in6_addr ipv6; > + valid_gatewap_ip = ipv6_parse(ctx->argv[3], &ipv6); > + } > + > + if (!valid_gatewap_ip) { > + VLOG_WARN("Invalid Gateway ip format '%s'", ctx->argv[3]); > + return; > + } > + > + bool enable_dhcp = true; > + if (ctx->argc == 5) { > + if (!strcmp(ctx->argv[4], "false")) { > + enable_dhcp = false; > + } > + } > + > + struct nbrec_subnet *subnet = nbrec_subnet_insert(ctx->txn); > + nbrec_subnet_set_cidr(subnet, ctx->argv[2]); > + nbrec_subnet_set_ip_version(subnet, ip_version); > + nbrec_subnet_set_gateway_ip(subnet, ctx->argv[3]); > + nbrec_subnet_set_enable_dhcp(subnet, enable_dhcp); > + > + /* Insert the subnet into the logical switch */ > + nbrec_logical_switch_verify_subnets(lswitch); > + struct nbrec_subnet **new_subnets = xmalloc(sizeof *new_subnets * > + (lswitch->n_subnets + 1)); > + memcpy(new_subnets, lswitch->subnets, > + sizeof *new_subnets * lswitch->n_subnets); > + new_subnets[lswitch->n_subnets] = subnet; > + nbrec_logical_switch_set_subnets(lswitch, new_subnets, > + lswitch->n_subnets + 1); > + free(new_subnets); > +} > + > +static void > +remove_subnet(const struct nbrec_logical_switch *lswitch, size_t idx) > +{ > + const struct nbrec_subnet *subnet = lswitch->subnets[idx]; > + > + struct nbrec_subnet **new_subnets > + = xmemdup(lswitch->subnets, sizeof *new_subnets * > lswitch->n_subnets); > + new_subnets[idx] = new_subnets[lswitch->n_subnets - 1]; > + nbrec_logical_switch_verify_subnets(lswitch); > + nbrec_logical_switch_set_subnets(lswitch, new_subnets, > lswitch->n_subnets - 1); > + free(new_subnets); > + > + nbrec_subnet_delete(subnet); > +} > + > +static const struct nbrec_subnet * > +subnet_get(struct ctl_context *ctx, char *id) > +{ > + struct uuid subnet_uuid; > + if (uuid_from_string(&subnet_uuid, id)) { > + return nbrec_subnet_get_for_uuid(ctx->idl, &subnet_uuid); > + } > + > + return NULL; > +} > + > +static void > +nbctl_subnet_del(struct ctl_context *ctx) > +{ > + const struct nbrec_subnet *subnet = subnet_get(ctx, ctx->argv[1]); > + if (!subnet) { > + VLOG_WARN("subnet not found for '%s'", ctx->argv[1]); > + return; > + } > + > + /* Find the switch that contains 'subnet', then delete it. */ > + const struct nbrec_logical_switch *lswitch; > + NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, ctx->idl) { > + for (size_t i = 0; i < lswitch->n_subnets; i++) { > + if (lswitch->subnets[i] == subnet) { > + remove_subnet(lswitch, i); > + return; > + } > + } > + } > + > + VLOG_WARN("subnet %s is not part of any logical switch", > + ctx->argv[1]); > +} > + > +static void > +nbctl_subnet_list(struct ctl_context *ctx) > +{ > + const struct nbrec_logical_switch *lswitch; > + lswitch = lswitch_by_name_or_uuid(ctx, ctx->argv[1], false); > + if (!lswitch) { > + return; > + } > + > + for (size_t i = 0; i < lswitch->n_subnets; i++) { > + const struct nbrec_subnet *subnet = lswitch->subnets[i]; > + ds_put_format(&ctx->output, UUID_FMT " (%s)\n", > + UUID_ARGS(&subnet->header_.uuid), subnet->cidr); > + } > +} > + > +static void > +nbctl_subnet_set_dhcp_options(struct ctl_context *ctx) > +{ > + const struct nbrec_subnet *subnet = subnet_get(ctx, ctx->argv[1]); > + if (!subnet) { > + VLOG_WARN("subnet not found for '%s'", ctx->argv[1]); > + return; > + } > + > + 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_subnet_set_dhcp_options(subnet, &dhcp_options); > + smap_destroy(&dhcp_options); > +} > + > +static void > +nbctl_subnet_get_dhcp_options(struct ctl_context *ctx) > +{ > + const struct nbrec_subnet *subnet = subnet_get(ctx, ctx->argv[1]); > + if (!subnet) { > + VLOG_WARN("subnet not found for '%s'", ctx->argv[1]); > + return; > + } > + > + struct smap_node *node; > + SMAP_FOR_EACH(node, &subnet->dhcp_options) { > + ds_put_format(&ctx->output, "%s=%s\n", node->key, node->value); > + } > +} > + > enum { > DIR_FROM_LPORT, > DIR_TO_LPORT > @@ -1120,6 +1302,10 @@ static const struct ctl_table_class tables[] = { > {{NULL, NULL, NULL}, > {NULL, NULL, NULL}}}, > > + {&nbrec_table_subnet, > + {{&nbrec_table_subnet, NULL, NULL}, > + {NULL, NULL, NULL}}}, > + > {&nbrec_table_logical_router, > {{&nbrec_table_logical_router, &nbrec_logical_router_col_name, NULL}, > {NULL, NULL, NULL}}}, > @@ -1386,6 +1572,16 @@ static const struct ctl_command_syntax > nbctl_commands[] = { > { "lport-get-options", 1, 1, "LPORT", NULL, nbctl_lport_get_options, > NULL, > "", RO }, > > + /* subnet commands */ > + {"subnet-add", 3, 4, "LSWITCH CIDR GATEWAY_IP ENABLE_DHCP", NULL, > + nbctl_subnet_add, NULL, "", RW }, > + {"subnet-del", 1, 1, "SUBNET", NULL, nbctl_subnet_del, NULL, "", RW}, > + {"subnet-list", 1, 1, "LSWITCH", NULL, nbctl_subnet_list, NULL, "", RO}, > + {"subnet-set-dhcp-options", 1, INT_MAX, "SUBNET KEY=VALUE > [KEY=VALUE]...", > + NULL, nbctl_subnet_set_dhcp_options, NULL, "", RW }, > + {"subnet-get-dhcp-options", 1, 1, "SUBNET", NULL, > + nbctl_subnet_get_dhcp_options, NULL, "", RO }, > + > {NULL, 0, 0, NULL, NULL, NULL, NULL, "", RO}, > }; > > diff --git a/tests/ovn.at b/tests/ovn.at > index b5f33ef..e9877e7 100644 > --- a/tests/ovn.at > +++ b/tests/ovn.at > @@ -2663,3 +2663,253 @@ AT_CHECK([ovstest test-ovn-put-dhcp-opts-action reg0 > $dhcp_options $expected_dhc > [1], [ignore]) > > AT_CLEANUP > + > +AT_SETUP([ovn -- dhcp : 1 HV, 2 LS, 3 lports]) > +AT_KEYWORDS([dhcp]) > +AT_SKIP_IF([test $HAVE_PYTHON = no]) > +ovn_start > + > +ovn-nbctl lswitch-add ls1 > +ovn-nbctl lswitch-add ls2 > + > +ovn-nbctl -- --id=@s1 create Subnet cidr=10.0.0.0/24 gateway_ip=10.0.0.1 \ > +ip_version=4 enable_dhcp=true dhcp_options="\"server_id\"=\"10.0.0.1\" \ > +\"server_mac\"=\"00:00:00:10:00:01\" \"lease_time\"=\"36\"" \ > +-- add Logical_Switch ls1 subnets @s1 > + > +ovn-nbctl lport-add ls1 ls1-lp1 \ > +-- lport-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4" > + > +ovn-nbctl lport-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4" > + > +ovn-nbctl lport-add ls2 ls2-lp1 \ > +-- lport-set-addresses ls2-lp1 "f0:00:00:00:00:02 20.0.0.3" > + > +ovn-nbctl lport-set-port-security ls2-lp1 "f0:00:00:00:00:02 20.0.0.3" > + > +ovn-nbctl lport-add ls1 ls1-lp2 \ > +-- lport-set-addresses ls1-lp2 "f0:00:00:00:00:03 10.0.0.5" > + > +ovn-nbctl lport-set-port-security ls1-lp2 "f0:00:00:00:00:03 10.0.0.5" > +#disable dhcp on lport - ls1-lp2 > +ovn-nbctl lport-set-options ls1-lp2 "dhcp_disabled=true" > + > +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=ls2-lp1 \ > + 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=ls1-lp2 \ > + options:tx_pcap=hv1/vif3-tx.pcap \ > + options:rxq_pcap=hv1/vif3-rx.pcap \ > + ofport-request=3 > + > +ovn_populate_arp > + > +sleep 2 > + > +# This shell function sends a DHCP request packet > +# test_dhcp INPORT SRC_MAC DHCP_TYPE OUTPORT... > +# The OUTPORTs (zero or more) list the VIFs on which the original DHCP > +# packet should be received twice (one from ovn-controller and the other > +# from the "ovs-ofctl monitor br-int resume" > +test_dhcp() { > + local inport=$1 src_mac=$2 dhcp_type=$3 > + 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 > + shift; shift; shift; > + for outport; do > + # the packet will be received twice, one from ovn-controller > + # and the other from ovs-ofctl monitor br-int resume > + echo $request | trim_zeros >> $outport.expected > + echo $request | trim_zeros >> $outport.expected > + done > + 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 > +} > + > +trim_zeros() { > + sed 's/\(00\)\{1,\}$//' > +} > + > +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 DHCP DISCOVER > +test_dhcp 1 f00000000001 01 > + > +# Wait for the expected number of NXT_RESUMEs to be logged. > +echo "waiting for 1 NXT_RESUMEs..." > +OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) > + > +# Extract the dhcp options in userdata. It will be in the format > +# ' userdata=00.00.00.02.00.00.00.00.00.00.00.00.0a.00.00.04.01.04.ff......' > +# Skip 59 columns - userdata=<DHCP_ACTION_CODE (8 B)><REG-IDX (4B)><OFFER-IP > (4 B)> > +dhcp_opts_in_user_data=`cat ofctl_monitor0.log | grep userdata | cut -c 59-` > + > +AT_CHECK([ovstest test-ovn-dhcp hv1/vif1-tx.pcap 10.0.0.4 10.0.0.1 \ > + 00:00:00:10:00:01 1 $dhcp_opts_in_user_data], [0], [ignore]) > + > +# 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 > +# for "test-ovn-dhcp" to validate the DHCP reply packet > +reset_pcap_file hv1-vif1 hv1/vif1 > +reset_pcap_file hv1-vif2 hv1/vif2 > +reset_pcap_file hv1-vif3 hv1/vif3 > + > +# run dhcp test on ls2-lp1. There are no dhcp options defined. So the dhcp > +# packet should not be handled by ovn-controller > +test_dhcp 2 f00000000002 01 > + > +# NXT_RESUMEs should be 1. > +echo "waiting for 1 NXT_RESUMEs..." > +cat ofctl_monitor*.log > +echo "###########################################" > + > +OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) > + > +ovn-nbctl subnet-add ls2 20.0.0.0/24 20.0.0.1 true > +subnet_id=`ovn-nbctl subnet-list ls2 | cut -c -37` > + > +ovn-nbctl subnet-set-dhcp-options $subnet_id "server_id=20.0.0.1" \ > +"lease_time=4500" > + > +sleep 1 > + > +# there is no "server_mac" field in the dhcp options. So there should be no > +# dhcp flow for this port > +OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | \ > +grep -c "actions=controller(userdata=00.00.00.02.00.00.00"`]) > + > +ovn-nbctl subnet-set-dhcp-options $subnet_id "server_id=20.0.0.1" \ > +"server_mac=00:00:00:10:00:02" "lease_time=4500" > + > +sleep 2 > +OVS_WAIT_UNTIL([test 2 = `as hv1 ovs-ofctl dump-flows br-int | \ > +grep -c "actions=controller(userdata=00.00.00.02.00.00.00"`]) > + > +test_dhcp 2 f00000000002 03 > + > +# NXT_RESUMEs should be 2. > +echo "waiting for 2 NXT_RESUMEs..." > +OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) > + > +dhcp_opts_in_user_data=`cat ofctl_monitor0.log | grep userdata | \ > +cut -c 59- | sed '1d'` > + > +AT_CHECK([ovstest test-ovn-dhcp hv1/vif2-tx.pcap 20.0.0.3 20.0.0.1 \ > + 00:00:00:10:00:02 3 $dhcp_opts_in_user_data], [0], [ignore]) > + > +reset_pcap_file hv1-vif1 hv1/vif1 > +reset_pcap_file hv1-vif2 hv1/vif2 > +reset_pcap_file hv1-vif3 hv1/vif3 > + > +#enable dhcp on lport - ls1-lp2 > +ovn-nbctl lport-set-options ls1-lp2 "dhcp_disabled=false" > + > +sleep 2 > + > +OVS_WAIT_UNTIL([test 3 = `as hv1 ovs-ofctl dump-flows br-int | \ > +grep -c "actions=controller(userdata=00.00.00.02.00.00.00"`]) > + > +#send invalid dhcp packet on hv1/vif3 (ie ls1-lp3). > +# This packet should be received by hv1/vif1 (ls1-lp1) > +test_dhcp 3 f00000000003 04 1 > + > +# NXT_RESUMEs should be 3. > +echo "waiting for 3 NXT_RESUMEs..." > +OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) > + > +# vif3-tx.pcap should not have received the DHCP reply packet > +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap | trim_zeros > > 3.packets > +AT_CHECK([cat 3.packets], [0], []) > + > +# vif1-tx.pcap should have received the DHCP (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]) > + > + > +echo "---------NB dump-----" > +ovn-nbctl show > +echo "---------SB dump-----" > +ovn-sbctl list datapath_binding > +echo "---------------------" > +ovn-sbctl list logical_flow > +ovn-sbctl list port_binding > +echo "---------------------" > +ovn-sbctl dump-flows > +echo "---------------------" > + > +echo "------ hv1 dump ----------" > +as hv1 ovs-ofctl dump-flows br-int > + > +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 > diff --git a/tests/test-ovn-dhcp.c b/tests/test-ovn-dhcp.c > index 79cee08..b879a03 100644 > --- a/tests/test-ovn-dhcp.c > +++ b/tests/test-ovn-dhcp.c > @@ -16,6 +16,10 @@ > #include <assert.h> > #include <config.h> > #include "command-line.h" > +#include "dp-packet.h" > +#include <errno.h> > +#include "flow.h" > +#include "lib/dhcp.h" > #include "openvswitch/ofp-actions.h" > #include "ovstest.h" > #include "ovn/lib/actions.h" > @@ -23,6 +27,7 @@ > #include "ovn/lib/expr.h" > #include "ovn/lib/logical-fields.h" > #include "shash.h" > +#include "pcap-file.h" > > static void > add_logical_register(struct shash *symtab, enum mf_field_id id) > @@ -146,4 +151,134 @@ test_put_dhcp_opts_action(int argc OVS_UNUSED, char > *argv[] OVS_UNUSED) > exit(0); > } > > +static void > +test_ovn_dhcp_main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) > +{ > + if (argc != 7) { > + printf("Usage: %s pcap-file offer-ip server-ip" > + " server-mac dhcp-type userdata\n", argv[0]); > + exit(1); > + } > + > + int retval = 1; > + FILE *pcap; > + > + set_program_name(argv[0]); > + > + pcap = fopen(argv[1], "rb"); > + if (!pcap) { > + ovs_fatal(errno, "failed to open %s", argv[1]); > + } > + > + retval = ovs_pcap_read_header(pcap); > + if (retval) { > + ovs_fatal(retval > 0 ? retval : 0, "reading pcap header failed"); > + } > + > + /* verify if the offer-ip is in proper format */ > + ovs_be32 expected_offer_ip; > + if (!ovs_scan(argv[2], IP_SCAN_FMT, IP_SCAN_ARGS(&expected_offer_ip))) { > + ovs_fatal(1, "invalid expected offer ip"); > + } > + > + /* verify if the server-ip is in proper format */ > + ovs_be32 server_ip; > + if (!ovs_scan(argv[3], IP_SCAN_FMT, IP_SCAN_ARGS(&server_ip))) { > + ovs_fatal(1, "invalid expected server ip"); > + } > + > + struct eth_addr server_mac; > + if (!eth_addr_from_string(argv[4], &server_mac)) { > + ovs_fatal(1, "invalid expected server mac"); > + } > + > + struct dp_packet *packet = NULL; > + retval = ovs_pcap_read(pcap, &packet, NULL); > + if (retval == EOF) { > + ovs_fatal(0, "unexpected end of file reading pcap file : [%s]\n", > + argv[1]); > + } else if (retval) { > + ovs_fatal(retval, "error reading pcap file"); > + } > + > + int exit_code = 1; > + struct flow flow; > + flow_extract(packet, &flow); > + > + struct dhcp_header const *dhcp_data = dp_packet_get_udp_payload(packet); > + if (dhcp_data->op != (uint8_t)2) { > + printf("Invalid dhcp op reply code : %d\n", dhcp_data->op); > + goto exit; > + } > + > + if (flow.tp_src != htons(DHCP_SERVER_PORT) && > + flow.tp_dst != htons(DHCP_CLIENT_PORT)) { > + printf("Error. Not a dhcp response packet \n"); > + goto exit; > + } > + > + if (flow.nw_dst != expected_offer_ip) { > + printf("Error. Offered ip : "IP_FMT " : Expected ip : %s\n", > + IP_ARGS(flow.nw_dst), argv[2]); > + goto exit; > + } > + > + if(dhcp_data->yiaddr != expected_offer_ip) { > + printf("Error. Offered yiaddr : "IP_FMT " : Expected ip : %s\n", > + IP_ARGS(dhcp_data->yiaddr), argv[2]); > + goto exit; > + } > + > + /* Verify the dhcp option cookie */ > + uint8_t const *footer = (uint8_t *)dhcp_data + sizeof(*dhcp_data); > + ovs_be32 dhcp_cookie = htonl(DHCP_MAGIC_COOKIE); > + if (memcmp(footer, &dhcp_cookie, sizeof(ovs_be32))) { > + printf("Error. Invalid dhcp magic cookie\n"); > + goto exit; > + } > + > + /* Validate userdata. It should be ASCII hex */ > + uint64_t dhcp_opts_stub[1024 / 8]; > + struct ofpbuf dhcp_opts = OFPBUF_STUB_INITIALIZER(dhcp_opts_stub); > + if (atoi(argv[5]) == 1) { > + /* DHCP reply type should be OFFER (02) */ > + ofpbuf_put_hex(&dhcp_opts, "350102", NULL); > + } else { > + /* DHCP reply type should be ACK (05) */ > + ofpbuf_put_hex(&dhcp_opts, "350105", NULL); > + } > + > + if (ofpbuf_put_hex(&dhcp_opts, argv[6], NULL)[0] != '\0') { > + printf("Error. Invalid userdata\n"); > + goto exit; > + } > + > + /* 4 bytes padding, 1 byte FF and 4 bytes padding */ > + ofpbuf_put_hex(&dhcp_opts, "00000000FF00000000", NULL); > + > + footer += sizeof(uint32_t); > + > + size_t dhcp_opts_size = (const char *)dp_packet_tail(packet) - ( > + const char *)footer; > + if (dhcp_opts_size != dhcp_opts.size) { > + printf("Error. dhcp options size mismatch\n"); > + goto exit; > + } > + > + if (memcmp(footer, dhcp_opts.data, dhcp_opts.size)) { > + printf("Error. Invalid dhcp options present\n"); > + goto exit; > + } > + > + exit_code = 0; > + > +exit: > + fclose(pcap); > + if (packet) { > + dp_packet_delete(packet); > + } > + exit(exit_code); > +} > + > OVSTEST_REGISTER("test-ovn-put-dhcp-opts-action", test_put_dhcp_opts_action); > +OVSTEST_REGISTER("test-ovn-dhcp", test_ovn_dhcp_main); > -- > 2.5.5 > > _______________________________________________ > dev mailing list > dev@openvswitch.org > http://openvswitch.org/mailman/listinfo/dev _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev