avahi_entry_group_add_record 在头文件注释及官方文档中没有任何的说明,如果想使用该接口发送自定义的消息会一点头疼,这里通过分析avahi源码来介绍一下使用方法,下面的内容需要对mDNS以及DNS-SD协议有一定程度的了解。
# avahi-client/publish.h 实现在 avahi-client/entrygroup.c
client与daemon之间通过dbus通信
avahi_entry_group_add_record()
dbus_message_new_method_call(..., "AddRecord")// 注意此处传递的method参数
dbus_message_append_args()
dbus_connection_send_with_reply_and_block()// 通过dbus发送消息给avahi-daemon
# avahi-daemon/dbus-entry-group.c
client的消息发送后由daemon负责处理,通过搜索"AddRecord"定位到avahi-daemon的处理动作
else if(dbus_message_is_method_call(..., "AddRecord")) {
avahi_rdata_parse() {
parse_rdata()
}
avahi_server_add()
}
# avahi-core/dns.c
1. 在最终的处理函数consume_labels中,第一件事情是获取字符串的长度字节。参考 RFC6763 - DNS-SD 第6章中关于TXT记录的字符串的介绍,TXT记录允许传入多个TXT 的字符串,每一个前面使用一个1字节的长度字段来标识长度,示例:
---------------------------------------------------------------------
| 0x09 | key=value | 0x08 | paper=A4 | 0x07 | passreq |
---------------------------------------------------------------------
2. consume_labels结束处理的条件为 if(!n),也就是检查到'\0'的字符串结束符,因此我们传入的字符串中必须带有'\0'结束符,size中也必须算上这个'\0'结束符,否则程序会在不正确的位置结束处理。
3. PTR和TXT记录的处理都比较简单,SRV需要使用自定义结构体来传递数据。
static int parse_rdata(AvahiDnsPacket *p, AvahiRecord *r, uint16_t rdlength) {
avahi_dns_packet_consume_name() {
switch (r->key->type) {
case AVAHI_DNS_TYPE_PTR:
case AVAHI_DNS_TYPE_CNAME:
case AVAHI_DNS_TYPE_NS:
if (avahi_dns_packet_consume_name(p, buf, sizeof(buf)) < 0)
return -1;
case AVAHI_DNS_TYPE_SRV:
// SRV记录使用到了 AvahiRecord.data.srv 结构体
if (avahi_dns_packet_consume_uint16(p, &r->data.srv.priority) < 0 ||
avahi_dns_packet_consume_uint16(p, &r->data.srv.weight) < 0 ||
avahi_dns_packet_consume_uint16(p, &r->data.srv.port) < 0 ||
avahi_dns_packet_consume_name(p, buf, sizeof(buf)) < 0)
return -1;
case AVAHI_DNS_TYPE_TXT:
if (avahi_string_list_parse(avahi_dns_packet_get_rptr(p), rdlength, &r->data.txt.string_list) < 0)
return -1;
}
}
}
static int consume_labels(AvahiDnsPacket *p, unsigned idx, char *ret_name, size_t l) {
for (i = 0; i < AVAHI_DNS_LABELS_MAX; i++) {
n = AVAHI_DNS_PACKET_DATA(p)[idx]; // 获取字符串长度字节,参考 RFC6763 - DNS-SD 第6章
if(!n)
// 结束
else if(n <= 63)
// 未压缩的标签,参考RFC6763 7.2节,标签长度不得大于63
else if ((n & 0xC0) == 0xC0)
// 压缩的标签
}
}
这里我们按照DNS-SD协议的要求对外发布一项服务:
1. 发布一条PTR记录指向SRV记录
2. 发布一条SRV记录描述服务内容
3. 发布一条TXT记录描述附加信息(可选)
4. 发布一条dns-sd PTR记录用于avahi-browser的查找
注:avahi_entry_group_add_record 第四个参数flags不能为0,使用avahi_entry_group_add_service时因为会自动添加默认的flag所以可以正常使用,但是add_record则不行,必须手动将所有使用到的flag添加上。
// 参考 avahi-core/rr.h: AvahiRecord.data.srv
typedef struct mdns_dns_srv {
uint16_t priority;
uint16_t weight;
uint16_t port;
uint16_t pad; // 对齐64位
} mdns_dns_srv_t;
int mdns_add_service() {
/***************************************************************/
char full_address[] = "service1._http._tcp.local";
char service_name[] = "_http._tcp.local";
char service_type[] = "_http._tcp";
char dns_sd_service_name[] = "_services._dns-sd._udp.local"
const char *host_name = avahi_client_get_host_name(client);
uint8_t rrlen;
int rr_size;
uint8_t *memptr;
/***************************************************************/
// 记录的格式为 (1B 字符串长度) + (字符串) + (1B '\0'字符串结束符)
// 参考 RFC6763 - DNS-SD 第6章
rrlen = strlen(full_address);
rr_size = rrlen + 2; // 1B rrlen + 1B '\0'
memptr = malloc(rr_size);
memcpy(memptr, &rrlen, 1);
strcpy(memptr+1, full_address);
// PTR 记录
ret = avahi_entry_group_add_record(entry_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
AVAHI_PUBLISH_UNIQUE | AVAHI_PUBLISH_USE_MULTICAST,
service_name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_PTR, AVAHI_DEFAULT_TTL,
memptr, rr_size);
free(memptr);
/***************************************************************/
mdns_dns_srv_t srv;
srv.priority = 0;
srv.weight = 0;
srv.port = 1234;
srv.pad = 0;
// 记录的内容为 (3 * uint16_t) + 一个带长度的字符串
rrlen = strlen(host_name);
rr_size = sizeof(uint16_t) * 3 + (strlen(host_name) + 2); // 1B rrlen + 1B '\0'
uint8_t *srv_mem = malloc(rr_size);
memcpy(srv_mem, &srv, sizeof(uint16_t) * 3);
memcpy(srv_mem + sizeof(uint16_t) * 3, &rrlen, 1);
strcpy(srv_mem + sizeof(uint16_t) * 3 + 1, host_name);
// SRV 记录
ret = avahi_entry_group_add_record(entry_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
AVAHI_PUBLISH_UNIQUE | AVAHI_PUBLISH_USE_MULTICAST,
full_address, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_SRV, AVAHI_DEFAULT_TTL_HOST_NAME,
srv_mem, rr_size);
free(srv_mem);
/***************************************************************/
rrlen = 3;
// (2 * 1B rrlen) + 1B '\0'
rr_size = (rrlen+1) * 2 + 1;
memptr = malloc(rr_size);
memcpy(memptr, &rrlen, 1);
strcpy(memptr + 1, "a=1");
memcpy(memptr + rrlen + 1, &rrlen, 1);
strcpy(memptr + rrlen + 1 + 1, "b=2");
// TXT 记录
ret = avahi_entry_group_add_record(entry_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
AVAHI_PUBLISH_UNIQUE | AVAHI_PUBLISH_USE_MULTICAST,
full_address, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_TXT, AVAHI_DEFAULT_TTL,
memptr, rr_size);
free(memptr);
/***************************************************************/
rrlen = strlen(service_type);
rr_size = rrlen + 2;
memptr = malloc(rr_size);
memcpy(memptr, &rrlen, 1);
strcpy(memptr + 1, service_type);
// DNS-SD PTR 记录
ret = avahi_entry_group_add_record(entry_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
AVAHI_PUBLISH_UNIQUE | AVAHI_PUBLISH_USE_MULTICAST,
dns_sd_service_name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_PTR, AVAHI_DEFAULT_TTL,
memptr, rr_size);
free(memptr);
}