2015年12月5日 星期六

Use Avahi as a library for Discovering mDNS Service


     Avahi(http://www.avahi.org/) is an implement of multicast Domain Name System (mDNS) in Linux/Android. mDNS is a (local) network service broadcasting/discovering mechanism: for example,  the IP printers or IP cameras,  could be found via mDNS functions.

    Avahi libraries is easy to be use relatively.  There are  adequate examples to illuminate how to use those API. In here I would like to make them be ample. I would explain about discovering (the serivces) part in this article.


Note : avahi brower would depend on avahi-daemon, this post's goal is to remove the dependency.



  零. 
      Download the avahi, for me, I use version 0.6.31. Then uncompress the tar ball.

 一.      Go into the avahi folder, configure the avahi with gtk2 , qt4 and dbus

./configure --prefix=$PWD/built --disable-qt3 --disable-gtk3 --disable-mono --enable-tests --enable-core-docs --disable-python --disable-pygtk

     After the configuration has be done,type make and make install.


二. 
    Go to the built//bin folder, execute the avahi-browse:

You would get the similar result with mine:


./avahi-browse -a
+  wlan0 IPv6 i5-3210m [60:67:20:a5:44:ae]                  _workstation._tcp    local
+  wlan0 IPv4 i5-3210m [60:67:20:a5:44:ae]                  _workstation._tcp    local
+  wlan0 IPv6 i5-3210m                                      _udisks-ssh._tcp     local
+  wlan0 IPv4 i5-3210m                                      _udisks-ssh._tcp     local
+  wlan0 IPv4 GAIGER NETEYE CS2230                          _http._tcp           local




But if you stop the avahi-daemon and re-execute the avahi-browe:

sudo service avahi-daemon stop
avahi-daemon stop/waiting

./avahi-browse -a
Failed to create client object: 幕後程式沒有在執行中 

the Chinese words read "the daemon programming is not on running"

That is, the avahi-browe depends on avahi-daemon, which be the provider of service of discover.

But, If you run the avahi-discover-standalone, there is a window for reporting discovered service:



The GAIGER NETEYE CS2230 be a IP camera in the same lan domain.

     The other services discovered by avahi-browe are local services provided by avahi-daemon, those would vanish when avahi-daemon stopped.

    You could study what is the different between avahi-browe and avahi-discover-standalone,   the source codes be in folder examples/client-browse-services.c and  avahi-discover-standalone/main.c respectively.

   For most useful and portable for libraries usage, the avahi-discover-standalone/main.c should be removed from UI, and be independent of the others libraries (dbus and so forth).


三.
   Re-configure or uncompress the same tar ball again , use the configure arguments:


./configure --prefix=$PWD/built --disable-dbus --disable-mono --disable-monodoc --disable-qt4 --disable-qt3 --disable-glib --disable-gobject --disable-gtk --disable-gtk3 --disable-gdbm --disable-dbm --disable-python --disable-pygtk

And make -j2 && make install.

That is for minimization the dependence.


四.
   Create a folder be under avahi source root , the folder name be sandyboxcode for me.
   my standalone code be inside the folder:

sandbox-discover-standalone.c:


#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <time.h>

#include <net/if.h>

#include "avahi-core/lookup.h"

#include <avahi-common/simple-watch.h>
#include <avahi-common/malloc.h>
#include <avahi-common/error.h>


#ifndef FALSE
 #define FALSE       (0)
#endif

#ifndef TRUE
 #define TRUE       (1)
#endif

#define MAX_STR_LEN        (256)

typedef struct ServiceInfo 
{ 
 char *pServiceType;
 char *pServiceName;
 char *pDomainName;
 char *pInterface;
 char *pProtocolNumber;
 char *pAddr;
 unsigned short port;
 char *pHostName;
 char *pText;
 
 struct ServiceInfo *pNext;
}ServiceInfo, ServiceInfoList;


const char unUsedServiceInfoMemHead[] = 
{
 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff ,
 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff 
};//the impossible memory location value


static AvahiServer *pServer;
static AvahiSimplePoll *pSimplePoll;


ServiceInfoList *CreateServiceInfoList(void)
{
 ServiceInfo *pServiceInfo;
    
 pServiceInfo = (ServiceInfo*)malloc(sizeof(ServiceInfo));
 memset(pServiceInfo, (unsigned char)0xff, sizeof(ServiceInfo));   
 
 return pServiceInfo;
}/*CreateServiceInfoList*/

#define SAFE_FREE(PTR)     if(NULL != (PTR))\
         free(PTR);\
        PTR = NULL


void DestroyServiceInfo(ServiceInfo *pServiceInfo)
{
 if(NULL == pServiceInfo)
  return;

 SAFE_FREE(pServiceInfo->pServiceType); SAFE_FREE(pServiceInfo->pServiceName);
 SAFE_FREE(pServiceInfo->pDomainName);
 SAFE_FREE(pServiceInfo->pInterface);SAFE_FREE(pServiceInfo->pProtocolNumber);
 SAFE_FREE(pServiceInfo->pAddr);SAFE_FREE(pServiceInfo->pHostName);

 if(NULL != pServiceInfo->pText)
  avahi_free(pServiceInfo->pText);  
 pServiceInfo->pText = NULL;

 SAFE_FREE(pServiceInfo);
}/*DestoryServiceInfo*/


void DestroyServiceInfoList(ServiceInfoList *pServiceInfoList)
{
 ServiceInfo *pCurrent;

 pCurrent = pServiceInfoList;

 if( 0 == memcmp(pServiceInfoList, &unUsedServiceInfoMemHead[0], sizeof(unUsedServiceInfoMemHead)) )
 {
  SAFE_FREE(pCurrent); 
  return;
 }/*if unused*/

 while(NULL != pCurrent)
 {
  ServiceInfo *pNext;

  pNext = pCurrent->pNext;
  DestroyServiceInfo(pCurrent);
  pCurrent = pNext;
 }/*while*/
    
}/*CleanServiceInfoList*/


void ServiceResolverCallback(
 AvahiSServiceResolver *pResolver,
 AvahiIfIndex interface,
 AvahiProtocol protocol,
 AvahiResolverEvent event,
 const char *pServiceName,
 const char *pType,
 const char *pDomain,
 const char *pHostName,
 const AvahiAddress *pAvahiAddress,
 uint16_t port,
 AvahiStringList *pText,
 AvahiLookupResultFlags flags,
 void* pUserdata)
{
 char addrStr[AVAHI_ADDRESS_STR_MAX];
 ServiceInfo *pServiceInfo;

 //printf("__FUNCTION__ = %s\n", __FUNCTION__);
 if (AVAHI_RESOLVER_FOUND != event)
 {
  printf("NOT AVAHI_RESOLVER_FOUND\n");
  return;
 }/* AVAHI_RESOLVER_FOUND != event */

 
 pServiceInfo = (ServiceInfo*)pUserdata;
 
 if( 0 == memcmp(pServiceInfo, &unUsedServiceInfoMemHead[0], sizeof(unUsedServiceInfoMemHead)) )
 {
  memset(pServiceInfo, 0, sizeof(ServiceInfo)); 
 }
 else
 {
  while(NULL != pServiceInfo->pNext)  
   pServiceInfo = pServiceInfo->pNext;
  
  pServiceInfo->pNext = (ServiceInfo*)malloc(sizeof(ServiceInfo));
  memset(pServiceInfo->pNext, 0, sizeof(ServiceInfo));
  
  pServiceInfo = pServiceInfo->pNext;
 }/*if zero*/  

 avahi_address_snprint(&addrStr[0], AVAHI_ADDRESS_STR_MAX, pAvahiAddress);
 
 pServiceInfo->pServiceType = strndup(pType, MAX_STR_LEN);
 pServiceInfo->pServiceName = strndup(pServiceName, MAX_STR_LEN);
 pServiceInfo->pDomainName = strndup(pDomain, MAX_STR_LEN);
 pServiceInfo->pInterface = strndup(if_indextoname(interface, (char*)pServiceName), MAX_STR_LEN);
 pServiceInfo->pProtocolNumber = strndup(avahi_proto_to_string(protocol), MAX_STR_LEN);

 pServiceInfo->pAddr = strndup(&addrStr[0], MAX_STR_LEN);
 pServiceInfo->pHostName = strndup(pHostName, MAX_STR_LEN);

 pServiceInfo->port = port;

 if(NULL != pText)
 {
  char *pAvahiText = avahi_string_list_to_string(pText) ;
  pServiceInfo->pText = strndup(pAvahiText, MAX_STR_LEN);
  avahi_free(pAvahiText); 
 }/*if NULL != pText*/ 

#if(0)
 printf( 
   "Service Type: %s\n"
   "Service Name: %s\n"
   "Domain Name: %s\n"
   "Interface: %s %s\n"
   "Address: %s/%s:%u\n"
   "TEXT Data: %s\n"
   ,
   pServiceInfo->pServiceType,
   pServiceInfo->pServiceName, 
   pServiceInfo->pDomainName, 
   pServiceInfo->pInterface, pServiceInfo->pProtocolNumber,
   pServiceInfo->pAddr, pServiceInfo->pHostName, pServiceInfo->port,
   NULL == pServiceInfo->pText ? "\x1B[1;36m" "NULL" "\x1B[0m": pServiceInfo->pText       
   );   
#endif   
 
 if (NULL != pResolver)
  avahi_s_service_resolver_free(pResolver); 

}/*ServiceResolverCallback*/
 
 
void ServiceBrowserCallback(
 AvahiSServiceBrowser *pBrower,
 AvahiIfIndex interface,
 AvahiProtocol protocol,
 AvahiBrowserEvent event,
 const char *pServiceName,
 const char *pServiceType,
 const char *pDomainName,
 AvahiLookupResultFlags flags,
 void *pUserdata) 
{
 AvahiSServiceResolver *pResolver;

 //printf("__FUNCTION__ = %s\n", __FUNCTION__);
 if(NULL == pServiceName || NULL == pServiceType
  || NULL == pDomainName) 
 {
  return;
 }/*if NULL*/

 /*
  TODO:
   a service filter should be put in here. 
 */
 
 pResolver = avahi_s_service_resolver_new(pServer, interface, protocol, pServiceName, 
 pServiceType, pDomainName, AVAHI_PROTO_UNSPEC, 0, ServiceResolverCallback, 
 (void*)pUserdata);

}/*ServiceBrowserCallback*/


void ServiceTypeBrowserCallback( 
 AvahiSServiceTypeBrowser *pTypeBrower,
 AvahiIfIndex interface,
 AvahiProtocol protocol,
 AvahiBrowserEvent event,
 const char *pType,
 const char *pDomain,
 AvahiLookupResultFlags flags,
  void *pUserdata) 
{
 
 assert(pTypeBrower);

 switch (event) 
 {
 case AVAHI_BROWSER_FAILURE:
 printf("AVAHI_BROWSER_FAILURE\n");
 avahi_simple_poll_quit(pSimplePoll);
 break;

 case AVAHI_BROWSER_NEW:
 //printf("AVAHI_BROWSER_NEW\n");
 {  
  /* We ignore the returned resolver object. In the callback
     function we free it. If the server is terminated before
     the callback function is called the server will free
     the resolver for us. */
             
  if( NULL == avahi_s_service_browser_new(pServer, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 
   pType, pDomain, 0, ServiceBrowserCallback, pUserdata))
  {
   fprintf(stderr, "error in avahi_s_service_browser_new\n");
  }/*if NULL*/

 }/*local Variables*/
 break;

 case AVAHI_BROWSER_REMOVE:
 //printf("AVAHI_BROWSER_REMOVE\n");
 break;

 case AVAHI_BROWSER_ALL_FOR_NOW:
 //printf("AVAHI_BROWSER_ALL_FOR_NOW\n");
 avahi_simple_poll_quit(pSimplePoll);
 break;
   
 case AVAHI_BROWSER_CACHE_EXHAUSTED:
 //printf("AVAHI_BROWSER_CACHE_EXHAUSTED\n"); 
 break;
 }/*switch event*/

}/*service_type_browser_callback*/


void TimeoutCallback(AvahiTimeout *pTimeout, void *pUserdata) 
{
 printf("__FUNCTION__ = %s\n", __FUNCTION__); 
 avahi_simple_poll_quit(pSimplePoll);
}/*TimeoutCallback*/


int MulticastDomainNameSystemServiceDiscover(int *pNumber, ServiceInfoList *pServiceInfoList, 
 int maxTimeoutInMilliSec)
{
 AvahiServerConfig config;
 AvahiSServiceTypeBrowser *pServiceTypeBrowser;
 ServiceInfo *pServiceInfoHead;

 int error;
 int i;
 int ret;
 struct timeval tv;
  
 pSimplePoll = NULL;
 pServiceTypeBrowser = NULL;

 *pNumber = 0;
 pServiceInfoHead = pServiceInfoList;
 if(NULL == pServiceInfoHead)
 {
  fprintf(stderr, "ServiceInfo should be initialized \n");
  ret = -1;
  goto flag_Fail;
 }/*if */

 avahi_set_allocator(NULL);
 pSimplePoll = avahi_simple_poll_new();

 if (NULL == pSimplePoll) 
 {
  fprintf(stderr, "Failed to create simple poll object.\n");
  ret = -2;
  goto flag_Fail;
 }/*if*/

 avahi_server_config_init(&config);
 config.publish_hinfo = config.publish_addresses = FALSE;
 config.publish_domain = config.publish_workstation = FALSE;
 
 pServer = avahi_server_new(avahi_simple_poll_get (pSimplePoll), &config, NULL, NULL, &error);
 avahi_server_config_free(&config);
  
 if (NULL == pServer) 
 {
  fprintf(stderr, "Failed to create server : %s\n", avahi_strerror(error));
  ret = -3;
  goto flag_Fail;
 }/*if NULL == pServer*/

 pServiceTypeBrowser = avahi_s_service_type_browser_new(pServer, 
  AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, NULL, 0, 
  ServiceTypeBrowserCallback, pServiceInfoHead);

 if(NULL == pServiceTypeBrowser)
 {
  fprintf(stderr, "Failed to create brower \n");
  ret = -4;
  goto flag_Fail;
 }/*if NULL == pServiceTypeBrowser*/

 avahi_simple_poll_get(pSimplePoll)->timeout_new(
  avahi_simple_poll_get(pSimplePoll),
  avahi_elapse_time(&tv, maxTimeoutInMilliSec, 0),
  TimeoutCallback,
  NULL);    
       
 /* Run the main loop */
 avahi_simple_poll_loop(pSimplePoll);
 

 if( 0 == memcmp(pServiceInfoHead, &unUsedServiceInfoMemHead[0], 
  sizeof(unUsedServiceInfoMemHead)) )
 {
  goto flag_Fail ; 
 }/*if no service found*/


 {/*count  the list length*/  
  ServiceInfo *pServiceInfo;
  pServiceInfo = pServiceInfoHead;
  *pNumber = 0;  
  while(NULL != pServiceInfo)
  {
   pServiceInfo = pServiceInfo->pNext;
   (*pNumber)++;
  }/*NULL != pServiceInfo*/
 }/*local variable */    

 ret = *pNumber; 

flag_Fail:

 if(NULL != pServer)
  avahi_server_free(pServer);
 pServer = NULL;

 if(NULL != pSimplePoll) 
   avahi_simple_poll_free(pSimplePoll);
 pSimplePoll = NULL;

 return ret;
}/*MulticastDomainNameSystemServiceDiscover*/



#define TIMEOUT_VALUE      (5000)

int main(int argc, char *argv[])
{ 
 int n, i;
 ServiceInfoList *pServiceInfoList;
 struct timespec timeStart, timeEnd;
 
 pServiceInfoList = CreateServiceInfoList();

 clock_gettime(CLOCK_REALTIME, &timeStart);

 MulticastDomainNameSystemServiceDiscover(&n, pServiceInfoList, TIMEOUT_VALUE);
 
 clock_gettime(CLOCK_REALTIME, &timeEnd);

 printf("%d services have been found\n", n);
 if(0 != n )
 {
  ServiceInfo *pServiceInfo;
  pServiceInfo = pServiceInfoList;
  
  while(NULL != pServiceInfo)
  {  
   printf("\n");
   printf( 
     "Service Type: %s\n"
     "Service Name: %s\n"
     "Domain Name: %s\n"
     "Interface: %s %s\n"
     "Address: %s/%s:%u\n"
     "TEXT Data: %s\n"
     ,
     pServiceInfo->pServiceType,
     pServiceInfo->pServiceName, 
     pServiceInfo->pDomainName, 
     pServiceInfo->pInterface, pServiceInfo->pProtocolNumber,
     pServiceInfo->pAddr, pServiceInfo->pHostName, pServiceInfo->port,
     NULL == pServiceInfo->pText ? "\x1B[1;36m" "NULL" "\x1B[0m": pServiceInfo->pText       
     );   
     
   pServiceInfo = pServiceInfo->pNext;  
  }/*while*/
  printf("\n");
 }/*if 0 != n*/

 {
  int  elaspeTimeMicroSec; 
  elaspeTimeMicroSec = (timeEnd.tv_sec - timeStart.tv_sec) *1e6 + 
     (timeEnd.tv_nsec - timeStart.tv_nsec) /1e3;

  printf("discover cast %u msec\n", elaspeTimeMicroSec/1000);  
 }     
 DestroyServiceInfoList(pServiceInfoList);
 
 return 0;
}/*main*/

 


The corresponding Makefile be :


CC := gcc
CXX := g++


INC := -I../built/include 

LIB_PATH := ../built/lib

LIB += $(LIB_PATH)/libavahi-core.a
LIB += $(LIB_PATH)/libavahi-common.a


CFLAGS := -g

all :
 $(CC) $(CFLAGS) $(INC)  sandbox-discover-standalone.c  $(LIB)  -o sandbox-discover-standalone 

clean :
 rm sandbox-discover-standalone -rf


 
The code is very easy to be  modified as library form.

五. 
   Discover result:


./sandbox-discover-standalone
Joining mDNS multicast group on interface wlan0.IPv6 with address fe80::6267:20ff:fea5:44ae.
New relevant interface wlan0.IPv6 for mDNS.
Joining mDNS multicast group on interface wlan0.IPv4 with address 192.168.13.7.
New relevant interface wlan0.IPv4 for mDNS.
Network interface enumeration completed.
Leaving mDNS multicast group on interface wlan0.IPv6 with address fe80::6267:20ff:fea5:44ae.
Leaving mDNS multicast group on interface wlan0.IPv4 with address 192.168.13.7.
n = 1

Service Type: _http._tcp
Service Name: GAIGER NETEYE CS2230
Domain Name: local
Interface: wlan0 IPv4
Address: 192.168.13.107/NETEYECS2230-001A97018882.local:80
TEXT Data: NULL


Of course, the avahi-daemon be in stop status.


沒有留言:

張貼留言