[systemd-devel] Enable port forwarding via upnp

Kai Krakow hurikhan77 at gmail.com
Wed Feb 25 13:12:29 PST 2015


Lennart Poettering <lennart at poettering.net> schrieb:

> On Wed, 25.02.15 21:14, Kai Krakow (hurikhan77 at gmail.com) wrote:
> 
>> > That said I think it wouldn't be too far off I figure to add logic for
>> > this to networkd. I mean, it speaks a variety of client side protocols
>> > already, and this could just be one more. The major difference though
>> > is that upnp is a frickin insane pseudo-HTTP XML craziness, while
>> > DHCP, IPv4LL and so on are much more low-level. That said, I figure
>> > we'll have some kind of HTTP logic in networkd eventually anyway to
>> > support wispr and things like that, so maybe doing upnp wouldn't be
>> > completely off either...
>> 
>> To make sure we speak the same language: I propose to have a upnp client
>> only, not a server - my system is a desktop, not a router.
> 
> Yes, that's what I understood.

Fine. :-)

>> I just want to offer some services on my public router IP to get
>> access from outside without having to resort to static IP assignment
>> and without having to edit port numbers in two or more places. As
>> far as I know, there's already a upnp client library one could link
>> to and use that to talk to the upnp server on the router.
> 
> Well, to get this natively integrated in networkd I'd prefer avoiding
> an extra dep. I wonder if we can avoid pulling this in if we stick to
> the minimal upnp bits necessary to set up the port forwarding. I mean,
> this is after all just a client, not a server...

I'm not sure if I'd like to have it in networkd natively after all. But I 
agree to pulling in only the minimal upnp bits. There's also NAT-PMP which 
is much simpler but probably much less common on generic consumer routers, 
plus it can only do single port forwardings.

>> My idea is to add a config option to socket activated services which just
>> reuses the Listen directives to publish the ports to upnp on the router.
>> Maybe something like
>> 
>> [Socket]
>> ListenStream=22
>> PublishStream=yes
>> 
>> which would instruct yet the to be planned upnp client to create a port
>> forwarding for 22/tcp on the router. The same could work for
>> ListenDatagram. Of course this only makes sense, if sockets are not bound
>> to localhost only.
> 
> Yeah, I agree. Though I'd explicitly clarify that this is about
> Upnp. Hence call it ExposeUPNP= or so.

Good point, I like that suggestion. But how would you differentiate udp and 
tcp then?

>> It may make sense to move "PublishStream" to the service section of a
>> unit (as "PublishStreams=yes") so it could publish all sockets from each
>> socket unit associated with it - though I'm not sure if this is wanted as
>> it moves socket logic into another section. And it probably doesn't make
>> sense either because the service is started lazily when triggered by the
>> sockets - which suggests the upnp mapping should have run earlier (when
>> the socket unit was started).
>> 
>> But it would make sense for non-socket-activated services where the
>> PublishStreams directive could just be reused in the following form:
>> 
>> [Service]
>> PublishStreams=22 # SSH server
> 
> I'd avoid this. If people don't use socket activation, then they
> might as well configure this in networkd itself...

Yeah, I already thought about that, too. And now that you also mention it, 
I'd like to follow that idea. So, while (see above) I think the function 
should be provided as a separate daemon, it would have it's own 
configuration file - similar to how journald has it, or even networkd has it 
with its /etc/systemd/network configuration directory.

I'd even suggest one could simply let alone service files and just list the 
mappings here, independent of services. The user would just list all tcp and 
udp sockets to expose via the router, and "systemd-upnpclientd" would match 
up those ports with tcp and udp listening sockets as they appear and vanish.

Such a configuration could be made up of multiple files so complete feature 
sets or single services could be configured with single files. The daemon 
reads all files and compiles it into one configuration at runtime:

# ls /etc/systemd/upnp-mappings.d/
lamp-stack.conf
ssh.conf

# cat lamp-stack.conf
[ExposeTCP]
Ports=80,443,3306

# cat ssh.conf
[ExposeTCP]
Ports=22022:22 # map external port 22022 to local port 22

What do you think?

>> It should not take much more than a wrapper around libminiupnpc.so, it's
>> designed around having the smallest possible footprint:
>> 
>> http://miniupnp.free.fr/
>> 
>> It comes with a small test client program "upnpc" which provides all the
>> stuff needed to implement the above ideas.
> 
> Hmm, the API doesn't look that great to me that I'd be enthusiastic to
> depend on this I must say...

I didn't look at the API yet (just very very roughly) though I prefer to 
reuse work already done. Do you think your concern is an as big issue if it 
was used by a separate daemon only even if it would become part of the 
systemd distribution?

OTOH, that library could be used as a template to recreate a better library 
with nicer API targetted to being shipped within systemd source - following 
your idea to ship with the minimal upnp bits needed to create port mappings.

>> Well, then I suppose it would be better to have a standalone daemon,
>> let's call it systemd-upnpclientd.
>> 
>> This service would subscribe to rtnl you mentioned above. This will make
>> it independent of systemd-networkd and could support other
>> implementations like NM etc.
> 
> Sure, you could hack that up with the daemon that miniupnp provides.

I think the daemon that it provides is a minimal upnp server interfacing 
with iptables to create port mappings. As already mentioned I don't intend 
to provide that kind of service.

But still I'll try what I could some up with, creating a minimal daemon that 
listens to rtnl and pulling info from dbus to setup some portmappings. Maybe 
you'd like to take a look then and we'll see if it could become a candidate 
for systemd integration (especially wrt/ coding style and design rules) or 
being provided as a separate distribution. My schedule is tight currently 
but I'll try in the weekend.

>> Next step, I'm not sure however: It also needs to gain knowledge about
>> every socket monitored by systemd and every socket mentioned in service
>> files, as outlined in my examples. But I'm sure PID1 could simply export
>> such information. Or alternatively: You mentioned that one could
>> subscribe to units starting and stopping, so we could simply pull out the
>> information needed.
> 
> PID 1 exports this already, via dbus. It's all readily accessible.

Very nice... Is there documentation and/or example code for this? I'm really 
not familiar with dbus. And my C programming skills are a few years back. 
I'm good at reading and understanding it, but writing C code lacks a lot of 
practice meanwhile so I often find myself in reading documentation and 
googling for API/library functions/calls.

Thanks,
Kai

-- 
Replies to list only preferred.



More information about the systemd-devel mailing list