Skip to content

Commit

Permalink
ovn-controller: Add 'dns_lookup' action
Browse files Browse the repository at this point in the history
This patch adds a new OVN action 'dns_lookup' to support native DNS.
ovn-controller parses this action and adds a NXT_PACKET_IN2
OF flow with 'pause' flag set.

A new table 'DNS' is added in the SB DB to look up and resolve
the DNS queries. When a valid DNS packet is received by
ovn-controller, it looks up the DNS name in the 'DNS' table
and if successful, it frames a DNS reply, resumes the packet
and stores 1 in the 1-bit subfield. If the packet is invalid
or cannot be resolved, it resumes the packet without any
modifications and stores 0 in the 1-bit subfield.

reg0[4] = dns_lookup(); next;

An upcoming patch will use this action and adds logical flows.

Signed-off-by: Numan Siddique <[email protected]>
Acked-by: Gurucharan Shetty <[email protected]>
Signed-off-by: Ben Pfaff <[email protected]>
  • Loading branch information
numansiddique authored and blp committed May 2, 2017
1 parent f7c5e0d commit ea991ad
Show file tree
Hide file tree
Showing 9 changed files with 463 additions and 9 deletions.
17 changes: 16 additions & 1 deletion include/ovn/actions.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ struct simap;
OVNACT(PUT_ND, ovnact_put_mac_bind) \
OVNACT(PUT_DHCPV4_OPTS, ovnact_put_dhcp_opts) \
OVNACT(PUT_DHCPV6_OPTS, ovnact_put_dhcp_opts) \
OVNACT(SET_QUEUE, ovnact_set_queue)
OVNACT(SET_QUEUE, ovnact_set_queue) \
OVNACT(DNS_LOOKUP, ovnact_dns_lookup)

/* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
enum OVS_PACKED_ENUM ovnact_type {
Expand Down Expand Up @@ -258,6 +259,12 @@ struct ovnact_set_queue {
uint16_t queue_id;
};

/* OVNACT_DNS_LOOKUP. */
struct ovnact_dns_lookup {
struct ovnact ovnact;
struct expr_field dst; /* 1-bit destination field. */
};

/* Internal use by the helpers below. */
void ovnact_init(struct ovnact *, enum ovnact_type, size_t len);
void *ovnact_put(struct ofpbuf *, enum ovnact_type, size_t len);
Expand Down Expand Up @@ -385,6 +392,14 @@ enum action_opcode {
* - Any number of DHCPv6 options.
*/
ACTION_OPCODE_PUT_DHCPV6_OPTS,

/* "result = dns_lookup()".
* Arguments follow the action_header, in this format:
* - A 32-bit or 64-bit OXM header designating the result field.
* - A 32-bit integer specifying a bit offset within the result field.
*
*/
ACTION_OPCODE_DNS_LOOKUP,
};

/* Header. */
Expand Down
24 changes: 24 additions & 0 deletions lib/packets.h
Original file line number Diff line number Diff line change
Expand Up @@ -1199,4 +1199,28 @@ void compose_nd_na(struct dp_packet *, const struct eth_addr eth_src,
uint32_t packet_csum_pseudoheader(const struct ip_header *);
void IP_ECN_set_ce(struct dp_packet *pkt, bool is_ipv6);

#define DNS_HEADER_LEN 12
struct dns_header {
ovs_be16 id;
uint8_t lo_flag; /* QR (1), OPCODE (4), AA (1), TC (1) and RD (1) */
uint8_t hi_flag; /* RA (1), Z (3) and RCODE (4) */
ovs_be16 qdcount; /* Num of entries in the question section. */
ovs_be16 ancount; /* Num of resource records in the answer section. */

/* Num of name server records in the authority record section. */
ovs_be16 nscount;

/* Num of resource records in the additional records section. */
ovs_be16 arcount;
};

BUILD_ASSERT_DECL(DNS_HEADER_LEN == sizeof(struct dns_header));

#define DNS_QUERY_TYPE_A 0x01
#define DNS_QUERY_TYPE_AAAA 0x1c
#define DNS_QUERY_TYPE_ANY 0xff

#define DNS_CLASS_IN 0x01
#define DNS_DEFAULT_RR_TTL 3600

#endif /* packets.h */
257 changes: 253 additions & 4 deletions ovn/controller/pinctrl.c
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,251 @@ pinctrl_handle_put_dhcpv6_opts(
}

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);

Expand Down Expand Up @@ -721,6 +965,10 @@ process_packet_in(const struct ofp_header *msg)
&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));
Expand All @@ -729,7 +977,8 @@ process_packet_in(const struct ofp_header *msg)
}

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));
Expand All @@ -741,7 +990,7 @@ pinctrl_recv(const struct ofp_header *oh, enum ofptype type)
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);
Expand Down Expand Up @@ -787,7 +1036,7 @@ pinctrl_run(struct controller_ctx *ctx, const struct lport_index *lports,
enum ofptype type;

ofptype_decode(&type, oh);
pinctrl_recv(oh, type);
pinctrl_recv(oh, type, ctx);
ofpbuf_delete(msg);
}
}
Expand Down
Loading

0 comments on commit ea991ad

Please sign in to comment.