[Kde-hardware-devel] [GSoC] Preliminary UPnP support proposal

Tuomo Penttinen tp at herqq.org
Sun Apr 11 19:08:14 CEST 2010


Hello all,

On Fri, April 9, 2010 4:20 pm, Friedrich W. H. Kossebau wrote:
> Vendredi, le 9 avril 2010, à 03:15, Tuomo Penttinen a écrit:
>> Hello all,
>>
>> On Wed, April 7, 2010 3:09 pm, Friedrich W. H. Kossebau wrote:
>> > Hi all (and Tuomo),
>> >
>> > Mardi, le 6 avril 2010, à 23:41, Kevin Ottens a écrit:
>> >> On Wednesday 31 March 2010 14:42:03 Tuomo Penttinen wrote:
>> >> > I agree. The UPnP discovery protocol is lightweight enough that I
>> >> > wouldn't worry about it in most environments.
>> >
>> > Emphasis on _most_ environments ;)
>>
>> Indeed. ;)
>
> And add "today" ;)

Well, if it isn't a problem today, I'd say it's less likely to be a
problem tomorrow, right? ;)

>> >> Actually I like numbers. :-)
>> >>
>> >> My worry lies in the fact that we're going for the full game of
>> >> having it in libsolid, which means that such a discovery will be
>> >> triggered by any apps which starts and ask for a StorageAccess
>> >> (which means at least anything which spawns a file dialog).
>> >
>> > And slightly worse. Any SSDP message coming in will wake up all of the
>> > processes which listen for these, just to find out if it is
>> > interesting to them or not. Minor case, but still burns ressources
>> > (think of mobile uses) and should be avoided as a standard pattern.
>>
>> So you would replace the aforementioned "wakeup of all" with some type
>> of central registration mechanism? This system would enable processes
>> to tell the registrar which messages / resources they are interested of
>> and in turn the registrar would inform them upon reception of desired
>> message types?
>
> Exactly. I could also envision a trigger system to have the central system
> (like Cagibi) additionally start dedicated handlers if certain devices
> show up or disappear.

Okay, I'm starting to see where you're going. Rather than spread my
thoughts concerning this here and there I'll comment the topic at the end
and focus on individual issues as we go along.

>> Fair enough, but to me it would seem that maintaining and using such a
>> registry comes up with a price, which justification still isn't so clear
>> to me. Of course, it doesn't have to be, but I find this interesting
>> enough to comment. :-)
>>
>> First, it's not entirely straightforward to pull numbers in this matter
>> to show that the registration mechanism is so much more efficient that
>> more complex code and a dependency to an external service are warranted.
>
> I don't think the code is that more complex. Old and known patterns, did
> it in a few days ;) External service: yes. But the KDE platform is full
> with these, so one more is not really a problem per se.

This relates to the few points I'll make at the end, so I'm skipping it here.

>> Second, in the worst case the registrar would have to inform every
>> registrant upon receiving a SSDP message via some IPC mechanism anyway.
>
> Yes, but just the worst case (really, what kind of registrants would be
> interested in all devices besides device browsers?).

And registrants who just don't care or want more control and want to make
the decisions by themselves. Device sniffers and validators probably
belong to this category as well.

>> Third, UPnP discovery is lightweight compared to the description phase,
>> action invocation and eventing, so I'm not sure caching the results of
>> discovery alone will have any meaningful impact on a large scale. I say
>> this because if an application is interested in SSDP messages it is
>> probably interested of UPnP in general, which includes action invocation
>> and eventing. You can't really cache these and incidentally these two
>> are potentially far worse resource consumers.
>
> This doesn't stop SSDP from being outsourced to a central process with
> some gain.

No, but my point was that optimizations are best targeted to issues that
measurably matter. Inlining a call to a function that performs a
bubblesort doesn't change the fact that the algorithm is still quadratic.
;)

>> > Though HAL/Solid isn't any better here, both also don't offer any kind
>> > of active search/query (cmp. view in database), if I looked correctly
>> > (DeviceNotifier reports simply about all devices), so wake up all of
>> > the processes.
>> >
>> > Still no numbers. And these might be small. But all those small
>> > numbers add up :)
>>
>> Indeed they do and I'm not saying that you shouldn't consider the little
>> details. I'm just not sure if devising a cache mechanism for UPnP
>> discovery will provide any true benefit.
>>
>> In any case, here's some numbers and background information about the
>> UPnP discovery for the interested.
>>
>> To discover all UPnP devices on a network a control point usually sends
>> 1-3 UDP messages to the 239.255.255.250 multicast address. A single
>> message is enough, but more may be sent due to the unreliable nature of
>> UDP. Each UPnP root device in turn should respond *directly* to the
>> control point with 3+2d+k UDP messages, where 'd' is the number of
>> embedded devices and 'k' is the number of distinct service types within
>> a device tree. Often UPnP devices do not have any embedded devices and
>> contain only a few services. But again, the datagrams may be sent more
>> than once due to the unreliable nature of the protocol.
>
> Does it really have to be 3+2d+k UDP messages? For Cagibi I just query for
> all "urn:schemas-upnp-org:device:upnp:rootdevice:1", listen to the
> answers/notifications, read the device description from the given location
> and should have a complete view of everything, no?

It does if the UPnP device follows the UDA specification. And yes, all of
those messages point a control point to the location where the device
description can be retrieved. The device description in turn details the
locations of service descriptions and icons, so yes, once you have
retrieved a device description you have all the information you need in
order to build the "complete view".

I'm guessing the primary motivation for these messages was that together
they give a fairly good picture of a UPnP device, even without the
descriptions. You can't invoke actions or subcsribe to events without the
descriptions, but not all control points want to do that with every device
anyway. Since the descriptions can be of arbitrary size the designers
probably envisioned this as a possibility for an optimization as well.

> At least I remember someone once teached me that UPnP devices are static
> in their setup, so only whole UPnP (root) devices come and go, not also
> embedded devices and services individually. Is that wrong?

No, that's correct. A standard-compliant UPnP device should function like
that, with a small twist. UDA 1.1 introduces the concept of
"configuration", which in essence refers to a static set of (device &
service) descriptions with constant content. A configuration cannot
change, but there can be many configurations. When a UPnP v1.1 device
advertises a new configuration UPnP v1.1 control points should detect this
and they should build their internal models again. This concept gives a
UPnP device the possibility of updating its advertized capabilities /
published information without changing its Unique Device Name. Changing
the name would essentially "create a new device". So UPnP v1.1 devices are
not necessarily "static" in their setup, but embedded devices and services
still cannot go offline independently. But then again I have to mention
that I've yet to see this used in practise. The majority of UPnP devices
still seem to follow the UDA 1.0.

>> On the other hand, a UPnP device has to announce itself to the network
>> using the said multicast address when it becomes online. After that it
>> has to repeat the announcements periodically before they expire until it
>> goes offline, which it also has to announce. A root UPnP device announces
>> itself, its embedded devices and distinct service types similar to the
>> response it sends to a discovery issued by a control point. The interval
>> between the repeated announcements seems to be quite often something
>> around 5-15 minutes, although the UPnP device architecture specification
>> instructs to use values greater than or equal to 30 minutes.
>
> Caring for the (repeating) announcements/responces of the locally active
> UPnP device implementations is also a task I see for Cagibi (no need to
> wake up all processes offering UPnP devices for an alive-update or
> discover-answer). Bonus for still sending byebye for crashed device
> processes ;) There could be also support for not running instances, so
> Cagibi would all the time report about the existance of the UPnP device,
> the program itself is only started if some controlpoint starts to talk to
> it. And then can suspend itself again after idling a while.

This too strengthens my assumption about where you're going, so I'm
skipping this here and I'll get back to this at the end.

