Finding Your Local Network
by havoc
At Red Hat’s somewhat-in-the-vicinity-of-Boston offices, a few of us
have been trying to understand the local service discovery and
presence work others have been doing and figure out how we’d use it to
do cool things in apps.
Today I’ll blog ideas and questions about local service discovery and
later I have some additional questions to throw out there for the
presence experts.
I still don’t understand everything all that well, and all the docs
I’ve found assume you know a lot more about DNS than I do, but here’s
my summary of how local service discovery works. Avahi implements the
mDNS/DNS-SD/Rendezvous/Bonjour/Zeroconf/whatever set of specs, which
in combination form a system for service discovery on a local
network. There are three steps to discovering information on the
network:
-
First, you can browse services. In this stage your program gets a list of
hostnames, one per service offered and that’s it. For example in
link-local
XMPP, you ask for all hostnames that end in
“._presence._tcp.local.” and by convention those names will be
username@machinename, so the full host is
“johndoe@example._presence._tcp.local.” or something like that.
The only information you get from this step is
the “johndoe@example” for link-local XMPP, or for browsing WebDAV
shares you might get “Havoc’s Files” for example. there is no way to
get or provide information beyond the one string. -
Second, you can resolve the services. This means making a request for
each service, and getting back the service’s IP address and port so
you can connect to it. You can also get some key-value pairs
called TXT records, but there can’t be too many of these and they
can’t be too big. In link-local XMPP, the TXT records optionally
contain the jabber ID, email address, user name, and a hash of the
user’s photo, among other things. As I read the specs, TXT records
should always be optional optimizations and not required – that is,
you should be able to get the same information by connecting to the
service and asking. I’m not sure of the rationale here. -
Third, you can connect to the service and talk to it using its
application-specific protocol, whatever that protocol is – WebDAV,
XMPP, or whatever. You could do this just to find out stuff missing
from the TXT records, or you could do it in order to take a
service-specific action such as store a file, send an XMPP message,
or print a document.
As best I can tell, the more of these steps you do, the more expensive
it is. I don’t understand how expensive since I know zip
about networking below the session layer. But
it seems intuitive that if you had say a network of 500 machines and
all 500 made an XMPP connection to the other 499 to get user photos,
there could be some issues.
You can find the Avahi API
for the first two steps here, or on the publish/server side, you use this
API. The docs also have a
simple publish example and a simple
browse example. Note that the publish example doesn’t do
anything, it advertises services but does not create them (does not listen on any
ports or implement a protocol).
Right now you have to write a network server (potentially inventing
a new protocol) to use Avahi. See the third step I mentioned
above. There are a few problems here; one, it’s too hard. Two, it’s a
pretty rough security footprint to audit if every app that uses local
discovery is listening on a port. Three, like me I’m sure app authors
don’t understand the performance and other issues involved in using
Avahi directly – it requires a lot of knowledge. (The Avahi API is
quite nice, but it is on the level of manually doing the
above-mentioned three steps, which are low-level steps.)
Right now services are not tied to sessions or people.
Say I browse the network and it has “Havoc’s Files” and “Bryan’s
Files” on there. It also has link-local XMPP for both Havoc and
Bryan. However, there is no way for me to associate the files with the
XMPP (as far as I know). So if I wanted to show a “See Bryan’s Files”
button next to his photo in chat, that would be challenging.
If I parsed what Dan Williams had to say, Sugar somewhat addresses
both of these issues. First, Sugar defines a “person” as a keypair
generated on each laptop. Since there’s one laptop per child, keypair
equals laptop equals person. Next, Sugar uses a hash of the keypair
for its XMPP identifiers. So it can map from server-based or local
XMPP to people. Finally, Sugar uses XMPP not only for chat but for all
IPC over the network. To avoid using XMPP directly, it tunnels
arbitrary IPC through XMPP by serializing D-Bus messages and stuffing
them into XMPP messages (known as Tubes).
With the Sugar platform then, instead of using Avahi directly at all,
what you could do is add additional properties available via
link-local XMPP. A small number of simple properties could go in the
link-local XMPP TXT records perhaps, while larger or more complex
properties would require each client to establish an XMPP connection
and ask for the property.
What should we do for GNOME and its new improved flavor,
Online Desktop?
The API I’m thinking would be most convenient for apps is something
like the following. First, the Mugshot client for example could do:
AddInfoToOurSession("org.mugshot.MugshotInfo", { 'mugshotId' : 'whatever', 'name' : 'Havoc', 'photoUrl' : 'http://whatever' })
or say a video game:
AddInfoToOurSession("org.gameserverexample.Server", { 'serverName' : 'whatever', 'username' : 'Havoc' })
Then, any other app in this session or anywhere on the network could
ask for “info bundles” by name as follows:
mugshot_infos = GetInfoFromOtherSessions("org.mugshot.MugshotInfo");
Where each info in the list would be a pair of two dictionaries, one
representing the session and the other representing the requested info
from that session:
( { 'session' : 'guid-for-session', 'machine' : 'guid-for-machine', ... }, { 'mugshotId' : 'whatever', 'name' : 'Havoc', 'photoUrl' : 'http://whatever' } )
(D-Bus already provides a guid for the session and machine, if you’re
wondering where those come from.)
On the network we have a set of sessions; each session contains named
“infos” or bundles of related properties; clients can ask for a list
of all “infos” on the network of a particular kind.
This hypothetical API would allow apps to advertise information
without writing a network server or listening on a port. It would also
automatically link all advertised information to a session ID, and if
some of the info advertised identifies the user, the session can in
turn be linked to a user.
This kind of API does not offer a way to establish a connection or
communicate, though; just a way to advertise.
Something like Fast User Switching could even use this – instead of
using gdm to keep track of users to switch to, it could list sessions
on the local machine, and one of the properties of each session would
be the local username and a name/photo.
If we add features such as link-local chat, or link-local file
sharing, I would want them to work with other sessions on the same
machine, in the same way they work with sessions on other machines.
A simple API like this reduces the surface area for security
exploits in the sense that there would be only one codebase that was
network-facing. Of course, the issue of which things are safe
to publish on the local network remains, but that is unavoidably going
to be up to the judgment of app developers.
To implement an API like this I can imagine multiple approaches, and I
don’t know which is best. The three I have come up with are:
-
No network-facing daemon, just a little daemon on the session bus that
allows apps to register the info bundles, and then advertises each one
with a separate hostname and puts the “info bundle” properties in TXT
records. This is a thin convenience layer over Avahi. -
The same little daemon on the session bus, but it also listens on the
network and allows clients to connect and get “info bundle”
properties that are too large to fit in the TXT records. -
Link-local XMPP. Add a session ID to the link-local XMPP TXT
records. Add special XMPP messages, or Tubes messages, to get the
“info bundles” that each app has made available. This approach
involves by far the most code, though much of that code is already
written.
So the no-network-facing daemon approach would be a thing like:
Havoc's Mugshot Stuff.mugshot.org._gnome_session.local TXT session=guid-for-session TXT machine=guid-for-machine TXT mugshotId=whatever TXT photoUrl=http://whatever Havoc's UNIX Account.unixinfo.gnome.org._gnome_session.local TXT session=guid-for-session TXT machine=guid-for-machine TXT uid=503 TXT gecos=Havoc Havoc's Card Game Account.cardgame.gnome.org._gnome_session.local TXT session=guid-for-session TXT machine=guid-for-machine TXT game_server=http://whatever TXT server_username=foobar ...
In this approach, only small properties can be advertised. There are
lots of hostnames to browse and resolve, but no services to connect
to. The TXT records are essential (the whole approach) and not an
optimization.
Remember this approach is still implemented with a daemon, but it’s a
small service on the session bus, not a network-facing daemon.
If you make the daemon network-facing also, it can support connections
to get all the information, so only needs to advertise one time on Avahi.
Havoc's Stuff._gnome_session.local TXT session=guid-for-session (optional, could also ask service) TXT machine=guid-for-machine (optional, could also ask service)
And then listening at “Havoc’s Stuff._gnome_session.local” is a custom
server that supports asking for the machine/session ID and all the
“info bundles” available from the session.
I started experimenting with this second approach by writing the small
service on the session bus, and then having it also create a custom DBusServer
(not a bus) listening on the network. Since D-Bus took most of the
work out of writing my own network server and the Avahi API is simple,
I got it going instantly (other than a long detour to make libdbus
support anonymous clients).
The third approach is essentially equivalent to the second, except it
uses link-local XMPP instead of D-Bus for the network-facing protocol.
With Tubes, the end result is pretty similar (the API is D-Bus, but
the D-Bus is pushed through XMPP behind the scenes). There is a LOT
more code involved – the Telepathy + Salut + Tubes stack is in the
tens of thousands range, vs. a couple thousand for approach 2. But if
that code is going to be used anyway for other purposes, it may not
matter.
Again here’s the simple API for apps I was thinking of:
AddInfoToOurSession("org.mugshot.MugshotInfo", { 'mugshotId' : 'whatever', 'name' : 'Havoc', 'photoUrl' : 'http://whatever' })
If we have that, then we could experiment with these three approaches
or the no doubt better ideas other people have without too much
trouble.
I don’t know what the performance issues with these various approaches
are. For example, is it worse to have tons of advertised services as
in approach one, or to have to connect to each service every time, as
in the second two approaches? Or are all three of these approaches
doomed in some way? On some level, it seems like 500 sessions each
loading stuff from each of the others just can’t work. What are the
tricks to make it work?
And what about change notification? Is it possible? How is it done
without causing 499 other computers to all ask my computer for new
information at once as soon as I change my user photo on a large network?
Since my blog lacks comments, feel free to send thoughts to the online desktop
group, you don’t have to subscribe to receive mail in order to
post, and you can post from the web UI.
(This post was originally found at http://log.ometer.com/2007-06.html#14)