11

(On Stock Debian testing aka stretch aka 9, so I have a regular systemd+logind+NetworkManager+GNOME stack)

I have a pair of script that I want to run on startup/shutdown and resume/suspend. This script requires networking to be present when it runs. I have attempt this with the following script:

[Unit]
Description=Yamaha Reciever power
Requires=network-online.target
After=network-online.target
Before=sleep.target
Conflicts=sleep.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/av-up
ExecStop=/usr/local/bin/av-down
RemainAfterExit=yes

[Install]
WantedBy=graphical.target

This works correctly on startup/shutdown, however during suspend it runs after the network has shutdown and therefore fails.

I have determined that the reason for this is how suspend proceeds under systemd:

  1. Suspend is initiated by a program (e.g. systemctl suspend) sending a dbus call to logind.
  2. Logind then sends a PrepareToShutdown dbus signal to anyone listening.
  3. Logind then sends a StartUnit dbus call to systemd to run the suspend.target unit.

NetworkManager listens to PrepareToShutdown, so removes the network at (2), while my unit is triggered when systemd actually starts suspending at (3). NetworkManager keeps an "inhibit" lock with logind to ensure it shuts the network down before (3). (Side note: it seems crazy to have something like systemd control ordering of suspend/resume, only to subvert it with logind making stuff circumvent this)

What is the right way to trigger a program to run on suspend/resume while networking is still running?

Should I use NetworkManager pre-down scripts? If so how do I stop it triggering if the network goes down but I'm not suspending?

Is there a way to hook into the suspend process earlier?

Is there a way to make NetworkManager keep the network up longer?

NB: this is distinct from How to write a Systemd unit that will fire before networking goes down as I am talking about suspend/resume.

1
  • So many layers of inhibitions, redirections and signalling yet there is no way to make user-defined scripts run before everything else..... May 3, 2017 at 16:37

4 Answers 4

6

Inspired by https://unix.stackexchange.com/a/139664/160746 I upgraded my start/stop scripts to a full blown daemon and made it listen for PrepareToShutdown signal. This is a race against NetworkManager every start/stop, but it seems to work reliably on my system.

I have uploaded my code and systemd unit at https://github.com/davidn/av.

1
  • Thanks for your code! Unfortunately, on my system (using systemd-networkd) I get a urlopen error [Errno 101] Network is unreachable from the script when I resume. So this is still missing a way to make sure that the network is back. :(
    – josch
    Aug 1, 2018 at 13:13
3

Acccording to the systemd-suspend documentation, as well as the systemctl man page systemctl suspend activates the suspend.target.

systemctl list-dependencies suspend.target --after --all shows that suspend.target calls systemctl-suspend.service then sleep.target. This means when you call systemctl suspend the default order of operations are:

suspend.target
|-systemd-suspend.service
  |-sleep.target

If you placed Before=sleep.target, then your order of operations is likely:

suspend.target
|-systemd-suspend.service
  |-[custom service]
    |-sleep.target

So you're service runs after systemd-suspend.service does its thing, which is likely your issue.


You can add to your service file to get the correct results:

Before=systemd-suspend.service

After calling systemctl daemon-reload you should be able to use systemctl list-dependencies suspend.target --after --all to see your service appear between suspend.target and systemd-suspend.service. Your final order of operations should be:

suspend.target
|-[custom service]
  |-systemd-suspend.service
    |-sleep.target
6
  • I tried this but no dice. I tried with "Before=sleep.service systemd-suspend.service" and got the same behaviour (my script runs before sleep, but after network is down.). With just "Before=systemd-suspend.service" the script isn't called at all during suspend.
    – David
    Jan 27, 2017 at 6:50
  • When you changed your Before what did you get when running systemctl daemon-reload ; systemctl list-dependencies suspend.target --after --all?
    – Centimane
    Jan 27, 2017 at 19:42
  • I have copied the first 100 lines of output to pastebin.com/ZsdP9Evs. In all 3 cases it seems to print infinitely. That's probably a bad sign. I have saved the log from a suspend-resume cycle with Before=sleep.target at pastebin.com/ziYdyKtr. You can see NetworkManager disconnects way before Systemd attempts to suspend, which is why I looked to see what triggers NM, which led me to my above conclusion that NM kicks in independently of systemd.
    – David
    Jan 29, 2017 at 20:55
  • @David Did you use --after or --before? Looks like --before (which is the wrong direction). Also, if you're getting an infinite printout, that suggests you've created a cyclical dependency (systemd will still start/stop services successfully because it wont try to start a service that's already started, but still worth looking into). I'm not sure enough of your output is pasted for me to find the service in question, but I would recommend removing any before/after/conflicts in custom services that you're unsure of.
    – Centimane
    Jan 31, 2017 at 4:04
  • It was definitely after. I reran: pastebin.com/AYzhcgKu. Here is before: pastebin.com/mF60w1wJ. I have basically given up on getting systemd to do it. I am removing all reference to sleep, so at least the script correctly runs for boot/shutdown without getting into state failed if I suspend in between.
    – David
    Feb 4, 2017 at 5:21
1

According to a reply from systemd-devel mailing list, this is impossible using solely systemd while using Network Manager as Network Manager listens for the sleep signal on its own.  A workaround is to put what you need to run into:

/etc/NetworkManager/dispatcher.d/pre-down.d/

Note that this will trigger the script whenever an interface goes down, wich is fine on my machine but might not be on yours. One could probably check whether a D-Bus signal to suspend has been emitted, but I have not explored that. There is a ticket about this on Network Manager gitlab tracker: https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/910.

1

I have working solution. It is not the best but non of other works for me. Put to /lib/systemd/system-sleep/tv_on_off with something like:

#!/bin/sh
# should be placed at 
# /lib/systemd/system-sleep

if [ "${1}" = "pre" ]; then
   # about to suspend no network here
   systemctl restart network-manager.service
   nm-online

   # code that requires network
   /usr/local/bin/av-down

elif [ "${1}" = "post" ]; then
   # about to resume 
   systemctl restart network-manager.service
   nm-online

   # code that requires network
   /usr/local/bin/av-up
fi

I have tried systemd and dbus methods mentioned by @David. Even with dbus message handling network get down before my code runs. Or in parallel.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.