>> A typical discovery request is around 125 bytes in size and a typical
>> response to that or an "alive announcement" is between 300 and 400
>> bytes.
>
> The response is done randomly in a time span of 0 to max (e.g. 3, as given
> in the request) seconds, right?

Yes that's how it is specified and the 'max' should not be greater than 5
seconds.

>> It is important to note that control points should perform discovery
>> only
>> when they enter network, since devices are required by the UDA
>> specification to announce their presence as outlined above.
>
> With a central proxy/cache like Cagibi this also would improve, as
> discovery is only done once at the start of Cagibi, not on every start of
> every program with interest in UPnP (like there is by the Solid UPnP
> backend).

Yes, it would be an improvement in terms of resource usage, but I'm still
not convinced (as that would require testing & profiling :)) that such an
improvement is truly worthwhile as in itself. Now I know you are not going
for that and you have much more than that in mind, but this is still a
good chance to repeat my view for future reference; optimizations that
have an effect to the system design should be shown by some type of
measurements to have any real benefit to any of the metrics that are held
important.

>> >> > In my opinion, even the description
>> >> > phase doesn't add that much of network load that I'd want to pay
>> >> > the price of added complexity, which the inter-process caching
>> >> > solution introduces.
>> >>
>> >> Fair enough, let's avoid premature optimization. And it's all buried
>> >> in the backend anyway so it's not like we're going to break BC later
>> >> on if we introduce complexity for the optimizations.
>>
>> My thoughts exactly.
>
> Well, having done some work already in this area I don't think this is
> premature now. Remember Solid is linked to almost every KDE program. Which
> means the backend to integrate UPnP stuff is active in many processes,
> even those not interested in UPnP devices. And for the UPnP backend to
> work I have to have a complete cache of all UPnP devices there.  When I
> played with Cagibi as a lib for the Solid UPnP backend I had to link
> Solid also to QtNetwork. With Cagibid as a daemon I don't. And I also do
> not need to maintain a local cache, just like the HAL backend on demand
> relays requests to the HAL daemon the Cagibi backend relays requests to
> the Cagibi daemon. The code has become even smaller compared to before.
> Also for the network:/ kio-slave this is all that is needed. It just cares
> for the description and the presentation url, never interacts with the
> devices itself. The overhead for UPnP listing this way is just around a
> hundred lines of code altogether.

Unfortunately I can't really comment this due to my lack of understanding
of Solid. Although I must say that I don't like a situation either where a
process *completely* uninterested of UPnP has a link time dependency to a
UPnP library. That should be avoided if at all possible.

>> > Currently KDE software basically will be a client to services from
>> > UPnP devices (being control point in UPnP terms). So if there is a
>> > convenience lib one should be just for client stuff IMHO. Server stuff
>> > should be handled by a different lib. I suppose that code for server
>> > stuff is larger and would just be unneeded payload for most
>> > applications (also in disk size). (not sure how the P2P situation with
>> > mobile devices proves me wrong here)
>> >
>> > We also wouldn't put http server stuff into the http access lib, would
>> > we?
>>
>> Actually, that's not entirely true. UPnP eventing requires a control
>> point to listen for asynchronous events published by UPnP devices. The
>> protocol is called "GENA", which is layered above HTTP and it requires
>> minimal HTTP servers on both sides. In addition, I wouldn't say that the
>> server stuff is larger or more complex compared to a proper control
>> point. In many ways a UPnP device is actually more straightforward to
>> implement compared to a control point that implements the UPnP stack in
>> full and provides some type of an API for users.
>
> Now, I would also put the eventing stuff into a dedicated proxy process,
> if only for firewall and security reasons. Or am I the only one to
> consider it a less good idea to have a full UI program with my user
> rights accessable from the network?
> Just that I have no real clue yet how this could be done best. Due to
> authorization stuff there possibly should be one central process per user,
> not globally.

This is another very interesting topic for discussion. :) I'd say it is
about UPnP security (or lack of it) in general, not just about listening
sockets. I think it is fair to say that the UPnP base design is not
secure. There are the DeviceSecurity and SecurityConsole device templates
to address at least some of the more prevalent security issues, but the
base architecture is not secure. Because of that the use of UPnP without
additional security measures in a public network is an inherent security
risk no matter where you offload the socket code.

