Skip to content

Allow policy daemon to listen on Unix domain socket and systemd socket activation#3677

Open
ntninja wants to merge 4 commits into
modoboa:masterfrom
ntninja:patch-policy-daemon-over-uds
Open

Allow policy daemon to listen on Unix domain socket and systemd socket activation#3677
ntninja wants to merge 4 commits into
modoboa:masterfrom
ntninja:patch-policy-daemon-over-uds

Conversation

@ntninja
Copy link
Copy Markdown
Contributor

@ntninja ntninja commented Aug 13, 2025

Description of the issue/feature this PR addresses:

  1. Allowing policy daemon to listen on Unix domain socket means no port conflicts and less attack surface (since only processes with appropriate permissions may connect to the socket)
  2. systemd socket activation means policy daemon is only started on first received message rather than directly on boot

Current behavior before PR:
Policy daemon must be started on boot and bind on loopback TCP

Desired behavior after PR is merged:
Both limitations are lifted

@codecov
Copy link
Copy Markdown

codecov Bot commented Aug 13, 2025

Codecov Report

❌ Patch coverage is 25.00000% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.73%. Comparing base (bed431c) to head (bbe58a5).
⚠️ Report is 1 commits behind head on master.

❌ Your patch check has failed because the patch coverage (25.00%) is below the target coverage (70.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3677      +/-   ##
==========================================
- Coverage   83.87%   83.73%   -0.14%     
==========================================
  Files         320      320              
  Lines       15775    15791      +16     
  Branches     2091     2095       +4     
==========================================
- Hits        13231    13223       -8     
- Misses       1843     1864      +21     
- Partials      701      704       +3     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

coro = asyncio.start_server(
core.new_connection, options["host"], options["port"]
)
if os.environ.get("LISTEN_PID", "") == str(os.getpid()) and os.environ.get("LISTEN_FDS", "").isnumeric():
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you rely on those environment variables? Could you use command line arguments instead?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And actually, could you please explain what those variables mean?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a short implementation of systemd socket activation in Python

For a background see:

sd_listen_fds is defined as:

#define SD_LISTEN_FDS_START 3
int sd_listen_fds(int unset_environment) {
        int r, fd;
        const char *e;
        char *p = NULL;
        unsigned long l;

        if (!(e = getenv("LISTEN_PID"))) {
                r = 0;
                goto finish;
        }

        errno = 0;
        l = strtoul(e, &p, 10);

        if (errno != 0) {
                r = -errno;
                goto finish;
        }

        if (!p || *p || l <= 0) {
                r = -EINVAL;
                goto finish;
        }

        /* Is this for us? */
        if (getpid() != (pid_t) l) {
                r = 0;
                goto finish;
        }

        if (!(e = getenv("LISTEN_FDS"))) {
                r = 0;
                goto finish;
        }

        errno = 0;
        l = strtoul(e, &p, 10);

        if (errno != 0) {
                r = -errno;
                goto finish;
        }

        if (!p || *p) {
                r = -EINVAL;
                goto finish;
        }

        for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + (int) l; fd ++) {
                int flags;

                if ((flags = fcntl(fd, F_GETFD)) < 0) {
                        r = -errno;
                        goto finish;
                }

                if (flags & FD_CLOEXEC)
                        continue;

                if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) {
                        r = -errno;
                        goto finish;
                }
        }

        r = (int) l;

finish:
        if (unset_environment) {
                unsetenv("LISTEN_PID");
                unsetenv("LISTEN_FDS");
        }

        return r;
}

The code in question reimplements this one function in Python, taking care to:

  • Ensure the listen FDs are intended for our process, rather than the parent (safety check)
  • Not inherit the listen FDs to child processes after exec (O_CLOEXEC)
  • Unset the environment variables after use

The environment variables mean:

  • LISTEN_PID: The PID of the process expected to perform socket activation
  • LISTEN_FDS: The number of file descriptors inherited to the target process (starting at 3)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatives to implementing this here include the following Python libraries:

  • systemd-socketserver – Basically what is implemented here (pure Python)
  • systemd-python – Offical libsystemd Python bindings (C extension module – requires C compiler or distribution packages)
  • cysystemd – Unofficial libsystemd Python bindings using Cython (distributed as statically linked binary wheels)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I see. Could you cover this with some tests?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tonioo: So inline implementation is fine, then? (Just checking to make sure.)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it will be easier to maintain

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ntninja Any news about tests?

@ntninja
Copy link
Copy Markdown
Contributor Author

ntninja commented Nov 13, 2025 via email

@kryskool kryskool added the missing tests Some tests are missing label Jan 7, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 7, 2026

This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 14 days.

@github-actions github-actions Bot added the stale stale status label Apr 7, 2026
@ntninja
Copy link
Copy Markdown
Contributor Author

ntninja commented Apr 7, 2026

Still planning on doing this, just forgot.

@ntninja ntninja force-pushed the patch-policy-daemon-over-uds branch from 1afc518 to fd8c312 Compare May 7, 2026 21:25
@ntninja
Copy link
Copy Markdown
Contributor Author

ntninja commented May 7, 2026

Well, I finally added tests 🥵 (tough)

For some opaque reason it seems to fail on CPython 3.10 and 3.11, I have no idea what the problem is there, but it does not seem to be related to the test.

ntninja and others added 4 commits May 21, 2026 14:10
Socket activation replaces manual socket binding if present and otherwise has
no effect.
Redis may still be used in other cases, but it shouldn’t be a needless baseline
requirement.
@tonioo
Copy link
Copy Markdown
Member

tonioo commented May 21, 2026

@ntninja Tests are failing under Python 3.10 and 3.11. Could you check why please?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feedback-needed missing tests Some tests are missing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants