}
static void
-process_packet_in(const struct ofp_header *msg)
+put_be16(struct ofpbuf *buf, ovs_be16 x)
+{
+ ofpbuf_put(buf, &x, sizeof x);
+}
+
+static void
+put_be32(struct ofpbuf *buf, ovs_be32 x)
+{
+ ofpbuf_put(buf, &x, sizeof x);
+}
+
+static void
+pinctrl_handle_dns_lookup(
+ struct dp_packet *pkt_in, struct ofputil_packet_in *pin,
+ struct ofpbuf *userdata, struct ofpbuf *continuation,
+ struct controller_ctx *ctx)
+{
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ enum ofp_version version = rconn_get_version(swconn);
+ enum ofputil_protocol proto = ofputil_protocol_from_ofp_version(version);
+ struct dp_packet *pkt_out_ptr = NULL;
+ uint32_t success = 0;
+
+ /* Parse result field. */
+ const struct mf_field *f;
+ enum ofperr ofperr = nx_pull_header(userdata, NULL, &f, NULL);
+ if (ofperr) {
+ VLOG_WARN_RL(&rl, "bad result OXM (%s)", ofperr_to_string(ofperr));
+ goto exit;
+ }
+
+ /* Parse result offset. */
+ ovs_be32 *ofsp = ofpbuf_try_pull(userdata, sizeof *ofsp);
+ if (!ofsp) {
+ VLOG_WARN_RL(&rl, "offset not present in the userdata");
+ goto exit;
+ }
+
+ /* Check that the result is valid and writable. */
+ struct mf_subfield dst = { .field = f, .ofs = ntohl(*ofsp), .n_bits = 1 };
+ ofperr = mf_check_dst(&dst, NULL);
+ if (ofperr) {
+ VLOG_WARN_RL(&rl, "bad result bit (%s)", ofperr_to_string(ofperr));
+ goto exit;
+ }
+
+ /* Extract the DNS header */
+ struct dns_header const *in_dns_header = dp_packet_get_udp_payload(pkt_in);
+
+ /* Check if it is DNS request or not */
+ if (in_dns_header->lo_flag & 0x80) {
+ /* It's a DNS response packet which we are not interested in */
+ goto exit;
+ }
+
+ /* Check if at least one query request is present */
+ if (!in_dns_header->qdcount) {
+ goto exit;
+ }
+
+ struct udp_header *in_udp = dp_packet_l4(pkt_in);
+ size_t udp_len = ntohs(in_udp->udp_len);
+ size_t l4_len = dp_packet_l4_size(pkt_in);
+ uint8_t *end = (uint8_t *)in_udp + MIN(udp_len, l4_len);
+ uint8_t *in_dns_data = (uint8_t *)(in_dns_header + 1);
+ uint8_t *in_queryname = in_dns_data;
+ uint8_t idx = 0;
+ struct ds query_name;
+ ds_init(&query_name);
+ /* Extract the query_name. If the query name is - 'www.ovn.org' it would be
+ * encoded as (in hex) - 03 77 77 77 03 6f 76 63 03 6f 72 67 00.
+ */
+ while ((in_dns_data + idx) < end && in_dns_data[idx]) {
+ uint8_t label_len = in_dns_data[idx++];
+ if (in_dns_data + idx + label_len > end) {
+ ds_destroy(&query_name);
+ goto exit;
+ }
+ ds_put_buffer(&query_name, (const char *) in_dns_data + idx, label_len);
+ idx += label_len;
+ ds_put_char(&query_name, '.');
+ }
+
+ idx++;
+ ds_chomp(&query_name, '.');
+ in_dns_data += idx;
+
+ /* Query should have TYPE and CLASS fields */
+ if (in_dns_data + (2 * sizeof(ovs_be16)) > end) {
+ ds_destroy(&query_name);
+ goto exit;
+ }
+
+ uint16_t query_type = ntohs(*ALIGNED_CAST(const ovs_be16 *, in_dns_data));
+ /* Supported query types - A, AAAA and ANY */
+ if (!(query_type == DNS_QUERY_TYPE_A || query_type == DNS_QUERY_TYPE_AAAA
+ || query_type == DNS_QUERY_TYPE_ANY)) {
+ ds_destroy(&query_name);
+ goto exit;
+ }
+
+ uint64_t dp_key = ntohll(pin->flow_metadata.flow.metadata);
+ const struct sbrec_dns *sbrec_dns;
+ const char *answer_ips = NULL;
+ SBREC_DNS_FOR_EACH(sbrec_dns, ctx->ovnsb_idl) {
+ for (size_t i = 0; i < sbrec_dns->n_datapaths; i++) {
+ if (sbrec_dns->datapaths[i]->tunnel_key == dp_key) {
+ answer_ips = smap_get(&sbrec_dns->records,
+ ds_cstr(&query_name));
+ if (answer_ips) {
+ break;
+ }
+ }
+ }
+
+ if (answer_ips) {
+ break;
+ }
+ }
+
+ ds_destroy(&query_name);
+ if (!answer_ips) {
+ goto exit;
+ }
+
+ struct lport_addresses ip_addrs;
+ if (!extract_ip_addresses(answer_ips, &ip_addrs)) {
+ goto exit;
+ }
+
+ uint16_t ancount = 0;
+ uint64_t dns_ans_stub[128 / 8];
+ struct ofpbuf dns_answer = OFPBUF_STUB_INITIALIZER(dns_ans_stub);
+
+ if (query_type == DNS_QUERY_TYPE_A || query_type == DNS_QUERY_TYPE_ANY) {
+ for (size_t i = 0; i < ip_addrs.n_ipv4_addrs; i++) {
+ /* Copy the answer section */
+ /* Format of the answer section is
+ * - NAME -> The domain name
+ * - TYPE -> 2 octets containing one of the RR type codes
+ * - CLASS -> 2 octets which specify the class of the data
+ * in the RDATA field.
+ * - TTL -> 32 bit unsigned int specifying the time
+ * interval (in secs) that the resource record
+ * may be cached before it should be discarded.
+ * - RDLENGTH -> 16 bit integer specifying the length of the
+ * RDATA field.
+ * - RDATA -> a variable length string of octets that
+ * describes the resource. In our case it will
+ * be IP address of the domain name.
+ */
+ ofpbuf_put(&dns_answer, in_queryname, idx);
+ put_be16(&dns_answer, htons(DNS_QUERY_TYPE_A));
+ put_be16(&dns_answer, htons(DNS_CLASS_IN));
+ put_be32(&dns_answer, htonl(DNS_DEFAULT_RR_TTL));
+ put_be16(&dns_answer, htons(sizeof(ovs_be32)));
+ put_be32(&dns_answer, ip_addrs.ipv4_addrs[i].addr);
+ ancount++;
+ }
+ }
+
+ if (query_type == DNS_QUERY_TYPE_AAAA ||
+ query_type == DNS_QUERY_TYPE_ANY) {
+ for (size_t i = 0; i < ip_addrs.n_ipv6_addrs; i++) {
+ ofpbuf_put(&dns_answer, in_queryname, idx);
+ put_be16(&dns_answer, htons(DNS_QUERY_TYPE_AAAA));
+ put_be16(&dns_answer, htons(DNS_CLASS_IN));
+ put_be32(&dns_answer, htonl(DNS_DEFAULT_RR_TTL));
+ const struct in6_addr *ip6 = &ip_addrs.ipv6_addrs[i].addr;
+ put_be16(&dns_answer, htons(sizeof *ip6));
+ ofpbuf_put(&dns_answer, ip6, sizeof *ip6);
+ ancount++;
+ }
+ }
+
+ destroy_lport_addresses(&ip_addrs);
+
+ if (!ancount) {
+ ofpbuf_uninit(&dns_answer);
+ goto exit;
+ }
+
+ uint16_t new_l4_size = ntohs(in_udp->udp_len) + dns_answer.size;
+ size_t new_packet_size = pkt_in->l4_ofs + new_l4_size;
+ struct dp_packet pkt_out;
+ dp_packet_init(&pkt_out, new_packet_size);
+ dp_packet_clear(&pkt_out);
+ dp_packet_prealloc_tailroom(&pkt_out, new_packet_size);
+ pkt_out_ptr = &pkt_out;
+
+ /* Copy the L2 and L3 headers from the pkt_in as they would remain same.*/
+ dp_packet_put(
+ &pkt_out, dp_packet_pull(pkt_in, pkt_in->l4_ofs), pkt_in->l4_ofs);
+
+ pkt_out.l2_5_ofs = pkt_in->l2_5_ofs;
+ pkt_out.l2_pad_size = pkt_in->l2_pad_size;
+ pkt_out.l3_ofs = pkt_in->l3_ofs;
+ pkt_out.l4_ofs = pkt_in->l4_ofs;
+
+ struct udp_header *out_udp = dp_packet_put(
+ &pkt_out, dp_packet_pull(pkt_in, UDP_HEADER_LEN), UDP_HEADER_LEN);
+
+ /* Copy the DNS header. */
+ struct dns_header *out_dns_header = dp_packet_put(
+ &pkt_out, dp_packet_pull(pkt_in, sizeof *out_dns_header),
+ sizeof *out_dns_header);
+
+ /* Set the response bit to 1 in the flags. */
+ out_dns_header->lo_flag |= 0x80;
+
+ /* Set the answer RR. */
+ out_dns_header->ancount = htons(ancount);
+
+ /* Copy the Query section. */
+ dp_packet_put(&pkt_out, dp_packet_data(pkt_in), dp_packet_size(pkt_in));
+
+ /* Copy the answer sections. */
+ dp_packet_put(&pkt_out, dns_answer.data, dns_answer.size);
+ ofpbuf_uninit(&dns_answer);
+
+ out_udp->udp_len = htons(new_l4_size);
+ out_udp->udp_csum = 0;
+
+ struct ip_header *out_ip = dp_packet_l3(&pkt_out);
+ out_ip->ip_tot_len = htons(pkt_out.l4_ofs - pkt_out.l3_ofs + new_l4_size);
+ /* Checksum needs to be initialized to zero. */
+ out_ip->ip_csum = 0;
+ out_ip->ip_csum = csum(out_ip, sizeof *out_ip);
+
+ pin->packet = dp_packet_data(&pkt_out);
+ pin->packet_len = dp_packet_size(&pkt_out);
+
+ success = 1;
+exit:
+ if (!ofperr) {
+ union mf_subvalue sv;
+ sv.u8_val = success;
+ mf_write_subfield(&dst, &sv, &pin->flow_metadata);
+ }
+ queue_msg(ofputil_encode_resume(pin, continuation, proto));
+ dp_packet_uninit(pkt_out_ptr);
+}
+
+static void
+process_packet_in(const struct ofp_header *msg, struct controller_ctx *ctx)
{
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
&continuation);
break;
+ case ACTION_OPCODE_DNS_LOOKUP:
+ pinctrl_handle_dns_lookup(&packet, &pin, &userdata, &continuation, ctx);
+ break;
+
default:
VLOG_WARN_RL(&rl, "unrecognized packet-in opcode %"PRIu32,
ntohl(ah->opcode));
}
static void
-pinctrl_recv(const struct ofp_header *oh, enum ofptype type)
+pinctrl_recv(const struct ofp_header *oh, enum ofptype type,
+ struct controller_ctx *ctx)
{
if (type == OFPTYPE_ECHO_REQUEST) {
queue_msg(make_echo_reply(oh));
config.miss_send_len = UINT16_MAX;
set_switch_config(swconn, &config);
} else if (type == OFPTYPE_PACKET_IN) {
- process_packet_in(oh);
+ process_packet_in(oh, ctx);
} else if (type != OFPTYPE_ECHO_REPLY && type != OFPTYPE_BARRIER_REPLY) {
if (VLOG_IS_DBG_ENABLED()) {
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300);
enum ofptype type;
ofptype_decode(&type, oh);
- pinctrl_recv(oh, type);
+ pinctrl_recv(oh, type, ctx);
ofpbuf_delete(msg);
}
}
ovnact_set_queue_free(struct ovnact_set_queue *a OVS_UNUSED)
{
}
+
+static void
+parse_dns_lookup(struct action_context *ctx, const struct expr_field *dst,
+ struct ovnact_dns_lookup *dl)
+{
+ lexer_get(ctx->lexer); /* Skip dns_lookup. */
+ lexer_get(ctx->lexer); /* Skip '('. */
+ if (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
+ lexer_error(ctx->lexer, "dns_lookup doesn't take any parameters");
+ return;
+ }
+ /* Validate that the destination is a 1-bit, modifiable field. */
+ char *error = expr_type_check(dst, 1, true);
+ if (error) {
+ lexer_error(ctx->lexer, "%s", error);
+ free(error);
+ return;
+ }
+ dl->dst = *dst;
+ add_prerequisite(ctx, "udp");
+}
+
+static void
+format_DNS_LOOKUP(const struct ovnact_dns_lookup *dl, struct ds *s)
+{
+ expr_field_format(&dl->dst, s);
+ ds_put_cstr(s, " = dns_lookup();");
+}
+
+static void
+encode_DNS_LOOKUP(const struct ovnact_dns_lookup *dl,
+ const struct ovnact_encode_params *ep OVS_UNUSED,
+ struct ofpbuf *ofpacts)
+{
+ struct mf_subfield dst = expr_resolve_field(&dl->dst);
+
+ size_t oc_offset = encode_start_controller_op(ACTION_OPCODE_DNS_LOOKUP,
+ true, ofpacts);
+ nx_put_header(ofpacts, dst.field->id, OFP13_VERSION, false);
+ ovs_be32 ofs = htonl(dst.ofs);
+ ofpbuf_put(ofpacts, &ofs, sizeof ofs);
+ encode_finish_controller_op(oc_offset, ofpacts);
+}
+
+
+static void
+ovnact_dns_lookup_free(struct ovnact_dns_lookup *dl OVS_UNUSED)
+{
+}
\f
/* Parses an assignment or exchange or put_dhcp_opts action. */
static void
&& lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
parse_put_dhcp_opts(ctx, &lhs, ovnact_put_PUT_DHCPV6_OPTS(
ctx->ovnacts));
+ } else if (!strcmp(ctx->lexer->token.s, "dns_lookup")
+ && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
+ parse_dns_lookup(ctx, &lhs, ovnact_put_DNS_LOOKUP(ctx->ovnacts));
} else {
parse_assignment_action(ctx, false, &lhs);
}
transmitted and received with reasonable performance. It is a hint
to senders transmitting data to this chassis that they should use
checksums to protect OVN metadata. <code>ovn-controller</code>
- populates this key with the value defined in
+ populates this key with the value defined in
<ref table="Open_vSwitch" column="external_ids:ovn-encap-csum"/> column
of the Open_vSwitch database's <ref table="Open_vSwitch"
db="Open_vSwitch"/> table. Other applications should treat this key as
packet in that connection.
</p>
</dd>
+
+ <dt>
+ <code><var>R</var> = dns_lookup();</code>
+ </dt>
+
+ <dd>
+ <p>
+ <b>Parameters</b>: No parameters.
+ </p>
+
+ <p>
+ <b>Result</b>: stored to a 1-bit subfield <var>R</var>.
+ </p>
+
+ <p>
+ Valid only in the ingress pipeline.
+ </p>
+
+ <p>
+ When this action is applied to a valid DNS request (a UDP packet
+ typically directed to port 53), it attempts to resolve the query
+ using the contents of the <ref table="DNS"/> table. If it is
+ successful, it changes the packet into a DNS reply and stores 1 in
+ <var>R</var>. If the action is applied to a non-DNS packet, an
+ invalid DNS request packet, or a valid DNS request for which the
+ <ref table="DNS"/> table does not supply an answer, it leaves the
+ packet unchanged and stores 0 in <var>R</var>.
+ </p>
+
+ <p>
+ Regardless of success, the action does not make any of the changes
+ to the flow that are necessary to direct the packet back to the
+ requester. The logical pipeline can implement this behavior with
+ matches and actions in later tables.
+ </p>
+
+ <p>
+ <b>Example:</b>
+ <code>
+ reg0[3] = dns_lookup();
+ </code>
+ </p>
+
+ <p>
+ <b>Prerequisite:</b> <code>udp</code>
+ </p>
+ </dd>
</dl>
<p>
<column name="external_ids"/>
</group>
</table>
+ <table name="DNS" title="Native DNS resolution">
+ <p>
+ Each row in this table stores the DNS records. The OVN action
+ <code>dns_lookup</code> uses this table for DNS resolution.
+ </p>
+
+ <column name="records">
+ Key-value pair of DNS records with <code>DNS query name</code> as the key
+ and a string of IP address(es) separated by comma or space as the
+ value.
+
+ <p><b>Example: </b> "vm1.ovn.org" = "10.0.0.4 aef0::4"</p>
+ </column>
+
+ <column name="datapaths">
+ The DNS records defined in the column <ref column="records"/> will be
+ applied only to the DNS queries originating from the datapaths defined
+ in this column.
+ </column>
+
+ <group title="Common Columns">
+ <column name="external_ids">
+ See <em>External IDs</em> at the beginning of this document.
+ </column>
+ </group>
+ </table>
</database>