Compare commits

...

2 Commits

Author SHA1 Message Date
3d5c99570f mdns: Allow TXT entries larger than 63 bytes; add configurable maximum TXT RDATA size
This can be used to fix device adoption in ESPHome, which uses a long
"package_import_url" string to advertise the location of the sourcecode
for the device's configuration.
2026-01-02 00:44:16 -07:00
c01c655c5f mdns: add some txtdata unit tests 2026-01-02 00:09:35 -07:00
6 changed files with 197 additions and 10 deletions

View File

@@ -92,9 +92,9 @@ If this call returns successfully, the following queries will be answered:
LWIP_ERROR("mdns add service txt failed\n", (res == ERR_OK), return);
}
Since a hostname struct is used for TXT storage each single item can be max
63 bytes long, and the total max length (including length bytes for each
item) is 255 bytes.
Each item is encoded as a length byte followed by the data, so each single
item can be max 255 bytes long, and the total max length (including length
bytes for each item) is defined by MDNS_TXT_RDATA_SIZE (default 255).
If your device runs a webserver on port 80, an example call might be:

View File

@@ -1272,7 +1272,7 @@ mdns_parse_pkt_known_answers(struct netif *netif, struct mdns_packet *pkt,
} else if (match & REPLY_SERVICE_TXT) {
mdns_prepare_txtdata(service);
if (service->txtdata.length == ans.rd_length &&
pbuf_memcmp(pkt->pbuf, ans.rd_offset, service->txtdata.name, ans.rd_length) == 0) {
pbuf_memcmp(pkt->pbuf, ans.rd_offset, service->txtdata.rdata, ans.rd_length) == 0) {
LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: TXT\n"));
reply->serv_replies[i] &= ~REPLY_SERVICE_TXT;
}
@@ -2050,7 +2050,7 @@ mdns_handle_response(struct mdns_packet *pkt, struct netif *netif)
} else if (ans.info.type == DNS_RRTYPE_TXT) {
mdns_prepare_txtdata(service);
if (service->txtdata.length == ans.rd_length &&
pbuf_memcmp(pkt->pbuf, ans.rd_offset, service->txtdata.name, ans.rd_length) == 0) {
pbuf_memcmp(pkt->pbuf, ans.rd_offset, service->txtdata.rdata, ans.rd_length) == 0) {
LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: response equals our own TXT record -> no conflict\n"));
conflict = 0;
}
@@ -2613,10 +2613,29 @@ mdns_resp_rename_service(struct netif *netif, u8_t slot, const char *name)
return ERR_OK;
}
/* Adds an RFC 1035 character-string to TXT RDATA. */
static err_t
mdns_txt_add_charstr(struct mdns_txtdata *txtdata, const char *value, u8_t len)
{
if (1 + len + txtdata->length > MDNS_TXT_RDATA_SIZE) {
LWIP_DEBUGF(MDNS_DEBUG, ("mdns_txt_add_charstr: adding string would exceed buffer (1+%d+%d > %d). Consider increasing MDNS_TXT_RDATA_SIZE.\n",
len, txtdata->length, MDNS_TXT_RDATA_SIZE));
return ERR_MEM;
}
txtdata->rdata[txtdata->length] = len;
txtdata->length++;
if (len) {
MEMCPY(&txtdata->rdata[txtdata->length], value, len);
txtdata->length += len;
}
return ERR_OK;
}
/**
* @ingroup mdns
* Call this function from inside the service_get_txt_fn_t callback to add text data.
* Buffer for TXT data is 256 bytes, and each field is prefixed with a length byte.
* Buffer for TXT data is MDNS_TXT_RDATA_SIZE (default 256) bytes, and each
* field is prefixed with a length byte.
* @param service The service provided to the get_txt callback
* @param txt String to add to the TXT field.
* @param txt_len Length of string
@@ -2629,7 +2648,7 @@ mdns_resp_add_service_txtitem(struct mdns_service *service, const char *txt, u8_
LWIP_ASSERT("mdns_resp_add_service_txtitem: service != NULL", service);
/* Use a mdns_domain struct to store txt chunks since it is the same encoding */
return mdns_domain_add_label(&service->txtdata, txt, txt_len);
return mdns_txt_add_charstr(&service->txtdata, txt, txt_len);
}
#if LWIP_MDNS_SEARCH

