/* * Basic multicast DNS responder * * Advertises the IP address, port, and characteristics of a service to other devices using multicast DNS on the same LAN, * so they can find devices with addresses dynamically allocated by DHCP. See avahi, Bonjour, etc * See RFC6762, RFC6763 * * This sample code is in the public domain. * * by M J A Hamel 2016 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mdnsresponder.h" #define qDebugLog // Log activity generally #define qLogIncoming // Log all arriving multicast packets #define qLogAllTraffic // Log and decode all mDNS packets #define kMDNSStackSize 800 #define DNS_MULTICAST_ADDRESS "224.0.0.251" // RFC 6762 #define DNS_MDNS_PORT 5353 // RFC 6762 #define DNS_MSG_SIZE 512 //------------------------------------------------------------------- #ifdef PACK_STRUCT_USE_INCLUDES # include "arch/bpstruct.h" #endif PACK_STRUCT_BEGIN /** DNS message header */ struct mdns_hdr { PACK_STRUCT_FIELD(u16_t id); PACK_STRUCT_FIELD(u8_t flags1); PACK_STRUCT_FIELD(u8_t flags2); PACK_STRUCT_FIELD(u16_t numquestions); PACK_STRUCT_FIELD(u16_t numanswers); PACK_STRUCT_FIELD(u16_t numauthrr); PACK_STRUCT_FIELD(u16_t numextrarr); }PACK_STRUCT_STRUCT; PACK_STRUCT_END #define SIZEOF_DNS_HDR 12 PACK_STRUCT_BEGIN /** MDNS query message structure */ struct mdns_query { /* MDNS query record starts with either a domain name or a pointer to a name already present somewhere in the packet. */ PACK_STRUCT_FIELD(u16_t type); PACK_STRUCT_FIELD(u16_t class); }PACK_STRUCT_STRUCT; PACK_STRUCT_END #define SIZEOF_DNS_QUERY 4 PACK_STRUCT_BEGIN /** MDNS answer message structure */ struct mdns_answer { /* MDNS answer record starts with either a domain name or a pointer to a name already present somewhere in the packet. */ PACK_STRUCT_FIELD(u16_t type); PACK_STRUCT_FIELD(u16_t class); PACK_STRUCT_FIELD(u32_t ttl); PACK_STRUCT_FIELD(u16_t len); }PACK_STRUCT_STRUCT; PACK_STRUCT_END #define SIZEOF_DNS_ANSWER 10 PACK_STRUCT_BEGIN struct mdns_rr_srv { /* RR SRV */ PACK_STRUCT_FIELD(u16_t prio); PACK_STRUCT_FIELD(u16_t weight); PACK_STRUCT_FIELD(u16_t port); }PACK_STRUCT_STRUCT; PACK_STRUCT_END #define SIZEOF_DNS_RR_SRV 6 #ifdef PACK_STRUCT_USE_INCLUDES # include "arch/epstruct.h" #endif #define vTaskDelayMs(ms) vTaskDelay((ms)/portTICK_PERIOD_MS) #define UNUSED_ARG(x) (void)x #define kDummyDataSize 8 // arbitrary, dynamically resized #define kMaxNameSize 64 #define kMaxQStr 128 // max incoming question key handled typedef struct mdns_rsrc { struct mdns_rsrc* rNext; u16_t rType; u32_t rTTL; u16_t rKeySize; u16_t rDataSize; char rData[kDummyDataSize]; // Key, as C str with . seperators, followed by data in network-ready form // at rData[rKeySize] } mdns_rsrc; static struct udp_pcb* gMDNS_pcb = NULL; static ip_addr_t gMulticastAddr; // == DNS_MULTICAST_ADDRESS static mdns_rsrc* gDictP = NULL; // RR database, linked list //---------------------- Debug/logging utilities ------------------------- #ifdef qDebugLog // DNS field TYPE used for "Resource Records", some additions #define DNS_RRTYPE_AAAA 28 /* IPv6 host address */ #define DNS_RRTYPE_SRV 33 /* Service record */ #define DNS_RRTYPE_OPT 41 /* EDNS0 OPT record */ #define DNS_RRTYPE_NSEC 47 /* NSEC record */ #define DNS_RRTYPE_TSIG 250 /* Transaction Signature */ #define DNS_RRTYPE_ANY 255 /* Not a DNS type, but a DNS query type, meaning "all types"*/ // DNS field CLASS used for "Resource Records" #define DNS_RRCLASS_ANY 255 /* Any class (q) */ #define DNS_FLAG1_RESP 0x80 #define DNS_FLAG1_OPMASK 0x78 #define DNS_FLAG1_AUTH 0x04 #define DNS_FLAG1_TRUNC 0x02 #define DNS_FLAG1_RD 0x01 #define DNS_FLAG2_RA 0x80 #define DNS_FLAG2_RESMASK 0x0F static char qstr[12]; static char* mdns_qrtype(uint16_t typ) { switch(typ) { case DNS_RRTYPE_A : return ("A"); case DNS_RRTYPE_NS : return ("NS"); case DNS_RRTYPE_PTR : return ("PTR"); case DNS_RRTYPE_TXT : return ("TXT "); case DNS_RRTYPE_AAAA : return ("AAAA"); case DNS_RRTYPE_SRV : return ("SRV "); case DNS_RRTYPE_NSEC : return ("NSEC "); case DNS_RRTYPE_ANY : return ("ANY"); } sprintf(qstr,"type %d",typ); return qstr; } #ifdef qLogAllTraffic static void mdns_printhex(u8_t* p, int n) { int i; for (i=0; i",n); n = 0; } else { for (i=0; i0); return (u8_t*)cp; } static u8_t* mdns_print_header(struct mdns_hdr* hdr) { if (hdr->flags1 & DNS_FLAG1_RESP) { printf("Response, ID $%X %s ", htons(hdr->id), (hdr->flags1 & DNS_FLAG1_AUTH) ? "Auth " : "Non-auth "); if (hdr->flags2 & DNS_FLAG2_RA) printf("RA "); if ((hdr->flags2 & DNS_FLAG2_RESMASK)==0) printf("noerr"); else printf("err %d", hdr->flags2 & DNS_FLAG2_RESMASK); } else { printf("Query, ID $%X op %d", htons(hdr->id), (hdr->flags1>>4) & 0x7 ); } if (hdr->flags1 & DNS_FLAG1_RD) printf("RD "); if (hdr->flags1 & DNS_FLAG1_TRUNC) printf("[TRUNC] "); printf(": %d questions", htons(hdr->numquestions) ); if (hdr->numanswers != 0) printf(", %d answers",htons(hdr->numanswers)); if (hdr->numauthrr != 0) printf(", %d auth RR",htons(hdr->numauthrr)); if (hdr->numextrarr != 0) printf(", %d extra RR",htons(hdr->numextrarr)); putchar('\n'); return (u8_t*)hdr + SIZEOF_DNS_HDR; } static u8_t* mdns_print_query(u8_t* p) // Copy needed because it may be misaligned { struct mdns_query q; uint16_t c; memcpy(&q,p,SIZEOF_DNS_QUERY); c = htons(q.class); printf(" %s %s", mdns_qrtype(htons(q.type)), mdns_qclass(c & 0x7FFF) ); if (c & 0x8000) printf(" unicast-req"); printf("\n"); return p + SIZEOF_DNS_QUERY; } static u8_t* mdns_print_answer(u8_t* p, struct mdns_hdr* hp) // Copy needed because it may be misaligned { struct mdns_answer ans; u16_t rrlen, atype, rrClass;; memcpy(&ans,p,SIZEOF_DNS_ANSWER); atype = htons(ans.type); rrlen = htons(ans.len); rrClass = htons(ans.class); printf(" %s %s TTL %d ", mdns_qrtype(atype), mdns_qclass(rrClass & 0x7FFF), htonl(ans.ttl)); if (rrClass & 0x8000) printf("cache-flush "); if (rrlen > 0) { u8_t* rp = p + SIZEOF_DNS_ANSWER; if (atype==DNS_RRTYPE_A && rrlen==4) { printf("%d.%d.%d.%d\n",rp[0],rp[1],rp[2],rp[3]); } else if (atype==DNS_RRTYPE_PTR) { mdns_print_name(rp, hp); printf("\n"); } else if (atype==DNS_RRTYPE_TXT) { mdns_print_pstr(rp); printf("\n"); } else if (atype==DNS_RRTYPE_SRV && rrlen > SIZEOF_DNS_RR_SRV) { struct mdns_rr_srv srvRR; memcpy(&srvRR,rp,SIZEOF_DNS_RR_SRV); printf("prio %d, weight %d, port %d, target ", srvRR.prio, srvRR.weight, srvRR.port); mdns_print_name(rp + SIZEOF_DNS_RR_SRV, hp); printf("\n"); } else { printf("%db:",rrlen); mdns_printhex(rp,rrlen); } } else printf("\n"); return p + SIZEOF_DNS_ANSWER + rrlen; } static int mdns_print_msg(u8_t* msgP, int msgLen) { int i; u8_t* tp; u8_t* limP = msgP + msgLen; struct mdns_hdr* hdr; hdr = (struct mdns_hdr*) msgP; tp = mdns_print_header(hdr); for (i=0; inumquestions); i++) { printf(" Q%d: ",i+1); tp = mdns_print_name(tp,hdr); tp = mdns_print_query(tp); if (tp > limP) return 0; } for (i=0; inumanswers); i++) { printf(" A%d: ",i+1); tp = mdns_print_name(tp,hdr); tp = mdns_print_answer(tp,hdr); if (tp > limP) return 0; } for (i=0; inumauthrr); i++) { printf(" AuRR%d: ",i+1); tp = mdns_print_name(tp,hdr); tp = mdns_print_answer(tp,hdr); if (tp > limP) return 0; } for (i=0; inumextrarr); i++) { printf(" ExRR%d: ",i+1); tp = mdns_print_name(tp,hdr); tp = mdns_print_answer(tp,hdr); if (tp > limP) return 0; } return 1; } #endif #endif // qDebugLog //--------------------------------------------------------------------------- static u8_t* mdns_labels2str(u8_t* hdrP, u8_t* p, char* qStr) // Convert a DNS domain name label sequence into C string with . seperators // Handles compression { int i, n; do { n = *p++; if ((n & 0xC0) == 0xC0) { n = (n & 0x3F) << 8; n |= (u8_t)*p++; mdns_labels2str( hdrP, hdrP + n, qStr); return p; } else if (n & 0xC0) { printf(">>> mdns_labels2str,label $%X?",n); return p; } else { for (i=0; i0); return p; } static int mdns_str2labels(const char* name, u8_t* lseq, int max) // Encode a .. as a sequence of labels, return length { int i,n,sdx,idx = 0; int lc = 0; do { sdx = idx; while (name[idx] != '.' && name[idx] != 0) idx++; n = idx-sdx; *lseq++ = n; lc++; if (lc+n > max) { printf(">>> mdns_str2labels: oversize (%d)\n",lc+n); return 0; } for (i=0; i0); return lc; } static u8_t* mdns_get_question(u8_t* hdrP, u8_t* qp, char* qStr, uint16_t* qClass, uint16_t* qType, u8_t* qUnicast) // Unpack a DNS question RR at qp, return pointer to next RR { struct mdns_query qr; uint16_t cls; qp = mdns_labels2str(hdrP, qp, qStr); memcpy(&qr,qp,SIZEOF_DNS_QUERY); *qType = htons(qr.type); cls = htons(qr.class); *qUnicast = cls>>15; *qClass = cls & 0x7FFF; return qp + SIZEOF_DNS_QUERY; } //--------------------------------------------------------------------------- static void mdns_add_response(const char* vKey, u16_t vType, u32_t ttl, const void* dataP, u16_t vDataSize) // Add a record to the RR database list { mdns_rsrc* rsrcP; int keyLen, recSize; keyLen = strlen(vKey) + 1; recSize = sizeof(mdns_rsrc) - kDummyDataSize + keyLen + vDataSize; rsrcP = (mdns_rsrc*)malloc(recSize); if (rsrcP==NULL) printf(">>> mdns_add_response: couldn't alloc %d\n",recSize); else { rsrcP->rType = vType; rsrcP->rTTL = ttl; rsrcP->rKeySize = keyLen; rsrcP->rDataSize = vDataSize; memcpy(rsrcP->rData, vKey, keyLen); memcpy(&rsrcP->rData[keyLen], dataP, vDataSize); rsrcP->rNext = gDictP; gDictP = rsrcP; #ifdef qDebugLog printf("mDNS added RR '%s' %s, %d bytes\n", vKey, mdns_qrtype(vType), vDataSize); #endif } } void mdns_add_PTR(const char* rKey, u32_t ttl, const char* nmStr) { int nl; u8_t lBuff[kMaxNameSize]; nl = mdns_str2labels(nmStr,lBuff,sizeof(lBuff)); if (nl>0) mdns_add_response(rKey, DNS_RRTYPE_PTR, ttl, lBuff, nl); } void mdns_add_SRV(const char* rKey, u32_t ttl, u16_t rPort, const char* targName) { typedef struct SrvRec { struct mdns_rr_srv srvRR; u8_t lBuff[kMaxNameSize]; } __attribute__((packed)) SrvRec; int nl; SrvRec temp; temp.srvRR.prio = 0; temp.srvRR.weight = 0; temp.srvRR.port = htons(rPort); nl = mdns_str2labels(targName,temp.lBuff,sizeof(temp.lBuff)); if (nl>0) mdns_add_response(rKey, DNS_RRTYPE_SRV, ttl, &temp, SIZEOF_DNS_RR_SRV + nl); } void mdns_add_TXT(const char* rKey, u32_t ttl, const char* txStr) // Single TXT str, can be concatenated { char pstr[256]; u16_t n = strlen(txStr); if (n > 255) printf(">>> mdns_add_TXT oversize (%d)\n",n); else { pstr[0] = n; memcpy(&pstr[1],txStr,n); mdns_add_response(rKey, DNS_RRTYPE_TXT, ttl, txStr, n+1); } } void mdns_add_A(const char* rKey, u32_t ttl, ip_addr_t addr) { mdns_add_response(rKey, DNS_RRTYPE_A, ttl, &addr, sizeof(addr)); } void mdns_add_facility( const char* instanceName, // Friendly name, need not be unique const char* serviceName, // Must be "name", e.g. "hap" or "http" const char* addText, // Must be = mdns_flags flags, // TCP or UDP u16_t onPort, // port number u32_t ttl // seconds ) { char key[64]; char fullName[128]; char devName[96]; struct ip_info ipInfo; #ifdef qDebugLog printf("\nmDNS advertising instance %s protocol %s text %s on port %d %s TTL %d secs\n", instanceName, serviceName, addText, onPort, (flags & mdns_UDP) ? "UDP" : "TCP", ttl); #endif snprintf(key, sizeof(key), "%s.%s.local.", serviceName, (flags & mdns_UDP) ? "_udp" :"_tcp"); snprintf(fullName, sizeof(fullName), "%s.%s", instanceName, key); snprintf(devName, sizeof(devName), "%s.local.", instanceName); if (!sdk_wifi_get_ip_info(STATION_IF,&ipInfo)) ipInfo.ip.addr = IPADDR_NONE; // Order has significance for extraRR feature mdns_add_TXT(fullName, ttl, addText); mdns_add_A(devName, ttl, ipInfo.ip); mdns_add_SRV(fullName, ttl, onPort, devName); mdns_add_PTR(key, ttl, fullName); // Optional, makes us browsable if (flags & mdns_Browsable) mdns_add_PTR("_services._dns-sd._udp.local.",ttl,key); } static void mdns_update_ipaddr(struct ip_info* ipInfo) // IP address has been defined/changed: update any A records with the new IP { mdns_rsrc* rp = gDictP; while (rp != NULL) { if (rp->rType==DNS_RRTYPE_A) { #ifdef qDebugLog printf("Updating A record for '%s' to %d.%d.%d.%d\n", rp->rData, ip4_addr1(&ipInfo->ip), ip4_addr2(&ipInfo->ip), ip4_addr3(&ipInfo->ip), ip4_addr4(&ipInfo->ip)); #endif memcpy(&rp->rData[rp->rKeySize], &ipInfo->ip, sizeof(ip_addr_t)); } rp = rp->rNext; } } static mdns_rsrc* mdns_match(const char* qstr, u16_t qType) { mdns_rsrc* rp = gDictP; while (rp != NULL) { if (rp->rType==qType || qType==DNS_RRTYPE_ANY) { if (strcasecmp(rp->rData,qstr)==0) { #ifdef qDebugLog printf(" - matched '%s' %s\n",qstr,mdns_qrtype(rp->rType)); #endif break; } } rp = rp->rNext; } return rp; } static int mdns_add_to_answer(mdns_rsrc* rsrcP, u8_t* resp, int respLen) // Create answer RR and append to resp[respLen], return new length { // Key is stored as C str, convert to labels respLen += mdns_str2labels(rsrcP->rData, &resp[respLen], DNS_MSG_SIZE-respLen); // Answer fields: may be misaligned, so build and memcpy struct mdns_answer ans; ans.type = htons(rsrcP->rType); ans.class = htons(DNS_RRCLASS_IN); ans.ttl = htonl(rsrcP->rTTL); ans.len = htons(rsrcP->rDataSize); memcpy(&resp[respLen], &ans, SIZEOF_DNS_ANSWER); respLen += SIZEOF_DNS_ANSWER; // Data for this key memcpy(&resp[respLen], &rsrcP->rData[rsrcP->rKeySize], rsrcP->rDataSize); respLen += rsrcP->rDataSize; return respLen; } //--------------------------------------------------------------------------- static void mdns_send_mcast(u8_t* msgP, int nBytes) // Send UDP to multicast address { struct pbuf* p; err_t err; p = pbuf_alloc(PBUF_TRANSPORT, nBytes, PBUF_RAM); if (p) { memcpy(p->payload, msgP, nBytes); err = udp_sendto(gMDNS_pcb, p, &gMulticastAddr, DNS_MDNS_PORT); if (err==ERR_OK) { #ifdef qDebugLog printf(" - responded with %d bytes err %d\n",nBytes,err); #endif } else printf(">>> mdns_send failed %d\n",err); pbuf_free(p); } else printf(">>> mdns_send: alloc failed[%d]\n",nBytes); } static void mdns_reply(struct mdns_hdr* hdrP) // Message has passed tests, may want to send an answer { int i, nquestions, respLen; struct mdns_hdr* rHdr; mdns_rsrc* extra; u8_t* qBase = (u8_t*)hdrP; u8_t* qp; u8_t* mdns_response; mdns_response = malloc(DNS_MSG_SIZE); if (mdns_response==NULL) { printf(">>> mdns_reply could not alloc %d\n",DNS_MSG_SIZE); return; } // Build response header rHdr = (struct mdns_hdr*) mdns_response; rHdr->id = hdrP->id; rHdr->flags1 = DNS_FLAG1_RESP + DNS_FLAG1_AUTH; rHdr->flags2 = 0; rHdr->numquestions = 0; rHdr->numanswers = 0; rHdr->numauthrr = 0; rHdr->numextrarr = 0; respLen = SIZEOF_DNS_HDR; extra = NULL; qp = qBase + SIZEOF_DNS_HDR; nquestions = htons(hdrP->numquestions); for (i=0; inumanswers = htons( htons(rHdr->numanswers) + 1 ); // Extra RR logic: if SRV follows PTR, or A follows SRV, volunteer it in extraRR // Not required, but could do more here, see RFC6763 s12 if (qType==DNS_RRTYPE_PTR) { if (rsrcP->rNext && rsrcP->rNext->rType==DNS_RRTYPE_SRV) extra = rsrcP->rNext; } else if (qType==DNS_RRTYPE_SRV) { if (rsrcP->rNext && rsrcP->rNext->rType==DNS_RRTYPE_A) extra = rsrcP->rNext; } } } } // for nQuestions if (respLen > SIZEOF_DNS_HDR) { if (extra) { respLen = mdns_add_to_answer(extra, mdns_response, respLen); rHdr->numextrarr = htons( htons(rHdr->numextrarr) + 1 ); } mdns_send_mcast(mdns_response, respLen); } free(mdns_response); } static void mdns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, ip_addr_t *addr, u16_t port) // Callback from udp_recv { UNUSED_ARG(pcb); UNUSED_ARG(port); u8_t* mdns_payload; int plen; // Sanity checks on size plen = p->tot_len; if (plen > DNS_MSG_SIZE) { printf(">>> mdns_recv: pbuf too big\n"); } else if (plen < (SIZEOF_DNS_HDR + SIZEOF_DNS_QUERY + 1 + SIZEOF_DNS_ANSWER + 1)) { printf(">>> mdns_recv: pbuf too small\n"); } else { #ifdef qLogIncoming printf("\n\nmDNS got %d bytes from %d.%d.%d.%d\n",plen, ip4_addr1(addr), ip4_addr2(addr), ip4_addr3(addr), ip4_addr4(addr)); #endif mdns_payload = malloc(plen); if (!mdns_payload) printf(">>> mdns_recv, could not alloc %d\n",plen); else { if (pbuf_copy_partial(p, mdns_payload, plen, 0) == plen) { struct mdns_hdr* hdrP = (struct mdns_hdr*) mdns_payload; #ifdef qLogAllTraffic mdns_print_msg(mdns_payload, plen); #endif if ( (hdrP->flags1 & (DNS_FLAG1_RESP + DNS_FLAG1_OPMASK + DNS_FLAG1_TRUNC) ) == 0 && hdrP->numquestions > 0 ) mdns_reply(hdrP); } free(mdns_payload); } } pbuf_free(p); } static void mdns_start() // If we are in station mode and have an IP address, start a multicast UDP receive { struct ip_info ipInfo; err_t err; if (sdk_wifi_get_opmode() != STATION_MODE) { printf(">>> mDNS_start: wifi opmode not station\n"); return; } if (!sdk_wifi_get_ip_info(STATION_IF,&ipInfo)) { printf(">>> mDNS_start: no IP addr\n"); return; } mdns_update_ipaddr(&ipInfo); // Start IGMP on the netif for our interface: this isn't done for us struct netif* nfp = netif_list; while (nfp!=NULL) { if ( ip_addr_cmp(&ipInfo.ip, &(nfp->ip_addr)) ) { if (!(nfp->flags & NETIF_FLAG_IGMP)) { nfp->flags |= NETIF_FLAG_IGMP; err = igmp_start(nfp); if (err != ERR_OK) { printf(">>> mDNS_start: igmp_start on %c%c failed %d\n",nfp->name[0], nfp->name[1],err); return; } } } nfp = nfp->next; } gMDNS_pcb = udp_new(); if (!gMDNS_pcb) { printf(">>> mDNS_start: udp_new failed\n"); return; } if ((err=igmp_joingroup(&ipInfo.ip, &gMulticastAddr)) != ERR_OK) { printf(">>> mDNS_start: igmp_join failed %d\n",err); return; } if ((err=udp_bind(gMDNS_pcb, IP_ADDR_ANY, DNS_MDNS_PORT)) != ERR_OK) { printf(">>> mDNS_start: udp_bind failed %d\n",err); return; } udp_recv(gMDNS_pcb, mdns_recv, NULL); } static void mdns_close() { udp_remove(gMDNS_pcb); gMDNS_pcb = NULL; #ifdef qDebugLog printf("Closing mDNS\n"); #endif } static void mdns_task(void *pvParameters) { uint8_t hasIP = 0; uint8_t status; UNUSED_ARG(pvParameters); ipaddr_aton(DNS_MULTICAST_ADDRESS, &gMulticastAddr); // Wait until we have joined AP and are assigned an IP while (1) { status = (sdk_wifi_station_get_connect_status() == STATION_GOT_IP); if (status != hasIP) { if (status) mdns_start(); else mdns_close(); hasIP = status; } vTaskDelayMs(status ? 1000 : 100); } } void mdns_init() { #if LWIP_IGMP xTaskCreate(mdns_task, "MDNS", kMDNSStackSize, NULL, 2, NULL); #else #error "LWIP_IGMP needs to be defined in lwipopts.h" #endif }