Message ID | 20170417151321.1467-1-nusiddiq@redhat.com |
---|---|
State | Superseded |
Delegated to: | Guru Shetty |
Headers | show |
On 17 April 2017 at 08:13, <nusiddiq@redhat.com> wrote: > From: Numan Siddique <nusiddiq@redhat.com> > > OVN implements native DNS resolution which can be used to resolve the > internal DNS names belonging to a logical datapath. > > To support this, a new table 'DNS' is added in the NB DB. A new column > 'dns_records' is added in 'Logical_Switch' table which references to the > 'DNS' table. > > Following flows are added for each logical switch if configured with > DNS records in the 'dns_records' column > - A logical flow in DNS_LOOKUP stage which uses the action 'dns_lookup' > to transform the DNS query to DNS reply packet and advances > to the next stage - DNS_RESPONSE. > > - A logical flow in DNS_RESPONSE stage which implements the DNS responder > by sending the DNS reply from previous stage back to the inport. > > Signed-off-by: Numan Siddique <nusiddiq@redhat.com> > Acked-by: Gurucharan Shetty <guru@ovn.org> A few comments below. > --- > ovn/northd/ovn-northd.8.xml | 85 +++++++++- > ovn/northd/ovn-northd.c | 183 ++++++++++++++++++++- > ovn/ovn-nb.ovsschema | 20 ++- > ovn/ovn-nb.xml | 27 +++- > ovn/utilities/ovn-nbctl.c | 3 + > tests/ovn.at | 377 ++++++++++++++++++++++++++++++ > ++++++++++++++ > 6 files changed, 682 insertions(+), 13 deletions(-) > > diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml > index ab8fd88..b7e2325 100644 > --- a/ovn/northd/ovn-northd.8.xml > +++ b/ovn/northd/ovn-northd.8.xml > @@ -724,7 +724,71 @@ output; > </li> > </ul> > > - <h3>Ingress Table 13 Destination Lookup</h3> > + <h3>Ingress Table 13 DNS Lookup</h3> > + > + <p> > + This table looks up and resolves the DNS names of the logical ports > + if configured with the host names. > I think the above should say something like: This table looks up and resolves the DNS names to the corresponding configured IP address. > + </p> > + > + <ul> > + <li> > + <p> > + A priority-100 logical flow for each logical switch datapath > + if it is configured with DNS records, which matches the IPv4 > and IPv6 > + packets with <code>udp.dst</code> = 53 and applies the action > + <code>dns_lookup</code> and advances the packet to the next > table. > + </p> > + > + <pre> > +reg0[4] = dns_lookup(); next; > + </pre> > + > + <p> > + For valid DNS packets, this transforms the packet into a DNS > + reply if the DNS name can be resolved, and stores 1 into > reg0[4]. > + For failed DNS resolution or other kinds of packets, it just > stores > + 0 into reg0[4]. Either way, it continues to the next table. > + </p> > + </li> > + </ul> > + > + <h3>Ingress Table 14 DNS Responses</h3> > + > + <p> > + This table implements DNS responder for the DNS replies generated by > + the previous table. > + </p> > + > + <ul> > + <li> > + <p> > + A priority-100 logical flow for each logical switch datapath > + if it is configured with DNS records, which matches the IPv4 > and IPv6 > + packets with <code>udp.dst = 53 && reg0[4] == 1</code> > + and responds back to the <code>inport</code> after applying > these > + actions. If <code>reg0[4]</code> is set to 1, it means that the > + action <code>dns_lookup</code> was successful. > + </p> > + > + <pre> > +eth.dst <-> eth.src; > +ip4.src <-> ip4.dst; > +udp.dst = udp.src; > +udp.src = 53; > +outport = <var>P</var>; > +flags.loopback = 1; > +output; > + </pre> > + > + <p> > + (This terminates ingress packet processing; the packet does not > go > + to the next ingress table.) > + </p> > + </li> > + </ul> > + > + <h3>Ingress Table 15 Destination Lookup</h3> > > <p> > This table implements switching behavior. It contains these logical > @@ -834,11 +898,22 @@ output; > </p> > > <p> > - Also a priority 34000 logical flow is added for each logical port > which > - has DHCPv4 options defined to allow the DHCPv4 reply packet and > which has > - DHCPv6 options defined to allow the DHCPv6 reply packet from the > - <code>Ingress Table 12: DHCP responses</code>. > + Also the following flows are added. > </p> > + <ul> > + <li> > + A priority 34000 logical flow is added for each logical port which > + has DHCPv4 options defined to allow the DHCPv4 reply packet and > which has > + DHCPv6 options defined to allow the DHCPv6 reply packet from the > + <code>Ingress Table 12: DHCP responses</code>. > + </li> > + > + <li> > + A priority 34000 logical flow is added for each logical switch > datapath > + if it is configured with DNS records, which allows the DNS reply > packet > + from the <code>Ingress Table 14:DNS responses</code>. > Usually we mention the match and actions that are added, right? > + </li> > + </ul> > > <h3>Egress Table 7: Egress Port Security - IP</h3> > > diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c > index 027e5a1..226d05c 100644 > --- a/ovn/northd/ovn-northd.c > +++ b/ovn/northd/ovn-northd.c > @@ -112,7 +112,9 @@ enum ovn_stage { > PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 10, "ls_in_arp_rsp") > \ > PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 11, "ls_in_dhcp_options") > \ > PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 12, "ls_in_dhcp_response") > \ > - PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 13, "ls_in_l2_lkup") > \ > + PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 13, "ls_in_dns_lookup") \ > + PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 14, "ls_in_dns_response") \ > + PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 15, "ls_in_l2_lkup") > \ > \ > /* Logical switch egress stages. */ \ > PIPELINE_STAGE(SWITCH, OUT, PRE_LB, 0, "ls_out_pre_lb") \ > @@ -160,6 +162,7 @@ enum ovn_stage { > #define REGBIT_CONNTRACK_COMMIT "reg0[1]" > #define REGBIT_CONNTRACK_NAT "reg0[2]" > #define REGBIT_DHCP_OPTS_RESULT "reg0[3]" > +#define REGBIT_DNS_LOOKUP_RESULT "reg0[4]" > > /* Register definitions for switches and routers. */ > #define REGBIT_NAT_REDIRECT "reg9[0]" > @@ -2658,6 +2661,22 @@ ip_address_and_port_from_lb_key(const char *key, > char **ip_address, > free(start); > } > > +/* > + * Returns true if logical switch is configured with DNS records, false > + * otherwise. > + */ > +static bool > +ls_has_dns_records(const struct nbrec_logical_switch *nbs) > +{ > + for (size_t i = 0; i < nbs->n_dns_records; i++) { > + if (!smap_is_empty(&nbs->dns_records[i]->records)) { > + return true; > + } > + } > + > + return false; > +} > + > static void > build_pre_lb(struct ovn_datapath *od, struct hmap *lflows) > { > @@ -2942,7 +2961,8 @@ 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*/ > + * logical ports of the datapath if the CMS has configured DHCPv4 > options. > + * */ > for (size_t i = 0; i < od->nbs->n_ports; i++) { > if (od->nbs->ports[i]->dhcpv4_options) { > const char *server_id = smap_get( > @@ -2993,6 +3013,16 @@ build_acls(struct ovn_datapath *od, struct hmap > *lflows) > } > } > } > + > + /* Add one 34000 priority flow to allow DNS reply from ovn-controller > to > + * if the CMS has configured DNS records for the datapath. > The comment above looks to be incomplete. > + */ > + if (ls_has_dns_records(od->nbs)) { > + const char *actions = has_stateful ? "ct_commit; next;" : "next;"; > + ovn_lflow_add( > + lflows, od, S_SWITCH_OUT_ACL, 34000, "udp.src == 53", > + actions); > + } } > static void > @@ -3430,8 +3460,43 @@ build_lswitch_flows(struct hmap *datapaths, struct > hmap *ports, > } > } > > + /* Logical switch ingress table 13 and 14: DNS lookup and response > + * priority 100 flows.*/ > A space after the comma above. > + HMAP_FOR_EACH (od, key_node, datapaths) { > + if (!od->nbs || !ls_has_dns_records(od->nbs)) { > + continue; > + } > + > + struct ds match; > + struct ds action; > + ds_init(&match); > + ds_init(&action); > + ds_put_cstr(&match, "udp.dst == 53"); > + ds_put_format(&action, > + REGBIT_DNS_LOOKUP_RESULT" = dns_lookup(); next;"); > + ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 100, > + ds_cstr(&match), ds_cstr(&action)); > + ds_clear(&action); > + ds_put_cstr(&match, " && "REGBIT_DNS_LOOKUP_RESULT); > + ds_put_format(&action, "eth.dst <-> eth.src; ip4.src <-> ip4.dst; > " > + "udp.dst = udp.src; udp.src = 53; outport = inport; > " > + "flags.loopback = 1; output;"); > + ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100, > + ds_cstr(&match), ds_cstr(&action)); > + ds_clear(&action); > + ds_put_format(&action, "eth.dst <-> eth.src; ip6.src <-> ip6.dst; > " > + "udp.dst = udp.src; udp.src = 53; outport = inport; > " > + "flags.loopback = 1; output;"); > + ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100, > + ds_cstr(&match), ds_cstr(&action)); > + ds_destroy(&match); > + ds_destroy(&action); > + } > + > /* Ingress table 11 and 12: DHCP options and response, by default > goto next. > - * (priority 0). */ > + * (priority 0). > + * Ingress table 13 and 14: DNS lookup and response, by default goto > next. > + * (priority 0).*/ > > HMAP_FOR_EACH (od, key_node, datapaths) { > if (!od->nbs) { > @@ -3440,9 +3505,11 @@ build_lswitch_flows(struct hmap *datapaths, struct > hmap *ports, > > 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;"); > + ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 0, "1", > "next;"); > + ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 0, "1", > "next;"); > } > > - /* Ingress table 13: Destination lookup, broadcast and multicast > handling > + /* Ingress table 15: Destination lookup, broadcast and multicast > handling > * (priority 100). */ > HMAP_FOR_EACH (op, key_node, ports) { > if (!op->nbsp) { > @@ -5369,6 +5436,108 @@ sync_address_sets(struct northd_context *ctx) > } > shash_destroy(&sb_address_sets); > } > + > +/* > + * struct 'dns_info' is used to sync the DNS records between OVN > Northbound db > + * and Southbound db. > + */ > +struct dns_info { > + struct hmap_node hmap_node; > + const struct nbrec_dns *nb_dns; /* DNS record in the Northbound db. */ > + const struct sbrec_dns *sb_dns; /* DNS record in the Soutbound db. */ > + > + /* Datapaths to which the DNS entry is associated with it. */ > + const struct sbrec_datapath_binding **sbs; > + size_t n_sbs; > +}; > + > +static inline struct dns_info * > +get_dns_info_from_hmap(struct hmap *dns_map, struct uuid *uuid) > +{ > + struct dns_info *dns_info; > + size_t hash = uuid_hash(uuid); > + HMAP_FOR_EACH_WITH_HASH (dns_info, hmap_node, hash, dns_map) { > + if (uuid_equals(&dns_info->nb_dns->header_.uuid, uuid)) { > + return dns_info; > + } > + } > + > + return NULL; > +} > + > +static void > +sync_dns_entries(struct northd_context *ctx, struct hmap *datapaths) > +{ > + struct hmap dns_map = HMAP_INITIALIZER(&dns_map); > + struct ovn_datapath *od; > + HMAP_FOR_EACH (od, key_node, datapaths) { > + if (!od->nbs || !od->nbs->n_dns_records) { > + continue; > + } > + > + for (size_t i = 0; i < od->nbs->n_dns_records; i++) { > + struct dns_info *dns_info = get_dns_info_from_hmap( > + &dns_map, &od->nbs->dns_records[i]->header_.uuid); > + if (!dns_info) { > + size_t hash = uuid_hash( > + &od->nbs->dns_records[i]->header_.uuid); > + dns_info = xzalloc(sizeof *dns_info);; > + dns_info->nb_dns = od->nbs->dns_records[i]; > + hmap_insert(&dns_map, &dns_info->hmap_node, hash); > + } > + > + dns_info->n_sbs++; > + dns_info->sbs = xrealloc(dns_info->sbs, > + dns_info->n_sbs * sizeof > *dns_info->sbs); > + dns_info->sbs[dns_info->n_sbs - 1] = od->sb; > + } > + } > + > + const struct sbrec_dns *sbrec_dns, *next; > + SBREC_DNS_FOR_EACH_SAFE (sbrec_dns, next, ctx->ovnsb_idl) { > + const char *nb_dns_uuid = smap_get(&sbrec_dns->external_ids, > "dns_id"); > + struct uuid dns_uuid; > + if (!nb_dns_uuid || !uuid_from_string(&dns_uuid, nb_dns_uuid)) { > + sbrec_dns_delete(sbrec_dns); > + continue; > + } > + > + struct dns_info *dns_info = > + get_dns_info_from_hmap(&dns_map, &dns_uuid); > + if (dns_info) { > + dns_info->sb_dns = sbrec_dns; > + } else { > + sbrec_dns_delete(sbrec_dns); > + } > + } > + > + struct dns_info *dns_info; > + HMAP_FOR_EACH_POP (dns_info, hmap_node, &dns_map) { > + if (!dns_info->sb_dns) { > + struct sbrec_dns *sbrec_dns = sbrec_dns_insert(ctx->ovnsb_ > txn); > + dns_info->sb_dns = sbrec_dns; > + char *dns_id = xasprintf( > + UUID_FMT, UUID_ARGS(&dns_info->nb_dns->header_.uuid)); > + const struct smap external_ids = > + SMAP_CONST1(&external_ids, "dns_id", dns_id); > + sbrec_dns_set_external_ids(sbrec_dns, &external_ids); > + free(dns_id); > + } > + > + /* Set the datapaths and records. If nothing has changed, then > + * this will be a no-op. > + */ > + sbrec_dns_set_datapaths( > + dns_info->sb_dns, > + (struct sbrec_datapath_binding **)dns_info->sbs, > + dns_info->n_sbs); > + sbrec_dns_set_records(dns_info->sb_dns, > &dns_info->nb_dns->records); > + free(dns_info->sbs); > + free(dns_info); > + } > + hmap_destroy(&dns_map); > +} > + > > static void > ovnnb_db_run(struct northd_context *ctx, struct ovsdb_idl_loop *sb_loop) > @@ -5383,6 +5552,7 @@ ovnnb_db_run(struct northd_context *ctx, struct > ovsdb_idl_loop *sb_loop) > build_lflows(ctx, &datapaths, &ports); > > sync_address_sets(ctx); > + sync_dns_entries(ctx, &datapaths); > > struct ovn_datapath *dp, *next_dp; > HMAP_FOR_EACH_SAFE (dp, next_dp, key_node, &datapaths) { > @@ -5782,6 +5952,11 @@ main(int argc, char *argv[]) > add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_name); > add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_ > addresses); > > + ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_dns); > + add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dns_col_datapaths); > + add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dns_col_records); > + add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dns_col_external_ids); > + > ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_chassis); > ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_chassis_col_nb_cfg); > > diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema > index dd0ac3d..4662653 100644 > --- a/ovn/ovn-nb.ovsschema > +++ b/ovn/ovn-nb.ovsschema > @@ -1,7 +1,7 @@ > { > "name": "OVN_Northbound", > - "version": "5.5.0", > - "cksum": "2099428463 14236", > + "version": "5.5.1", > + "cksum": "1743459612 15019", > "tables": { > "NB_Global": { > "columns": { > @@ -45,6 +45,11 @@ > "refType": "strong"}, > "min": 0, > "max": "unlimited"}}, > + "dns_records": {"type": {"key": {"type": "uuid", > + "refTable": "DNS", > + "refType": "weak"}, > + "min": 0, > + "max": "unlimited"}}, > "other_config": { > "type": {"key": "string", "value": "string", > "min": 0, "max": "unlimited"}}, > @@ -265,6 +270,17 @@ > "max": "unlimited"}, > "ephemeral": true}}, > "indexes": [["target"]]}, > + "DNS": { > + "columns": { > + "records": {"type": {"key": "string", > + "value": "string", > + "min": 0, > + "max": "unlimited"}}, > + "external_ids": {"type": {"key": "string", > + "value": "string", > + "min": 0, > + "max": "unlimited"}}}, > + "isRoot": true}, > "SSL": { > "columns": { > "private_key": {"type": "string"}, > diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml > index 2b416ce..11e32f0 100644 > --- a/ovn/ovn-nb.xml > +++ b/ovn/ovn-nb.xml > @@ -134,6 +134,12 @@ > QOS marking rules that apply to packets within the logical switch. > </column> > > + <column name="dns_records"> > + This column defines the DNS records to be used for resolving > internal > + DNS queries within the logical switch by the native DNS resolver. > + Please see the <ref table="DNS"/> table. > + </column> > + > <group title="IP Address Assignment"> > <p> > These options control automatic IP address management (IPAM) for > ports > @@ -1471,7 +1477,7 @@ > <column name="options" key="lease_time" > type='{"type": "integer", "minInteger": 0, "maxInteger": > 4294967295}'> > <p> > - The offered lease time in seconds, > + The offered lease time in seconds, > </p> > > <p> > @@ -1963,6 +1969,24 @@ > <column name="other_config"/> > </group> > </table> > + <table name="DNS" title="Native DNS resolution"> > + <p> > + Each row in this table stores the DNS records. The > + <ref table="Logical_Switch"/> table's <ref table="Logical_Switch" > + column="dns_records"/> references these records. > + </p> > + > + <column name="records"> > + Key-value pair of DNS records with <code>hostname</code> as the key > + and value as a string of IP address(es) separated by comma or space. > + > + <p><b>Example: </b> "vm1.ovn.org" = "10.0.0.4 aef0::4"</p> > + </column> > + > + <column name="external_ids"> > + See <em>External IDs</em> at the beginning of this document. > + </column> > + </table> > <table name="SSL"> > SSL configuration for ovn-nb database access. > > @@ -2001,5 +2025,4 @@ > <column name="external_ids"/> > </group> > </table> > - > </database> > diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c > index e9dcde7..3df8b81 100644 > --- a/ovn/utilities/ovn-nbctl.c > +++ b/ovn/utilities/ovn-nbctl.c > @@ -3062,6 +3062,9 @@ static const struct ctl_table_class > tables[NBREC_N_TABLES] = { > > [NBREC_TABLE_SSL].row_ids[0] > = {&nbrec_table_nb_global, NULL, &nbrec_nb_global_col_ssl}, > + > + [NBREC_TABLE_DNS].row_ids[0] > + = {&nbrec_table_dns, NULL, &nbrec_dns_col_records}, > }; > > static void > diff --git a/tests/ovn.at b/tests/ovn.at > index 53a2dd5..b9f8114 100644 > --- a/tests/ovn.at > +++ b/tests/ovn.at > @@ -6435,6 +6435,383 @@ OVS_APP_EXIT_AND_WAIT([ovsdb-server]) > > AT_CLEANUP > > +AT_SETUP([ovn -- dns lookup : 1 HV, 2 LS, 2 LSPs/LS]) > +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 aef0::4" > + > +ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4 > aef0::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" > + > +DNS1=`ovn-nbctl create DNS records={}` > +DNS2=`ovn-nbctl create DNS records={}` > + > +ovn-nbctl set DNS $DNS1 records:vm1.ovn.org="10.0.0.4 aef0::4" > +ovn-nbctl set DNS $DNS1 records:vm2.ovn.org="10.0.0.6 20.0.0.4" > +ovn-nbctl set DNS $DNS2 records:vm3.ovn.org="40.0.0.4" > + > +ovn-nbctl set Logical_switch ls1 dns_records="$DNS1" > + > +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 > + > +ovn_populate_arp > +sleep 2 > +as hv1 ovs-vsctl show > + > +echo "*************************" > +ovn-sbctl list DNS > +echo "*************************" > + > +ip_to_hex() { > + printf "%02x%02x%02x%02x" "$@" > +} > + > +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 > +} > + > +# set_dns_params host_name > +# Sets the dns_req_data and dns_resp_data > +set_dns_params() { > + local hname=$1 > + local ttl=00000e10 > + an_count=0001 > + type=0001 > + case $hname in > + vm1) > + # vm1.ovn.org > + hostname=03766d31036f766e036f726700 > + # IPv4 address - 10.0.0.4 > + expected_dns_answer=${hostname}00010001${ttl}00040a000004 > + ;; > + vm2) > + # vm2.ovn.org > + hostname=03766d32036f766e036f726700 > + # IPv4 address - 10.0.0.6 > + expected_dns_answer=${hostname}00010001${ttl}00040a000006 > + # IPv4 address - 20.0.0.4 > + expected_dns_answer=${expected_dns_answer}${ > hostname}00010001${ttl}000414000004 > + an_count=0002 > + ;; > + vm3) > + # vm3.ovn.org > + hostname=03766d33036f766e036f726700 > + # IPv4 address - 40.0.0.4 > + expected_dns_answer=${hostname}00010001${ttl}000428000004 > + ;; > + vm1_ipv6_only) > + # vm1.ovn.org > + hostname=03766d31036f766e036f726700 > + # IPv6 address - aef0::4 > + type=001c > + expected_dns_answer=${hostname}${type}0001${ttl} > 0010aef00000000000000000000000000004 > + ;; > + vm1_ipv4_v6) > + # vm1.ovn.org > + hostname=03766d31036f766e036f726700 > + type=00ff > + an_count=0002 > + # IPv4 address - 10.0.0.4 > + # IPv6 address - aef0::4 > + expected_dns_answer=${hostname}00010001${ttl}00040a000004 > + expected_dns_answer=${expected_dns_answer}${ > hostname}001c0001${ttl}0010 > + expected_dns_answer=${expected_dns_answer} > aef00000000000000000000000000004 > + ;; > + vm1_invalid_type) > + # vm1.ovn.org > + hostname=03766d31036f766e036f726700 > + # IPv6 address - aef0::4 > + type=0002 > + ;; > + vm1_incomplete) > + # set type to none > + type='' > + esac > + # TTL - 3600 > + local dns_req_header=010201200001000000000000 > + local dns_resp_header=010281200001${an_count}00000000 > + dns_req_data=${dns_req_header}${hostname}${type}0001 > + dns_resp_data=${dns_resp_header}${hostname}${type}0001$ > {expected_dns_answer} > +} > + > +# This shell function sends a DNS request packet > +# test_dns INPORT SRC_MAC DST_MAC SRC_IP DST_IP DNS_QUERY EXPEC > +test_dns() { > + local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 dns_reply=$6 > + local dns_query_data=$7 > + shift; shift; shift; shift; shift; shift; shift; > + # Packet size => IPv4 header (20) + UDP header (8) + > + # DNS data (header + query) > + ip_len=`expr 28 + ${#dns_query_data} / 2` > + udp_len=`expr $ip_len - 20` > + ip_len=$(printf "%x" $ip_len) > + udp_len=$(printf "%x" $udp_len) > + local request=${dst_mac}${src_mac}0800450000${ip_len}0000000080110000 > + request=${request}${src_ip}${dst_ip}9234003500${udp_len}0000 > + # dns data > + request=${request}${dns_query_data} > + > + if test $dns_reply != 0; then > + local dns_reply=$1 > + ip_len=`expr 28 + ${#dns_reply} / 2` > + udp_len=`expr $ip_len - 20` > + ip_len=$(printf "%x" $ip_len) > + udp_len=$(printf "%x" $udp_len) > + local reply=${src_mac}${dst_mac}0800450000${ip_len} > 0000000080110000 > + reply=${reply}${dst_ip}${src_ip}0035923400${udp_len}0000${ > dns_reply} > + echo $reply >> $inport.expected > + else > + for outport; do > + echo $request >> $outport.expected > + done > + fi > + as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $request > +} > + > +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 > + > +set_dns_params vm2 > +src_ip=`ip_to_hex 10 0 0 4` > +dst_ip=`ip_to_hex 10 0 0 1` > +dns_reply=1 > +test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply > $dns_req_data $dns_resp_data > + > +# 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]) > + > +reset_pcap_file hv1-vif1 hv1/vif1 > +reset_pcap_file hv1-vif2 hv1/vif2 > +rm -f 1.expected > +rm -f 2.expected > + > +set_dns_params vm1 > +src_ip=`ip_to_hex 10 0 0 6` > +dst_ip=`ip_to_hex 10 0 0 1` > +dns_reply=1 > +test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply > $dns_req_data $dns_resp_data > + > +# 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 > + > +# Clear the hostname options for ls1-lp2 > +ovn-nbctl --wait=hv remove DNS $DNS1 records vm2.ovn.org > + > +set_dns_params vm2 > +src_ip=`ip_to_hex 10 0 0 4` > +dst_ip=`ip_to_hex 10 0 0 1` > +dns_reply=0 > +test_dns 1 f00000000001 f00000000002 $src_ip $dst_ip $dns_reply > $dns_req_data > + > +# 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/vif1-tx.pcap > 1.packets > +AT_CHECK([cat 1.packets], [0], []) > + > +reset_pcap_file hv1-vif1 hv1/vif1 > +reset_pcap_file hv1-vif2 hv1/vif2 > +rm -f 1.expected > +rm -f 2.expected > + > +# Clear the hostname for ls1-lp1 > +# Since ls1 has no hostnames configued, > +# ovn-northd should not add the DNS flows. > +ovn-nbctl --wait=hv remove DNS $DNS1 records vm1.ovn.org > + > +set_dns_params vm1 > +src_ip=`ip_to_hex 10 0 0 6` > +dst_ip=`ip_to_hex 10 0 0 1` > +dns_reply=0 > +test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply > $dns_req_data > + > +# NXT_RESUMEs should be 3 only. > +OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) > + > +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets > +AT_CHECK([cat 2.packets], [0], []) > + > +reset_pcap_file hv1-vif1 hv1/vif1 > +reset_pcap_file hv1-vif2 hv1/vif2 > +rm -f 1.expected > +rm -f 2.expected > + > +# Test IPv6 (AAAA records) using IPv4 packet. > +# Add back the DNS options for ls1-lp1. > +ovn-nbctl set DNS $DNS1 records:vm1.ovn.org="10.0.0.4 aef0::4" > + > +set_dns_params vm1_ipv6_only > +src_ip=`ip_to_hex 10 0 0 6` > +dst_ip=`ip_to_hex 10 0 0 1` > +dns_reply=1 > +test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply > $dns_req_data $dns_resp_data > + > +# NXT_RESUMEs should be 4. > +OVS_WAIT_UNTIL([test 4 = `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 > + > +# Test both IPv4 (A) and IPv6 (AAAA records) using IPv4 packet. > +set_dns_params vm1_ipv4_v6 > +src_ip=`ip_to_hex 10 0 0 6` > +dst_ip=`ip_to_hex 10 0 0 1` > +dns_reply=1 > +test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply > $dns_req_data $dns_resp_data > + > +# NXT_RESUMEs should be 5. > +OVS_WAIT_UNTIL([test 5 = `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 > + > +# Invalid type. > +set_dns_params vm1_invalid_type > +src_ip=`ip_to_hex 10 0 0 6` > +dst_ip=`ip_to_hex 10 0 0 1` > +dns_reply=0 > +test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply > $dns_req_data > + > +# NXT_RESUMEs should be 6. > +OVS_WAIT_UNTIL([test 6 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) > + > +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets > +AT_CHECK([cat 2.packets], [0], []) > + > +reset_pcap_file hv1-vif1 hv1/vif1 > +reset_pcap_file hv1-vif2 hv1/vif2 > +rm -f 1.expected > +rm -f 2.expected > + > +# Incomplete DNS packet. > +set_dns_params vm1_incomplete > +src_ip=`ip_to_hex 10 0 0 6` > +dst_ip=`ip_to_hex 10 0 0 1` > +dns_reply=0 > +test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply > $dns_req_data > + > +# NXT_RESUMEs should be 7. > +OVS_WAIT_UNTIL([test 7 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) > + > +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets > +AT_CHECK([cat 2.packets], [0], []) > + > +reset_pcap_file hv1-vif1 hv1/vif1 > +reset_pcap_file hv1-vif2 hv1/vif2 > +rm -f 1.expected > +rm -f 2.expected > + > +# Add one more DNS record to the ls1. > +ovn-nbctl --wait=hv set Logical_switch ls1 dns_records="$DNS1 $DNS2" > + > +set_dns_params vm3 > +src_ip=`ip_to_hex 10 0 0 4` > +dst_ip=`ip_to_hex 10 0 0 1` > +dns_reply=1 > +test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply > $dns_req_data $dns_resp_data > + > +# NXT_RESUMEs should be 8. > +OVS_WAIT_UNTIL([test 8 = `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]) > + > +reset_pcap_file hv1-vif1 hv1/vif1 > +reset_pcap_file hv1-vif2 hv1/vif2 > +rm -f 1.expected > +rm -f 2.expected > + > +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 -- 1 LR with distributed router gateway port]) > AT_SKIP_IF([test $HAVE_PYTHON = no]) > ovn_start > -- > 2.9.3 > > _______________________________________________ > dev mailing list > dev@openvswitch.org > https://mail.openvswitch.org/mailman/listinfo/ovs-dev >
diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml index ab8fd88..b7e2325 100644 --- a/ovn/northd/ovn-northd.8.xml +++ b/ovn/northd/ovn-northd.8.xml @@ -724,7 +724,71 @@ output; </li> </ul> - <h3>Ingress Table 13 Destination Lookup</h3> + <h3>Ingress Table 13 DNS Lookup</h3> + + <p> + This table looks up and resolves the DNS names of the logical ports + if configured with the host names. + </p> + + <ul> + <li> + <p> + A priority-100 logical flow for each logical switch datapath + if it is configured with DNS records, which matches the IPv4 and IPv6 + packets with <code>udp.dst</code> = 53 and applies the action + <code>dns_lookup</code> and advances the packet to the next table. + </p> + + <pre> +reg0[4] = dns_lookup(); next; + </pre> + + <p> + For valid DNS packets, this transforms the packet into a DNS + reply if the DNS name can be resolved, and stores 1 into reg0[4]. + For failed DNS resolution or other kinds of packets, it just stores + 0 into reg0[4]. Either way, it continues to the next table. + </p> + </li> + </ul> + + <h3>Ingress Table 14 DNS Responses</h3> + + <p> + This table implements DNS responder for the DNS replies generated by + the previous table. + </p> + + <ul> + <li> + <p> + A priority-100 logical flow for each logical switch datapath + if it is configured with DNS records, which matches the IPv4 and IPv6 + packets with <code>udp.dst = 53 && reg0[4] == 1</code> + and responds back to the <code>inport</code> after applying these + actions. If <code>reg0[4]</code> is set to 1, it means that the + action <code>dns_lookup</code> was successful. + </p> + + <pre> +eth.dst <-> eth.src; +ip4.src <-> ip4.dst; +udp.dst = udp.src; +udp.src = 53; +outport = <var>P</var>; +flags.loopback = 1; +output; + </pre> + + <p> + (This terminates ingress packet processing; the packet does not go + to the next ingress table.) + </p> + </li> + </ul> + + <h3>Ingress Table 15 Destination Lookup</h3> <p> This table implements switching behavior. It contains these logical @@ -834,11 +898,22 @@ output; </p> <p> - Also a priority 34000 logical flow is added for each logical port which - has DHCPv4 options defined to allow the DHCPv4 reply packet and which has - DHCPv6 options defined to allow the DHCPv6 reply packet from the - <code>Ingress Table 12: DHCP responses</code>. + Also the following flows are added. </p> + <ul> + <li> + A priority 34000 logical flow is added for each logical port which + has DHCPv4 options defined to allow the DHCPv4 reply packet and which has + DHCPv6 options defined to allow the DHCPv6 reply packet from the + <code>Ingress Table 12: DHCP responses</code>. + </li> + + <li> + A priority 34000 logical flow is added for each logical switch datapath + if it is configured with DNS records, which allows the DNS reply packet + from the <code>Ingress Table 14:DNS responses</code>. + </li> + </ul> <h3>Egress Table 7: Egress Port Security - IP</h3> diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index 027e5a1..226d05c 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -112,7 +112,9 @@ enum ovn_stage { PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 10, "ls_in_arp_rsp") \ PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 11, "ls_in_dhcp_options") \ PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 12, "ls_in_dhcp_response") \ - PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 13, "ls_in_l2_lkup") \ + PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 13, "ls_in_dns_lookup") \ + PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 14, "ls_in_dns_response") \ + PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 15, "ls_in_l2_lkup") \ \ /* Logical switch egress stages. */ \ PIPELINE_STAGE(SWITCH, OUT, PRE_LB, 0, "ls_out_pre_lb") \ @@ -160,6 +162,7 @@ enum ovn_stage { #define REGBIT_CONNTRACK_COMMIT "reg0[1]" #define REGBIT_CONNTRACK_NAT "reg0[2]" #define REGBIT_DHCP_OPTS_RESULT "reg0[3]" +#define REGBIT_DNS_LOOKUP_RESULT "reg0[4]" /* Register definitions for switches and routers. */ #define REGBIT_NAT_REDIRECT "reg9[0]" @@ -2658,6 +2661,22 @@ ip_address_and_port_from_lb_key(const char *key, char **ip_address, free(start); } +/* + * Returns true if logical switch is configured with DNS records, false + * otherwise. + */ +static bool +ls_has_dns_records(const struct nbrec_logical_switch *nbs) +{ + for (size_t i = 0; i < nbs->n_dns_records; i++) { + if (!smap_is_empty(&nbs->dns_records[i]->records)) { + return true; + } + } + + return false; +} + static void build_pre_lb(struct ovn_datapath *od, struct hmap *lflows) { @@ -2942,7 +2961,8 @@ 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*/ + * logical ports of the datapath if the CMS has configured DHCPv4 options. + * */ for (size_t i = 0; i < od->nbs->n_ports; i++) { if (od->nbs->ports[i]->dhcpv4_options) { const char *server_id = smap_get( @@ -2993,6 +3013,16 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows) } } } + + /* Add one 34000 priority flow to allow DNS reply from ovn-controller to + * if the CMS has configured DNS records for the datapath. + */ + if (ls_has_dns_records(od->nbs)) { + const char *actions = has_stateful ? "ct_commit; next;" : "next;"; + ovn_lflow_add( + lflows, od, S_SWITCH_OUT_ACL, 34000, "udp.src == 53", + actions); + } } static void @@ -3430,8 +3460,43 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } + /* Logical switch ingress table 13 and 14: DNS lookup and response + * priority 100 flows.*/ + HMAP_FOR_EACH (od, key_node, datapaths) { + if (!od->nbs || !ls_has_dns_records(od->nbs)) { + continue; + } + + struct ds match; + struct ds action; + ds_init(&match); + ds_init(&action); + ds_put_cstr(&match, "udp.dst == 53"); + ds_put_format(&action, + REGBIT_DNS_LOOKUP_RESULT" = dns_lookup(); next;"); + ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 100, + ds_cstr(&match), ds_cstr(&action)); + ds_clear(&action); + ds_put_cstr(&match, " && "REGBIT_DNS_LOOKUP_RESULT); + ds_put_format(&action, "eth.dst <-> eth.src; ip4.src <-> ip4.dst; " + "udp.dst = udp.src; udp.src = 53; outport = inport; " + "flags.loopback = 1; output;"); + ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100, + ds_cstr(&match), ds_cstr(&action)); + ds_clear(&action); + ds_put_format(&action, "eth.dst <-> eth.src; ip6.src <-> ip6.dst; " + "udp.dst = udp.src; udp.src = 53; outport = inport; " + "flags.loopback = 1; output;"); + ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100, + ds_cstr(&match), ds_cstr(&action)); + ds_destroy(&match); + ds_destroy(&action); + } + /* Ingress table 11 and 12: DHCP options and response, by default goto next. - * (priority 0). */ + * (priority 0). + * Ingress table 13 and 14: DNS lookup and response, by default goto next. + * (priority 0).*/ HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbs) { @@ -3440,9 +3505,11 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, 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;"); + ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 0, "1", "next;"); } - /* Ingress table 13: Destination lookup, broadcast and multicast handling + /* Ingress table 15: Destination lookup, broadcast and multicast handling * (priority 100). */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbsp) { @@ -5369,6 +5436,108 @@ sync_address_sets(struct northd_context *ctx) } shash_destroy(&sb_address_sets); } + +/* + * struct 'dns_info' is used to sync the DNS records between OVN Northbound db + * and Southbound db. + */ +struct dns_info { + struct hmap_node hmap_node; + const struct nbrec_dns *nb_dns; /* DNS record in the Northbound db. */ + const struct sbrec_dns *sb_dns; /* DNS record in the Soutbound db. */ + + /* Datapaths to which the DNS entry is associated with it. */ + const struct sbrec_datapath_binding **sbs; + size_t n_sbs; +}; + +static inline struct dns_info * +get_dns_info_from_hmap(struct hmap *dns_map, struct uuid *uuid) +{ + struct dns_info *dns_info; + size_t hash = uuid_hash(uuid); + HMAP_FOR_EACH_WITH_HASH (dns_info, hmap_node, hash, dns_map) { + if (uuid_equals(&dns_info->nb_dns->header_.uuid, uuid)) { + return dns_info; + } + } + + return NULL; +} + +static void +sync_dns_entries(struct northd_context *ctx, struct hmap *datapaths) +{ + struct hmap dns_map = HMAP_INITIALIZER(&dns_map); + struct ovn_datapath *od; + HMAP_FOR_EACH (od, key_node, datapaths) { + if (!od->nbs || !od->nbs->n_dns_records) { + continue; + } + + for (size_t i = 0; i < od->nbs->n_dns_records; i++) { + struct dns_info *dns_info = get_dns_info_from_hmap( + &dns_map, &od->nbs->dns_records[i]->header_.uuid); + if (!dns_info) { + size_t hash = uuid_hash( + &od->nbs->dns_records[i]->header_.uuid); + dns_info = xzalloc(sizeof *dns_info);; + dns_info->nb_dns = od->nbs->dns_records[i]; + hmap_insert(&dns_map, &dns_info->hmap_node, hash); + } + + dns_info->n_sbs++; + dns_info->sbs = xrealloc(dns_info->sbs, + dns_info->n_sbs * sizeof *dns_info->sbs); + dns_info->sbs[dns_info->n_sbs - 1] = od->sb; + } + } + + const struct sbrec_dns *sbrec_dns, *next; + SBREC_DNS_FOR_EACH_SAFE (sbrec_dns, next, ctx->ovnsb_idl) { + const char *nb_dns_uuid = smap_get(&sbrec_dns->external_ids, "dns_id"); + struct uuid dns_uuid; + if (!nb_dns_uuid || !uuid_from_string(&dns_uuid, nb_dns_uuid)) { + sbrec_dns_delete(sbrec_dns); + continue; + } + + struct dns_info *dns_info = + get_dns_info_from_hmap(&dns_map, &dns_uuid); + if (dns_info) { + dns_info->sb_dns = sbrec_dns; + } else { + sbrec_dns_delete(sbrec_dns); + } + } + + struct dns_info *dns_info; + HMAP_FOR_EACH_POP (dns_info, hmap_node, &dns_map) { + if (!dns_info->sb_dns) { + struct sbrec_dns *sbrec_dns = sbrec_dns_insert(ctx->ovnsb_txn); + dns_info->sb_dns = sbrec_dns; + char *dns_id = xasprintf( + UUID_FMT, UUID_ARGS(&dns_info->nb_dns->header_.uuid)); + const struct smap external_ids = + SMAP_CONST1(&external_ids, "dns_id", dns_id); + sbrec_dns_set_external_ids(sbrec_dns, &external_ids); + free(dns_id); + } + + /* Set the datapaths and records. If nothing has changed, then + * this will be a no-op. + */ + sbrec_dns_set_datapaths( + dns_info->sb_dns, + (struct sbrec_datapath_binding **)dns_info->sbs, + dns_info->n_sbs); + sbrec_dns_set_records(dns_info->sb_dns, &dns_info->nb_dns->records); + free(dns_info->sbs); + free(dns_info); + } + hmap_destroy(&dns_map); +} + static void ovnnb_db_run(struct northd_context *ctx, struct ovsdb_idl_loop *sb_loop) @@ -5383,6 +5552,7 @@ ovnnb_db_run(struct northd_context *ctx, struct ovsdb_idl_loop *sb_loop) build_lflows(ctx, &datapaths, &ports); sync_address_sets(ctx); + sync_dns_entries(ctx, &datapaths); struct ovn_datapath *dp, *next_dp; HMAP_FOR_EACH_SAFE (dp, next_dp, key_node, &datapaths) { @@ -5782,6 +5952,11 @@ main(int argc, char *argv[]) add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_name); add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_addresses); + ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_dns); + add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dns_col_datapaths); + add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dns_col_records); + add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dns_col_external_ids); + ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_chassis); ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_chassis_col_nb_cfg); diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema index dd0ac3d..4662653 100644 --- a/ovn/ovn-nb.ovsschema +++ b/ovn/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "5.5.0", - "cksum": "2099428463 14236", + "version": "5.5.1", + "cksum": "1743459612 15019", "tables": { "NB_Global": { "columns": { @@ -45,6 +45,11 @@ "refType": "strong"}, "min": 0, "max": "unlimited"}}, + "dns_records": {"type": {"key": {"type": "uuid", + "refTable": "DNS", + "refType": "weak"}, + "min": 0, + "max": "unlimited"}}, "other_config": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, @@ -265,6 +270,17 @@ "max": "unlimited"}, "ephemeral": true}}, "indexes": [["target"]]}, + "DNS": { + "columns": { + "records": {"type": {"key": "string", + "value": "string", + "min": 0, + "max": "unlimited"}}, + "external_ids": {"type": {"key": "string", + "value": "string", + "min": 0, + "max": "unlimited"}}}, + "isRoot": true}, "SSL": { "columns": { "private_key": {"type": "string"}, diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml index 2b416ce..11e32f0 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -134,6 +134,12 @@ QOS marking rules that apply to packets within the logical switch. </column> + <column name="dns_records"> + This column defines the DNS records to be used for resolving internal + DNS queries within the logical switch by the native DNS resolver. + Please see the <ref table="DNS"/> table. + </column> + <group title="IP Address Assignment"> <p> These options control automatic IP address management (IPAM) for ports @@ -1471,7 +1477,7 @@ <column name="options" key="lease_time" type='{"type": "integer", "minInteger": 0, "maxInteger": 4294967295}'> <p> - The offered lease time in seconds, + The offered lease time in seconds, </p> <p> @@ -1963,6 +1969,24 @@ <column name="other_config"/> </group> </table> + <table name="DNS" title="Native DNS resolution"> + <p> + Each row in this table stores the DNS records. The + <ref table="Logical_Switch"/> table's <ref table="Logical_Switch" + column="dns_records"/> references these records. + </p> + + <column name="records"> + Key-value pair of DNS records with <code>hostname</code> as the key + and value as a string of IP address(es) separated by comma or space. + + <p><b>Example: </b> "vm1.ovn.org" = "10.0.0.4 aef0::4"</p> + </column> + + <column name="external_ids"> + See <em>External IDs</em> at the beginning of this document. + </column> + </table> <table name="SSL"> SSL configuration for ovn-nb database access. @@ -2001,5 +2025,4 @@ <column name="external_ids"/> </group> </table> - </database> diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c index e9dcde7..3df8b81 100644 --- a/ovn/utilities/ovn-nbctl.c +++ b/ovn/utilities/ovn-nbctl.c @@ -3062,6 +3062,9 @@ static const struct ctl_table_class tables[NBREC_N_TABLES] = { [NBREC_TABLE_SSL].row_ids[0] = {&nbrec_table_nb_global, NULL, &nbrec_nb_global_col_ssl}, + + [NBREC_TABLE_DNS].row_ids[0] + = {&nbrec_table_dns, NULL, &nbrec_dns_col_records}, }; static void diff --git a/tests/ovn.at b/tests/ovn.at index 53a2dd5..b9f8114 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -6435,6 +6435,383 @@ OVS_APP_EXIT_AND_WAIT([ovsdb-server]) AT_CLEANUP +AT_SETUP([ovn -- dns lookup : 1 HV, 2 LS, 2 LSPs/LS]) +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 aef0::4" + +ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4 aef0::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" + +DNS1=`ovn-nbctl create DNS records={}` +DNS2=`ovn-nbctl create DNS records={}` + +ovn-nbctl set DNS $DNS1 records:vm1.ovn.org="10.0.0.4 aef0::4" +ovn-nbctl set DNS $DNS1 records:vm2.ovn.org="10.0.0.6 20.0.0.4" +ovn-nbctl set DNS $DNS2 records:vm3.ovn.org="40.0.0.4" + +ovn-nbctl set Logical_switch ls1 dns_records="$DNS1" + +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 + +ovn_populate_arp +sleep 2 +as hv1 ovs-vsctl show + +echo "*************************" +ovn-sbctl list DNS +echo "*************************" + +ip_to_hex() { + printf "%02x%02x%02x%02x" "$@" +} + +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 +} + +# set_dns_params host_name +# Sets the dns_req_data and dns_resp_data +set_dns_params() { + local hname=$1 + local ttl=00000e10 + an_count=0001 + type=0001 + case $hname in + vm1) + # vm1.ovn.org + hostname=03766d31036f766e036f726700 + # IPv4 address - 10.0.0.4 + expected_dns_answer=${hostname}00010001${ttl}00040a000004 + ;; + vm2) + # vm2.ovn.org + hostname=03766d32036f766e036f726700 + # IPv4 address - 10.0.0.6 + expected_dns_answer=${hostname}00010001${ttl}00040a000006 + # IPv4 address - 20.0.0.4 + expected_dns_answer=${expected_dns_answer}${hostname}00010001${ttl}000414000004 + an_count=0002 + ;; + vm3) + # vm3.ovn.org + hostname=03766d33036f766e036f726700 + # IPv4 address - 40.0.0.4 + expected_dns_answer=${hostname}00010001${ttl}000428000004 + ;; + vm1_ipv6_only) + # vm1.ovn.org + hostname=03766d31036f766e036f726700 + # IPv6 address - aef0::4 + type=001c + expected_dns_answer=${hostname}${type}0001${ttl}0010aef00000000000000000000000000004 + ;; + vm1_ipv4_v6) + # vm1.ovn.org + hostname=03766d31036f766e036f726700 + type=00ff + an_count=0002 + # IPv4 address - 10.0.0.4 + # IPv6 address - aef0::4 + expected_dns_answer=${hostname}00010001${ttl}00040a000004 + expected_dns_answer=${expected_dns_answer}${hostname}001c0001${ttl}0010 + expected_dns_answer=${expected_dns_answer}aef00000000000000000000000000004 + ;; + vm1_invalid_type) + # vm1.ovn.org + hostname=03766d31036f766e036f726700 + # IPv6 address - aef0::4 + type=0002 + ;; + vm1_incomplete) + # set type to none + type='' + esac + # TTL - 3600 + local dns_req_header=010201200001000000000000 + local dns_resp_header=010281200001${an_count}00000000 + dns_req_data=${dns_req_header}${hostname}${type}0001 + dns_resp_data=${dns_resp_header}${hostname}${type}0001${expected_dns_answer} +} + +# This shell function sends a DNS request packet +# test_dns INPORT SRC_MAC DST_MAC SRC_IP DST_IP DNS_QUERY EXPEC +test_dns() { + local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 dns_reply=$6 + local dns_query_data=$7 + shift; shift; shift; shift; shift; shift; shift; + # Packet size => IPv4 header (20) + UDP header (8) + + # DNS data (header + query) + ip_len=`expr 28 + ${#dns_query_data} / 2` + udp_len=`expr $ip_len - 20` + ip_len=$(printf "%x" $ip_len) + udp_len=$(printf "%x" $udp_len) + local request=${dst_mac}${src_mac}0800450000${ip_len}0000000080110000 + request=${request}${src_ip}${dst_ip}9234003500${udp_len}0000 + # dns data + request=${request}${dns_query_data} + + if test $dns_reply != 0; then + local dns_reply=$1 + ip_len=`expr 28 + ${#dns_reply} / 2` + udp_len=`expr $ip_len - 20` + ip_len=$(printf "%x" $ip_len) + udp_len=$(printf "%x" $udp_len) + local reply=${src_mac}${dst_mac}0800450000${ip_len}0000000080110000 + reply=${reply}${dst_ip}${src_ip}0035923400${udp_len}0000${dns_reply} + echo $reply >> $inport.expected + else + for outport; do + echo $request >> $outport.expected + done + fi + as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $request +} + +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 + +set_dns_params vm2 +src_ip=`ip_to_hex 10 0 0 4` +dst_ip=`ip_to_hex 10 0 0 1` +dns_reply=1 +test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data + +# 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]) + +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +rm -f 1.expected +rm -f 2.expected + +set_dns_params vm1 +src_ip=`ip_to_hex 10 0 0 6` +dst_ip=`ip_to_hex 10 0 0 1` +dns_reply=1 +test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data + +# 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 + +# Clear the hostname options for ls1-lp2 +ovn-nbctl --wait=hv remove DNS $DNS1 records vm2.ovn.org + +set_dns_params vm2 +src_ip=`ip_to_hex 10 0 0 4` +dst_ip=`ip_to_hex 10 0 0 1` +dns_reply=0 +test_dns 1 f00000000001 f00000000002 $src_ip $dst_ip $dns_reply $dns_req_data + +# 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/vif1-tx.pcap > 1.packets +AT_CHECK([cat 1.packets], [0], []) + +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +rm -f 1.expected +rm -f 2.expected + +# Clear the hostname for ls1-lp1 +# Since ls1 has no hostnames configued, +# ovn-northd should not add the DNS flows. +ovn-nbctl --wait=hv remove DNS $DNS1 records vm1.ovn.org + +set_dns_params vm1 +src_ip=`ip_to_hex 10 0 0 6` +dst_ip=`ip_to_hex 10 0 0 1` +dns_reply=0 +test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data + +# NXT_RESUMEs should be 3 only. +OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets +AT_CHECK([cat 2.packets], [0], []) + +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +rm -f 1.expected +rm -f 2.expected + +# Test IPv6 (AAAA records) using IPv4 packet. +# Add back the DNS options for ls1-lp1. +ovn-nbctl set DNS $DNS1 records:vm1.ovn.org="10.0.0.4 aef0::4" + +set_dns_params vm1_ipv6_only +src_ip=`ip_to_hex 10 0 0 6` +dst_ip=`ip_to_hex 10 0 0 1` +dns_reply=1 +test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data + +# NXT_RESUMEs should be 4. +OVS_WAIT_UNTIL([test 4 = `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 + +# Test both IPv4 (A) and IPv6 (AAAA records) using IPv4 packet. +set_dns_params vm1_ipv4_v6 +src_ip=`ip_to_hex 10 0 0 6` +dst_ip=`ip_to_hex 10 0 0 1` +dns_reply=1 +test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data + +# NXT_RESUMEs should be 5. +OVS_WAIT_UNTIL([test 5 = `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 + +# Invalid type. +set_dns_params vm1_invalid_type +src_ip=`ip_to_hex 10 0 0 6` +dst_ip=`ip_to_hex 10 0 0 1` +dns_reply=0 +test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data + +# NXT_RESUMEs should be 6. +OVS_WAIT_UNTIL([test 6 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets +AT_CHECK([cat 2.packets], [0], []) + +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +rm -f 1.expected +rm -f 2.expected + +# Incomplete DNS packet. +set_dns_params vm1_incomplete +src_ip=`ip_to_hex 10 0 0 6` +dst_ip=`ip_to_hex 10 0 0 1` +dns_reply=0 +test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data + +# NXT_RESUMEs should be 7. +OVS_WAIT_UNTIL([test 7 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets +AT_CHECK([cat 2.packets], [0], []) + +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +rm -f 1.expected +rm -f 2.expected + +# Add one more DNS record to the ls1. +ovn-nbctl --wait=hv set Logical_switch ls1 dns_records="$DNS1 $DNS2" + +set_dns_params vm3 +src_ip=`ip_to_hex 10 0 0 4` +dst_ip=`ip_to_hex 10 0 0 1` +dns_reply=1 +test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $dns_resp_data + +# NXT_RESUMEs should be 8. +OVS_WAIT_UNTIL([test 8 = `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]) + +reset_pcap_file hv1-vif1 hv1/vif1 +reset_pcap_file hv1-vif2 hv1/vif2 +rm -f 1.expected +rm -f 2.expected + +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 -- 1 LR with distributed router gateway port]) AT_SKIP_IF([test $HAVE_PYTHON = no]) ovn_start