blob: 816cf6e066567f503be208241d318213c1c78818 [file] [log] [blame]
/** @file
* @brief LLMNR responder
*
* This listens to LLMNR queries and responds to them.
*/
/*
* Copyright (c) 2018 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_llmnr_responder, CONFIG_LLMNR_RESPONDER_LOG_LEVEL);
#include <zephyr/kernel.h>
#include <zephyr/init.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <stdlib.h>
#include <zephyr/net/net_ip.h>
#include <zephyr/net/net_pkt.h>
#include <zephyr/net/dns_resolve.h>
#include <zephyr/net/udp.h>
#include <zephyr/net/igmp.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 */
#if defined(CONFIG_NET_IPV4)
static struct net_context *ipv4;
static struct sockaddr_in local_addr4;
#endif
#if defined(CONFIG_NET_IPV6)
static struct net_context *ipv6;
#endif
static struct net_mgmt_event_callback mgmt_cb;
#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 2
#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);
if (!udp_hdr) {
return;
}
addr->sin6_family = AF_INET6;
addr->sin6_port = udp_hdr->src_port;
net_ipv6_addr_copy_raw((uint8_t *)&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);
if (!udp_hdr) {
NET_ERR("could not get UDP header");
return;
}
addr->sin_family = AF_INET;
addr->sin_port = udp_hdr->src_port;
net_ipv4_addr_copy_raw((uint8_t *)&addr->sin_addr, NET_IPV4_HDR(pkt)->src);
}
#endif
static void llmnr_iface_event_handler(struct net_mgmt_event_callback *cb,
uint32_t mgmt_event, struct net_if *iface)
{
if (mgmt_event == NET_EVENT_IF_UP) {
#if defined(CONFIG_NET_IPV4)
int ret = net_ipv4_igmp_join(iface, &local_addr4.sin_addr);
if (ret < 0) {
NET_DBG("Cannot add IPv4 multicast address to iface %p",
iface);
}
#endif /* defined(CONFIG_NET_IPV4) */
}
}
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;
}
static void setup_dns_hdr(uint8_t *buf, uint16_t answers, uint16_t dns_id)
{
uint16_t offset;
uint16_t flags;
/* See RFC 1035, ch 4.1.1 and RFC 4795 ch 2.1.1 for header details */
flags = BIT(15); /* This is response */
UNALIGNED_PUT(htons(dns_id), (uint16_t *)(buf));
offset = DNS_HEADER_ID_LEN;
UNALIGNED_PUT(htons(flags), (uint16_t *)(buf+offset));
offset += DNS_HEADER_FLAGS_LEN;
UNALIGNED_PUT(htons(1), (uint16_t *)(buf + offset));
offset += DNS_QDCOUNT_LEN;
UNALIGNED_PUT(htons(answers), (uint16_t *)(buf + offset));
offset += DNS_ANCOUNT_LEN;
UNALIGNED_PUT(0, (uint16_t *)(buf + offset));
offset += DNS_NSCOUNT_LEN;
UNALIGNED_PUT(0, (uint16_t *)(buf + offset));
}
static void add_question(struct net_buf *query, enum dns_rr_type qtype)
{
char *dot = query->data + DNS_MSG_HEADER_SIZE;
char *prev = NULL;
uint16_t offset;
while ((dot = strchr(dot, '.'))) {
if (!prev) {
prev = dot++;
continue;
}
*prev = dot - prev - 1;
prev = dot++;
}
if (prev) {
*prev = strlen(prev) - 1;
}
offset = DNS_MSG_HEADER_SIZE + query->len + 1;
UNALIGNED_PUT(htons(qtype), (uint16_t *)(query->data+offset));
offset += DNS_QTYPE_LEN;
UNALIGNED_PUT(htons(DNS_CLASS_IN), (uint16_t *)(query->data+offset));
}
static int add_answer(struct net_buf *query, uint32_t ttl,
uint16_t addr_len, const uint8_t *addr)
{
const uint16_t q_len = query->len + 1 + DNS_QTYPE_LEN + DNS_QCLASS_LEN;
uint16_t offset = DNS_MSG_HEADER_SIZE + q_len;
memcpy(query->data + offset, query->data + DNS_MSG_HEADER_SIZE, q_len);
offset += q_len;
UNALIGNED_PUT(htonl(ttl), query->data + offset);
offset += DNS_TTL_LEN;
UNALIGNED_PUT(htons(addr_len), query->data + offset);
offset += DNS_RDLENGTH_LEN;
memcpy(query->data + offset, addr, addr_len);
return offset + addr_len;
}
static int create_answer(struct net_context *ctx,
enum dns_rr_type qtype,
struct net_buf *query,
uint16_t dns_id,
uint16_t addr_len, const uint8_t *addr)
{
/* Prepare the response into the query buffer: move the name
* query buffer has to get enough free space: dns_hdr + query + answer
*/
if ((query->size - query->len) < (DNS_MSG_HEADER_SIZE +
(DNS_QTYPE_LEN + DNS_QCLASS_LEN) * 2 +
DNS_TTL_LEN + DNS_RDLENGTH_LEN +
addr_len + query->len)) {
return -ENOBUFS;
}
memmove(query->data + DNS_MSG_HEADER_SIZE, query->data, query->len);
setup_dns_hdr(query->data, 1, dns_id);
add_question(query, qtype);
query->len = add_answer(query, LLMNR_TTL, addr_len, addr);
return 0;
}
#if defined(CONFIG_NET_IPV4)
static const uint8_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 uint8_t *)addr;
}
#endif
#if defined(CONFIG_NET_IPV6)
static const uint8_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 uint8_t *)addr;
}
#endif
#if defined(CONFIG_NET_IPV4)
static int create_ipv4_answer(struct net_context *ctx,
struct net_pkt *pkt,
union net_ip_header *ip_hdr,
enum dns_rr_type qtype,
struct net_buf *query,
uint16_t dns_id,
struct sockaddr *dst,
socklen_t *dst_len)
{
const uint8_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),
(struct in6_addr *)ip_hdr->ipv6->src);
if (!addr) {
return -ENOENT;
}
addr_len = sizeof(struct in6_addr);
#else
addr = NULL;
addr_len = 0;
#endif
} else {
return -EINVAL;
}
if (create_answer(ctx, qtype, query, dns_id, addr_len, addr)) {
return -ENOMEM;
}
net_context_set_ipv4_ttl(ctx, 255);
return 0;
}
#endif /* CONFIG_NET_IPV4 */
static int create_ipv6_answer(struct net_context *ctx,
struct net_pkt *pkt,
union net_ip_header *ip_hdr,
enum dns_rr_type qtype,
struct net_buf *query,
uint16_t dns_id,
struct sockaddr *dst,
socklen_t *dst_len)
{
#if defined(CONFIG_NET_IPV6)
const uint8_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),
(struct in6_addr *)ip_hdr->ipv6->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),
(struct in_addr *)ip_hdr->ipv4->src);
if (!addr) {
return -ENOENT;
}
addr_len = sizeof(struct in_addr);
#else
addr_len = 0;
#endif
} else {
return -EINVAL;
}
if (create_answer(ctx, qtype, query, dns_id, addr_len, addr)) {
return -ENOMEM;
}
net_context_set_ipv6_hop_limit(ctx, 255);
#endif /* CONFIG_NET_IPV6 */
return 0;
}
static int send_response(struct net_context *ctx, struct net_pkt *pkt,
union net_ip_header *ip_hdr, struct net_buf *reply,
enum dns_rr_type qtype, uint16_t dns_id)
{
struct sockaddr dst;
socklen_t dst_len;
int ret;
if (IS_ENABLED(CONFIG_NET_IPV4) &&
net_pkt_family(pkt) == AF_INET) {
ret = create_ipv4_answer(ctx, pkt, ip_hdr, qtype, reply,
dns_id, &dst, &dst_len);
if (ret < 0) {
return ret;
}
} else if (IS_ENABLED(CONFIG_NET_IPV6) &&
net_pkt_family(pkt) == AF_INET6) {
ret = create_ipv6_answer(ctx, pkt, ip_hdr, qtype, reply,
dns_id, &dst, &dst_len);
if (ret < 0) {
return ret;
}
} else {
/* TODO: support also service PTRs */
return -EPFNOSUPPORT;
}
ret = net_context_sendto(ctx, reply->data, reply->len, &dst,
dst_len, NULL, K_NO_WAIT, NULL);
if (ret < 0) {
NET_DBG("Cannot send LLMNR reply to %s (%d)",
net_pkt_family(pkt) == AF_INET ?
net_sprint_ipv4_addr(&net_sin(&dst)->sin_addr) :
net_sprint_ipv6_addr(&net_sin6(&dst)->sin6_addr),
ret);
}
return ret;
}
static int dns_read(struct net_context *ctx,
struct net_pkt *pkt,
union net_ip_header *ip_hdr,
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;
uint16_t dns_id = 0U;
int data_len;
int queries;
int ret;
data_len = MIN(net_pkt_remaining_data(pkt), DNS_RESOLVER_MAX_BUF_SIZE);
/* Store the DNS query name into a temporary net_buf, which will be
* eventually used to send a response
*/
result = net_buf_alloc(&llmnr_dns_msg_pool, BUF_ALLOC_TIMEOUT);
if (!result) {
ret = -ENOMEM;
goto quit;
}
/* TODO: Instead of this temporary copy, just use the net_pkt directly.
*/
ret = net_pkt_read(pkt, dns_data->data, 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 ?
net_sprint_ipv4_addr(&ip_hdr->ipv4->src) :
net_sprint_ipv6_addr(&ip_hdr->ipv6->src),
dns_id);
do {
enum dns_rr_type qtype;
enum dns_class qclass;
(void)memset(result->data, 0, result->size);
result->len = 0U;
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",
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);
ret = send_response(ctx, pkt, ip_hdr, result, qtype,
dns_id);
if (ret < 0) {
NET_DBG("Cannot send response (%d)", ret);
}
}
} while (--queries);
ret = 0;
quit:
if (result) {
net_buf_unref(result);
}
return ret;
}
static void recv_cb(struct net_context *net_ctx,
struct net_pkt *pkt,
union net_ip_header *ip_hdr,
union net_proto_header *proto_hdr,
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, ip_hdr, 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)",
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;
int ret;
ret = net_ipv4_igmp_join(iface, addr);
if (ret < 0) {
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)
{
setup_ipv4_addr(&local_addr4);
ipv4 = get_ctx(AF_INET);
ret = bind_ctx(ipv4, (struct sockaddr *)&local_addr4,
sizeof(local_addr4));
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(const struct device *dev)
{
ARG_UNUSED(dev);
net_mgmt_init_event_callback(&mgmt_cb, llmnr_iface_event_handler,
NET_EVENT_IF_UP);
net_mgmt_add_event_callback(&mgmt_cb);
return init_listener();
}
SYS_INIT(llmnr_responder_init, APPLICATION, CONFIG_LLMNR_RESPONDER_INIT_PRIO);