46836e2005-04-09Jonas Wallden /* || This file is part of Pike. For copyright information see COPYRIGHT. || Pike is distributed under GPL, LGPL and MPL. See the file COPYING || for more information.
fd37f52011-04-25Martin Stjernholm || $Id$
46836e2005-04-09Jonas Wallden */ /* Glue for DNS Service Discovery, which is built on top of e.g. Multicast
cc46f92008-07-18H. William Welliver III  DNS (ZeroConf/Rendezvous/Bonjour). Using this API a Pike program can register a service (e.g. a web server) and have other applications on the local network detect it without additional configuration.
46836e2005-04-09Jonas Wallden  The specification can be found at <http://www.dns-sd.org/>.
cc46f92008-07-18H. William Welliver III  The implementation requires either the dns_ds.h (found in Mac OS X and SunOS 5.11+) or howl.h (part of libhowl on Linux).
46836e2005-04-09Jonas Wallden */ #include "global.h" #include "config.h" #include "pike_macros.h" #include "stralloc.h" #include "pike_error.h" #include "object.h" #include "constants.h" #include "interpret.h" #include "svalue.h" #include "mapping.h" #include "builtin_functions.h" #include "module_support.h" #include "threads.h" #include <signal.h> #ifdef THIS #undef THIS #endif #define THIS ((struct service *)(Pike_fp->current_storage)) #define sp Pike_sp #if defined(HAVE_DNS_SD) || defined(HAVE_HOWL) #ifdef HAVE_DNS_SD /* Mac OS X interface is defined in <dns_sd.h> */ #ifdef HAVE_DNS_SD_H
7005732005-05-29Jonas Wallden  /* Workaround for typo in 10.3 header (which 10.4 doesn't preserve) */ #define kDNSServiceErr_BadinterfaceIndex kDNSServiceErr_BadInterfaceIndex
46836e2005-04-09Jonas Wallden #include <dns_sd.h> #endif #define IS_ERR(x) ((x) != kDNSServiceErr_NoError) /* Instance variables for each service registration */ struct service { DNSServiceRef service_ref; }; static void raise_error(char *msg, DNSServiceErrorType err) { char *reason; if (err == kDNSServiceErr_NoError) return; switch (err) { case kDNSServiceErr_NoSuchName: reason = "No such name"; break; case kDNSServiceErr_NoMemory: reason = "No memory"; break; case kDNSServiceErr_BadParam: reason = "Bad parameter"; break; case kDNSServiceErr_BadReference: reason = "Bad reference"; break; case kDNSServiceErr_BadState: reason = "Bad state"; break; case kDNSServiceErr_BadFlags: reason = "Bad flags"; break; case kDNSServiceErr_Unsupported: reason = "Unsupported"; break; case kDNSServiceErr_AlreadyRegistered: reason = "Already registered"; break; case kDNSServiceErr_NameConflict: reason = "Name conflict"; break; case kDNSServiceErr_Invalid: reason = "Invalid"; break; case kDNSServiceErr_Incompatible: reason = "Incompatible"; break;
7005732005-05-29Jonas Wallden  case kDNSServiceErr_BadInterfaceIndex:
46836e2005-04-09Jonas Wallden  reason = "Bad interface index"; break; default: reason = "Unknown error"; break; } Pike_error("DNS_SD: %s Reason: %s (%d)\n", msg, reason, err); } static void start_service_callback(DNSServiceRef ref, DNSServiceFlags flags, DNSServiceErrorType error, const char *name, const char *regtype, const char *domain, void *context) { } static DNSServiceErrorType start_service(struct service *svc, char *name, char *service, char *domain, int port, char *txt, int txtlen) { DNSServiceErrorType err; DNSServiceRef ref; /* Empty strings should be passed as NULL in order to get default values */ if (name && !strlen(name)) name = NULL; if (domain && !strlen(domain)) domain = NULL; if (txt && !txtlen) txt = NULL;
cc46f92008-07-18H. William Welliver III  // port must be provided in network byte-order. port = htons(port);
46836e2005-04-09Jonas Wallden  svc->service_ref = NULL; err = DNSServiceRegister(&ref, 0, 0, name, service, domain, NULL, port, txtlen, txt, start_service_callback, NULL); if (err == kDNSServiceErr_NoError) svc->service_ref = ref;
65acbc2005-10-21H. William Welliver III  err = DNSServiceProcessResult(ref);
46836e2005-04-09Jonas Wallden  return err; } static void stop_service(struct service *svc) { if (svc->service_ref) { DNSServiceRefDeallocate(svc->service_ref); svc->service_ref = NULL; } } static DNSServiceErrorType update_txt_record(struct service *svc, char *txt, int txtlen) { if (svc->service_ref) { int ttl = 0; return DNSServiceUpdateRecord(svc->service_ref, NULL, 0, txtlen, txt, ttl); } return kDNSServiceErr_Invalid; } #endif /* HAVE_DNS_SD */ #if defined(HAVE_HOWL) && !defined(HAVE_DNS_SD) /* Load <howl.h> to indirectly get <discovery/discovery.h> */ #ifdef HAVE_HOWL_H #include <howl.h> #endif #define IS_ERR(x) ((x) != SW_OKAY) /* Global session shared for all objects */ static sw_discovery service_session = NULL; static THREAD_T service_thread; struct service { sw_discovery_oid service_ref; }; static void raise_error(char *msg, sw_result err) { char *reason; if (err == SW_OKAY) return; switch (err) { case SW_DISCOVERY_E_NO_MEM: reason = "No memory"; break; case SW_DISCOVERY_E_BAD_PARAM: reason = "Bad parameter"; break; case SW_DISCOVERY_E_UNKNOWN: default: reason = "Unknown error"; break; } Pike_error("DNS_SD: %s Reason: %s (%d)\n", msg, reason, err); } static sw_result start_reply_fn(sw_discovery discovery, sw_discovery_publish_status status, sw_discovery_oid oid, sw_opaque extra) { // Do nothing return SW_OKAY; } static sw_result start_service(struct service *svc, char *name, char *service, char *domain, int port, char *txt, int txtlen) { sw_result err = SW_DISCOVERY_E_UNKNOWN; /* Empty strings should be passed as NULL in order to get default values */ if (domain && !strlen(domain)) domain = NULL; if (txt && !txtlen) txt = NULL; err = sw_discovery_publish(service_session, 0, name, service, domain, NULL, port, txt, txtlen, start_reply_fn, NULL, &svc->service_ref); return err; } static void stop_service(struct service *svc) { if (svc->service_ref) { /* Abort registration */ sw_discovery_cancel(service_session, svc->service_ref); } } static sw_result update_txt_record(struct service *svc, char *txt, int txtlen) { if (svc->service_ref) { return sw_discovery_publish_update(service_session, svc->service_ref, txt, txtlen); } return SW_DISCOVERY_E_UNKNOWN; } static void * howl_thread(void *arg) { sw_discovery_run(service_session); return NULL; } static void init_howl_module() { if (sw_discovery_init(&service_session) == SW_OKAY) { th_create_small(&service_thread, howl_thread, NULL); } } static void exit_howl_module() { /* Close active session */ if (service_session) sw_discovery_fina(service_session); /* Kill Howl thread if running */ if (service_thread) th_kill(service_thread, SIGCHLD); } #endif /* defined(HAVE_HOWL) && !defined(HAVE_DNS_SD) */ static void f_update_txt(INT32 args) { check_all_args("Service->update_txt", args, BIT_STRING, /* txt */ 0); /* Can only be called if we have valid service */ if (THIS->service_ref) { char *txt = sp[0 - args].u.string->str; int txtlen = sp[0 - args].u.string->len; int err = update_txt_record(THIS, txt, txtlen); if (IS_ERR(err)) raise_error("Could not update TXT record.", err); } pop_n_elems(args); } static void f_create(INT32 args) { char *name, *service, *domain, *txt; int port, txtlen, err; check_all_args("Service->create", args, BIT_STRING, /* name */ BIT_STRING, /* service */ BIT_STRING, /* domain */ BIT_INT, /* port */ BIT_STRING | BIT_VOID, /* txt */ 0); /* Stop existing service if one is running */ stop_service(THIS); /* Get string arguments which should already be UTF-8 and clipped to appropriate lengths. */ name = sp[0 - args].u.string->str; service = sp[1 - args].u.string->str; domain = sp[2 - args].u.string->str; port = sp[3 - args].u.integer; /* Optional TXT record may theoretically contain NUL chars so we can't trust strlen. */ txt = (args == 5) ? sp[4 - args].u.string->str : NULL; txtlen = txt ? sp[4 - args].u.string->len : 0; /* Register new service */ err = start_service(THIS, name, service, domain, port, txt, txtlen); if (IS_ERR(err)) raise_error("Could not register service.", err); pop_n_elems(args); } static void init_service_struct(struct object *o) { THIS->service_ref = 0; } static void exit_service_struct(struct object *o) { /* Stop an existing service */ stop_service(THIS); } PIKE_MODULE_INIT { start_new_program(); ADD_STORAGE(struct service); set_init_callback(init_service_struct); set_exit_callback(exit_service_struct); /* function(string, string, string, int, string|void:void) */ ADD_FUNCTION("create", f_create, tFunc(tStr tStr tStr tInt tOr(tStr, tVoid), tVoid), 0); /* function(string:void) */ ADD_FUNCTION("update_txt", f_update_txt, tFunc(tStr, tVoid), 0); end_class("Service", 0); #ifdef HAVE_HOWL init_howl_module(); #endif } PIKE_MODULE_EXIT { #ifdef HAVE_HOWL exit_howl_module(); #endif } #else /* defined(HAVE_DNS_SD) || defined(HAVE_HOWL) */ PIKE_MODULE_INIT {} PIKE_MODULE_EXIT {} #endif /* defined(HAVE_DNS_SD) || defined(HAVE_HOWL) */