View File

@@ -62,7 +62,7 @@ static void mdns_clear_outmsg(struct mdns_outmsg *outmsg);
void
mdns_prepare_txtdata(struct mdns_service *service)
{
memset(&service->txtdata, 0, sizeof(struct mdns_domain));
memset(&service->txtdata, 0, sizeof(struct mdns_txtdata));
if (service->txt_fn) {
service->txt_fn(service, service->txt_userdata);
}
@@ -508,7 +508,7 @@ mdns_add_txt_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg,
}
LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with TXT record\n"));
return mdns_add_answer(reply, &service_instance, DNS_RRTYPE_TXT, DNS_RRCLASS_IN,
msg->cache_flush, ttl, (u8_t *) &service->txtdata.name,
msg->cache_flush, ttl, service->txtdata.rdata,
service->txtdata.length, NULL);
}

View File

@@ -88,6 +88,12 @@
#define MDNS_OUTPUT_PACKET_SIZE ((MDNS_MAX_SERVICES == 1) ? 512 : 1450)
#endif
/** The maximum size of TXT RDATA allocated for each service.
*/
#ifndef MDNS_TXT_RDATA_SIZE
# define MDNS_TXT_RDATA_SIZE 256
#endif
/** MDNS_RESP_USENETIF_EXTCALLBACK==1: register an ext_callback on the netif
* to automatically restart probing/announcing on status or address change.
*/

View File

