Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package nqptp for openSUSE:Factory checked in at 2024-09-05 15:47:36 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/nqptp (Old) and /work/SRC/openSUSE:Factory/.nqptp.new.10096 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "nqptp" Thu Sep 5 15:47:36 2024 rev:2 rq:1198918 version:1.2.4 Changes: -------- --- /work/SRC/openSUSE:Factory/nqptp/nqptp.changes 2023-06-26 18:16:51.066672213 +0200 +++ /work/SRC/openSUSE:Factory/.nqptp.new.10096/nqptp.changes 2024-09-05 15:48:26.875211250 +0200 @@ -1,0 +2,24 @@ +Tue Sep 3 09:06:57 UTC 2024 - Wolfgang Frisch <wolfgang.fri...@suse.com> + +- Backports from 1.2.5-dev + - Add backport-b5321a88d21b854aaa461dc0f6c226d650309b91.patch + Remove setcap call. + - Add backport-050a8c2de9f3e1f4859abf9b36d2f18afd4c34d7.patch + Set capability in the systemd unit instead. + +- Add disable-user-group-generation.patch + Disable user/group generation in the Makefile. + Let systemd-sysusers handle this instead. + +- Update to 1.2.4 + - Further changes are introduced to make the communication path between NQPTP + and Shairport Sync resistant to outside interference. These changes have + necessitated changing the SMI interface. The SMI interface is now at + version 10, and Shairport Sync must also be updated to be compatible with + it. + +- Update to 1.2.3 + - Fix CVE-2023-43771: nqptp: NULL pointer dereference caused by invalid + control port message (boo#1213060) + +------------------------------------------------------------------- Old: ---- nqptp-1.2.1.tar.gz New: ---- backport-050a8c2de9f3e1f4859abf9b36d2f18afd4c34d7.patch backport-b5321a88d21b854aaa461dc0f6c226d650309b91.patch disable-user-group-generation.patch nqptp-1.2.4.tar.gz nqptp-user.conf BETA DEBUG BEGIN: New: Remove setcap call. - Add backport-050a8c2de9f3e1f4859abf9b36d2f18afd4c34d7.patch Set capability in the systemd unit instead. New:- Backports from 1.2.5-dev - Add backport-b5321a88d21b854aaa461dc0f6c226d650309b91.patch Remove setcap call. New: - Add disable-user-group-generation.patch Disable user/group generation in the Makefile. BETA DEBUG END: ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ nqptp.spec ++++++ --- /var/tmp/diff_new_pack.fOWLv9/_old 2024-09-05 15:48:27.271227696 +0200 +++ /var/tmp/diff_new_pack.fOWLv9/_new 2024-09-05 15:48:27.275227862 +0200 @@ -1,7 +1,7 @@ # # spec file for package nqptp # -# Copyright (c) 2023 SUSE LLC +# Copyright (c) 2024 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,16 +17,24 @@ Name: nqptp -Version: 1.2.1 +Version: 1.2.4 Release: 0 Summary: Not Quite PTP License: GPL-2.0-only URL: https://github.com/mikebrady/nqptp Source0: https://github.com/mikebrady/%{name}/archive/%{version}/%{name}-%{version}.tar.gz +Source1: nqptp-user.conf +# Backported from 1.2.5-dev: +Patch0: backport-050a8c2de9f3e1f4859abf9b36d2f18afd4c34d7.patch +# Backported from 1.2.5-dev: +Patch1: backport-b5321a88d21b854aaa461dc0f6c226d650309b91.patch +Patch2: disable-user-group-generation.patch BuildRequires: autoconf BuildRequires: automake BuildRequires: systemd-rpm-macros +BuildRequires: sysuser-tools %{?systemd_ordering} +%sysusers_requires %description nqptp is a daemon that monitors timing data from any PTP clocks â up to 64 â it @@ -37,18 +45,20 @@ for AirPlay 2 operation. %prep -%autosetup +%autosetup -p1 %build autoreconf -i -f %configure --with-systemd-startup %make_build +%sysusers_generate_pre %{SOURCE1} nqptp nqptp-user.conf %install %make_install mkdir -p %{buildroot}%{_unitdir} mv %{buildroot}%{_libdir}/systemd/system/%{name}.service \ %{buildroot}%{_unitdir}/%{name}.service +install -D -m 0644 %{SOURCE1} %{buildroot}%{_sysusersdir}/nqptp.conf %pre %service_add_pre %{name}.service @@ -67,4 +77,5 @@ %doc README.md RELEASE_NOTES.md %{_bindir}/%{name} %{_unitdir}/%{name}.service +%{_sysusersdir}/nqptp.conf ++++++ backport-050a8c2de9f3e1f4859abf9b36d2f18afd4c34d7.patch ++++++ >From 050a8c2de9f3e1f4859abf9b36d2f18afd4c34d7 Mon Sep 17 00:00:00 2001 From: Hs_Yeah <bye...@gmail.com> Date: Tue, 19 Sep 2023 03:12:47 +0800 Subject: [PATCH] Added AmbientCapabilities to nqptp.service.in Added AmbientCapabilities=CAP_NET_BIND_SERVICE so that the systemd service can be used without the capability set on the built nqptp binary. --- nqptp.service.in | 1 + 1 file changed, 1 insertion(+) diff --git a/nqptp.service.in b/nqptp.service.in index 6f1eb0c..53e6a2e 100644 --- a/nqptp.service.in +++ b/nqptp.service.in @@ -8,6 +8,7 @@ Before=shairport-sync.service ExecStart=@prefix@/bin/nqptp User=nqptp Group=nqptp +AmbientCapabilities=CAP_NET_BIND_SERVICE [Install] WantedBy=multi-user.target ++++++ backport-b5321a88d21b854aaa461dc0f6c226d650309b91.patch ++++++ >From b5321a88d21b854aaa461dc0f6c226d650309b91 Mon Sep 17 00:00:00 2001 From: Mike Brady <4265913+mikebr...@users.noreply.github.com> Date: Tue, 19 Sep 2023 11:08:27 +0100 Subject: [PATCH] Improve some of the error messages. Remove the setcap command from Makefile.am, since we are now using an AmbientCapabilities setting in the systemd service file. --- Makefile.am | 5 +++-- configure.ac | 2 +- nqptp-utilities.c | 14 +++++--------- nqptp.c | 2 +- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/Makefile.am b/Makefile.am index 78f36d7..d2b3992 100644 --- a/Makefile.am +++ b/Makefile.am @@ -19,8 +19,9 @@ endif install-exec-hook: if BUILD_FOR_LINUX -# NQPTP runs as user/group nqptp/nqptp on Linux and uses setcap to access ports 319 and 320 - setcap 'cap_net_bind_service=+ep' $(bindir)/nqptp +# Note: NQPTP runs as user/group nqptp/nqptp on Linux. +# Access is given via AmbientCapabilities in the service file. +# If you want to run it from the command line, e.g. for debugging, run it as root user. # no installer for System V if INSTALL_SYSTEMD_STARTUP getent group nqptp &>/dev/null || groupadd -r nqptp &>/dev/null diff --git a/nqptp-utilities.c b/nqptp-utilities.c index 9d6a95d..9964b22 100644 --- a/nqptp-utilities.c +++ b/nqptp-utilities.c @@ -105,15 +105,11 @@ void open_sockets_at_port(const char *node, uint16_t port, } freeaddrinfo(info); if (sockets_opened == 0) { - if (port < 1024) - die("unable to listen on port %d. The error is: \"%s\". NQPTP must run as root to access " - "this port. Or is another PTP daemon -- possibly another instance on NQPTP -- running " - "already?", - port, strerror(errno)); - else - die("unable to listen on port %d. The error is: \"%s\". " - "Is another instance on NQPTP running already?", - port, strerror(errno)); + if (errno == EACCES) { + die("nqptp does not have permission to access port %u. It must (a) [Linux only] have been given CAP_NET_BIND_SERVICE capabilities using e.g. setcap or systemd's AmbientCapabilities, or (b) run as root.", port); + } else { + die("nqptp is unable to listen on port %u. The error is: %d, \"%s\".", port, errno, strerror(errno)); + } } } diff --git a/nqptp.c b/nqptp.c index e5f2988..a1a3c76 100644 --- a/nqptp.c +++ b/nqptp.c @@ -198,7 +198,7 @@ int main(int argc, char **argv) { mode_t oldumask = umask(0); shm_fd = shm_open(NQPTP_INTERFACE_NAME, O_RDWR | O_CREAT, 0644); if (shm_fd == -1) { - die("cannot open shared memory \"%s\".", NQPTP_INTERFACE_NAME); + die("nqptp cannot open the shared memory \"%s\" for writing. Is another copy of nqptp (e.g. an nqptp daemon) running already?", NQPTP_INTERFACE_NAME); } (void)umask(oldumask); ++++++ disable-user-group-generation.patch ++++++ Index: nqptp-1.2.4/Makefile.am =================================================================== --- nqptp-1.2.4.orig/Makefile.am +++ nqptp-1.2.4/Makefile.am @@ -24,8 +24,6 @@ if BUILD_FOR_LINUX # If you want to run it from the command line, e.g. for debugging, run it as root user. # no installer for System V if INSTALL_SYSTEMD_STARTUP - getent group nqptp &>/dev/null || groupadd -r nqptp &>/dev/null - getent passwd nqptp &> /dev/null || useradd -r -M -g nqptp -s /usr/sbin/nologin nqptp &>/dev/null [ -e $(DESTDIR)$(libdir)/systemd/system ] || mkdir -p $(DESTDIR)$(libdir)/systemd/system # don't replace a service file if it already exists... [ -e $(DESTDIR)$(libdir)/systemd/system/nqptp.service ] || cp nqptp.service $(DESTDIR)$(libdir)/systemd/system ++++++ nqptp-1.2.1.tar.gz -> nqptp-1.2.4.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nqptp-1.2.1/Makefile.am new/nqptp-1.2.4/Makefile.am --- old/nqptp-1.2.1/Makefile.am 2023-05-22 11:18:48.000000000 +0200 +++ new/nqptp-1.2.4/Makefile.am 2023-09-16 19:51:55.000000000 +0200 @@ -19,14 +19,20 @@ install-exec-hook: if BUILD_FOR_LINUX +# NQPTP runs as user/group nqptp/nqptp on Linux and uses setcap to access ports 319 and 320 + setcap 'cap_net_bind_service=+ep' $(bindir)/nqptp +# no installer for System V if INSTALL_SYSTEMD_STARTUP + getent group nqptp &>/dev/null || groupadd -r nqptp &>/dev/null + getent passwd nqptp &> /dev/null || useradd -r -M -g nqptp -s /usr/sbin/nologin nqptp &>/dev/null [ -e $(DESTDIR)$(libdir)/systemd/system ] || mkdir -p $(DESTDIR)$(libdir)/systemd/system # don't replace a service file if it already exists... [ -e $(DESTDIR)$(libdir)/systemd/system/nqptp.service ] || cp nqptp.service $(DESTDIR)$(libdir)/systemd/system endif endif - # no installer for FreeBSD yet + if BUILD_FOR_FREEBSD +# NQPTP runs as root on FreeBSD to access ports 319 and 320 if INSTALL_FREEBSD_STARTUP cp nqptp.freebsd /usr/local/etc/rc.d/nqptp chmod 555 /usr/local/etc/rc.d/nqptp diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nqptp-1.2.1/README.md new/nqptp-1.2.4/README.md --- old/nqptp-1.2.1/README.md 2023-05-22 11:18:48.000000000 +0200 +++ new/nqptp-1.2.4/README.md 2023-09-16 19:51:55.000000000 +0200 @@ -1,11 +1,8 @@ # NQPTP â Not Quite PTP -`nqptp` is a daemon that monitors timing data from any [PTP](https://en.wikipedia.org/wiki/Precision_Time_Protocol) clocks â up to 64 â it sees on ports 319 and 320. It maintains records for each clock, identified by Clock ID and IP. +`nqptp` is a daemon that monitors timing data from [PTP](https://en.wikipedia.org/wiki/Precision_Time_Protocol) clocks it sees on ports 319 and 320. It maintains records for one clock, identified by its Clock ID. It is a companion application to [Shairport Sync](https://github.com/mikebrady/shairport-sync) and provides timing information for AirPlay 2 operation. -# Development -This branch -- the `development` branch -- changes relatively rapidly and may contain significant bugs. The `main` branch is the most stable. Use it with the `development` branch of Shairport Sync. - ## Installation This guide is for recent Linux and FreeBSD systems. @@ -25,9 +22,10 @@ ``` ### Remove Old Service Files #### Linux -If you are updating from version 1.1-dev-51 or earlier in Linux, remove the service file `nqptp.service` from the directory `/lib/systemd/system` (you'll need superuser privileges): +If you are updating from version `1.2.4d0` or earlier in Linux, remove the service file `nqptp.service` from the directory `/lib/systemd/system` (you'll need superuser privileges): ``` # rm /lib/systemd/system/nqptp.service +# systemctl daemon-reload ``` Don't worry if you get a message stating that the file doesn't exist -- no harm done. @@ -44,8 +42,6 @@ ``` $ git clone https://github.com/mikebrady/nqptp.git $ cd nqptp -$ git checkout development -$ autoreconf -fi $ ./configure --with-systemd-startup $ make # make install @@ -54,7 +50,6 @@ ``` $ git clone https://github.com/mikebrady/nqptp.git $ cd nqptp -$ git checkout development $ autoreconf -fi $ ./configure --with-freebsd-startup $ make @@ -111,9 +106,10 @@ ``` ## Notes -Please note that `nqptp` must run in `root` mode to be able to access ports 319 and 320. - -Since `nqptp` uses ports 319 and 320, it can not coexist with any other user of those ports, such as full PTP service daemons. +The `nqptp` application requires exclusive access to ports 319 and 320. +This means that it can not coexist with any other user of those ports, such as full PTP service daemons. +In Linux, `nqptp` runs as a low-priviliged user but is given special access to ports 319 and 320 during installation using the `setcap` utility. +In FreeBSD, `nqptp` runs as `root` user. ## Programming Notes Commands and status information are sent to `nqptp` over port 9000. @@ -123,23 +119,30 @@ Here are details of the interface: ```c -struct shm_structure { - pthread_mutex_t shm_mutex; // for safely accessing the structure - uint16_t version; // check this is equal to NQPTP_SHM_STRUCTURES_VERSION +typedef struct { uint64_t master_clock_id; // the current master clock - char master_clock_ip[64]; // where it's coming from uint64_t local_time; // the time when the offset was calculated uint64_t local_to_master_time_offset; // add this to the local time to get master clock time uint64_t master_clock_start_time; // this is when the master clock became master +} shm_structure_set; + +// The actual interface comprises a shared memory region of type struct shm_structure. +// This comprises two records of type shm_structure_set. +// The secondary record is written strictly after all writes to the main record are +// complete. This is ensured using the __sync_synchronize() construct. +// The reader should ensure that both copies match for a read to be valid. +// For safety, the secondary record should be read strictly after the first. + +struct shm_structure { + uint16_t version; // check this is equal to NQPTP_SHM_STRUCTURES_VERSION + shm_structure_set main; + shm_structure_set secondary; }; ``` -If you wish to use the shared mutex to ensure records are not altered while you are accessing them, you should open your side of the shared memory interface with read-write permission. Be aware that while your program has the mutex lock, it is in a "critical region" where it can halt `nqptp`, so keep any activity while you have the lock very short and very simple, e.g. copying the contents of shared memory to local memory. - Clock records that are not updated for a period are deleted. - ## Known Issues -* `nqptp` has not been checked or audited for security issues. Note that it must run in `root` mode. +* `nqptp` has not been thoroughly checked or audited for security issues. Note that it runs in `root` mode on FreeBSD. * It's probably buggy! * `nqptp` does not take advantage of hardware timestamping. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nqptp-1.2.1/RELEASE_NOTES.md new/nqptp-1.2.4/RELEASE_NOTES.md --- old/nqptp-1.2.1/RELEASE_NOTES.md 2023-05-22 11:18:48.000000000 +0200 +++ new/nqptp-1.2.4/RELEASE_NOTES.md 2023-09-16 19:51:55.000000000 +0200 @@ -1,10 +1,20 @@ -## Version: 1.2 - -***Pesky Changes You Can't Ignore*** +## Version: 1.2.4 +This is an important security update. The Shared Memory Interface of the updated NQPTP is now 10, i.e. `smi10`: +``` +$ nqptp -V +Version: 1.2.4. Shared Memory Interface Version: smi10. +``` +1. When updating NQPTP on Linux, be sure to remove the old service file as directed in the [README](https://github.com/mikebrady/nqptp/blob/main/README.md#remove-old-service-files). +2. You must update Shairport Sync to ensure that it's Shared Memory Interface version is also 10 in order to be compatible with this NQPTP update. +3. Having completed both updates and installations, remember to restart NQPTP first and then restart Shairport Sync. -* **Important**. The protocol that Shairport Sync and NQPTP use to communicate with one another has been updated to reflect changes in NQPTP's operation. Please update both NQPTP and Shairport Sync so that they both use the same Shared Memory Interface Version. +**Security Updates** +* A crashing bug in NQPTP has been fixed. +* The communications protocol used between NQPTP and Shairport Sync has been revised and made more resilient to attempted misuse. +* In Linux systems, NQPTP no longer runs as `root` -- instead it is installed at the `# make install` stage to run as the restriced user `nqptp`, with access to ports 319 and 320 set by the installer via the `setcap` utility. - This version of NQPTP uses Shared Memory Interface Version `smi9`. (You can check `nqptp` using `$ nqptp -V`.) +## Version: 1.2 +* The protocol that Shairport Sync and NQPTP use to communicate with one another has been updated to reflect changes in NQPTP's operation. Please update both NQPTP and Shairport Sync so that they both use the same Shared Memory Interface Version. **Enhancements** * Enable NQPTP to respond to information about the state of the player -- whether is is playing, stopped or paused. The "B" command is a message that the client -- which generates the clock -- is about to start playing. The "E" command signifies that the client has stopped playing and that the clock may shortly sleep. The "P" command signifies that play has paused (buffered audio only). The clock seems to stay running in this state. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nqptp-1.2.1/configure.ac new/nqptp-1.2.4/configure.ac --- old/nqptp-1.2.1/configure.ac 2023-05-22 11:18:48.000000000 +0200 +++ new/nqptp-1.2.4/configure.ac 2023-09-16 19:51:55.000000000 +0200 @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.68]) -AC_INIT([nqptp], [1.2.1], [4265913+mikebr...@users.noreply.github.com]) +AC_INIT([nqptp], [1.2.4], [4265913+mikebr...@users.noreply.github.com]) AM_INIT_AUTOMAKE AC_CANONICAL_HOST diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nqptp-1.2.1/nqptp-clock-sources.c new/nqptp-1.2.4/nqptp-clock-sources.c --- old/nqptp-1.2.1/nqptp-clock-sources.c 2023-05-22 11:18:48.000000000 +0200 +++ new/nqptp-1.2.4/nqptp-clock-sources.c 2023-09-16 19:51:55.000000000 +0200 @@ -80,8 +80,6 @@ i++; } if (response != -1) { - pthread_mutexattr_t shared; - int err; strncpy(clients[i].shm_interface_name, client_shared_memory_interface_name, sizeof(clients[i].shm_interface_name)); // create the named smi interface @@ -125,23 +123,6 @@ memset(clients[i].shared_memory, 0, sizeof(struct shm_structure)); clients[i].shared_memory->version = NQPTP_SHM_STRUCTURES_VERSION; - /*create mutex attr */ - err = pthread_mutexattr_init(&shared); - if (err != 0) { - die("mutex attribute initialization failed - %s.", strerror(errno)); - } - pthread_mutexattr_setpshared(&shared, 1); - /*create a mutex */ - err = pthread_mutex_init((pthread_mutex_t *)&clients[i].shared_memory->shm_mutex, &shared); - if (err != 0) { - die("mutex initialization failed - %s.", strerror(errno)); - } - - err = pthread_mutexattr_destroy(&shared); - if (err != 0) { - die("mutex attribute destruction failed - %s.", strerror(errno)); - } - // for (i = 0; i < MAX_CLOCKS; i++) { // clocks_private[i].client_flags[response] = // 0; // turn off all client flags in every clock for this client @@ -248,26 +229,21 @@ void update_master_clock_info(uint64_t master_clock_id, const char *ip, uint64_t local_time, uint64_t local_to_master_offset, uint64_t mastership_start_time) { - // debug(1,"update_master_clock_info clock: % " PRIx64 ", offset: %" PRIx64 ".", - // master_clock_id, local_to_master_offset); - int rc = pthread_mutex_lock(&shared_memory->shm_mutex); - if (rc != 0) - warn("Can't acquire mutex to update master clock!"); - shared_memory->master_clock_id = master_clock_id; + // to ensure that a full update has taken place, the + // reader must ensure that the main and secondary + // structures are identical + + shared_memory->main.master_clock_id = master_clock_id; if (ip != NULL) { - strncpy((char *)&shared_memory->master_clock_ip, ip, - FIELD_SIZEOF(struct shm_structure, master_clock_ip) - 1); - shared_memory->master_clock_start_time = mastership_start_time; - shared_memory->local_time = local_time; - shared_memory->local_to_master_time_offset = local_to_master_offset; + shared_memory->main.master_clock_start_time = mastership_start_time; + shared_memory->main.local_time = local_time; + shared_memory->main.local_to_master_time_offset = local_to_master_offset; } else { - shared_memory->master_clock_ip[0] = '\0'; - shared_memory->master_clock_start_time = 0; - shared_memory->local_time = 0; - shared_memory->local_to_master_time_offset = 0; - } - rc = pthread_mutex_unlock(&shared_memory->shm_mutex); - if (rc != 0) - warn("Can't release mutex after updating master clock!"); - // debug(1,"update_master_clock_info done"); + shared_memory->main.master_clock_start_time = 0; + shared_memory->main.local_time = 0; + shared_memory->main.local_to_master_time_offset = 0; + } + __sync_synchronize(); + shared_memory->secondary = shared_memory->main; + __sync_synchronize(); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nqptp-1.2.1/nqptp-message-handlers.c new/nqptp-1.2.4/nqptp-message-handlers.c --- old/nqptp-1.2.1/nqptp-message-handlers.c 2023-05-22 11:18:48.000000000 +0200 +++ new/nqptp-1.2.4/nqptp-message-handlers.c 2023-09-16 19:51:55.000000000 +0200 @@ -51,133 +51,139 @@ clock_source_private_data *clock_private_info, uint64_t reception_time) { if (recv_len != -1) { - buf[recv_len - 1] = 0; // make sure there's a null in it! - debug(2, "New control port message: \"%s\".", buf); - // we need to get the client shared memory interface name from the front - char *ip_list = buf; - char *smi_name = strsep(&ip_list, " "); - char *command = NULL; - if (smi_name != NULL) { - if (ip_list != NULL) - command = strsep(&ip_list, " "); - - // "B" is for play begin/resume. Assumes a "T <ip>" already - // "E" is for play end/stop. - // "P" is for pause (currently Buffered Audio only). - // - // "T <ip>" is for the IP address of a timer. - // "T" means no active timer. - // clock_is_active is made true by Play and false by Pause or End. - - if ((strcmp(command, "B") == 0) && (ip_list == NULL)) { - debug(2, "Play."); - // We want to avoid, as much as possible, resetting the clock smoothing. - // If we know the clock is already active or - // if it's only been a short time since we know it was last active - // then we will not reset the clock. - if (clock_is_active) { - debug(2, "clock is already active"); - } else { - // Find out if the clock is active i.e. not sleeping. - // We know it is active between "B" and "E" commands. - // We also know it is active for brief periods after the "T" and "E" commands are - // received. If it is not definitely active, we will reset smoothing. - int will_ask_for_a_reset = 0; - if (clock_validity_expiration_time == 0) { - debug(1, "no clock_validity_expiration_time."); - will_ask_for_a_reset = 1; + if ((buf != NULL) && (recv_len > 0)) { + buf[recv_len - 1] = 0; // we know it's not empty, so make sure there's a null in it. + debug(2, "New control port message: \"%s\".", buf); + // we need to get the client shared memory interface name from the front + char *ip_list = buf; + char *smi_name = strsep(&ip_list, " "); + char *command = NULL; + if (smi_name != NULL) { + if (ip_list != NULL) + command = strsep(&ip_list, " "); + + // "B" is for play begin/resume. Assumes a "T <ip>" already + // "E" is for play end/stop. + // "P" is for pause (currently Buffered Audio only). + // + // "T <ip>" is for the IP address of a timer. + // "T" means no active timer. + // clock_is_active is made true by Play and false by Pause or End. + if (command != NULL) { + if ((strcmp(command, "B") == 0) && (ip_list == NULL)) { + debug(2, "Play."); + // We want to avoid, as much as possible, resetting the clock smoothing. + // If we know the clock is already active or + // if it's only been a short time since we know it was last active + // then we will not reset the clock. + if (clock_is_active) { + debug(2, "clock is already active"); + } else { + // Find out if the clock is active i.e. not sleeping. + // We know it is active between "B" and "E" commands. + // We also know it is active for brief periods after the "T" and "E" commands are + // received. If it is not definitely active, we will reset smoothing. + int will_ask_for_a_reset = 0; + if (clock_validity_expiration_time == 0) { + debug(1, "no clock_validity_expiration_time."); + will_ask_for_a_reset = 1; + } else { + int64_t time_to_clock_expiration = clock_validity_expiration_time - reception_time; + // timings obtained with an iPhone Xs Max on battery save + + // around 30 seconds at a buffered audio pause on an iphone. + // around 1 second after a buffered audio stop on an iphone + // 10 seconds after a "T" from an iPhone that immediately sleeps + // more than a minute from "T" from a HomePod mini. + + if (time_to_clock_expiration < 0) { + debug(2, "Clock validity may have expired, so ask for a reset."); + will_ask_for_a_reset = 1; + } + } + if (will_ask_for_a_reset != 0) { + debug(2, "Reset clock smoothing"); + reset_clock_smoothing = 1; + } + } + clock_is_active = 1; + clock_validity_expiration_time = 0; + } else if ((strcmp(command, "E") == 0) && (ip_list == NULL)) { + debug(2, "Stop"); + if (clock_is_active) { + debug(2, "reset clock_validity_expiration_time to 2.25 seconds in the future."); + clock_validity_expiration_time = + reception_time + 2250000000; // expiration time can be very soon after an "E" + clock_is_active = 0; + } else { + debug(2, "clock is already inactive."); + } + } else if ((strcmp(command, "P") == 0) && (ip_list == NULL)) { + debug(2, "Pause"); + // A pause always seems to turn into a Stop in now more than a few seconds, and the + // clock keeps going, it seems so there is nothing to do here. + } else if ((command == NULL) || ((strcmp(command, "T") == 0) && (ip_list == NULL))) { + debug(2, "Stop Timing"); + clock_is_active = 0; + debug(2, "Clear timing peer group."); + // dirty experimental hack -- delete all the clocks + int gc; + for (gc = 0; gc < MAX_CLOCKS; gc++) { + memset(&clock_private_info[gc], 0, sizeof(clock_source_private_data)); + } + update_master_clock_info(0, NULL, 0, 0, 0); // the SMI may have obsolete stuff in it } else { - int64_t time_to_clock_expiration = clock_validity_expiration_time - reception_time; - // timings obtained with an iPhone Xs Max on battery save - - // around 30 seconds at a buffered audio pause on an iphone. - // around 1 second after a buffered audio stop on an iphone - // 10 seconds after a "T" from an iPhone that immediately sleeps - // more than a minute from "T" from a HomePod mini. - - if (time_to_clock_expiration < 0) { - debug(2, "Clock validity may have expired, so ask for a reset."); - will_ask_for_a_reset = 1; + debug(2, "Start Timing"); + // dirty experimental hack -- delete all the clocks + int gc; + for (gc = 0; gc < MAX_CLOCKS; gc++) { + memset(&clock_private_info[gc], 0, sizeof(clock_source_private_data)); } - } - if (will_ask_for_a_reset != 0) { - debug(2, "Reset clock smoothing"); - reset_clock_smoothing = 1; - } - } - clock_is_active = 1; - clock_validity_expiration_time = 0; - } else if ((strcmp(command, "E") == 0) && (ip_list == NULL)) { - debug(2, "Stop"); - if (clock_is_active) { - debug(2, "reset clock_validity_expiration_time to 2.25 seconds in the future."); - clock_validity_expiration_time = - reception_time + 2250000000; // expiration time can be very soon after an "E" - clock_is_active = 0; - } else { - debug(2, "clock is already inactive."); - } - } else if ((strcmp(command, "P") == 0) && (ip_list == NULL)) { - debug(2, "Pause"); - // A pause always seems to turn into a Stop in now more than a few seconds, and the clock - // keeps going, it seems so there is nothing to do here. - } else if ((command == NULL) || ((strcmp(command, "T") == 0) && (ip_list == NULL))) { - debug(2, "Stop Timing"); - clock_is_active = 0; - debug(2, "Clear timing peer group."); - // dirty experimental hack -- delete all the clocks - int gc; - for (gc = 0; gc < MAX_CLOCKS; gc++) { - memset(&clock_private_info[gc], 0, sizeof(clock_source_private_data)); - } - update_master_clock_info(0, NULL, 0, 0, 0); // the SMI may have obsolete stuff in it - } else { - debug(2, "Start Timing"); - // dirty experimental hack -- delete all the clocks - int gc; - for (gc = 0; gc < MAX_CLOCKS; gc++) { - memset(&clock_private_info[gc], 0, sizeof(clock_source_private_data)); - } - debug(2, "get or create new record for \"%s\".", smi_name); - // client_id = get_client_id(smi_name); // create the record if it doesn't exist - // if (client_id != -1) { - if (strcmp(command, "T") == 0) { - int i; - for (i = 0; i < MAX_CLOCKS; i++) { - clock_private_info[i].announcements_without_followups = - 0; // to allow a possibly silent clock to be revisited when added to a timing - // peer list - clock_private_info[i].follow_up_number = 0; - } + debug(2, "get or create new record for \"%s\".", smi_name); + // client_id = get_client_id(smi_name); // create the record if it doesn't exist + // if (client_id != -1) { + if (strcmp(command, "T") == 0) { + int i; + for (i = 0; i < MAX_CLOCKS; i++) { + clock_private_info[i].announcements_without_followups = + 0; // to allow a possibly silent clock to be revisited when added to a timing + // peer list + clock_private_info[i].follow_up_number = 0; + } - // take the first ip and make it the master, permanently + // take the first ip and make it the master, permanently - if (ip_list != NULL) { - char *new_ip = strsep(&ip_list, " "); - // look for the IP in the list of clocks, and create an inert entry if not there - if ((new_ip != NULL) && (new_ip[0] != 0)) { - int t = find_clock_source_record(new_ip, clock_private_info); - if (t == -1) - t = create_clock_source_record(new_ip, clock_private_info); - if (t != -1) { // if the clock table is not full, okay - debug(2, "Monitor clock at %s.", new_ip); + if (ip_list != NULL) { + char *new_ip = strsep(&ip_list, " "); + // look for the IP in the list of clocks, and create an inert entry if not there + if ((new_ip != NULL) && (new_ip[0] != 0)) { + int t = find_clock_source_record(new_ip, clock_private_info); + if (t == -1) + t = create_clock_source_record(new_ip, clock_private_info); + if (t != -1) { // if the clock table is not full, okay + debug(2, "Monitor clock at %s.", new_ip); + } + // otherwise, drop it + } } - // otherwise, drop it + // a new clock timing record will be started now + debug(2, "reset clock_validity_expiration_time to 5.0 seconds in the future."); + clock_validity_expiration_time = + reception_time + 5000000000L; // clock can stop as soon as 6 seconds after a "T" + } else { + warn("Unrecognised string on the control port."); } + // } else { + // warn("Could not find or create a record for SMI Interface \"%s\".", + // smi_name); + // } } - // a new clock timing record will be started now - debug(2, "reset clock_validity_expiration_time to 5.0 seconds in the future."); - clock_validity_expiration_time = - reception_time + 5000000000L; // clock can stop as soon as 6 seconds after a "T" - } else { - warn("Unrecognised string on the control port."); } - // } else { - // warn("Could not find or create a record for SMI Interface \"%s\".", smi_name); - // } + } else { + warn("SMI Interface Name not found on the control port."); } } else { - warn("SMI Interface Name not found on the control port."); + warn("Missing or empty packet on the control port."); } } else { warn("Bad packet on the control port."); @@ -445,7 +451,7 @@ smoothed_offset = clock_private_info->previous_offset; if (mastership_time > 1000000000) smoothed_offset += clamped_jitter / 256; // later, if jitter is negative - } else if (mastership_time < 1000000000) { // at the beginning + } else if (mastership_time < 1000000000) { // at the beginning smoothed_offset = clock_private_info->previous_offset + jitter / diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nqptp-1.2.1/nqptp-shm-structures.h new/nqptp-1.2.4/nqptp-shm-structures.h --- old/nqptp-1.2.1/nqptp-shm-structures.h 2023-05-22 11:18:48.000000000 +0200 +++ new/nqptp-1.2.4/nqptp-shm-structures.h 2023-09-16 19:51:55.000000000 +0200 @@ -1,6 +1,6 @@ /* * This file is part of the nqptp distribution (https://github.com/mikebrady/nqptp). - * Copyright (c) 2021--2022 Mike Brady. + * Copyright (c) 2021--2023 Mike Brady. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,7 +22,7 @@ #define NQPTP_INTERFACE_NAME "/nqptp" -#define NQPTP_SHM_STRUCTURES_VERSION 9 +#define NQPTP_SHM_STRUCTURES_VERSION 10 #define NQPTP_CONTROL_PORT 9000 // The control port expects a UDP packet with the first character being a command letter @@ -50,20 +50,29 @@ // When the clock is inactive, it can stop running. This causes the offset to decrease. // NQPTP clock smoothing would treat this as a network delay, causing true sync to be lost. // To avoid this, when the clock goes from inactive to active, -// NQPTP resets clock smoothing to the new offset. - +// NQPTP resets clock smoothing to the new offset. #include <inttypes.h> #include <pthread.h> -struct shm_structure { - pthread_mutex_t shm_mutex; // for safely accessing the structure - uint16_t version; // check this is equal to NQPTP_SHM_STRUCTURES_VERSION +typedef struct { uint64_t master_clock_id; // the current master clock - char master_clock_ip[64]; // where it's coming from uint64_t local_time; // the time when the offset was calculated uint64_t local_to_master_time_offset; // add this to the local time to get master clock time uint64_t master_clock_start_time; // this is when the master clock became master +} shm_structure_set; + +// The actual interface comprises a shared memory region of type struct shm_structure. +// This comprises two records of type shm_structure_set. +// The secondary record is written strictly after all writes to the main record are +// complete. This is ensured using the __sync_synchronize() construct. +// The reader should ensure that both copies match for a read to be valid. +// For safety, the secondary record should be read strictly after the first. + +struct shm_structure { + uint16_t version; // check this is equal to NQPTP_SHM_STRUCTURES_VERSION + shm_structure_set main; + shm_structure_set secondary; }; #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nqptp-1.2.1/nqptp-utilities.c new/nqptp-1.2.4/nqptp-utilities.c --- old/nqptp-1.2.1/nqptp-utilities.c 2023-05-22 11:18:48.000000000 +0200 +++ new/nqptp-1.2.4/nqptp-utilities.c 2023-09-16 19:51:55.000000000 +0200 @@ -29,11 +29,11 @@ #endif #ifdef CONFIG_FOR_FREEBSD -#include <sys/types.h> -#include <unistd.h> #include <net/if_dl.h> #include <net/if_types.h> #include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> #endif #include <netdb.h> // getaddrinfo etc. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nqptp-1.2.1/nqptp.c new/nqptp-1.2.4/nqptp.c --- old/nqptp-1.2.1/nqptp.c 2023-05-22 11:18:48.000000000 +0200 +++ new/nqptp-1.2.4/nqptp.c 2023-09-16 19:51:55.000000000 +0200 @@ -96,11 +96,13 @@ // close off new smi // mmap cleanup if (munmap(shared_memory, sizeof(struct shm_structure)) != 0) { - debug(1, "error unmapping shared memory \"%s\": \"%s\".", NQPTP_INTERFACE_NAME, strerror(errno)); + debug(1, "error unmapping shared memory \"%s\": \"%s\".", NQPTP_INTERFACE_NAME, + strerror(errno)); } // shm_open cleanup if (shm_unlink(NQPTP_INTERFACE_NAME) == -1) { - debug(1, "error unlinking shared memory \"%s\": \"%s\".", NQPTP_INTERFACE_NAME, strerror(errno)); + debug(1, "error unlinking shared memory \"%s\": \"%s\".", NQPTP_INTERFACE_NAME, + strerror(errno)); } if (shm_fd != -1) @@ -131,8 +133,8 @@ if (strcmp(argv[i] + 1, "V") == 0) { #ifdef CONFIG_USE_GIT_VERSION_STRING if (git_version_string[0] != '\0') - fprintf(stdout, "Version: %s. Shared Memory Interface Version: smi%u.\n", git_version_string, - NQPTP_SHM_STRUCTURES_VERSION); + fprintf(stdout, "Version: %s. Shared Memory Interface Version: smi%u.\n", + git_version_string, NQPTP_SHM_STRUCTURES_VERSION); else #endif @@ -191,13 +193,10 @@ // open the SMI - pthread_mutexattr_t shared; - int err; - shm_fd = -1; mode_t oldumask = umask(0); - shm_fd = shm_open(NQPTP_INTERFACE_NAME, O_RDWR | O_CREAT, 0666); + shm_fd = shm_open(NQPTP_INTERFACE_NAME, O_RDWR | O_CREAT, 0644); if (shm_fd == -1) { die("cannot open shared memory \"%s\".", NQPTP_INTERFACE_NAME); } @@ -230,23 +229,6 @@ memset(shared_memory, 0, sizeof(struct shm_structure)); shared_memory->version = NQPTP_SHM_STRUCTURES_VERSION; - /*create mutex attr */ - err = pthread_mutexattr_init(&shared); - if (err != 0) { - die("mutex attribute initialization failed - %s.", strerror(errno)); - } - pthread_mutexattr_setpshared(&shared, 1); - /*create a mutex */ - err = pthread_mutex_init((pthread_mutex_t *)&shared_memory->shm_mutex, &shared); - if (err != 0) { - die("mutex initialization failed - %s.", strerror(errno)); - } - - err = pthread_mutexattr_destroy(&shared); - if (err != 0) { - die("mutex attribute destruction failed - %s.", strerror(errno)); - } - ssize_t recv_len; char buf[BUFLEN]; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nqptp-1.2.1/nqptp.service.in new/nqptp-1.2.4/nqptp.service.in --- old/nqptp-1.2.1/nqptp.service.in 2023-05-22 11:18:48.000000000 +0200 +++ new/nqptp-1.2.4/nqptp.service.in 2023-09-16 19:51:55.000000000 +0200 @@ -6,8 +6,8 @@ [Service] ExecStart=@prefix@/bin/nqptp -User=root -Group=root +User=nqptp +Group=nqptp [Install] WantedBy=multi-user.target ++++++ nqptp-user.conf ++++++ # Type Name ID GECOS [HOME] g nqptp - - u nqptp - "nqptp daemon" / /sbin/nologin