So before getting too carried away with security issues related to UPnP, I
think it would be important to define the use-cases and requirements to
find out how you are really going to approach the UPnP world. These should
help in defining the security requirements and responsibilities of the
system. If the requirements define proper security as an important system
design attribute and that it is something *you* are responsible for,
you're going to need whole lot more than the aforementioned offloading of
socket code into a presumably safe process.

>> The HUPnP shared library is about 1.2 megabytes built on my 64 bit
>> kubuntu machine. I haven't optimized for the binary size yet, so there
>> could be some leeway. The object codes for control point and upnp device
>> functionality are roughly equal in size and they actually share quite a
>> bit of the code base, including the "device model" users code against on
>> client and server side. Taking purely the server stuff out would
>> probably yield a save of 300-350 kilobytes. Certainly, purely UPnP device
>> (server) stuff isn't required at a client that is purely a control
>> point. On the other hand, there are valid use-cases where a client
>> application will need both the server and client code.
>
> Poses the question if it is a good idea to implement both controlpoint and
> device in the same instance. Again for firewall and security reasons I
> would run the server stuff in a separate program/process. Don't you think
> this is a valid concern?

As noted above, I don't think security in this case is this simple. But
since I find data/information security fascinating, I can't help but bite
and forget that. ;)

So, I'm guessing you mean there's more attack surface when client and
server code are run in the same process? Basically you're indicating that
it increases the possibility for an attack, where the exploitable bug is
either in client or in server, but the input that triggers the control
flow leading to that would come in from the other? I admit, this *could*
be more prevalent when the two are in the same process, but there are no
guarantees that the malicious input can't reach the exploitable code even
if the two live in different processes. Furthermore, separating the two as
described definitely incurs a performance + complexity overhead, which
might matter.

>> For instance, there could be a device or
>> an application that is both a media renderer (a UPnP device) and a
>> control point. The control point fetches the content from a media server
>> and uses the built-in media renderer to render the content.
>
> The program implementing this kind of UPnP device simply links to both
> libs, control-point and device-impl. But this kind of device is an
> exception, not the rule, isn't it? I expect most UPnP usages in KDEs
> software to be controlpoint-only, so the server part in a single lib
> would be just bloat to them.

You have a point there, especially if the library is not deployed
system-wide, but as stand-alone with programs that need it. In system-wide
deployment the presence of bloat is far less evident, since the library is
already in the system and a client application that does not use the
server part will not contain nor reference any part of the server code at
runtime. The linker most certainly will not index unreferenced symbols for
the loader to load. In system-wide deployment I'd say the point is truly
valid only if no program uses the server part or the server part is used
very rarely.

That being said, of course such a separation could be done. In case of
HUPnP it would mean that the code would be separated into three separate
libraries; one for client, one for server and one for common used by both,
each roughly around 400kb. But this is where opinions start to fly. Some
want shared libraries small, some a bit bigger and some even very large.
Consider any Qt library for instance. When you link to QtGui you rarely
(if ever) make full use of it, but it still makes sense to have a single
shared library instead of ten, even if it is a bit bigger and contains
"bloat" to you in some case. I could throw in a joke or two regarding
one's "preferences" concerning size, but I probably should not. ;)

>> > Doing the discovery in a proxy/cache not only avoids work duplication
>> > (increase of resource needs per process), it has also the advantage to
>> > be faster (cache). And it is done for other discovery systems, too:
>> > There is Avahi for DNS-SD, as a central small proxy process (our code
>> > layer to it is KDNSSD).
>>
>> It could be faster if properly implemented, I agree, but are such
>> (possibly very small) gains in speed in this particular area worth the
>> trouble? Furthermore, if the work to be done is very small the benefits
>> of avoiding it tend to be very small as well... ;) I'd still be inclined
>> to properly profile and test the need for such a system before actually
>> doing it. :)
>
> Sure, if it's just about the pure resource usage I agree. But I hope you
> understand that I think there is more than just this to consider.