@@ -90,10 +90,16 @@ struct mdns_request {
};
#endif
/** TXT record data */
struct mdns_txtdata {
u8_t rdata[MDNS_TXT_RDATA_SIZE];
u16_t length;
};
/** Description of a service */
struct mdns_service {
/** TXT record to answer with */
struct mdns_domain txtdata;
struct mdns_txtdata txtdata;
/** Name of service, like 'myweb' */
char name[MDNS_LABEL_MAXLEN + 1];
/** Type of service, like '_http' */

View File

@@ -35,6 +35,7 @@
#include "lwip/pbuf.h"
#include "lwip/apps/mdns.h"
#include "lwip/apps/mdns_domain.h"
#include "lwip/apps/mdns_out.h"
#include "lwip/apps/mdns_priv.h"
START_TEST(readname_basic)
@@ -876,6 +877,155 @@ START_TEST(compress_long_match)
}
END_TEST
#define TXT_STRING_1 "path=/"
#define TXT_LENGTH_1 6
#define TXT_LENSTR_1 "\006"
#define TXT_STRING_2 ""
#define TXT_LENGTH_2 0
#define TXT_LENSTR_2 "\000"
#define TXT_STRING_3 "This sentence is sixty-three bytes long, including punctuation."
#define TXT_LENGTH_3 63
#define TXT_LENSTR_3 "\077"
#define TXT_STRING_4 "This tests whether mdns_resp_add_service_txtitem can properly handle strings longer than 63 characters."
#define TXT_LENGTH_4 103
#define TXT_LENSTR_4 "\147"
START_TEST(txt_short_item)
{
const char *expected_txtdata = TXT_LENSTR_1 TXT_STRING_1;
const size_t expected_txtdata_length = 1 + TXT_LENGTH_1;
struct mdns_service service;
err_t res;
memset(&service, 0, sizeof(struct mdns_service));
mdns_prepare_txtdata(&service);
res = mdns_resp_add_service_txtitem(&service, TXT_STRING_1, TXT_LENGTH_1);
fail_unless(res == ERR_OK);
fail_unless(service.txtdata.length == expected_txtdata_length);
fail_if(memcmp(service.txtdata.rdata, expected_txtdata, expected_txtdata_length));
}
END_TEST
START_TEST(txt_empty_item)
{
const char *expected_txtdata = TXT_LENSTR_2 TXT_STRING_2;
const size_t expected_txtdata_length = 1 + TXT_LENGTH_2;
struct mdns_service service;
err_t res;
memset(&service, 0, sizeof(struct mdns_service));
mdns_prepare_txtdata(&service);
res = mdns_resp_add_service_txtitem(&service, TXT_STRING_2, TXT_LENGTH_2);
fail_unless(res == ERR_OK);
fail_unless(service.txtdata.length == expected_txtdata_length);
fail_if(memcmp(service.txtdata.rdata, expected_txtdata, expected_txtdata_length));
}
END_TEST
START_TEST(txt_long_item)
{
const char *expected_txtdata = TXT_LENSTR_4 TXT_STRING_4;
const size_t expected_txtdata_length = 1 + TXT_LENGTH_4;
struct mdns_service service;
err_t res;
memset(&service, 0, sizeof(struct mdns_service));
mdns_prepare_txtdata(&service);
res = mdns_resp_add_service_txtitem(&service, TXT_STRING_4, TXT_LENGTH_4);
fail_unless(res == ERR_OK);
fail_unless(service.txtdata.length == expected_txtdata_length);
fail_if(memcmp(service.txtdata.rdata, expected_txtdata, expected_txtdata_length));
}
END_TEST
START_TEST(txt_multiple_items)
{
const char *expected_txtdata = (
TXT_LENSTR_1
TXT_STRING_1
TXT_LENSTR_2
TXT_STRING_2
TXT_LENSTR_3
TXT_STRING_3
TXT_LENSTR_4
TXT_STRING_4
);
const size_t expected_txtdata_length = (
1 + TXT_LENGTH_1
+ 1 + TXT_LENGTH_2
+ 1 + TXT_LENGTH_3
+ 1 + TXT_LENGTH_4
);
struct mdns_service service;
err_t res;
memset(&service, 0, sizeof(struct mdns_service));
mdns_prepare_txtdata(&service);
res = mdns_resp_add_service_txtitem(&service, TXT_STRING_1, TXT_LENGTH_1);
fail_unless(res == ERR_OK); /* TXT_STRING_1 */
res = mdns_resp_add_service_txtitem(&service, TXT_STRING_2, TXT_LENGTH_2);
fail_unless(res == ERR_OK); /* TXT_STRING_1 */
res = mdns_resp_add_service_txtitem(&service, TXT_STRING_3, TXT_LENGTH_3);
fail_unless(res == ERR_OK); /* TXT_STRING_3 */
res = mdns_resp_add_service_txtitem(&service, TXT_STRING_4, TXT_LENGTH_4);
fail_unless(res == ERR_OK); /* TXT_STRING_4 */
fail_unless(service.txtdata.length == expected_txtdata_length);
fail_if(memcmp(service.txtdata.rdata, expected_txtdata, expected_txtdata_length));
}
END_TEST
START_TEST(txt_buffer_full)
{
const char *expected_txtdata = (
TXT_LENSTR_3 TXT_STRING_3
TXT_LENSTR_3 TXT_STRING_3
TXT_LENSTR_3 TXT_STRING_3
TXT_LENSTR_3 TXT_STRING_3
);
const size_t expected_txtdata_length = 256;
struct mdns_service service;
err_t res;
int i;
memset(&service, 0, sizeof(struct mdns_service));
mdns_prepare_txtdata(&service);
/* add a 64-byte string 4 times = 256 bytes */
for (i = 0; i < 4; i++) {
res = mdns_resp_add_service_txtitem(&service, TXT_STRING_3, TXT_LENGTH_3);
ck_assert_msg(res == ERR_OK,
"adding text item failed with error %d (i=%d, txtdata.length=%d)",
res, i, service.txtdata.length);
}
/* Try to add a few more strings while the buffer is full. This should fail. */
res = mdns_resp_add_service_txtitem(&service, "", 0);
fail_unless(res != ERR_OK); /* empty string */
res = mdns_resp_add_service_txtitem(&service, "path=/", 6);
fail_unless(res != ERR_OK); /* short string */
fail_unless(service.txtdata.length == expected_txtdata_length);
fail_if(memcmp(service.txtdata.rdata, expected_txtdata, expected_txtdata_length));
}
END_TEST
Suite* mdns_suite(void)
{
testfunc tests[] = {
@@ -911,6 +1061,12 @@ Suite* mdns_suite(void)
TESTFUNC(compress_2nd_label_short),
TESTFUNC(compress_jump_to_jump),
TESTFUNC(compress_long_match),
TESTFUNC(txt_short_item),
TESTFUNC(txt_empty_item),
TESTFUNC(txt_long_item),
TESTFUNC(txt_multiple_items),
TESTFUNC(txt_buffer_full),
};
return create_suite("MDNS", tests, sizeof(tests)/sizeof(testfunc), NULL, NULL);
}