publicservice.cpp
00001 /* This file is part of the KDE project 00002 * 00003 * Copyright (C) 2004, 2005 Jakub Stachowski <qbast@go2.pl> 00004 * 00005 * This library is free software; you can redistribute it and/or 00006 * modify it under the terms of the GNU Library General Public 00007 * License as published by the Free Software Foundation; either 00008 * version 2 of the License, or (at your option) any later version. 00009 * 00010 * This library is distributed in the hope that it will be useful, 00011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 * Library General Public License for more details. 00014 * 00015 * You should have received a copy of the GNU Library General Public License 00016 * along with this library; see the file COPYING.LIB. If not, write to 00017 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 * Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "config.h" 00022 00023 #include "publicservice.h" 00024 #ifdef HAVE_SYS_TYPES_H 00025 #include <sys/types.h> 00026 #endif 00027 #include <netinet/in.h> 00028 #include <sys/socket.h> 00029 #include <qapplication.h> 00030 #include <network/ksocketaddress.h> 00031 #include <kurl.h> 00032 #include <unistd.h> 00033 #include <avahi-client/client.h> 00034 #ifdef AVAHI_API_0_6 00035 #include <avahi-client/publish.h> 00036 #endif 00037 #include <avahi-common/alternative.h> 00038 #include <avahi-common/strlst.h> 00039 #include "sdevent.h" 00040 #include "responder.h" 00041 #include "servicebrowser.h" 00042 #include "settings.h" 00043 00044 namespace DNSSD 00045 { 00046 static unsigned long publicIP(); 00047 00048 void publish_callback (AvahiEntryGroup*, AvahiEntryGroupState s, void *context); 00049 00050 class PublicServicePrivate 00051 { 00052 public: 00053 PublicServicePrivate() : m_published(false), m_running(false), m_collision(false) 00054 {} 00055 bool m_published; 00056 bool m_running; 00057 bool m_collision; 00058 AvahiEntryGroup* m_group; 00059 void commit() 00060 { 00061 if (!m_collision) avahi_entry_group_commit(m_group); 00062 } 00063 00064 }; 00065 00066 PublicService::PublicService(const QString& name, const QString& type, unsigned int port, 00067 const QString& domain) 00068 : QObject(), ServiceBase(name, type, QString::null, domain, port) 00069 { 00070 d = new PublicServicePrivate; 00071 if (Responder::self().client()) d->m_group = avahi_entry_group_new(Responder::self().client(), 00072 publish_callback,this); 00073 connect(&Responder::self(),SIGNAL(stateChanged(AvahiClientState)),this,SLOT(clientState(AvahiClientState))); 00074 if (domain.isNull()) 00075 if (Configuration::publishType()==Configuration::EnumPublishType::LAN) m_domain="local."; 00076 else m_domain=Configuration::publishDomain(); 00077 } 00078 00079 00080 PublicService::~PublicService() 00081 { 00082 if (d->m_group) avahi_entry_group_free(d->m_group); 00083 delete d; 00084 } 00085 00086 void PublicService::tryApply() 00087 { 00088 if (fillEntryGroup()) d->commit(); 00089 else { 00090 stop(); 00091 emit published(false); 00092 } 00093 } 00094 00095 00096 void PublicService::setServiceName(const QString& serviceName) 00097 { 00098 m_serviceName = serviceName; 00099 if (d->m_running) { 00100 avahi_entry_group_reset(d->m_group); 00101 tryApply(); 00102 } 00103 } 00104 00105 void PublicService::setDomain(const QString& domain) 00106 { 00107 m_domain = domain; 00108 if (d->m_running) { 00109 avahi_entry_group_reset(d->m_group); 00110 tryApply(); 00111 } 00112 } 00113 00114 00115 void PublicService::setType(const QString& type) 00116 { 00117 m_type = type; 00118 if (d->m_running) { 00119 avahi_entry_group_reset(d->m_group); 00120 tryApply(); 00121 } 00122 } 00123 00124 void PublicService::setPort(unsigned short port) 00125 { 00126 m_port = port; 00127 if (d->m_running) { 00128 avahi_entry_group_reset(d->m_group); 00129 tryApply(); 00130 } 00131 } 00132 00133 void PublicService::setTextData(const QMap<QString,QString>& textData) 00134 { 00135 m_textData = textData; 00136 if (d->m_running) { 00137 avahi_entry_group_reset(d->m_group); 00138 tryApply(); 00139 } 00140 } 00141 00142 bool PublicService::isPublished() const 00143 { 00144 return d->m_published; 00145 } 00146 00147 bool PublicService::publish() 00148 { 00149 publishAsync(); 00150 while (d->m_running && !d->m_published) Responder::self().process(); 00151 return d->m_published; 00152 } 00153 00154 void PublicService::stop() 00155 { 00156 if (d->m_group) avahi_entry_group_reset(d->m_group); 00157 d->m_published = false; 00158 } 00159 bool PublicService::fillEntryGroup() 00160 { 00161 AvahiStringList *s=0; 00162 QMap<QString,QString>::ConstIterator itEnd = m_textData.end(); 00163 for (QMap<QString,QString>::ConstIterator it = m_textData.begin(); it!=itEnd ; ++it) 00164 s = avahi_string_list_add_pair(s, it.key().utf8(),it.data().utf8()); 00165 #ifdef AVAHI_API_0_6 00166 bool res = (!avahi_entry_group_add_service_strlst(d->m_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, (AvahiPublishFlags)0, 00167 m_serviceName.isNull() ? avahi_client_get_host_name(Responder::self().client()) : m_serviceName.utf8().data(), 00168 m_type.ascii(),domainToDNS(m_domain),m_hostName.utf8(),m_port,s)); 00169 #else 00170 bool res = (!avahi_entry_group_add_service_strlst(d->m_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 00171 m_serviceName.isNull() ? avahi_client_get_host_name(Responder::self().client()) : m_serviceName.utf8().data(), 00172 m_type.ascii(),m_domain.utf8(),m_hostName.utf8(),m_port,s)); 00173 #endif 00174 avahi_string_list_free(s); 00175 return res; 00176 } 00177 00178 void PublicService::clientState(AvahiClientState s) 00179 { 00180 if (!d->m_running) return; 00181 switch (s) { 00182 #ifdef AVAHI_API_0_6 00183 case AVAHI_CLIENT_FAILURE: 00184 #else 00185 case AVAHI_CLIENT_S_INVALID: 00186 case AVAHI_CLIENT_DISCONNECTED: 00187 #endif 00188 stop(); 00189 emit published(false); 00190 break; 00191 case AVAHI_CLIENT_S_REGISTERING: 00192 case AVAHI_CLIENT_S_COLLISION: 00193 avahi_entry_group_reset(d->m_group); 00194 d->m_collision=true; 00195 break; 00196 case AVAHI_CLIENT_S_RUNNING: 00197 if (d->m_collision) { 00198 d->m_collision=false; 00199 tryApply(); 00200 } 00201 } 00202 } 00203 00204 void PublicService::publishAsync() 00205 { 00206 if (d->m_running) stop(); 00207 00208 if (!d->m_group) { 00209 emit published(false); 00210 return; 00211 } 00212 AvahiClientState s=avahi_client_get_state(Responder::self().client()); 00213 d->m_running=true; 00214 d->m_collision=true; // make it look like server is getting out of collision to force registering 00215 clientState(s); 00216 } 00217 00218 void publish_callback (AvahiEntryGroup*, AvahiEntryGroupState s, void *context) 00219 { 00220 QObject *obj = reinterpret_cast<QObject*>(context); 00221 if (s!=AVAHI_ENTRY_GROUP_ESTABLISHED && s!=AVAHI_ENTRY_GROUP_COLLISION) return; 00222 PublishEvent* pev=new PublishEvent(s==AVAHI_ENTRY_GROUP_ESTABLISHED); 00223 QApplication::postEvent(obj, pev); 00224 } 00225 00226 const KURL PublicService::toInvitation(const QString& host) 00227 { 00228 KURL url; 00229 url.setProtocol("invitation"); 00230 if (host.isEmpty()) { // select best address 00231 unsigned long s_address = publicIP(); 00232 if (!s_address) return KURL(); 00233 KNetwork::KIpAddress addr(s_address); 00234 url.setHost(addr.toString()); 00235 } else url.setHost(host); 00236 //FIXME: if there is no public interface, select any non-loopback 00237 url.setPort(m_port); 00238 url.setPath("/"+m_type+"/"+KURL::encode_string(m_serviceName)); 00239 QString query; 00240 QMap<QString,QString>::ConstIterator itEnd = m_textData.end(); 00241 for (QMap<QString,QString>::ConstIterator it = m_textData.begin(); it!=itEnd ; ++it) 00242 url.addQueryItem(it.key(),it.data());; 00243 return url; 00244 } 00245 00246 void PublicService::customEvent(QCustomEvent* event) 00247 { 00248 if (event->type()==QEvent::User+SD_PUBLISH) { 00249 if (!static_cast<PublishEvent*>(event)->m_ok) { 00250 setServiceName(QString::fromUtf8(avahi_alternative_service_name(m_serviceName.utf8()))); 00251 return; 00252 } 00253 d->m_published=true; 00254 emit published(true); 00255 } 00256 } 00257 00258 void PublicService::virtual_hook(int, void*) 00259 { 00260 } 00261 00262 static unsigned long publicIP() 00263 { 00264 struct sockaddr_in addr; 00265 socklen_t len = sizeof(addr); 00266 int sock = socket(AF_INET,SOCK_DGRAM,0); 00267 if (sock == -1) return 0; 00268 addr.sin_family = AF_INET; 00269 addr.sin_port = 1; // Not important, any port and public address will do 00270 addr.sin_addr.s_addr = 0x11111111; 00271 if ((connect(sock,(const struct sockaddr*)&addr,sizeof(addr))) == -1) { close(sock); return 0; } 00272 if ((getsockname(sock,(struct sockaddr*)&addr, &len)) == -1) { close(sock); return 0; } 00273 ::close(sock); 00274 return addr.sin_addr.s_addr; 00275 } 00276 00277 00278 } 00279 00280 #include "publicservice.moc"