Yes I do now, but I didn't before this mail of yours. :)

I see now that you're thinking of building some type of a "master" control
point or a "activator/directory" service, which is instantiated either
only once for system-wide deployment or once for every user. As you said,
this is not just the matter of optimizing UPnP discovery any more. Or I
should say that it probably never was, but I didn't see that before.
Regardless, if you're going with some type of a central process idea that
does all that has been described, then going "all the way" is probably the
best way to do it. This could really have an impact on resource
reservation and it could have some other notable benefits as well.

By "going all the way" I mean you design and implement this central
"master control point" that practically does all the UPnP heavy lifting.
Benefits for such a system include (I'm mosly summarizing the things you
have already said):

* Resource reservation, in which various cache designs play a big role. At
minimum it would cache the SSDP stuff, UPnP descriptions, possibly device
events or at least relay them and perhaps even results of certain types of
action invocations. This could lead to better efficiency as well, but only
when cache hits are frequent.
* Directory service. It could detail the UPnP capabilities of the entire
host machine. Control points and devices.
* "Activation" service. With the help of the Directory service this could
save resources and enable the activation and de-activation of UPnP devices
when needed.
* Some security improvements. But as I noted before, this topic requires
vastly more thought anyway.

These benefits are noteworthy and I personally find the idea truly
interesting (and quite possibly worth implementing), but I'd like to raise
some of the disadvantages here as well, since they may very well outweight
the benefits depending of the desired use:

* Possibly significant increase of complexity. Now, I don't consider
writing such a central service a problem. It's basically a really robust
control point that offers an IPC interface for clients use. I do consider
using such a service "problem". A problem in a sense that it is much more
difficult to use compared to a library loaded in-process, which provides a
type-safe, hopefully very usable object model for interacting with UPnP
devices. Certainly you could write a helper library for the clients that
does exactly that: a decent object model for interacting with the central
service. It could even be written in Qt to allow seamless integration with
all the other Qt stuff. Regardless, all that definitely increases
complexity in various ways. I'd say the impact as a whole is notable.
* Writing multiplatform software becomes harder. I'm not sure if this is a
valid concern or not, but having a dependency to such a central service
requires the service to be present on all supported platforms as well. It
is no longer a matter of writing an application and linking it to a UPnP
library.
* Efficiency may be impaired in regard to action invocation. I don't think
this is a concern, but since the topic has been on the wall, I thought I
should mention this. I don't have any numbers to show right now, but it is
easy to assume (and be wrong ;)) the vast majority of resources are spent
on invoking actions and eventing. This is because to do anything with a
UPnP device you have to invoke an action and it is SOAP all the way.
Always going through a middle-man incurs overhead even if it only relays
the data.
* If the majority of the benefits of the system depend on everybody using
it, how can you enforce the use of it in favor of, say, using HUPnP
directly?

All in all, this is a very interesting field of discussion and I'm glad to
participate, but I must point out that I don't have such a good idea yet
what exactly are you planning to do with UPnP, who and what are involved
and so on. So, everything I just wrote could very well be pointless, which
of course makes me feel very good about spending a fair amount of time and
thought in writing this. ;-)

>> >> > By the way, if any of you have ideas or feature requests for HUPnP,
>> >> > or feedback in general, this is a good chance to influence the
>> >> > development before the API & ABI is locked for the first major
>> >> > release. I'd appreciate your comments.
>> >> >
>> > Would like to give you some more comments, but please accept my
>> > limited
>> > time and find my ideas/thoughts in these emails :)
>>
>> Of course, I did and I thank you. Some interesting thoughts there to
>> which I enjoyed answering. :)
>
> Thanks. Which I enjoyed to reply to again :)
>

And the trend continues... :)

Regards,

Tuomo




More information about the Kde-hardware-devel mailing list