#include "qtavahi.h" #include #include #include #include #include #include #include #include #include #include namespace Avahi { static QHostAddress fromAvahiAddress(const AvahiAddress *address, AvahiIfIndex interface) { switch (address->proto) { case AVAHI_PROTO_INET: return QHostAddress(qFromBigEndian((quint32) address->data.ipv4.address)); case AVAHI_PROTO_INET6: { struct sockaddr_in6 a; memset(&a, 0, sizeof(a)); a.sin6_family = AF_INET6; a.sin6_scope_id = interface; memcpy(a.sin6_addr.s6_addr, address->data.ipv6.address, 16); return QHostAddress((const sockaddr*) &a); } case AVAHI_PROTO_UNSPEC: return QHostAddress(); } return QHostAddress(); } namespace priv { static void browsep_client_callback(AvahiClient *c, AvahiClientState state, void * userdata); static void browsep_browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AvahiLookupResultFlags flags, void* userdata); static void browsep_resolve_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void* userdata); } } namespace Avahi { namespace priv { class BrowseP { public: static const ::AvahiPoll *m_qtpoll; QString m_serviceType; bool m_initialized; bool m_running; ::AvahiClient *m_client; ::AvahiServiceBrowser *m_browser; QByteArray m_service_type; Avahi::Browse *m_parent; QHash m_list; BrowseP(QString servicetype, Browse *parent) : m_serviceType(servicetype), m_initialized(false), m_running(false), m_client(0), m_browser(0), m_service_type(servicetype.toUtf8()), m_parent(parent) { int err; if (!m_qtpoll) { if (!(m_qtpoll = avahi_qt_poll_get())) { qWarning("avahi_qt_poll_get failed"); return; } } if (!(m_client = avahi_client_new(m_qtpoll, (AvahiClientFlags) 0, browsep_client_callback, this, &err))) { qWarning("avahi_client_new failed: %s", avahi_strerror(err)); return; } m_initialized = true; } void clearList() { QList l = m_list.values(); m_list.clear(); for (int i = 0; i < l.size(); ++i) { emit m_parent->lost(l.at(i)); } } void release() { stop(); if (!m_initialized) return; if (m_client) { avahi_client_free(m_client); m_client = 0; } m_initialized = false; } ~BrowseP() { release(); } void client_callback(AvahiClient *c, AvahiClientState state) { switch (state) { case AVAHI_CLIENT_S_RUNNING: break; case AVAHI_CLIENT_FAILURE: qWarning("avahi client failure: %s", avahi_strerror(avahi_client_errno(c))); release(); break; case AVAHI_CLIENT_S_COLLISION: case AVAHI_CLIENT_S_REGISTERING: break; case AVAHI_CLIENT_CONNECTING: break; } } void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AvahiLookupResultFlags /* flags */) { if (!m_browser) m_browser = b; Q_ASSERT(m_browser == b); if (protocol != AVAHI_PROTO_INET && protocol != AVAHI_PROTO_INET6) return; /* ignore non IPv[46] for now */ /* Called whenever a new services becomes available on the LAN or is removed from the LAN */ switch (event) { case AVAHI_BROWSER_FAILURE: qWarning("(Browser) %s", avahi_strerror(avahi_client_errno(m_client))); release(); return; case AVAHI_BROWSER_NEW: qDebug("(Browser) NEW: service '%s' of type '%s' in domain '%s'", name, type, domain); /* 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 (!(avahi_service_resolver_new(m_client, interface, protocol, name, type, domain, protocol, (AvahiLookupFlags) 0, browsep_resolve_callback, this))) { qWarning("Failed to resolve service '%s' on '%s': %s", name, domain, avahi_strerror(avahi_client_errno(m_client))); } break; case AVAHI_BROWSER_REMOVE: { ServiceKey k(interface, protocol, name, domain); Service s = m_list.value(k); if (s.valid()) { m_list.remove(k); emit m_parent->lost(s); } } break; case AVAHI_BROWSER_ALL_FOR_NOW: break; case AVAHI_BROWSER_CACHE_EXHAUSTED: break; } } void resolve_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags /* flags */) { Q_ASSERT(r); // if (protocol != AVAHI_PROTO_INET) goto done; /* ignore non IPv4 for now */ if (protocol != AVAHI_PROTO_INET && protocol != AVAHI_PROTO_INET6) goto done; /* ignore non IPv[46] for now */ /* Called whenever a service has been resolved successfully or timed out */ switch (event) { case AVAHI_RESOLVER_FAILURE: qDebug("(Resolver) Failed to resolve service '%s' of type '%s' at '%s' in domain '%s': %s", name, type, host_name, domain, avahi_strerror(avahi_client_errno(m_client))); break; case AVAHI_RESOLVER_FOUND: { qDebug("(Resolver) found service '%s' of type '%s' at '%s' in domain '%s'", name, type, host_name, domain); QHash qtxt; AvahiStringList *psl; for (psl = txt ; psl ; psl = psl->next) { QString e = QString::fromUtf8((const char*) psl->text, psl->size), key, value; int eqPos = e.indexOf(QChar('"')); if (eqPos == 0) continue; /* ignore empty keys */ if (eqPos < -1) { /* "boolean" */ key = e; } else { key = e.left(eqPos - 1); value = e.mid(eqPos + 1); } key = key.toLower(); qtxt.insert(key, value); } ServiceKey k(interface, protocol, name, domain); Service s = m_list.value(k); if (s.valid()) { m_list.remove(k); emit m_parent->lost(s); } s = Service(QString::fromUtf8(name), QString::fromUtf8(host_name), QString::fromUtf8(domain), fromAvahiAddress(address, interface), interface, protocol, port, qtxt); m_list.insert(k, s); emit m_parent->found(s); } } done: avahi_service_resolver_free(r); } bool start() { if (m_running) return true; if (!m_initialized) return false; clearList(); if (NULL == (m_browser = avahi_service_browser_new(m_client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, m_service_type.constData(), NULL, (AvahiLookupFlags) 0, browsep_browse_callback, this))) { qWarning("Failed to create service browser: %s", avahi_strerror(avahi_client_errno(m_client))); return false; } emit m_parent->started(); m_running = true; return true; } void stop() { clearList(); if (m_running) { emit m_parent->stopped(); m_running = false; } if (m_browser) { avahi_service_browser_free(m_browser); m_browser = 0; } } }; const AvahiPoll *BrowseP::m_qtpoll = 0; static void browsep_client_callback(AvahiClient *c, AvahiClientState state, void * userdata) { BrowseP *p = static_cast(userdata); p->client_callback(c, state); } static void browsep_browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AvahiLookupResultFlags flags, void* userdata) { BrowseP *p = static_cast(userdata); p->browse_callback(b, interface, protocol, event, name, type, domain, flags); } static void browsep_resolve_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void* userdata) { BrowseP *p = static_cast(userdata); p->resolve_callback(r, interface, protocol, event, name, type, domain, host_name, address, port, txt, flags); } static void announcep_client_callback(AvahiClient *c, AvahiClientState state, void * userdata); static void announcep_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata); class AnnounceP { public: static const AvahiPoll *m_qtpoll; QString m_serviceType, m_name; quint16 m_port; QHash m_txt; bool m_initialized; bool m_running; AvahiClient *m_client; AvahiEntryGroup *m_group; QByteArray m_service_type; Announce *m_parent; char *m_raw_name; /* avahi string */ QList m_txt_list; ::AvahiStringList *m_raw_txt; AnnounceP(QString serviceType, QString name, quint16 port, QHash txt, Announce *parent) : m_serviceType(serviceType), m_name(name), m_port(port), m_txt(txt), m_initialized(false), m_running(false), m_client(0), m_group(0), m_service_type(serviceType.toUtf8()), m_parent(parent), m_raw_name(avahi_strdup(name.toUtf8().constData())), m_raw_txt(0) { int err; QHashIterator i(m_txt); while (i.hasNext()) { QByteArray a; i.next(); if (i.value().isNull()) { m_txt_list.append(a = i.key().toUtf8()); } else { m_txt_list.append(a = QString("%1=%2").arg(i.key(), i.value()).toUtf8()); } AvahiStringList *l = avahi_string_list_new(a.constData(), NULL); l->next = m_raw_txt; m_raw_txt = l; } if (!m_qtpoll) { if (!(m_qtpoll = avahi_qt_poll_get())) { qWarning("avahi_qt_poll_get failed"); return; } } if (!(m_client = avahi_client_new(m_qtpoll, (AvahiClientFlags) 0, announcep_client_callback, this, &err))) { qWarning("avahi_client_new failed: %s", avahi_strerror(err)); return; } m_initialized = true; start(); } void release() { if (m_running) { m_running = false; } if (!m_initialized) return; if (m_group) { qDebug("avahi unregister service"); avahi_entry_group_reset(m_group); avahi_entry_group_free(m_group); m_group = 0; } if (m_client) { avahi_client_free(m_client); m_client = 0; } m_initialized = false; emit m_parent->stopped(); } ~AnnounceP() { release(); if (m_raw_name) { avahi_free(m_raw_name); m_raw_name = 0; } if (m_raw_txt) { avahi_string_list_free(m_raw_txt); m_raw_txt = 0; } } void client_callback(AvahiClient *c, AvahiClientState state) { m_client = c; switch (state) { case AVAHI_CLIENT_S_RUNNING: create_services(); break; case AVAHI_CLIENT_FAILURE: qDebug("avahi client failure"); release(); break; case AVAHI_CLIENT_S_COLLISION: case AVAHI_CLIENT_S_REGISTERING: if (m_group) { avahi_entry_group_reset(m_group); } break; case AVAHI_CLIENT_CONNECTING: break; } } void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state) { m_group = g; switch (state) { case AVAHI_ENTRY_GROUP_ESTABLISHED : emit m_parent->established(); break; case AVAHI_ENTRY_GROUP_COLLISION : { char *n = avahi_alternative_service_name(m_raw_name); avahi_free(m_raw_name); m_raw_name = n; m_name = QString::fromUtf8(m_raw_name); } /* And recreate the services */ avahi_entry_group_reset(m_group); create_services(); /* same name should result in AVAHI_ERR_COLLISION in create_services, emit collision there. */ break; case AVAHI_ENTRY_GROUP_FAILURE : qDebug("Entry group failure: %s", avahi_strerror(avahi_client_errno(m_client))); release(); break; case AVAHI_ENTRY_GROUP_UNCOMMITED: case AVAHI_ENTRY_GROUP_REGISTERING: break; } } bool create_services() { int ret; if (!m_group) { if (!(m_group = avahi_entry_group_new(m_client, announcep_entry_group_callback, this))) { qDebug("avahi_entry_group_new failed: %s", avahi_strerror(avahi_client_errno(m_client))); release(); return false; } } again: if (avahi_entry_group_is_empty(m_group)) { if ((ret = avahi_entry_group_add_service_strlst(m_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, (AvahiPublishFlags) 0, m_raw_name, m_service_type.constData(), NULL, NULL, m_port, m_raw_txt)) < 0) { if (ret == AVAHI_ERR_COLLISION) goto collision; qDebug("Failed to add '%s': %s", m_service_type.constData(), avahi_strerror(ret)); release(); return false; } if ((ret = avahi_entry_group_commit(m_group)) < 0) { qDebug("Failed to commit entry group: %s", avahi_strerror(ret)); release(); return false; } } return true; collision: qDebug("avahi name collision: %s, trying a new one", m_raw_name); { char *n = avahi_alternative_service_name(m_raw_name); avahi_free(m_raw_name); m_raw_name = n; m_name = QString::fromUtf8(m_raw_name); } avahi_entry_group_reset(m_group); emit m_parent->collision(); goto again; } bool start() { if (m_running) return true; if (!m_initialized) return false; m_running = true; emit m_parent->started(); return true; } void stop() { release(); } }; const AvahiPoll *AnnounceP::m_qtpoll = 0; static void announcep_client_callback(AvahiClient *c, AvahiClientState state, void * userdata) { AnnounceP *p = static_cast(userdata); p->client_callback(c, state); } static void announcep_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) { AnnounceP *p = static_cast(userdata); p->entry_group_callback(g, state); } } /* public interfaces */ Browse::Browse(QString serviceType, QObject *parent) : QObject(parent), m_priv(new priv::BrowseP(serviceType, this)) { } Browse::~Browse() { delete m_priv; m_priv = 0; } bool Browse::start() { return m_priv->start(); } void Browse::stop() { m_priv->stop(); } QString Browse::serviceType() const { return m_priv->m_serviceType; } QList Browse::currentList() const { return m_priv->m_list.values(); } Announce::Announce(QString serviceType, QString name, quint16 port, QHash txt, QObject *parent) : QObject(parent), m_priv(new priv::AnnounceP(serviceType, name, port, txt, this)) { } Announce::~Announce() { delete m_priv; m_priv = 0; } bool Announce::start() { return m_priv->start(); } void Announce::stop() { m_priv->stop(); } QString Announce::serviceType() const { return m_priv->m_serviceType; } QString Announce::name() const { return m_priv->m_name; } quint16 Announce::port() const { return m_priv->m_port; } QHash Announce::txt() const { return m_priv->m_txt; } QString Announce::txt(QString key) const { return m_priv->m_txt.value(key); } }