This repository has been archived on 2025-02-01. You can view files and clone it, but cannot push or open issues or pull requests.
reprapfirmware-dc42/Libraries/Lwip/contrib/apps/mdns/mdns_responder.c
David Crocker 4250e2dc54 Version 1.10
Added chrishamm's DHCP fixes
2016-03-24 10:33:15 +00:00

666 lines
18 KiB
C

/****************************************************************//**
*
* @file mdns_responder.c
*
* @author Logan Gunthorpe <logang@deltatee.com>
*
* @brief mdns service discovery
* reqirues SO_REUSE=1 and LWIP_IGMP=1
*
* Copyright (c) Deltatee Enterprises Ltd. 2013
* All rights reserved.
*
********************************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification,are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Author: Logan Gunthorpe <logang@deltatee.com>
*
*/
#include "mdns_responder.h"
#include "lwipopts.h"
#include <lwip/src/include/lwip/udp.h>
#include <lwip/src/include/ipv4/lwip/igmp.h>
#include <lwip/src/include/lwip/debug.h>
#include <lwip/src/include/lwip/mem.h>
#ifndef MDNS_DEBUG
#define MDNS_DEBUG LWIP_DBG_OFF
#endif
#ifndef MDNS_PORT
#define MDNS_PORT 5353
#endif
#include <stdio.h>
#include <string.h>
#ifndef ip_set_option
#define ip_set_option(pcb, opt) ((pcb)->so_options |= (opt))
#endif
extern struct netif gs_net_if;
#define FLAG_RESP 0x8400
const char *all_services = "\x09_services\x07_dns-sd\x04_udp\x05local";
#define QCLASS_IN 0x0001
#define CACHE_FLUSH 0x8000
#define QTYPE_A 0x0001
#define QTYPE_PTR 0x000C
#define QTYPE_TXT 0x0010
#define QTYPE_SRV 0x0021
#define DATA_POINTER 0xC000
#define TTL 10*60
static struct mdns_state mdns_state;
static const char dotlocal[] = "\x05local";
static const int dotlocal_len = sizeof(dotlocal);
struct mdns_header {
uint16_t id;
uint16_t flags;
uint16_t questions;
uint16_t answers;
uint16_t authorities;
uint16_t additionals;
};
struct record {
uint16_t qtype;
uint16_t qclass;
uint32_t ttl;
uint16_t data_length;
char data[];
} __attribute__((__packed__));
struct srv_record {
uint16_t priority;
uint16_t weight;
uint16_t port;
char target[];
} __attribute__((__packed__));
struct mdns_state {
char *hostnames[4];
char *service_host;
const struct mdns_service *services;
int num_services;
char *txt_records;
struct udp_pcb *sendpcb;
struct netif *netif;
};
static struct pbuf *populate_header(int answers, int authorities,
int additionals)
{
struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, sizeof(struct mdns_header),
PBUF_RAM);
struct mdns_header *hdr = (struct mdns_header *) p->payload;
hdr->id = 0;
hdr->flags = htons(FLAG_RESP);
hdr->questions = 0;
hdr->answers = htons(answers);
hdr->authorities = htons(authorities);
hdr->additionals = htons(additionals);
return p;
}
static int special_strlen(const char *name)
{
int ret = 0;
while (1) {
ret++;
int x = *name++;
if (x == 0)
return ret;
if (x & 0xC0)
return ret + 1;
ret +=x;
name += x;
}
}
static int special_strcpy(char * dest, const char *name,
const struct pbuf *p)
{
int ret = 0;
int link_ret = -1;
const char *end = ((char *) p->payload) + p->len;
while (1) {
int x = *name++;
ret++;
if (name > end) {
*dest = 0;
return -1;
}
if (x & 0xC0) {
ret++;
if (link_ret < 0) link_ret = ret;
x = (x << 8) | *name++;
name = &((char *)p->payload)[x & 0x3FFF];
continue;
}
*dest++ = x;
if (x == 0)
return (link_ret >= 0) ? link_ret : ret;
ret +=x;
memcpy(dest, name, x);
name += x;
dest += x;
}
}
static uint16_t populate_record(const char *name,
uint16_t qtype, uint16_t qclass,
uint32_t ttl, const void *data,
uint16_t datalen,
struct pbuf *dest)
{
int title_len = special_strlen(name);
int msglen = title_len + sizeof(struct record) + datalen;
struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, msglen, PBUF_RAM);
memcpy(p->payload, name, title_len);
char *end = ((char *)p->payload) + title_len;
struct record *rec = (struct record *) end;
rec->qtype = htons(qtype);
rec->qclass = htons(qclass);
rec->ttl = htonl(ttl);
rec->data_length = htons(datalen);
memcpy(rec->data, data, datalen);
uint16_t ret = dest->tot_len + title_len + sizeof(*rec);
pbuf_cat(dest, p);
return htons(DATA_POINTER | ret);
}
static void send_a_record(struct mdns_state *ms, const char *name)
{
struct pbuf *hdr = populate_header(1, 0, 0);
populate_record(name, QTYPE_A, QCLASS_IN,
TTL, &ms->netif->ip_addr,
sizeof(ms->netif->ip_addr),
hdr);
udp_send(ms->sendpcb, hdr);
LWIP_DEBUGF(MDNS_DEBUG | LWIP_DBG_STATE,
("mdns: sending A response\n"));
pbuf_free(hdr);
}
static void send_all_services(struct mdns_state *ms, const char *name)
{
struct pbuf *hdr = populate_header(ms->num_services, 0, 0);
uint16_t first_ptr = htons(DATA_POINTER | hdr->len);
for (int i = 0; i < ms->num_services; i++) {
if (i != 0)
name = (char *) &first_ptr;
populate_record(name, QTYPE_PTR, QCLASS_IN,
TTL, ms->services[i].name,
strlen(ms->services[i].name)+1,
hdr);
}
udp_send(ms->sendpcb, hdr);
LWIP_DEBUGF(MDNS_DEBUG | LWIP_DBG_STATE,
("mdns: sending ALL response\n"));
pbuf_free(hdr);
}
static void send_ptr_record(struct mdns_state *ms, const char *domain,
int service)
{
struct pbuf *hdr = populate_header(1, 0, 3);
uint16_t first_ptr = htons(DATA_POINTER | hdr->len);
//PTR Record
int service_len = strlen(ms->service_host);
char service_name[service_len + 2];
strcpy(service_name, ms->service_host);
memcpy(&service_name[service_len], &first_ptr, sizeof(first_ptr));
uint16_t srv_ptr = populate_record(domain, QTYPE_PTR, QCLASS_IN,
TTL, service_name,
sizeof(service_name),
hdr);
//SRV Record
char buf[sizeof(struct srv_record) + service_len + dotlocal_len];
struct srv_record *srv_rec = (struct srv_record *) buf;
srv_rec->priority = htons(50);
srv_rec->weight = htons(0);
srv_rec->port = htons(ms->services[service].port);
memcpy(srv_rec->target, ms->service_host, service_len);
memcpy(srv_rec->target + service_len, dotlocal, dotlocal_len);
uint16_t arec_ptr = populate_record((char *)&srv_ptr, QTYPE_SRV,
QCLASS_IN, TTL,
buf, sizeof(buf),
hdr);
arec_ptr = htons(ntohs(arec_ptr) + sizeof(struct srv_record));
//TXT Record
populate_record((char *) &srv_ptr, QTYPE_TXT,
QCLASS_IN, TTL,
ms->txt_records, strlen(ms->txt_records) + 1,
hdr);
//A Record
populate_record((char *) &srv_ptr, QTYPE_A,
QCLASS_IN, TTL,
&ms->netif->ip_addr,
sizeof(ms->netif->ip_addr),
hdr);
udp_send(ms->sendpcb, hdr);
LWIP_DEBUGF(MDNS_DEBUG | LWIP_DBG_STATE,
("mdns: sending PTR response\n"));
pbuf_free(hdr);
}
static void send_srv_record(struct mdns_state *ms, const char *domain,
int service)
{
struct pbuf *hdr = populate_header(1, 0, 2);
uint16_t first_ptr = htons(DATA_POINTER | hdr->len);
//SRV Record
int service_len = strlen(ms->service_host);
char buf[sizeof(struct srv_record) + service_len + dotlocal_len];
struct srv_record *srv_rec = (struct srv_record *) buf;
srv_rec->priority = htons(50);
srv_rec->weight = htons(0);
srv_rec->port = htons(ms->services[service].port);
memcpy(srv_rec->target, ms->service_host, service_len);
memcpy(srv_rec->target + service_len, dotlocal, dotlocal_len);
uint16_t arec_ptr = populate_record(domain, QTYPE_SRV,
QCLASS_IN, TTL,
buf, sizeof(buf),
hdr);
arec_ptr = htons(ntohs(arec_ptr) + sizeof(struct srv_record));
//TXT Record
const char txt[1] = "";
populate_record((char *) &first_ptr, QTYPE_TXT,
QCLASS_IN, TTL,
txt, sizeof(txt),
hdr);
//A Record
populate_record((char *) &arec_ptr, QTYPE_A,
QCLASS_IN, TTL,
&ms->netif->ip_addr,
sizeof(ms->netif->ip_addr),
hdr);
udp_send(ms->sendpcb, hdr);
LWIP_DEBUGF(MDNS_DEBUG | LWIP_DBG_STATE,
("mdns: sending SRV response\n"));
pbuf_free(hdr);
}
static void send_rev_record(struct mdns_state *ms, const char *domain)
{
struct pbuf *hdr = populate_header(1, 0, 0);
int service_len = strlen(ms->service_host);
char service_name[service_len + dotlocal_len];
strcpy(service_name, ms->service_host);
strcat(service_name, dotlocal);
populate_record(domain, QTYPE_PTR, QCLASS_IN,
TTL, service_name,
sizeof(service_name),
hdr);
udp_send(ms->sendpcb, hdr);
LWIP_DEBUGF(MDNS_DEBUG | LWIP_DBG_STATE,
("mdns: sending REV PTR response\n"));
pbuf_free(hdr);
}
static int query_hostname(struct mdns_state *ms, const char *domain)
{
for (int i = 0; i < sizeof(ms->hostnames) / sizeof(*ms->hostnames); i++) {
if (strcasecmp(domain, ms->hostnames[i]) == 0)
return 1;
}
return 0;
}
static int query_ptr(struct mdns_state *ms, const char *domain)
{
for (int i = 0; i < ms->num_services; i++) {
if (strcasecmp(domain, ms->services[i].name) == 0)
return i;
}
return -1;
}
static int query_service(struct mdns_state *ms, const char *domain)
{
int hostlen = strlen(ms->service_host);
if (!strncasecmp(domain, ms->service_host, hostlen))
return -1;
return query_ptr(ms, &domain[hostlen]);
}
static int compare_reverse_ptr(struct mdns_state *ms, char *buf)
{
buf++;
if (strtol(buf, &buf, 10) != ip4_addr4(&ms->netif->ip_addr))
return 0;
buf++;
if (strtol(buf, &buf, 10) != ip4_addr3(&ms->netif->ip_addr))
return 0;
buf++;
if (strtol(buf, &buf, 10) != ip4_addr2(&ms->netif->ip_addr))
return 0;
buf++;
if (strtol(buf, &buf, 10) != ip4_addr1(&ms->netif->ip_addr))
return 0;
if(strcmp(buf, "\x07in-addr\04arpa") == 0)
return 1;
return 0;
}
static int parse_question(struct mdns_state *ms, struct udp_pcb *upcb,
char *data, int qlen, struct pbuf *p)
{
int offset = 0;
char buf[255];
offset = special_strcpy(buf, data, p);
if (offset < 0)
return -1;
int qtype = (data[offset] << 8) | data[offset+1];
offset += 2;
int qclass = (data[offset] << 8) | data[offset+1];
(void)qclass; // dc42 suppress 'unused' warning when debug is disabled
offset += 2;
LWIP_DEBUGF(MDNS_DEBUG | LWIP_DBG_STATE,
("mdns: question '%s' type %d class %d\n",
&buf[1], qtype, qclass));
if (qtype == QTYPE_A) {
if (query_hostname(ms, buf))
send_a_record(ms, buf);
} else if (qtype == QTYPE_PTR) {
if (strcasecmp(all_services, buf) == 0) {
send_all_services(ms, buf);
} else if (compare_reverse_ptr(ms, buf)) {
send_rev_record(ms, buf);
} else {
int service;
if ((service = query_ptr(ms, buf)) >= 0)
send_ptr_record(ms, buf, service);
}
} else if (qtype == QTYPE_SRV || qtype == QTYPE_TXT) {
int service;
if ((service = query_service(ms, buf)) >= 0)
send_srv_record(ms, buf, service);
}
return offset;
}
static void recv(void *arg, struct udp_pcb *upcb, struct pbuf *p,
ip_addr_t *addr, u16_t port)
{
struct mdns_state *ms = (struct mdns_state *) arg;
struct mdns_header *h = (struct mdns_header *) p->payload;
char *questions = (char *) &h[1];
int qlen = p->len - sizeof(*h);
h->flags = ntohs(h->flags);
h->questions = ntohs(h->questions);
LWIP_DEBUGF(MDNS_DEBUG | LWIP_DBG_STATE,
("mdns: packet from "));
ip_addr_debug_print(MDNS_DEBUG | LWIP_DBG_STATE, addr);
LWIP_DEBUGF(MDNS_DEBUG | LWIP_DBG_STATE,
(" %04x %04x\n", h->flags, h->questions));
if (h->id != 0 || h->questions == 0)
goto free_and_return;
for (int i = 0; i < h->questions; i++) {
int offset = parse_question(ms, upcb, questions, qlen, p);
if (offset < 0)
goto free_and_return;
questions += offset;
qlen -= offset;
if (qlen <= 0)
break;
}
free_and_return:
pbuf_free(p);
}
static void free_hostnames(struct mdns_state *ms)
{
for (int i = 0; i < sizeof(ms->hostnames) / sizeof(*ms->hostnames); i++) {
if (ms->hostnames[i] != NULL) {
mem_free(ms->hostnames[i]);
ms->hostnames[i] = NULL;
}
}
if (ms->service_host != NULL) {
mem_free(ms->service_host);
ms->service_host = NULL;
}
}
static void setup_hostnames(struct mdns_state *ms, struct netif *netif)
{
int hostlen = strlen(netif->hostname);
ms->hostnames[0] = mem_malloc(hostlen + dotlocal_len + 1);
sprintf(ms->hostnames[0], "%c%s%s", hostlen, netif->hostname,
dotlocal);
ms->hostnames[1] = mem_malloc(hostlen + 2 + dotlocal_len + 2);
sprintf(ms->hostnames[1], "%c%s-%02X%s", hostlen+3, netif->hostname,
netif->hwaddr[5], dotlocal);
char macaddr[12];
sprintf(macaddr, "%02X%02X%02X%02X%02X%02X",
netif->hwaddr[0], netif->hwaddr[1], netif->hwaddr[2],
netif->hwaddr[3], netif->hwaddr[4], netif->hwaddr[5]);
int maclen = strlen(macaddr);
ms->hostnames[2] = mem_malloc(hostlen + maclen + dotlocal_len + 2);
sprintf(ms->hostnames[2], "%c%s-%s%s", maclen+hostlen+1, netif->hostname,
macaddr, dotlocal);
ms->hostnames[3] = mem_malloc(strlen(macaddr) + dotlocal_len + 1);
sprintf(ms->hostnames[3], "%c%s%s", maclen, macaddr, dotlocal);
for (int i = 0; i < sizeof(ms->hostnames) / sizeof(*ms->hostnames); i++) {
LWIP_DEBUGF(MDNS_DEBUG | LWIP_DBG_STATE,
("mdns: hostname registered: %s\n",
&ms->hostnames[i][1]));
}
ms->service_host = mem_malloc(hostlen + maclen + 3);
sprintf(ms->service_host, "%c%s-%s", maclen+hostlen+1, netif->hostname,
macaddr);
}
static void setup_txt_records(struct mdns_state *ms, const char *txt_records[])
{
int totlen = 0;
for (const char **rec = txt_records; *rec != NULL; rec++)
totlen += 1 + strlen(*rec);
ms->txt_records = mem_malloc(totlen+1);
char *t = ms->txt_records;
for (const char **rec = txt_records; *rec != NULL; rec++) {
int l = strlen(*rec);
*t++ = l;
strcpy(t, *rec);
t += l;
}
}
err_t mdns_responder_init(const struct mdns_service *services,
int num_services,
const char *txt_records[])
{
err_t ret;
memset(&mdns_state, 0, sizeof(mdns_state));
mdns_state.netif = &gs_net_if;
setup_hostnames(&mdns_state, &gs_net_if);
setup_txt_records(&mdns_state, txt_records);
mdns_state.services = services;
mdns_state.num_services = num_services;
struct ip_addr ipgroup;
IP4_ADDR(&ipgroup, 224, 0, 0, 251);
mdns_state.sendpcb = udp_new();
if (mdns_state.sendpcb == NULL)
return ERR_MEM;
struct udp_pcb *pcb = udp_new();
if (pcb == NULL) {
udp_remove(mdns_state.sendpcb);
return ERR_MEM;
}
if ((ret = igmp_joingroup(IP_ADDR_ANY, &ipgroup)) != ERR_OK)
return ret;
ip_set_option(pcb, SOF_REUSEADDR);
ip_set_option(mdns_state.sendpcb, SOF_REUSEADDR);
if ((ret = udp_bind(pcb, IP_ADDR_ANY, MDNS_PORT)) != ERR_OK)
goto error_exit;
udp_recv(pcb, recv, &mdns_state);
if ((ret = udp_bind(mdns_state.sendpcb, 0, MDNS_PORT)) != ERR_OK)
goto error_exit;
if ((ret = udp_connect(mdns_state.sendpcb, &ipgroup, MDNS_PORT)) != ERR_OK)
goto error_exit;
return ERR_OK;
error_exit:
udp_remove(pcb);
udp_remove(mdns_state.sendpcb);
return ret;
}
void mdns_update_hostname()
{
free_hostnames(&mdns_state);
setup_hostnames(&mdns_state, &gs_net_if);
}
void mdns_announce()
{
struct mdns_state *ms = &mdns_state;
int count = sizeof(ms->hostnames) / sizeof(*ms->hostnames);
struct pbuf *hdr = populate_header(count, 0, 0);
for (int i = 0; i < count; i++) {
populate_record(ms->hostnames[i], QTYPE_A, QCLASS_IN | CACHE_FLUSH,
TTL, &ms->netif->ip_addr,
sizeof(ms->netif->ip_addr),
hdr);
}
udp_send(ms->sendpcb, hdr);
LWIP_DEBUGF(MDNS_DEBUG | LWIP_DBG_STATE,
("mdns: sending announcment\n"));
pbuf_free(hdr);
}