关于零配置网络
任何一个设备要接入网络中,必须得有IP地址、子网掩码、网关IP地址等信息。嵌入式设备为了方便生产,一般会将这些信息都固定,在设备运行前再根据具体环境对设备进行配置,灵活性大大降低,体验更是渣到不行。而苹果设备易用性的其中一点体现就是,当设备接入一个网络时,不需要做任何的配置,就可以访问。
那苹果设备是如何做到的?
原因就是苹果的设备都使用一项名为Bonjour的技术。这个名字装过iTunes的土豪应该都见过,iTunes附带这个组建,因此装上iTunes的PC也可以很方便的访问苹果设备了。
Bonjour是零配置网络的一个实现,是开源的哦,另一个开源的实现叫avahi。零配置网络,英文全称Zero Configuration Networking,简称Zeroconf。实现零配置网络需要做到以下几点:
Allocate addresses without a DHCP server
在传统网络环境下,设备的IP地址通过两种方式获取,一种是静态配置,通过手工方式为设备指定一个IP地址,一种是动态配置,设备通过DHCP服务器获得动态的IP地址。在有DHCP服务器的网络下,自然可以免除配置,但是在无中心服务器的网络环境下,无法提供DHCP服务,这时候如何解决自动获取IP的问题?
在IPV6环境下,IPV6协议本身就提供了设备自指定IP地址的能力,所以是直接支持了。
在IPV4环境下,可以使用链路本地地址(Link-local address)。Translate between names and IP addresses without a DNS server
在传统网络环境下,名称和IP地址的对应关系是通过DNS服务解析的。在没有中心服务器的网络环境中,没有DNS服务器提供域名解析服务,这时候又该如何解决名称解析的问题?
Bonjour使用的是叫mDNS的解决方案,mDNS——全称Multicast DNS。Find services without a directory server
在前面,自动获取IP和名字转IP的问题都解决了。但是还是没能实现一个零配置的网络,因为我不知道设备提供了什么服务,还得手动去配置。所以,最后要解决的就是如何知道网络中的设备提供了哪些服务!!!
解决方案是DNS Service Discovery (DNS-SD)。
前面提到了两个开源的实现,都满足了上述3点。其中Bonjour有在MT7687上的移植,在SDK中可以找到,没看懂多少……………………
还好,lwIP 2.xx版本提供了mDNS,真的省了不少事。
lwIP提供的mDNS
首先要注意的是,lwIP(V2.1.2)提供的mDNS没有完全实现规范中的所有特性!!!
下面是没有实现的特性:
- Tiebreaking for simultaneous probing
- Sending goodbye messages (zero ttl) - shutdown, DHCP lease about to expire, DHCP turned off...
- Checking that source address of unicast requests are on the same network
- Limiting multicast responses to 1 per second per resource record
- Fragmenting replies if required
- Handling multi-packet known answers
- Individual known answer detection for all local IPv6 addresses
- Dynamic size of outgoing packet
虽然没有完全实现规范中的特性,但是看了下V2.1.2以来的更新日志,对MDNS有不少优化的地方,后面应该会更完善的。
使用前准备
1、在lwipopts.h
设置LWIP_MDNS_RESPONDER = 1
,使能这个特性;
2、定义单个网卡能提供的最大服务个数,MDNS_MAX_SERVICES
,默认值是1
;
3、MDNS需要一个PCB
,所以MEMP_NUM_UDP_PCB
增加1;
4、MDNS在网卡需要个入口点,所以LWIP_NUM_NETIF_CLIENT_DATA
增加1;
5、IPv4下需要设置LWIP_IGMP = 1
,最好也设置LWIP_AUTOIP = 1
;IPv6下需要设置LWIP_IPV6_MLD = 1
;(MDNS支持在IPv4 only, v6 only, 和 v4+v6下使用)
6、为了减少动态内存分配,MDNS代码的运行块放在堆中,最多可能使用1K的大小;
MDNS的使用
注意:LWIP_AUTOIP只有在网络中不存在DHCP Server时才起作用,为了适配两种场景,配置同时支持DHCP
和AUTOIP
!!!
/*
------------------------------------
---------- AUTOIP options ----------
------------------------------------
*/
#define LWIP_DHCP 1
#define LWIP_AUTOIP 1
#define LWIP_DHCP_AUTOIP_COOP 1
#define LWIP_DHCP_AUTOIP_COOP_TRIES 5 // 默认是9,调小可以在DHCP失败后更快的切换到AutoIP
/*
---------------------------------
---------- MDNS options ----------
---------------------------------
*/
#define LWIP_MDNS_RESPONDER 1
#define MDNS_MAX_SERVICES 1
#define LWIP_NUM_NETIF_CLIENT_DATA 1
/* Announce IP settings have changed on netif. Call this in your callback registered by [netif_set_status_callback()](group__netif.html#gadc8787b23ac0ee023979cbadf87813d4). No need to call this function when LWIP_NETIF_EXT_STATUS_CALLBACK==1, this handled automatically for you.
*/
#define LWIP_NETIF_EXT_STATUS_CALLBACK 1
直接上代码
const char hostname[] = "NUC472HI8AE";
static void srv_txt(struct mdns_service *service, void *txt_userdata)
{
err_enum_t res;
res = mdns_resp_add_service_txtitem(service, "path=/", 6);
LWIP_ERROR("mdns add service txt failed\n", (res == ERR_OK), return);
}
static void vWebTask( void *pvParameters )
{
tcpip_init(NULL, NULL);
netif_add(&netif, NULL, NULL, NULL, NULL, ethernetif_init, tcpip_input);
netif_set_default(&netif);
if (netif_is_link_up(&netif))
{
/* When the netif is fully configured this function must be called */
netif_set_up(&netif);
}
else
{
/* When the netif link is down this function must be called */
netif_set_down(&netif);
}
NVIC_SetPriority(EMAC_TX_IRQn, configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY + 1);
NVIC_EnableIRQ(EMAC_TX_IRQn);
NVIC_SetPriority(EMAC_RX_IRQn, configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY + 1);
NVIC_EnableIRQ(EMAC_RX_IRQn);
#if LWIP_DHCP
err_t err;
err = dhcp_start(&netif);
if (err == ERR_OK)
log_i("lwip dhcp init success...\n\n");
else
log_e("lwip dhcp init fail...\n\n");
#endif
http_server_netconn_init();
mdns_resp_init();
mdns_resp_add_netif(&netif, hostname, 3600);
mdns_resp_add_service(&netif, "myweb", "_http", DNSSD_PROTO_TCP, 80, 3600, srv_txt, NULL);
vTaskSuspend( NULL );
}
编译后下载到板子运行,从下图可以看到,192.168.1.159这个地址提供了一个http服务
使用wireshark抓包如下,为了方便查看只过滤192.168.1.159这个地址,从下图可以看出,设备在获取到IP后即广播了自己的服务信息。
访问该服务
尝试在windows 7下用chrome访问NUC472HI8AE.local:80
,不成功,在ubuntu上用firefox倒是成功了,可能是windows 7不支持mdns的原因。
遗憾
V2.1.2版本没有提供服务搜寻接口,所以无法在设备上去主动搜寻网络中的服务,下一版会解决这个,只能期待快点发布了。