[systemd-devel] Enable port forwarding via upnp
Kai Krakow
hurikhan77 at gmail.com
Wed Feb 25 12:14:57 PST 2015
Lennart Poettering <lennart at poettering.net> schrieb:
> On Wed, 25.02.15 08:16, Kai Krakow (hurikhan77 at gmail.com) wrote:
>
>> Hello!
>>
>> Is it possible to somehow create a service which enables port forwardings
>> on my router using upnp? Currently, I guess it is not possible (except
>> maybe using ExecPost or ExecPre and the upnpc program). But when my
>> client IP changes via DHCP, it should be reapplied. Also, it needs to be
>> maintained as the programmed port forwardings would timeout and be
>> cleared from the router after a while. So it needs to be hooked up to
>> systemd-networkd somehow (at least that is what I am using).
>
> Not really. Such a upnp port setup tool could just subscribe to rtnl
> and update the router's configuration dynamically each time the local
> configuration is changed, without having to know anything about
> networkd, NM or anything else.
Okay, didn't know about rtnl. I will look into it. It makes sense to keep
things separated to allow people to choose between networks, NM or anything
else.
> 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. 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.
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.
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
or
[Service]
PublishStreams=80,443 # WWW server
[Service]
PublishDatagrams=51900-51999 # example service publishing 51900-99/udp
[Service]
PublishDatagrams=51900-51999:5190
# publish 51900-99/udp as 5190-99/udp on eth*
[Service]
PublishStreams=8080:80 # publish 8080/tcp on the router as 80 on eth*
So instead of defining port mappings on the router manually, this would be
done automatically, depending on which services/sockets are started.
> I figure if we can do this with existing deps, the internal XML parser
> and so on, this could work. I.e. a minimal upnp port forwarding
> library รก la sd-dhcp would be acceptable...
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.
>> So I guess this logic should be built into systemd-networkd, maybe
>> offering some dbus interface also. And service and/or socket files could
>> have a flag to enable upnp forwards. This way, systemd-networkd knows
>> about the registered forwards and maintains them, and systemd will
>> trigger registration/unregistration on it whenever services start or stop
>> which have such flags enabled. By using dbus, this could be an interface
>> implemented by other network management daemons, too.
>
> This is already the second step... It kinda opens another can of worms
> though: we discussed on and off how integration between mDNS/DNS-SD
> and .socket units could look like. This port forwarding thing and the
> mDNS hookup are somewhat related: in both cases we have a concept of
> actively registering with some external code as long as as a socket is
> up.
>
> Originally, when we discussed that in the mDNS context I thought of
> simply using ExecStartPre= and ExecStopPre= for this, and forking off
> a tool that pushes the mDNS registration into avahi/resolved as long
> as the socket is up. But I ended not liking this idea, since in most
> cases this would mean having one addition process around, that does
> some bits when it starts up, then only hangs around, and then does a
> bit more when it shuts down. Hanging around pointlessly is bad
> though. So the next idea was maybe then PID 1 could simply call
> directly into avahi/resolved via async bus calls, so that we have no
> extra pointless processes hanging around.
>
> However, that idea I don't like either, because it makes PID 1 client
> of another daemon, and I really don't like that, I'd really prefer it
> the other way round: the higher level daemons should call into the
> lower level daemons, and subscribe to them, but the lower level
> daemons should never call into the higher level daemons...
>
> In networkd we followed this correct stacking design quite
> nicely. networkd never pushes NTP servers into timesyncd or DNS
> servers into resolved. Instead, timesyncd and resolved subscribe to
> what networkd announces and pull the data out that. Most likely that's
> the logic we should follow here too:
>
> - For the port forwarding logic we should introduce a new .socket
> property, that PID 1 doesn't really do much with, except exporting
> it on the bus
>
> - networkd susbcribes to .socket units starting and stopping in
> PID 1, and pulls out that one property of them, and acting on it.
>
> Similar, we would handle the mDNS case: the .socket units would gain a
> new DNSSDService property, that resolved then keeps track of an
> operates on.
>
> I hope that makes some sense?
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.
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.
I currently see problems with both approaches:
When getting exported info from systemd about sockets to be published on the
router: How do we get notified about changes introduced by starting or
stopping units?
When subscribing to starting and stopping of units, how do we pull the info
from the unit files if systemd-upnpclientd gets started after some units
wanting to publish sockets are already up?
Putting all together, and after reading through your thoughts, I'd favor a
solution of a standalone daemon instead of putting possibly unrelated stuff
into PID1 or networkd - with the exception of maybe exporting additional
information.
--
Replies to list only preferred.
More information about the systemd-devel
mailing list