| /** @file |
| * @brief LLMNR responder |
| * |
| * This listens to LLMNR queries and responds to them. |
| */ |
| |
| /* |
| * Copyright (c) 2018 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define LOG_MODULE_NAME net_llmnr_responder |
| #define NET_LOG_LEVEL CONFIG_LLMNR_RESPONDER_LOG_LEVEL |
| |
| #include <zephyr.h> |
| #include <init.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <errno.h> |
| #include <stdlib.h> |
| |
| #include <net/net_ip.h> |
| #include <net/net_pkt.h> |
| #include <net/dns_resolve.h> |
| #include <net/udp.h> |
| |
| #include "dns_pack.h" |
| #include "ipv6.h" |
| |
| #include "net_private.h" |
| |
| #define LLMNR_LISTEN_PORT 5355 |
| |
| #define LLMNR_TTL CONFIG_LLMNR_RESPONDER_TTL /* In seconds */ |
| |
| static struct net_context *ipv4; |
| |
| #if defined(CONFIG_NET_IPV6) |
| static struct net_context *ipv6; |
| #endif |
| |
| #define BUF_ALLOC_TIMEOUT K_MSEC(100) |
| |
| /* This value is recommended by RFC 1035 */ |
| #define DNS_RESOLVER_MAX_BUF_SIZE 512 |
| #define DNS_RESOLVER_MIN_BUF 1 |
| #define DNS_RESOLVER_BUF_CTR (DNS_RESOLVER_MIN_BUF + \ |
| CONFIG_LLMNR_RESOLVER_ADDITIONAL_BUF_CTR) |
| |
| NET_BUF_POOL_DEFINE(llmnr_dns_msg_pool, DNS_RESOLVER_BUF_CTR, |
| DNS_RESOLVER_MAX_BUF_SIZE, 0, NULL); |
| |
| #if defined(CONFIG_NET_IPV6) |
| static void create_ipv6_addr(struct sockaddr_in6 *addr) |
| { |
| addr->sin6_family = AF_INET6; |
| addr->sin6_port = htons(LLMNR_LISTEN_PORT); |
| |
| /* Well known IPv6 ff02::1:3 address */ |
| net_ipv6_addr_create(&addr->sin6_addr, |
| 0xff02, 0, 0, 0, 0, 0, 0x01, 0x03); |
| } |
| |
| static void create_ipv6_dst_addr(struct net_pkt *pkt, |
| struct sockaddr_in6 *addr) |
| { |
| struct net_udp_hdr *udp_hdr, hdr; |
| |
| udp_hdr = net_udp_get_hdr(pkt, &hdr); |
| |
| addr->sin6_family = AF_INET6; |
| addr->sin6_port = udp_hdr->src_port; |
| |
| net_ipaddr_copy(&addr->sin6_addr, &NET_IPV6_HDR(pkt)->src); |
| } |
| #endif |
| |
| #if defined(CONFIG_NET_IPV4) |
| static void create_ipv4_addr(struct sockaddr_in *addr) |
| { |
| addr->sin_family = AF_INET; |
| addr->sin_port = htons(LLMNR_LISTEN_PORT); |
| |
| /* Well known IPv4 224.0.0.252 address */ |
| addr->sin_addr.s_addr = htonl(0xE00000FC); |
| } |
| |
| static void create_ipv4_dst_addr(struct net_pkt *pkt, |
| struct sockaddr_in *addr) |
| { |
| struct net_udp_hdr *udp_hdr, hdr; |
| |
| udp_hdr = net_udp_get_hdr(pkt, &hdr); |
| |
| addr->sin_family = AF_INET; |
| addr->sin_port = udp_hdr->src_port; |
| |
| net_ipaddr_copy(&addr->sin_addr, &NET_IPV4_HDR(pkt)->src); |
| } |
| #endif |
| |
| static struct net_context *get_ctx(sa_family_t family) |
| { |
| struct net_context *ctx; |
| int ret; |
| |
| ret = net_context_get(family, SOCK_DGRAM, IPPROTO_UDP, &ctx); |
| if (ret < 0) { |
| NET_DBG("Cannot get context (%d)", ret); |
| return NULL; |
| } |
| |
| return ctx; |
| } |
| |
| static int bind_ctx(struct net_context *ctx, |
| struct sockaddr *local_addr, |
| socklen_t addrlen) |
| { |
| int ret; |
| |
| if (!ctx) { |
| return -EINVAL; |
| } |
| |
| ret = net_context_bind(ctx, local_addr, addrlen); |
| if (ret < 0) { |
| NET_DBG("Cannot bind to LLMNR %s port (%d)", |
| local_addr->sa_family == AF_INET ? |
| "IPv4" : "IPv6", ret); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| #define append(pkt, type, value) \ |
| do { \ |
| if (!net_pkt_append_##type##_timeout(pkt, value, \ |
| BUF_ALLOC_TIMEOUT)) { \ |
| ret = -ENOMEM; \ |
| goto drop; \ |
| } \ |
| } while (0) |
| |
| #define append_all(pkt, size, value) \ |
| do { \ |
| if (!net_pkt_append_all(pkt, size, value, \ |
| BUF_ALLOC_TIMEOUT)) { \ |
| ret = -ENOMEM; \ |
| goto drop; \ |
| } \ |
| } while (0) |
| |
| |
| static int setup_dns_hdr(struct net_pkt *pkt, u16_t answers, u16_t dns_id) |
| { |
| u16_t flags; |
| int ret; |
| |
| /* See RFC 1035, ch 4.1.1 and RFC 4795 ch 2.1.1 for header details */ |
| |
| flags = BIT(15); /* This is response */ |
| |
| append(pkt, be16, dns_id); |
| append(pkt, be16, flags); /* Flags and codes */ |
| append(pkt, be16, 1); /* Question count is set to 1, see |
| * (RFC 4795 ch 2.1.1) |
| */ |
| append(pkt, be16, answers); /* Answer RR count */ |
| append(pkt, be16, 0); /* Authority RR count */ |
| append(pkt, be16, 0); /* Additional RR count */ |
| |
| ret = 0; |
| drop: |
| return ret; |
| } |
| |
| static int add_question(struct net_pkt *pkt, enum dns_rr_type qtype, |
| struct net_buf *query) |
| { |
| char *dot = query->data; |
| char *prev = NULL; |
| int ret; |
| |
| while ((dot = strchr(dot, '.'))) { |
| if (!prev) { |
| prev = dot++; |
| continue; |
| } |
| |
| *prev = dot - prev - 1; |
| prev = dot++; |
| } |
| |
| if (prev) { |
| *prev = strlen(prev) - 1; |
| } |
| |
| append_all(pkt, query->len + 1, query->data); |
| append(pkt, be16, qtype); |
| append(pkt, be16, DNS_CLASS_IN); |
| |
| ret = 0; |
| drop: |
| return ret; |
| } |
| |
| static int add_answer(struct net_pkt *pkt, enum dns_rr_type qtype, |
| struct net_buf *query, u32_t ttl, |
| u16_t addr_len, const u8_t *addr) |
| { |
| int ret; |
| |
| ret = add_question(pkt, qtype, query); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| append(pkt, be32, ttl); |
| append(pkt, be16, addr_len); |
| append_all(pkt, addr_len, addr); |
| |
| ret = 0; |
| drop: |
| return ret; |
| } |
| |
| static struct net_pkt *create_answer(struct net_context *ctx, |
| sa_family_t family, |
| enum dns_rr_type qtype, |
| struct net_buf *query, |
| u16_t dns_id, |
| u16_t addr_len, const u8_t *addr) |
| { |
| struct net_pkt *pkt; |
| int ret; |
| |
| pkt = net_pkt_get_tx(ctx, BUF_ALLOC_TIMEOUT); |
| if (!pkt) { |
| return NULL; |
| } |
| |
| net_pkt_set_family(pkt, family); |
| |
| if (setup_dns_hdr(pkt, 1, dns_id) < 0) { |
| goto drop; |
| } |
| |
| ret = add_question(pkt, qtype, query); |
| if (ret < 0) { |
| goto drop; |
| } |
| |
| ret = add_answer(pkt, qtype, query, LLMNR_TTL, addr_len, addr); |
| if (ret < 0) { |
| goto drop; |
| } |
| |
| return pkt; |
| |
| drop: |
| net_pkt_unref(pkt); |
| return NULL; |
| } |
| |
| #if defined(CONFIG_NET_IPV4) |
| static const u8_t *get_ipv4_src(struct net_if *iface, struct in_addr *dst) |
| { |
| const struct in_addr *addr; |
| |
| addr = net_if_ipv4_select_src_addr(iface, dst); |
| if (!addr || net_ipv4_is_addr_unspecified(addr)) { |
| return NULL; |
| } |
| |
| return (const u8_t *)addr; |
| } |
| #endif |
| |
| #if defined(CONFIG_NET_IPV6) |
| static const u8_t *get_ipv6_src(struct net_if *iface, struct in6_addr *dst) |
| { |
| const struct in6_addr *addr; |
| |
| addr = net_if_ipv6_select_src_addr(iface, dst); |
| if (!addr || net_ipv6_is_addr_unspecified(addr)) { |
| return NULL; |
| } |
| |
| return (const u8_t *)addr; |
| } |
| #endif |
| |
| #if defined(CONFIG_NET_IPV4) |
| static int create_ipv4_answer(struct net_context *ctx, |
| struct net_pkt *pkt, |
| enum dns_rr_type qtype, |
| struct net_buf *query, |
| u16_t dns_id, |
| struct sockaddr *dst, |
| socklen_t *dst_len, |
| struct net_pkt **reply) |
| { |
| const u8_t *addr; |
| int addr_len; |
| |
| create_ipv4_dst_addr(pkt, net_sin(dst)); |
| *dst_len = sizeof(struct sockaddr_in); |
| |
| if (qtype == DNS_RR_TYPE_A) { |
| /* Select proper address according to destination */ |
| addr = get_ipv4_src(net_pkt_iface(pkt), |
| &net_sin(dst)->sin_addr); |
| if (!addr) { |
| return -ENOENT; |
| } |
| |
| addr_len = sizeof(struct in_addr); |
| |
| } else if (qtype == DNS_RR_TYPE_AAAA) { |
| #if defined(CONFIG_NET_IPV6) |
| addr = get_ipv6_src(net_pkt_iface(pkt), |
| &NET_IPV6_HDR(pkt)->src); |
| if (!addr) { |
| return -ENOENT; |
| } |
| |
| addr_len = sizeof(struct in6_addr); |
| #else |
| addr = NULL; |
| addr_len = 0; |
| #endif |
| } else { |
| return -EINVAL; |
| } |
| |
| *reply = create_answer(ctx, AF_INET, qtype, query, dns_id, addr_len, |
| addr); |
| if (!*reply) { |
| return -ENOMEM; |
| } |
| |
| net_pkt_set_ipv4_ttl(*reply, 255); |
| |
| return 0; |
| } |
| #endif /* CONFIG_NET_IPV4 */ |
| |
| #if defined(CONFIG_NET_IPV6) |
| static int create_ipv6_answer(struct net_context *ctx, |
| struct net_pkt *pkt, |
| enum dns_rr_type qtype, |
| struct net_buf *query, |
| u16_t dns_id, |
| struct sockaddr *dst, |
| socklen_t *dst_len, |
| struct net_pkt **reply) |
| { |
| const u8_t *addr; |
| int addr_len; |
| |
| create_ipv6_dst_addr(pkt, net_sin6(dst)); |
| *dst_len = sizeof(struct sockaddr_in6); |
| |
| if (qtype == DNS_RR_TYPE_AAAA) { |
| addr = get_ipv6_src(net_pkt_iface(pkt), |
| &NET_IPV6_HDR(pkt)->src); |
| if (!addr) { |
| return -ENOENT; |
| } |
| |
| addr_len = sizeof(struct in6_addr); |
| } else if (qtype == DNS_RR_TYPE_A) { |
| #if defined(CONFIG_NET_IPV4) |
| addr = get_ipv4_src(net_pkt_iface(pkt), |
| &NET_IPV4_HDR(pkt)->src); |
| if (!addr) { |
| return -ENOENT; |
| } |
| |
| addr_len = sizeof(struct in_addr); |
| #else |
| addr_len = 0; |
| #endif |
| } else { |
| return -EINVAL; |
| } |
| |
| *reply = create_answer(ctx, AF_INET6, qtype, query, dns_id, |
| addr_len, addr); |
| if (!*reply) { |
| return -ENOMEM; |
| } |
| |
| net_pkt_set_ipv6_hop_limit(*reply, 255); |
| |
| return 0; |
| } |
| #endif /* CONFIG_NET_IPV6 */ |
| |
| static int send_response(struct net_context *ctx, struct net_pkt *pkt, |
| struct net_buf *query, enum dns_rr_type qtype, |
| u16_t dns_id) |
| { |
| struct net_pkt *reply; |
| struct sockaddr dst; |
| socklen_t dst_len; |
| int ret; |
| |
| if (net_pkt_family(pkt) == AF_INET) { |
| #if defined(CONFIG_NET_IPV4) |
| ret = create_ipv4_answer(ctx, pkt, qtype, query, dns_id, |
| &dst, &dst_len, &reply); |
| if (ret < 0) { |
| return ret; |
| } |
| #else /* CONFIG_NET_IPV4 */ |
| return -EPFNOSUPPORT; |
| #endif /* CONFIG_NET_IPV4 */ |
| |
| } else if (net_pkt_family(pkt) == AF_INET6) { |
| #if defined(CONFIG_NET_IPV6) |
| ret = create_ipv6_answer(ctx, pkt, qtype, query, dns_id, |
| &dst, &dst_len, &reply); |
| if (ret < 0) { |
| return ret; |
| } |
| #else /* CONFIG_NET_IPV6 */ |
| return -EPFNOSUPPORT; |
| #endif /* CONFIG_NET_IPV6 */ |
| |
| } else { |
| /* TODO: support also service PTRs */ |
| return -EINVAL; |
| } |
| |
| ret = net_context_sendto(reply, &dst, dst_len, NULL, K_NO_WAIT, |
| NULL, NULL); |
| if (ret < 0) { |
| NET_DBG("Cannot send LLMNR reply to %s (%d)", |
| net_pkt_family(pkt) == AF_INET ? |
| log_strdup(net_sprint_ipv4_addr( |
| &net_sin(&dst)->sin_addr)) : |
| log_strdup(net_sprint_ipv6_addr( |
| &net_sin6(&dst)->sin6_addr)), |
| ret); |
| |
| net_pkt_unref(reply); |
| } |
| |
| return ret; |
| } |
| |
| static int dns_read(struct net_context *ctx, |
| struct net_pkt *pkt, |
| struct net_buf *dns_data, |
| struct dns_addrinfo *info) |
| { |
| /* Helper struct to track the dns msg received from the server */ |
| const char *hostname = net_hostname_get(); |
| int hostname_len = strlen(hostname); |
| struct net_buf *result; |
| struct dns_msg_t dns_msg; |
| u16_t dns_id = 0; |
| int data_len; |
| int queries; |
| int offset; |
| int ret; |
| |
| data_len = min(net_pkt_appdatalen(pkt), DNS_RESOLVER_MAX_BUF_SIZE); |
| offset = net_pkt_get_len(pkt) - data_len; |
| |
| /* Store the DNS query name into a temporary net_buf. This means |
| * that largest name we can resolve is CONFIG_NET_BUF_DATA_SIZE |
| * which typically is 128 bytes. This is done using net_buf so that |
| * we do not increase the stack usage of RX thread. |
| */ |
| result = net_pkt_get_data(ctx, BUF_ALLOC_TIMEOUT); |
| if (!result) { |
| ret = -ENOMEM; |
| goto quit; |
| } |
| |
| /* TODO: Instead of this temporary copy, just use the net_pkt directly. |
| */ |
| ret = net_frag_linear_copy(dns_data, pkt->frags, offset, data_len); |
| if (ret < 0) { |
| goto quit; |
| } |
| |
| dns_msg.msg = dns_data->data; |
| dns_msg.msg_size = data_len; |
| |
| ret = llmnr_unpack_query_header(&dns_msg, &dns_id); |
| if (ret < 0) { |
| ret = -EINVAL; |
| goto quit; |
| } |
| |
| queries = ret; |
| |
| NET_DBG("Received %d %s from %s (id 0x%04x)", queries, |
| queries > 1 ? "queries" : "query", |
| net_pkt_family(pkt) == AF_INET ? |
| log_strdup(net_sprint_ipv4_addr(&NET_IPV4_HDR(pkt)->src)) : |
| log_strdup(net_sprint_ipv6_addr(&NET_IPV6_HDR(pkt)->src)), |
| dns_id); |
| |
| do { |
| enum dns_rr_type qtype; |
| enum dns_class qclass; |
| |
| (void)memset(result->data, 0, net_buf_tailroom(result)); |
| result->len = 0; |
| |
| ret = dns_unpack_query(&dns_msg, result, &qtype, &qclass); |
| if (ret < 0) { |
| goto quit; |
| } |
| |
| NET_DBG("[%d] query %s/%s label %s (%d bytes)", queries, |
| qtype == DNS_RR_TYPE_A ? "A" : "AAAA", "IN", |
| log_strdup(result->data), ret); |
| |
| /* If the query matches to our hostname, then send reply */ |
| if (!strncasecmp(hostname, result->data + 1, hostname_len) && |
| (result->len - 1) >= hostname_len) { |
| NET_DBG("LLMNR query to our hostname %s", hostname); |
| send_response(ctx, pkt, result, qtype, dns_id); |
| } |
| } while (--queries); |
| |
| ret = 0; |
| |
| quit: |
| if (result) { |
| net_pkt_frag_unref(result); |
| } |
| |
| return ret; |
| } |
| |
| static void recv_cb(struct net_context *net_ctx, |
| struct net_pkt *pkt, |
| int status, |
| void *user_data) |
| { |
| struct net_context *ctx = user_data; |
| struct net_buf *dns_data = NULL; |
| struct dns_addrinfo info = { 0 }; |
| int ret; |
| |
| ARG_UNUSED(net_ctx); |
| NET_ASSERT(ctx == net_ctx); |
| |
| if (!pkt) { |
| return; |
| } |
| |
| if (status) { |
| goto quit; |
| } |
| |
| dns_data = net_buf_alloc(&llmnr_dns_msg_pool, BUF_ALLOC_TIMEOUT); |
| if (!dns_data) { |
| goto quit; |
| } |
| |
| ret = dns_read(ctx, pkt, dns_data, &info); |
| if (ret < 0 && ret != -EINVAL) { |
| NET_DBG("LLMNR read failed (%d)", ret); |
| } |
| |
| net_buf_unref(dns_data); |
| |
| quit: |
| net_pkt_unref(pkt); |
| } |
| |
| #if defined(CONFIG_NET_IPV6) |
| static void iface_ipv6_cb(struct net_if *iface, void *user_data) |
| { |
| struct in6_addr *addr = user_data; |
| int ret; |
| |
| ret = net_ipv6_mld_join(iface, addr); |
| if (ret < 0) { |
| NET_DBG("Cannot join %s IPv6 multicast group (%d)", |
| log_strdup(net_sprint_ipv6_addr(addr)), ret); |
| } |
| } |
| |
| static void setup_ipv6_addr(struct sockaddr_in6 *local_addr) |
| { |
| create_ipv6_addr(local_addr); |
| |
| net_if_foreach(iface_ipv6_cb, &local_addr->sin6_addr); |
| } |
| #endif /* CONFIG_NET_IPV6 */ |
| |
| #if defined(CONFIG_NET_IPV4) |
| static void iface_ipv4_cb(struct net_if *iface, void *user_data) |
| { |
| struct in_addr *addr = user_data; |
| struct net_if_mcast_addr *ifaddr; |
| |
| ifaddr = net_if_ipv4_maddr_add(iface, addr); |
| if (!ifaddr) { |
| NET_DBG("Cannot add IPv4 multicast address to iface %p", |
| iface); |
| } |
| } |
| |
| static void setup_ipv4_addr(struct sockaddr_in *local_addr) |
| { |
| create_ipv4_addr(local_addr); |
| |
| net_if_foreach(iface_ipv4_cb, &local_addr->sin_addr); |
| } |
| #endif /* CONFIG_NET_IPV4 */ |
| |
| static int init_listener(void) |
| { |
| int ret, ok = 0; |
| |
| #if defined(CONFIG_NET_IPV6) |
| { |
| static struct sockaddr_in6 local_addr; |
| |
| setup_ipv6_addr(&local_addr); |
| |
| ipv6 = get_ctx(AF_INET6); |
| |
| ret = bind_ctx(ipv6, (struct sockaddr *)&local_addr, |
| sizeof(local_addr)); |
| if (ret < 0) { |
| net_context_put(ipv6); |
| goto ipv6_out; |
| } |
| |
| ret = net_context_recv(ipv6, recv_cb, K_NO_WAIT, ipv6); |
| if (ret < 0) { |
| NET_WARN("Cannot receive IPv6 LLMNR data (%d)", ret); |
| net_context_put(ipv6); |
| } else { |
| ok++; |
| } |
| } |
| ipv6_out: |
| #endif /* CONFIG_NET_IPV6 */ |
| |
| #if defined(CONFIG_NET_IPV4) |
| { |
| static struct sockaddr_in local_addr; |
| |
| setup_ipv4_addr(&local_addr); |
| |
| ipv4 = get_ctx(AF_INET); |
| |
| ret = bind_ctx(ipv4, (struct sockaddr *)&local_addr, |
| sizeof(local_addr)); |
| if (ret < 0) { |
| net_context_put(ipv4); |
| goto ipv4_out; |
| } |
| |
| ret = net_context_recv(ipv4, recv_cb, K_NO_WAIT, ipv4); |
| if (ret < 0) { |
| NET_WARN("Cannot receive IPv4 LLMNR data (%d)", ret); |
| net_context_put(ipv4); |
| } else { |
| ok++; |
| } |
| } |
| ipv4_out: |
| #endif /* CONFIG_NET_IPV4 */ |
| |
| if (!ok) { |
| NET_WARN("Cannot start LLMNR responder"); |
| } |
| |
| return !ok; |
| } |
| |
| static int llmnr_responder_init(struct device *device) |
| { |
| ARG_UNUSED(device); |
| |
| return init_listener(); |
| } |
| |
| SYS_INIT(llmnr_responder_init, APPLICATION, CONFIG_LLMNR_RESPONDER_INIT_PRIO); |