diff --git a/mdns.c b/mdns.c new file mode 100644 index 0000000..744ec30 --- /dev/null +++ b/mdns.c @@ -0,0 +1,363 @@ +/* + * Copyright (c) 2006 Bjorn Andersson + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdns.h" + +#define MAX_PACKET_LEN 1024 + +struct servicetree services = RB_INITIALIZER(&services); + +int +servicecmp(struct mdns_service *a, struct mdns_service *b) +{ + return strcmp(a->net, b->net); +} + +RB_GENERATE(servicetree, mdns_service, entry, servicecmp); + +extern void peer_event(int, struct mdns_peer*); + +void +mdns_register_service(struct mdns_service *service) +{ + RB_INSERT(servicetree, &services, service); +} + +void +mdns_unregister_service(struct mdns_service *service) +{ + if(service) { + RB_REMOVE(servicetree, &services, service); + } +} + +static int +readname(char *packet, char *dst, char *src) +{ + char l; + int len; + int offset; + + len = 0; + + while(*src) { + l = *src++; + len++; + + if(l & 0x80 && l & 0x40) { + offset = ((src[-1] & 0x3f) << 8) | src[0]; + readname(packet, dst, packet + offset); + dst += strlen(dst); + break; + } + + while(l) { + *dst++ = *src++; + l--; + len++; + } + + *dst++ = '.'; + } + + *dst = '\0'; + src++; + len++; + + return len; +} + +#define READNAME(packet, dst, src) (src) += readname((packet), (dst), (src)); + +#define READSHORT(dst, src) \ + (dst) = ntohs(*(short*)(src)); (src)+=2; + +#define READLONG(dst, src) \ + (dst) = ntohl(*(long*)(src)); (src)+=4; + +#define READDATA(dst, src, len) \ + memcpy((dst), (src), (len)); (src)+=(len); + +int +mdns_open() +{ + int fd; + int ttl; + int flag; + struct sockaddr_in addr; + struct ip_mreq mc; + + bzero(&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(5353); + addr.sin_addr.s_addr = 0; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if(fd < 0) { + perror("socket"); + exit(1); + } + + flag = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &flag, sizeof(flag)); + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)); + + if(bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + perror("bind"); + exit(1); + } + + mc.imr_multiaddr.s_addr = inet_addr("224.0.0.251"); + mc.imr_interface.s_addr = inet_addr("192.168.7.208"); //htonl(INADDR_ANY); + if(setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mc, sizeof(mc)) < 0) { + perror("ip_add_membership"); + exit(1); + } + + ttl = 255; + setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); + + return fd; +} + +static int +host2dns(const char *host, char *buffer, int size) +{ + char *h; + char *p; + char *word; + + h = strdup(host); + memset(buffer, 0, size); + p = buffer; + + word = strtok(h, "."); + while(word) { + *p++ = (char)strlen(word); + memcpy(p, word, strlen(word)); + p += strlen(word); + + word = strtok(NULL, "."); + } + + *p++ = 0; + + free(h); + + return p - buffer; +} + +static void +mdns_respond(int fd, struct mdns_service *service) +{ + int len; + int size; + HEADER *header; + char *p; + int addrlen; + char buf[1024]; + char host[256]; + struct sockaddr_in addr; + + snprintf(host, sizeof(host), "%s.%s", service->name, service->net); + + memset(buf, 0, sizeof(buf)); + + len = 0; + header = (HEADER*)buf; + + header->id = 0; + header->qr = 1; + header->opcode = 0; + header->aa = 1; + header->tc = 0; + header->rd = 0; + header->ra = 0; + + header->ancount = htons(2); + + p = buf + sizeof(HEADER); + + p += host2dns(service->net, p, strlen(service->net)); + PUTSHORT(T_PTR, p); + PUTSHORT(C_IN, p); + PUTLONG(htons(3600), p); + + size = host2dns(host, p+2, strlen(host)); + PUTSHORT(size, p); + p += size; + + p += host2dns(host, p, strlen(host)); + PUTSHORT(T_SRV, p); + PUTSHORT(0x8001, p); + PUTLONG(service->ttl, p); + + size = host2dns(service->host, p+8, strlen(service->host)); + size += 6; + PUTSHORT(size, p); + PUTSHORT(0, p); + PUTSHORT(0, p); + PUTSHORT(service->port, p); + p+= size; + + bzero(&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr("224.0.0.251"); + addr.sin_port = htons(5353); + addrlen = sizeof(addr); + + len = p - buf; + sendto(fd, buf, len + 1, 0, (struct sockaddr*)&addr, addrlen); +} + +void +mdns_handle(int fd) +{ + int i; + int ssize; + long ttl; + short rlen; + short type; + short class; + short ancount; + short qdcount; + char *data; + char name[255]; + char rdata[256]; + HEADER *header; + struct mdns_peer *peer; + struct sockaddr_in from; + char buf[MAX_PACKET_LEN]; + + i = recvfrom(fd, buf, MAX_PACKET_LEN, 0, (struct sockaddr*)&from, &ssize); + + if(i == -1) { + perror("recvfrom"); + } else { + header = (HEADER*)buf; + + data = buf + sizeof(HEADER); + + if(header->qr) { /* qr=1 => response */ + peer = malloc(sizeof(struct mdns_peer)); + ancount = ntohs(header->ancount); + + for(i=0;i 6) { + char *r; + short priority; + short weight; + + r = rdata; + + peer->name = strdup(name); + peer->host = malloc(rlen); + peer->ttl = ttl; + + READSHORT(priority, r); + READSHORT(weight, r); + READSHORT(peer->port, r); + READNAME(buf, peer->host, r); + peer->host[strlen(peer->host)-1] = '\0'; + + peer_event(1, peer); + } + } + } else { /* qr=0 => query */ + qdcount = ntohs(header->qdcount); + + for(i=0;inet, name) == 0) { + mdns_respond(fd, service); + } + } + } + } + } +} + +void +mdns_query(int fd, char *host, int type) +{ + char *p; + int len; + int addrlen; + char buf[1024]; + HEADER *header; + struct sockaddr_in addr; + + len = 0; + memset(buf, 0, sizeof(buf)); + + header = (HEADER*)buf; + + header->id = 0; + header->qr = 0; + header->opcode = 0; + header->aa = 0; + header->tc = 0; + header->rd = 0; + header->ra = 0; + + header->qdcount = htons(1); + + p = buf + sizeof(HEADER); + p += host2dns(host, p, strlen(host)); + + PUTSHORT(type, p); + PUTSHORT(01, p); + + bzero(&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr("224.0.0.251"); + addr.sin_port = htons(5353); + addrlen = sizeof(addr); + + len = p - buf; + sendto(fd, buf, len+1, 0, (struct sockaddr*)&addr, addrlen); +} + diff --git a/mdns.h b/mdns.h new file mode 100644 index 0000000..5152c28 --- /dev/null +++ b/mdns.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2006 Bjorn Andersson + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __MDNS_H__ +#define __MDNS_H__ + +#include + +struct mdns_peer { + RB_ENTRY(mdns_peer) entry; + + char *name; + char *host; + short port; + short ttl; +}; + +struct mdns_service { + RB_ENTRY(mdns_service) entry; + + char *name; + char *net; + char *host; + short port; + short ttl; +}; + +RB_HEAD(servicetree, mdns_service); + +int mdns_open(); +void mdns_query(int, char*, int); +void mdns_handle(int); + +void mdns_register_service(struct mdns_service *); +void mdns_unregister_service(struct mdns_service *); + +#endif