pax_global_header00006660000000000000000000000064151433433420014514gustar00rootroot0000000000000052 comment=c17af00b40a454596ca43e1855c9aa13529ebc1f nqptp-1.2.6/000077500000000000000000000000001514334334200126645ustar00rootroot00000000000000nqptp-1.2.6/.gitignore000066400000000000000000000014601514334334200146550ustar00rootroot00000000000000# http://www.gnu.org/software/automake Makefile.in configure~ /ar-lib /mdate-sh /py-compile /test-driver /ylwrap # http://www.gnu.org/software/autoconf INSTALL autom4te.cache /autoscan.log /autoscan-*.log /aclocal.m4 /compile /config.* /configure /depcomp /install-sh /missing /stamp-h1 /.deps/ /*.service # https://www.gnu.org/software/libtool/ /ltmain.sh # http://www.gnu.org/software/texinfo /texinfo.tex # http://www.gnu.org/software/m4/ m4/libtool.m4 m4/ltoptions.m4 m4/ltsugar.m4 m4/ltversion.m4 m4/lt~obsolete.m4 # Generated Makefile # (meta build system like autotools, # can automatically generate from config.status script # (which is called by configure script)) Makefile # Object files /*.o # Executable nqptp # Version files generated from check-gitversion gitversion.h gitversion-stamp nqptp-1.2.6/AUTHORS000066400000000000000000000000001514334334200137220ustar00rootroot00000000000000nqptp-1.2.6/COPYING000066400000000000000000000001601514334334200137140ustar00rootroot00000000000000For copying terms, please refer to the LICENSE file in this directory. Commercial licensing is also available. nqptp-1.2.6/ChangeLog000066400000000000000000000000001514334334200144240ustar00rootroot00000000000000nqptp-1.2.6/LICENSE000066400000000000000000000432541514334334200137010ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. nqptp-1.2.6/Makefile.am000066400000000000000000000026271514334334200147270ustar00rootroot00000000000000bin_PROGRAMS = nqptp nqptp_SOURCES = nqptp.c nqptp-clock-sources.c nqptp-message-handlers.c nqptp-utilities.c general-utilities.c debug.c AM_CFLAGS = -fno-common -Wall -Wextra -pthread --include=config.h if USE_GIT_VERSION ## Check if the git version information has changed and rebuild gitversion.h if so .PHONY: gitversion-check gitversion-check: $(top_srcdir)/check-gitversion BUILT_SOURCES = gitversion-check CLEANFILES = gitversion-stamp gitversion.h endif install-exec-hook: if BUILD_FOR_LINUX # Note: 1. NQPTP runs as a dynamic user on systemd-based Linux. # 2. Access to ports 319 and 320 is given via AmbientCapabilities in the service file. # If you want to run NQPTP from the command line, e.g. for debugging, run it as root user. # no installer for System V if INSTALL_SYSTEMD_STARTUP [ -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 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 endif endif if BUILD_FOR_OPENBSD # NQPTP starts as root on OpenBSD to access ports 319 and 320 # and drops privileges to the user shairport is running as. endif nqptp-1.2.6/NEWS000066400000000000000000000000001514334334200133510ustar00rootroot00000000000000nqptp-1.2.6/README000066400000000000000000000000001514334334200135320ustar00rootroot00000000000000nqptp-1.2.6/README.md000066400000000000000000000144031514334334200141450ustar00rootroot00000000000000# NQPTP – Not Quite PTP `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. ## Installation This guide is for recent Linux and FreeBSD systems. As usual, you should first ensure everything is up to date. #### Please use `git`! As you probably know, you can download the repository in two ways: (1) using `git` to clone it -- recommended -- or (2) downloading the repository as a ZIP archive. Please use the `git` method. The reason it that when you use `git`, the build process can incorporate the `git` build information in the version string you get when you execute the command `$ nqptp -V`. This will be very useful for identifying the exact build if you are making comments or bug reports. Here is an example: ``` Version with git build information: Version: 1.1-dev-24-g0c00a79. Shared Memory Interface Version: 5. Version without git build information: Version: 1.1-dev. Shared Memory Interface Version: 5. ``` ### Remove Old Service Files #### Linux If you are updating from version `1.2.4` or earlier in Linux, remove the service file `nqptp.service` from the directories `/lib/systemd/system` and `/usr/local/lib/systemd/system` (you'll need superuser privileges): ``` # rm /lib/systemd/system/nqptp.service # rm /usr/local/lib/systemd/system/nqptp.service # systemctl daemon-reload ``` Don't worry if you get a message stating that the files doesn't exist -- no harm done. #### FreeBSD At present, there is no need to remove the old startup script as (in FreeBSD only) it is always replaced during the `# make install` step. The startup script is at `/usr/local/etc/rc.d/nqptp`. ### Build and Install Note that you will need superuser privileges to install, enable and start the daemon. #### Linux ``` $ git clone https://github.com/mikebrady/nqptp.git $ cd nqptp $ autoreconf -fi # about a minute on a Raspberry Pi. $ ./configure --with-systemd-startup $ make # make install ``` #### FreeBSD ``` $ git clone https://github.com/mikebrady/nqptp.git $ cd nqptp $ autoreconf -fi $ ./configure --with-freebsd-startup $ make # make install ``` The `make install` installs a startup script as requested. You should enable it and start it in the normal way: ### First Install or Update? #### Linux ##### First Install If you are installing `nqptp` for the first time, enable it and start it: ``` # systemctl enable nqptp # systemctl start nqptp ``` If Shairport Sync is already running, you should restart it after starting `nqptp`: ``` # systemctl restart shairport-sync ``` ##### Update If you are updating an existing installation of `nqptp`, after installing it you should restart it. You should then also restart Shairport Sync: ``` # systemctl restart nqptp # systemctl restart shairport-sync ``` #### FreeBSD ##### First Install If you are installing `nqptp` for the first time, add an automatic startup entry for it in `/etc/rc.local` and start it: 1. Edit `/etc/rc.conf` and add the following line: ``` nqptp_enable="YES" ``` 2. When you have finished editing `/etc/rc.conf`, you can start `nqptp` from the command line: ``` # service nqptp start ``` If Shairport Sync is already running, you should you restart it after starting `nqptp`: ``` # service shairport_sync restart ``` ##### Update If you are updating an existing installation of `nqptp`, after installing it you should restart it. You should then also restart Shairport Sync: ``` # service nqptp restart # service shairport_sync restart ``` ## Firewall If your system runs a firewall, ensure that ports 319 and 320 are open for UDP traffic in both directions. These ports are associated with PTP service and may be referred to as "PTP" in firewall rules. For example, the following would open ports 319 and 320 for Fedora, which uses `firewalld`: ``` # firewall-cmd --add-service=ptp # firewall-cmd --permanent --add-service=ptp # make it permanent across reboots ``` ## Notes 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 using systemd `AmbientCapabilities`. In FreeBSD, `nqptp` runs as `root` user. ## Programming Notes Commands and status information are sent to `nqptp` over port 9000. Information about the PTP clock is provided via a [POSIX shared memory](https://pubs.opengroup.org/onlinepubs/007908799/xsh/shm_open.html) interface. Here are details of the interface: ```c typedef struct { uint64_t master_clock_id; // the current master clock 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; }; ``` Clock records that are not updated for a period are deleted. ## Known Issues * `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. ## Things Can Change The `nqptp` daemon is under active development and, consequently, everything here can change, possibly very radically. ## NQPTP is not PTP! `nqptp` uses just a part of the [IEEE 1588-2008](https://standards.ieee.org/standard/1588-2008.html) protocol. It is not a PTP clock. nqptp-1.2.6/RELEASE_NOTES.md000066400000000000000000000331631514334334200152440ustar00rootroot00000000000000## Version 1.2.6 * This update should make synchronisation a little better and a little smoother. **Note** If you are updating an existing installation, you'll need to delete the existing service file as directed in the [README](https://github.com/mikebrady/nqptp/blob/main/README.md#remove-old-service-files). You must also redo the `./configure --with-systemd-startup` step to generate the updated `systemd` service file. ## Version: 1.2.5-dev-29-ga93ba70 * Use a new method to get versioning information from `git describe`. It recognises when a repository gets 'dirty' immediately. ## Version: 1.2.5-dev-27-gb59628b * Use Linux scheduling to give NQPTP slightly increased priority. This should make it more likely that NQPTP will be able to accurately time the arrival of timing packets and thus make synchronisation a little smoother. At startup, NQPTP will request FIFO scheduling and an associated priority. If it's not available, it will just carry on as before. The `systemd` service file has been updated to grant NQPTP limited permission to set scheduling and priority. ## Version: 1.2.5-dev-24-g494ff3f * Following a [suggestion](https://github.com/mikebrady/nqptp/issues/33) by [Jörn Nettingsmeier](https://github.com/nettings), use the `DynamicUser` facility provided by `systemd` to define the system user needed to run the NQPTP daemon. Many thanks to them. New installations will use this arrangement. Optionally, if you wish to use it on an existing installation: ``` # rm /usr/local/lib/systemd/system/nqptp.service # systemctl daemon-reload ``` before building and installing NQPTP. ## Version: 1.2.5-dev-18-gb8384c4 * Documentation fix. Thanks to [Rudi Heitbaum](https://github.com/heitbaum) for a documentation fix in [PR 34](https://github.com/mikebrady/nqptp/pull/34). ## Version: 1.2.5-dev-16-g32bfe78 * OpenBSD compatibility, thanks to the work of [Klemens Nanni](https://github.com/klemensn) in [PR 31](https://github.com/mikebrady/nqptp/pull/31). This work includes: using OpenBSD's `pledge(2)` facilities to harden the security of NQPTP, dropping priviliges as soon as possible, and running as the same user as the Shairport Sync application. (Note that at present on Linux, NQPTP runs as the user `nqptp` while Shairport Sync runs as the user `shairport-sync`. For consistency, this may change in future.) ## Version: 1.2.5-dev-5-g475d7a1 * Fixup for FreeBSD compilation error -- change the order of some of the `#include` files. ## Version: 1.2.5-dev-2-gb5321a8 * Stop using `setcap` on the `nqptp` binary at install time and instead use systemd's AmbientCapabilities to give the `nqptp` daemon the capability to access ports 319 and 320. Many thanks to [Hs_Yeah](https://github.com/Hs-Yeah) for the [PR](https://github.com/mikebrady/nqptp/pull/26). * Improve some error messages. * Update a few documentation errors. * Fix some compilation errors on FreeBSD. Thanks to [Emanuel Haupt](https://github.com/ehaupt) who also discovered these errors and proposed a fix. When updating NQPTP on Linux, be sure to remove old service files as directed in the [README](https://github.com/mikebrady/nqptp/blob/main/README.md#remove-old-service-files). Note: Shairport Sync must be using Shared Memory Interface Version: `smi10`. Check by running: ``` $ shairport-sync -V ``` ## 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 old service files 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. **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. ## 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. This is important because the clock from the source can stop or run slow when the source is not actively playing. This arrangement seems to be much more resilient than having NQPTP try to detect when a clock is stopped or running slow. It also allows the code to be simplified. ## Version: 1.1-dev-207-ge14575b **Bug Fix** * Due to a bug, the ports used by NQPTP -- ports 319, 320 and 9000 -- had to be available on all IP protocols on the system. For example, if IPv6 and IPv4 were available on the system and a port could be opened on IPv4 but not on IPv6 , Shairport Sync would fail. This has been fixed. As before, ports will be opened on all IP protocols available, but only one needs to be successfully opened. Many thanks to [Ferdynand Naczynski](https://github.com/nipsufn) for their detective work and for developing a fix. ## Version: 1.1-dev-199-g2b5490c **Bug Fixes** * Use the previous offset if a negative jitter for the first period. * Fix a misleading comment. **Enhancements** * Tune the weights of offset additions and reductions to further reduce the offset errors in the initial adjustment period. ## Version: 1.1-dev-196-g9fc0501 **Enhancement** * Finally (!) the suggestion made by [the0u](https://github.com/th0u) in [Issue #14]() has been acted upon and the suggested modifications made so that NQPTP will only listen to connections made to port 9000 coming from `localhost`. Thanks to [the0u](https://github.com/th0u) for the suggestion and the code. Thanks to [herrernst](https://github.com/herrernst) for the reminder! ## Version: 1.1-dev-195-g93f1e8a **Enhancement** * NQPTP has been simplified and is more resilient to adverse network conditions. ## Version: 1.1-dev-175-g264805d Weird build numbers. **Bug Fix** * Only try to start a silent clock if no follow_ups have _ever_ been received from it. ## Version: 1.1-dev-168-g3444047 ***Pesky Changes You Can't Ignore*** * **Important**. The Shared Memory Interface 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 version number -- 8. **FYI** * The ability to handle multiple instances of AirPlay-2-capable Shairport Sync on the same system has been removed. It seems that clients can not use this facility. **Enhancements** * Greatly simplify NQPTP by only monitoring PTP information coming from the client, ignoring all other PTP information. * In addition to trying to restart a clock that is silent, also send a restart to a clock if the clock's grandmaster appears to have stopped. ## Version: 1.1-dev-186-g4e54f1b **Bug Fixes** * Reorder system header files includes to fix a compilation error. ## Version: 1.1-dev-166-g46a9f1b * Update the wording in the INSTALL document to match the wording generated at the `autoreconf -fi` stage, so that `git` doesn't flag an altered document. Thanks to [David Leibovic](https://github.com/dasl-) for bringing this to notice. ## Version: 1.1-dev-161-g353093a **Bug Fix** * If a player (e.g. a HomePod mini) that was providing the master clock was removed from the set of devices playing, the new master clock retained out-of-date information about the old master clock. This could cause problems going to the next track or to a previous one, causing them not to be heard. Thanks (again!) to [Kristian Dimitrov](https://github.com/Kristian8606) for a precise description of how to cause the problem. ## Version: 1.1-dev-164-g086a123 **Enhancements** * Improve the accuracy of the clock by including data from the `correctionField` part of a PTP message. Most of the time, this is a fraction of a millisecond, but sometimes it can be larger. * If a clock timing sample is more than four seconds slow, treat it as the start of a new timing sequence rather than as an error in the current timing sequence. * Try to restart a clock that stops incrementing towards the start of a timing sequence. ## Version: 1.1-dev-154-g608980e **Bug Fix** * Some times, if a PTP clock went to sleep and then woke up, NQPTP would not recognise the new timing data, and, literally, get stuck in the past (!). Getting the problem to manifest itself was difficult -- the clock in the source device (e.g. an iPhone) had to sleep and restart at just the wrong time to cause the problem. Thanks to [Kristian Dimitrov](https://github.com/Kristian8606), [vision4u2](https://github.com/vision4u2) and others. ## Version: 1.1-dev-150-g7af5673 **Bug Fix** * Remove a noisy debug message that could fill the log. Thanks to [kevocl](https://github.com/kevocl) for the [report](https://github.com/mikebrady/shairport-sync/issues/1481). ## Version: 1.1-dev-142-g15b01c1 **Enhancement** * Support for FreeBSD ## Version: 1.1-dev-131-g44d4086 ***Pesky Change You Can't Ignore*** A change has been made the `nqptp` `systemd` service file, so before updating, please remove the existing service file with the following command: ``` # rm /usr/local/lib/systemd/system/nqptp.service ``` **Bug Fix** * Remove the invalid `Provides` entry. Thanks to [David Crook](https://github.com/idcrook) for bring this to notice for Shairport Sync. It also applies to NQPTP. ## Version: 1.1-dev-124-g9a57e77 ***Pesky Change You Can't Ignore*** A change has been made the `nqptp` `systemd` service file, so before updating, please remove the existing service file with the following command: ``` # rm /usr/local/lib/systemd/system/nqptp.service ``` **Enhancement** * Always create a new SHM interface for every new shm address provided. * Remove redundant code. * Add a few debug messages. * Enhance the service record to define the service provided. * Quieten some chatty debug messages. ## Version: 1.1-dev-117-g7e3c2b7 **Dedicated client interfaces** NQPTP has gone multi-client. Clients now specify a named SMI interface through which they can specify their own timing peers and through which PTP information for that group of clock peers is returned. Thus, multiple clients (e.g. multiple instances of Shairport Sync) can maintain synchrnoisation with their own individual clock groups. ## Version: 1.1-dev-108-ga378f07 **Enhancement** * Ensure the shared memory interface is updated when mastership is removed. ## Version: 1.1-dev-107-g811524b **Enhancement** * Further simplify things by turning off history completely and by discarding any mastership history prior to becoming part of a (possibly new) timing peer list. ## Version: 1.1-dev-104-gd78c84a **Enhancement** * Make `nqptp` tolerant of longer gaps in the reception of messages from the master clock -- up to 300 seconds. This may be overkill, since messages are meant to arrive 8 times per second, but experience shows that rather long gaps can indeed occur. ## Version: 1.1-dev-102-gf678f82 **Enhancement** * Try to improve the reliability of restarting a "silent clock" device, e.g. an Apple Silicon Mac or an AppleTV. Also turn off history (mostly) to see if we can make things simpler. ## Version: 1.1-dev-74-gf713183 **Enhancement** * Add code to activate a PTP clock that has become effectively silent. This can happen to a PTP clock on an Apple Silicon Mac after it has been woken from sleep. It may happen elsewhere. The new code begins and then rapidly terminates a clock mastership negotiation, and this causes the clock to become active. This 'silent clock' is unexpected behaviour and may be a bug. ## Version: 1.1-dev-51-g812326a ***Pesky Change You Can't Ignore*** A change has been made to where the `nqptp` `systemd` service file is placed. If you are updating from a previous version of `nqptp`, please do the following before you update: 1. Disable the `nqptp` service as follows: ``` # systemctl disable nqptp ``` 2. Remove the service file `nqptp.service` from the directory `/lib/systemd/system` (you'll need superuser privileges). A new service file will be installed in the correct location -- `/usr/local/lib/systemd/system` in Ubuntu 20.04 and Raspbian OS (Buster) -- during the `# make install` step. After updating, re-enable the `nqptp` service as follows: ``` # systemctl enable nqptp ``` **Enhancement** * Further modify `install-exec-hook` to also use the standard `$(libdir)` variable instead of a fixed location. Thanks to [FW](https://github.com/fwcd). ## Version: 1.1-dev-44-g827e624 * Modify `install-exec-hook` to use the standard `$DESTDIR` variable instead of the fixed location `/lib/systemd/`. This is to facilitate build environments that install into a separate directory (e.g. cross-compiling environments). Thanks to [HiFiBerry](https://github.com/hifiberry). ## Version: 1.1-dev-40-g6111d69 * Fix a crashing bug. Thanks to [ste94pz](https://github.com/ste94pz) for reporting and debugging. ## Version: 1.1-dev-36-g880b424 * Initial public version. nqptp-1.2.6/check-gitversion000077500000000000000000000027621514334334200160650ustar00rootroot00000000000000#! /bin/sh # echo "This is the git version checker" test -f gitversion-stamp && BGD=`cat gitversion-stamp` || : # echo "existing gitversion-stamp is $BGD" GD=`git describe --tags --dirty --broken --always 2>/dev/null` if [ x"$GD" = x ] ; then GD="NA" fi # echo "current git description is $GD" if [ x"$GD" != x"$BGD" ] ; then echo "build: $GD" # optional -- displays git version if new or different echo $GD > gitversion-stamp echo "// Do not edit!" > gitversion.h echo "// This file is automatically generated." >> gitversion.h echo -n " char git_version_string[] = \"" >> gitversion.h ## the tr is because we need to remove the trailing newline cat gitversion-stamp | tr -d '[[:space:]]' >> gitversion.h echo "\";" >> gitversion.h fi # Usage. Below is what you would add to Makefile.am. When it runs, the # following two files are generated: 'gitversion-stamp' and 'gitversion.h'. # gitversion-stamp stores the most recently used git description. # gitversion.h is a C header file containing that git description as a string. # Put this script in the top level source folder and make sure it has execute permission. # These are the lines (remove the leading '#' on each) to add to the Makefile.am file: # ## Check if the git version information has changed and rebuild gitversion.h if so. # .PHONY: gitversion-check # gitversion-check: # $(top_srcdir)/check-gitversion # ## You may have to change from += to = below: # BUILT_SOURCES += gitversion-check # CLEANFILES += gitversion-stamp gitversion.h nqptp-1.2.6/configure.ac000066400000000000000000000053561514334334200151630ustar00rootroot00000000000000# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ([2.68]) AC_INIT([nqptp], [1.2.6], [4265913+mikebrady@users.noreply.github.com]) : ${CFLAGS="-O3"} : ${CXXFLAGS="-O3"} AM_INIT_AUTOMAKE AC_CANONICAL_HOST build_linux=no build_freebsd=no build_openbsd=no # Detect the target system case "${host_os}" in linux*) build_linux=yes ;; freebsd*) build_freebsd=yes ;; openbsd*) build_openbsd=yes ;; *) AC_MSG_ERROR(["OS $host_os is not supported"]) ;; esac # Pass the conditionals to automake AM_CONDITIONAL([BUILD_FOR_LINUX], [test "$build_linux" = "yes"]) AM_CONDITIONAL([BUILD_FOR_FREEBSD], [test "$build_freebsd" = "yes"]) AM_CONDITIONAL([BUILD_FOR_OPENBSD], [test "$build_openbsd" = "yes"]) if test "x$build_linux" = "xyes" ; then AC_DEFINE([CONFIG_FOR_LINUX], 1, [Build for Linux.]) fi if test "x$build_freebsd" = "xyes" ; then AC_DEFINE([CONFIG_FOR_FREEBSD], 1, [Build for FreeBSD.]) fi if test "x$build_openbsd" = "xyes" ; then AC_DEFINE([CONFIG_FOR_OPENBSD], 1, [Build for OpenBSD.]) fi AC_CHECK_PROGS([GIT], [git]) if test -n "$GIT" && test -e ".git/index" ; then AC_DEFINE([CONFIG_USE_GIT_VERSION_STRING], 1, [Use the version string produced by running 'git describe --dirty'.]) fi AM_CONDITIONAL([USE_GIT_VERSION], [test -n "$GIT" && test -e ".git/index" ]) # Check to see if we should include the systemd stuff to define it as a service AC_ARG_WITH([systemd-startup],[AS_HELP_STRING([--with-systemd-startup],[install a systemd startup script during a make install])]) AM_CONDITIONAL([INSTALL_SYSTEMD_STARTUP], [test "x$with_systemd_startup" = "xyes"]) # Check to see if we should include the FreeBSD stuff to define it as a service AC_ARG_WITH([freebsd-startup],[AS_HELP_STRING([--with-freebsd-startup],[install a FreeBSD startup script during a make install])]) AM_CONDITIONAL([INSTALL_FREEBSD_STARTUP], [test "x$with_freebsd_startup" = "xyes"]) AC_CONFIG_SRCDIR([nqptp.c]) AC_CONFIG_HEADERS([config.h]) # Checks for programs. AC_PROG_CC AC_PROG_INSTALL # Checks for libraries. AC_CHECK_LIB([pthread],[pthread_create], , AC_MSG_ERROR(pthread library needed)) if test "x$build_openbsd" = "xno" ; then # part of libc AC_CHECK_LIB([rt],[clock_gettime], , AC_MSG_ERROR(librt needed for shared memory library)) fi # Checks for header files. AC_CHECK_HEADERS([arpa/inet.h inttypes.h netdb.h stdlib.h string.h sys/socket.h unistd.h]) # Checks for typedefs, structures, and compiler characteristics. AC_TYPE_SIZE_T AC_TYPE_UINT32_T # Checks for library functions. AC_FUNC_MALLOC AC_CHECK_FUNCS([clock_gettime inet_ntoa memset select socket strerror]) AC_CONFIG_FILES([Makefile nqptp.service]) AC_OUTPUT nqptp-1.2.6/debug.c000066400000000000000000000165621514334334200141300ustar00rootroot00000000000000/* * This file is part of the nqptp distribution (https://github.com/mikebrady/nqptp). * Copyright (c) 2021 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 * the Free Software Foundation, version 2. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Commercial licensing is also available. */ #include "debug.h" #include #include #include #include #include #include #include #include int debuglev = 0; int debugger_show_elapsed_time = 0; int debugger_show_relative_time = 0; int debugger_show_file_and_line = 1; uint64_t ns_time_at_startup = 0; uint64_t ns_time_at_last_debug_message; // always lock use this when accessing the ns_time_at_last_debug_message static pthread_mutex_t debug_timing_lock = PTHREAD_MUTEX_INITIALIZER; uint64_t get_absolute_time_in_ns() { uint64_t time_now_ns; struct timespec tn; // CLOCK_REALTIME because PTP uses it. clock_gettime(CLOCK_REALTIME, &tn); uint64_t tnnsec = tn.tv_sec; tnnsec = tnnsec * 1000000000; uint64_t tnjnsec = tn.tv_nsec; time_now_ns = tnnsec + tnjnsec; return time_now_ns; } void debug_init(int level, int show_elapsed_time, int show_relative_time, int show_file_and_line) { ns_time_at_startup = get_absolute_time_in_ns(); ns_time_at_last_debug_message = ns_time_at_startup; debuglev = level; debugger_show_elapsed_time = show_elapsed_time; debugger_show_relative_time = show_relative_time; debugger_show_file_and_line = show_file_and_line; } int debug_level() { return debuglev; } char *generate_preliminary_string(char *buffer, size_t buffer_length, double tss, double tsl, const char *filename, const int linenumber, const char *prefix) { size_t space_remaining = buffer_length; char *insertion_point = buffer; if (debugger_show_elapsed_time) { snprintf(insertion_point, space_remaining, "% 20.9f", tss); insertion_point = insertion_point + strlen(insertion_point); space_remaining = space_remaining - strlen(insertion_point); } if (debugger_show_relative_time) { snprintf(insertion_point, space_remaining, "% 20.9f", tsl); insertion_point = insertion_point + strlen(insertion_point); space_remaining = space_remaining - strlen(insertion_point); } if (debugger_show_file_and_line) { snprintf(insertion_point, space_remaining, " \"%s:%d\"", filename, linenumber); insertion_point = insertion_point + strlen(insertion_point); space_remaining = space_remaining - strlen(insertion_point); } if (prefix) { snprintf(insertion_point, space_remaining, "%s", prefix); insertion_point = insertion_point + strlen(insertion_point); space_remaining = space_remaining - strlen(insertion_point); } return insertion_point; } void _die(const char *filename, const int linenumber, const char *format, ...) { int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); char b[1024]; b[0] = 0; char *s; if (debuglev) { pthread_mutex_lock(&debug_timing_lock); uint64_t time_now = get_absolute_time_in_ns(); uint64_t time_since_start = time_now - ns_time_at_startup; uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message; ns_time_at_last_debug_message = time_now; pthread_mutex_unlock(&debug_timing_lock); s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000, 1.0 * time_since_last_debug_message / 1000000000, filename, linenumber, " *fatal error: "); } else { strncpy(b, "fatal error: ", sizeof(b)); s = b + strlen(b); } va_list args; va_start(args, format); vsnprintf(s, sizeof(b) - (s - b), format, args); va_end(args); // syslog(LOG_ERR, "%s", b); fprintf(stderr, "%s\n", b); pthread_setcancelstate(oldState, NULL); exit(EXIT_FAILURE); } void _warn(const char *filename, const int linenumber, const char *format, ...) { int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); char b[1024]; b[0] = 0; char *s; if (debuglev) { pthread_mutex_lock(&debug_timing_lock); uint64_t time_now = get_absolute_time_in_ns(); uint64_t time_since_start = time_now - ns_time_at_startup; uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message; ns_time_at_last_debug_message = time_now; pthread_mutex_unlock(&debug_timing_lock); s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000, 1.0 * time_since_last_debug_message / 1000000000, filename, linenumber, " *warning: "); } else { strncpy(b, "warning: ", sizeof(b)); s = b + strlen(b); } va_list args; va_start(args, format); vsnprintf(s, sizeof(b) - (s - b), format, args); va_end(args); // syslog(LOG_WARNING, "%s", b); fprintf(stderr, "%s\n", b); pthread_setcancelstate(oldState, NULL); } void _debug(const char *filename, const int linenumber, int level, const char *format, ...) { if (level > debuglev) return; int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); char b[1024]; b[0] = 0; pthread_mutex_lock(&debug_timing_lock); uint64_t time_now = get_absolute_time_in_ns(); uint64_t time_since_start = time_now - ns_time_at_startup; uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message; ns_time_at_last_debug_message = time_now; pthread_mutex_unlock(&debug_timing_lock); char *s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000, 1.0 * time_since_last_debug_message / 1000000000, filename, linenumber, " "); va_list args; va_start(args, format); vsnprintf(s, sizeof(b) - (s - b), format, args); va_end(args); // syslog(LOG_DEBUG, "%s", b); fprintf(stderr, "%s\n", b); pthread_setcancelstate(oldState, NULL); } void _inform(const char *filename, const int linenumber, const char *format, ...) { int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); char b[1024]; b[0] = 0; char *s; if (debuglev) { pthread_mutex_lock(&debug_timing_lock); uint64_t time_now = get_absolute_time_in_ns(); uint64_t time_since_start = time_now - ns_time_at_startup; uint64_t time_since_last_debug_message = time_now - ns_time_at_last_debug_message; ns_time_at_last_debug_message = time_now; pthread_mutex_unlock(&debug_timing_lock); s = generate_preliminary_string(b, sizeof(b), 1.0 * time_since_start / 1000000000, 1.0 * time_since_last_debug_message / 1000000000, filename, linenumber, " "); } else { s = b; } va_list args; va_start(args, format); vsnprintf(s, sizeof(b) - (s - b), format, args); va_end(args); // syslog(LOG_INFO, "%s", b); fprintf(stderr, "%s\n", b); pthread_setcancelstate(oldState, NULL); } nqptp-1.2.6/debug.h000066400000000000000000000031671514334334200141320ustar00rootroot00000000000000/* * This file is part of the nqptp distribution (https://github.com/mikebrady/nqptp). * Copyright (c) 2021 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 * the Free Software Foundation, version 2. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Commercial licensing is also available. */ // four level debug message utility giving file and line, total elapsed time, interval time // warn / inform / debug / die calls. // level 0 is no messages, level 3 is most messages void debug_init(int level, int show_elapsed_time, int show_relative_time, int show_file_and_line); int debug_level(); // get current debug level void _die(const char *filename, const int linenumber, const char *format, ...); void _warn(const char *filename, const int linenumber, const char *format, ...); void _inform(const char *filename, const int linenumber, const char *format, ...); void _debug(const char *filename, const int linenumber, int level, const char *format, ...); #define die(...) _die(__FILE__, __LINE__, __VA_ARGS__) #define debug(...) _debug(__FILE__, __LINE__, __VA_ARGS__) #define warn(...) _warn(__FILE__, __LINE__, __VA_ARGS__) #define inform(...) _inform(__FILE__, __LINE__, __VA_ARGS__) nqptp-1.2.6/general-utilities.c000066400000000000000000000050531514334334200164610ustar00rootroot00000000000000/* * This file is part of the nqptp distribution (https://github.com/mikebrady/nqptp). * Copyright (c) 2021-2022 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 * the Free Software Foundation, version 2. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Commercial licensing is also available. */ #include "general-utilities.h" #include "debug.h" #include // ntohl etc. #include // memcpy void hcton64(uint64_t num, uint8_t *p) { uint64_t numc = num; numc = numc >> 32; uint32_t num_32 = numc; uint32_t rev = htonl(num_32); memcpy(p, &rev, sizeof(uint32_t)); num_32 = num & 0xffffffff; p = p + 4; rev = htonl(num_32); memcpy(p, &rev, sizeof(uint32_t)); } uint64_t nctoh64(const uint8_t *p) { // read 4 characters from *p and do ntohl on them // this is to avoid possible aliasing violations uint64_t value = nctohl(p); uint64_t value_low = nctohl(p + 4); value = value << 32; value = value + value_low; return value; } uint32_t nctohl(const uint8_t *p) { // read 4 characters from *p and do ntohl on them // this is to avoid possible aliasing violations uint32_t holder; memcpy(&holder, p, sizeof(holder)); return ntohl(holder); } uint16_t nctohs(const uint8_t *p) { // read 2 characters from *p and do ntohs on them // this is to avoid possible aliasing violations uint16_t holder; memcpy(&holder, p, sizeof(holder)); return ntohs(holder); } uint64_t ntoh64(const uint64_t n) { uint64_t fiddle = n; uint32_t fiddle_hi = fiddle & 0xFFFFFFFF; fiddle_hi = ntohl(fiddle_hi); fiddle = fiddle >> 32; uint32_t fiddle_lo = fiddle & 0xFFFFFFFF; fiddle_lo = ntohl(fiddle_lo); fiddle = fiddle_hi; fiddle = fiddle << 32; fiddle = fiddle | fiddle_lo; return fiddle; } uint64_t timespec_to_ns(struct timespec *tn) { uint64_t tnfpsec = tn->tv_sec; uint64_t tnfpnsec = tn->tv_nsec; tnfpsec = tnfpsec * 1000000000; return tnfpsec + tnfpnsec; } uint64_t get_time_now() { struct timespec tn; #ifdef CLOCK_MONOTONIC_RAW clock_gettime(CLOCK_MONOTONIC_RAW, &tn); #else clock_gettime(CLOCK_MONOTONIC, &tn); #endif return timespec_to_ns(&tn); } nqptp-1.2.6/general-utilities.h000066400000000000000000000027431514334334200164710ustar00rootroot00000000000000/* * This file is part of the nqptp distribution (https://github.com/mikebrady/nqptp). * Copyright (c) 2021-2022 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 * the Free Software Foundation, version 2. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Commercial licensing is also available. */ #ifndef GENERAL_UTILITIES_H #define GENERAL_UTILITIES_H // functions that are pretty generic // specialised stuff should go in the nqptp-utilities #include #include #include // struct sockaddr_in6 is bigger than struct sockaddr. derp #ifdef AF_INET6 #define SOCKADDR struct sockaddr_storage #define SAFAMILY ss_family #else #define SOCKADDR struct sockaddr #define SAFAMILY sa_family #endif void hcton64(uint64_t num, uint8_t *p); // these are designed to avoid aliasing check errors uint64_t nctoh64(const uint8_t *p); uint32_t nctohl(const uint8_t *p); uint16_t nctohs(const uint8_t *p); uint64_t timespec_to_ns(struct timespec *tn); uint64_t get_time_now(); uint64_t ntoh64(const uint64_t n); #endifnqptp-1.2.6/nqptp-clock-sources.c000066400000000000000000000210051514334334200167420ustar00rootroot00000000000000/* * This file is part of the nqptp distribution (https://github.com/mikebrady/nqptp). * Copyright (c) 2021-2022 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 * the Free Software Foundation, version 2. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Commercial licensing is also available. */ #include "nqptp-clock-sources.h" #include "debug.h" #include "general-utilities.h" #include "nqptp-ptp-definitions.h" #include #include #include #include #include #include #include // for ftruncate and others #include // for ftruncate and others #include /* For O_* constants */ #include // for shared memory stuff #include // for fd_set #include // umask #ifdef CONFIG_FOR_FREEBSD #include #endif #ifndef FIELD_SIZEOF #define FIELD_SIZEOF(t, f) (sizeof(((t *)0)->f)) #endif int shm_fd; struct shm_structure *shared_memory; clock_source_private_data clocks_private[MAX_CLOCKS]; client_record clients[MAX_CLIENTS]; /* const char *get_client_name(int client_id) { if ((client_id >= 0) && (client_id < MAX_CLIENTS)) { return clients[client_id].shm_interface_name; } else { return ""; } } */ int get_client_id(char *client_shared_memory_interface_name) { int response = -1; // signify not found if (client_shared_memory_interface_name != NULL) { int i = 0; // first, see if yu can find it anywhere while ((response == -1) && (i < MAX_CLIENTS)) { if (strcmp(clients[i].shm_interface_name, client_shared_memory_interface_name) == 0) response = i; else i++; } if (response == -1) { // no match, so create one i = 0; while ((response == -1) && (i < MAX_CLIENTS)) { if (clients[i].shm_interface_name[0] == '\0') response = i; else i++; } if (response != -1) { strncpy(clients[i].shm_interface_name, client_shared_memory_interface_name, sizeof(clients[i].shm_interface_name)); // create the named smi interface // open a shared memory interface. debug(2, "Create a shm interface named \"%s\"", clients[i].shm_interface_name); clients[i].shm_fd = -1; mode_t oldumask = umask(0); clients[i].shm_fd = shm_open(client_shared_memory_interface_name, O_RDWR | O_CREAT, 0666); if (clients[i].shm_fd == -1) { die("cannot open shared memory \"%s\".", client_shared_memory_interface_name); } (void)umask(oldumask); if (ftruncate(clients[i].shm_fd, sizeof(struct shm_structure)) == -1) { die("failed to set size of shared memory \"%s\".", client_shared_memory_interface_name); } #ifdef CONFIG_FOR_FREEBSD clients[i].shared_memory = (struct shm_structure *)mmap(NULL, sizeof(struct shm_structure), PROT_READ | PROT_WRITE, MAP_SHARED, clients[i].shm_fd, 0); #endif #ifdef CONFIG_FOR_LINUX clients[i].shared_memory = (struct shm_structure *)mmap(NULL, sizeof(struct shm_structure), PROT_READ | PROT_WRITE, MAP_LOCKED | MAP_SHARED, clients[i].shm_fd, 0); #endif if (clients[i].shared_memory == (struct shm_structure *)-1) { die("failed to mmap shared memory \"%s\".", client_shared_memory_interface_name); } if ((close(clients[i].shm_fd) == -1)) { warn("error closing \"%s\" after mapping.", client_shared_memory_interface_name); } // zero it memset(clients[i].shared_memory, 0, sizeof(struct shm_structure)); clients[i].shared_memory->version = NQPTP_SHM_STRUCTURES_VERSION; // 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 // } } else { debug(1, "could not create a client record for client \"%s\".", client_shared_memory_interface_name); } } } else { debug(1, "no client_shared_memory_interface_name"); } debug(2, "get_client_id \"%s\" response %d", client_shared_memory_interface_name, response); return response; } int delete_client(int client_id) { int response = 0; // okay unless something happens if (clients[client_id].shm_interface_name[0] != '\0') { if (clients[client_id].shared_memory != NULL) { // mmap cleanup if (munmap(clients[client_id].shared_memory, sizeof(struct shm_structure)) != 0) { debug(1, "error unmapping shared memory"); response = -1; } // shm_open cleanup if (shm_unlink(clients[client_id].shm_interface_name) == -1) { debug(1, "error unlinking shared memory \"%s\"", clients[client_id].shm_interface_name); response = -1; } } clients[client_id].shm_interface_name[0] = '\0'; // remove the name to signify it's vacant } return response; } int delete_clients() { int response = 0; // okay unless something happens int i; for (i = 0; i < MAX_CLIENTS; i++) if (delete_client(i) != 0) response = -1; return response; } int find_clock_source_record(char *sender_string, clock_source_private_data *clocks_private_info) { // return the index of the clock in the clock information arrays or -1 int response = -1; int i = 0; int found = 0; while ((found == 0) && (i < MAX_CLOCKS)) { if (((clocks_private_info[i].flags & (1 << clock_is_in_use)) != 0) && (strcasecmp(sender_string, (const char *)&clocks_private_info[i].ip) == 0)) found = 1; else i++; } if (found == 1) response = i; return response; } int create_clock_source_record(char *sender_string, clock_source_private_data *clocks_private_info) { // return the index of a clock entry in the clock information arrays or -1 if full // initialise the entries in the shared and private arrays int response = -1; int i = 0; int found = 0; // trying to find an unused entry while ((found == 0) && (i < MAX_CLOCKS)) { if ((clocks_private_info[i].flags & (1 << clock_is_in_use)) == 0) found = 1; else i++; } if (found == 1) { int family = 0; // check its ipv4/6 family -- derived from https://stackoverflow.com/a/3736377, with thanks. struct addrinfo hint, *res = NULL; memset(&hint, '\0', sizeof hint); hint.ai_family = PF_UNSPEC; hint.ai_flags = AI_NUMERICHOST; if (getaddrinfo(sender_string, NULL, &hint, &res) == 0) { family = res->ai_family; freeaddrinfo(res); response = i; memset(&clocks_private_info[i], 0, sizeof(clock_source_private_data)); strncpy((char *)&clocks_private_info[i].ip, sender_string, FIELD_SIZEOF(clock_source_private_data, ip) - 1); clocks_private_info[i].family = family; clocks_private_info[i].flags |= (1 << clock_is_in_use); debug(2, "create record for ip: %s, family: %s.", &clocks_private_info[i].ip, clocks_private_info[i].family == AF_INET6 ? "IPv6" : "IPv4"); } else { debug(1, "cannot getaddrinfo for ip: %s.", &clocks_private_info[i].ip); } } else { debug(1, "Clock tables full!"); } return response; } 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) { // 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) { 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->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(); } nqptp-1.2.6/nqptp-clock-sources.h000066400000000000000000000067031514334334200167570ustar00rootroot00000000000000/* * This file is part of the nqptp distribution (https://github.com/mikebrady/nqptp). * Copyright (c) 2021-2022 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 * the Free Software Foundation, version 2. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Commercial licensing is also available. */ #ifndef NQPTP_CLOCK_SOURCES_H #define NQPTP_CLOCK_SOURCES_H #include "nqptp-shm-structures.h" #include "nqptp.h" typedef enum { clock_is_in_use, clock_is_master } clock_flags; // information about each clock source typedef struct { char ip[64]; // 64 is nicely aligned and bigger than INET6_ADDRSTRLEN (46) int family; // AF_INET or AF_INET6 int follow_up_number; int announcements_without_followups; // add 1 for every announce, reset with a followup uint64_t clock_id; uint64_t previous_offset, previous_offset_time, previous_offset_grandmaster, previous_preciseOriginTimestamp; uint64_t mastership_start_time; // set to the time of the first sample used as master // for garbage collection uint64_t time_of_last_use; // will be taken out of use if not used for a while and not in the // timing peer group uint8_t flags; // stuff related specifically to the clock itself // these are for finding the best clock to use // See Figure 27 and 27 pp 89 -- 90 for the Data set comparison algorithm uint16_t clock_port_number; // used along with the clock_id as the portIdentity uint8_t grandmasterPriority1; uint32_t grandmasterQuality; // class/accuracy/variance -- lower is better uint8_t grandmasterClass; uint8_t grandmasterAccuracy; uint16_t grandmasterVariance; uint8_t grandmasterPriority2; uint64_t grandmasterIdentity; uint16_t stepsRemoved; int identical_previous_preciseOriginTimestamp_count; int wakeup_sent; } clock_source_private_data; // information on each client typedef struct { int shm_fd; struct shm_structure *shared_memory; // the client's individual smi interface char shm_interface_name[64]; // it's name int client_id; // the 1-based index number of clocks' client_flags field associated with this // interface } client_record; extern int shm_fd; extern struct shm_structure *shared_memory; int find_clock_source_record(char *sender_string, clock_source_private_data *clocks_private_info); int create_clock_source_record(char *sender_string, clock_source_private_data *clocks_private_info); void update_clock_self_identifications(clock_source_private_data *clocks_private_info); void manage_clock_sources(uint64_t reception_time, clock_source_private_data *clocks_private_info); int get_client_id(char *client_shared_memory_interface_name); const char *get_client_name(int client_id); int delete_client(int client_id); int delete_clients(); extern clock_source_private_data clocks_private[MAX_CLOCKS]; 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); #endif nqptp-1.2.6/nqptp-message-handlers.c000066400000000000000000000626601514334334200174240ustar00rootroot00000000000000/* * This file is part of the nqptp distribution (https://github.com/mikebrady/nqptp). * Copyright (c) 2021-2022 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 * the Free Software Foundation, version 2. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Commercial licensing is also available. */ #include // ntohl and ntohs #include //strsep #include // snprintf #include // drand48 #include // usleep #include "debug.h" #include "general-utilities.h" #include "nqptp-message-handlers.h" #include "nqptp-ptp-definitions.h" #include "nqptp-utilities.h" char hexcharbuffer[16384]; int reset_clock_smoothing = 0; uint64_t clock_validity_expiration_time = 0; int clock_is_active = 0; char *hex_string(void *buf, size_t buf_len) { char *tbuf = (char *)buf; char *obfp = hexcharbuffer; size_t obfc; for (obfc = 0; obfc < buf_len; obfc++) { snprintf(obfp, 3, "%02X", *tbuf); obfp += 2; tbuf = tbuf + 1; }; *obfp = 0; return hexcharbuffer; } void handle_control_port_messages(char *buf, ssize_t recv_len, clock_source_private_data *clock_private_info, uint64_t reception_time) { if (recv_len != -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 " already // "E" is for play end/stop. // "P" is for pause (currently Buffered Audio only). // // "T " 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 { 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; } // 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); } // 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); // } } } } else { warn("SMI Interface Name not found on the control port."); } } else { warn("Missing or empty packet on the control port."); } } else { warn("Bad packet on the control port."); } } void handle_announce(char *buf, ssize_t recv_len, clock_source_private_data *clock_private_info, __attribute__((unused)) uint64_t reception_time) { // debug_print_buffer(1, buf, (size_t) recv_len); // make way for the new time if ((size_t)recv_len >= sizeof(struct ptp_announce_message)) { struct ptp_announce_message *msg = (struct ptp_announce_message *)buf; uint64_t packet_clock_id = nctohl(&msg->header.clockIdentity[0]); uint64_t packet_clock_id_low = nctohl(&msg->header.clockIdentity[4]); packet_clock_id = packet_clock_id << 32; packet_clock_id = packet_clock_id + packet_clock_id_low; clock_private_info->clock_id = packet_clock_id; clock_private_info->grandmasterPriority1 = msg->announce.grandmasterPriority1; // need this for possibly pinging it later... clock_private_info->grandmasterPriority2 = msg->announce.grandmasterPriority2; // need this for possibly pinging it later... debug(2, "announcement seen from %" PRIx64 " at %s.", clock_private_info->clock_id, clock_private_info->ip); if (clock_private_info->announcements_without_followups < 5) // don't keep going forever // a value of 4 means it's parked -- // it has seen three, poked the clock and doesn't want to do any more. clock_private_info->announcements_without_followups++; uint64_t grandmaster_clock_id = nctohl(&msg->announce.grandmasterIdentity[0]); uint64_t grandmaster_clock_id_low = nctohl(&msg->announce.grandmasterIdentity[4]); grandmaster_clock_id = grandmaster_clock_id << 32; grandmaster_clock_id = grandmaster_clock_id + grandmaster_clock_id_low; uint32_t clockQuality = ntohl(msg->announce.grandmasterClockQuality); uint8_t clockClass = (clockQuality >> 24) & 0xff; uint8_t clockAccuracy = (clockQuality >> 16) & 0xff; uint16_t offsetScaledLogVariance = clockQuality & 0xffff; uint16_t stepsRemoved = ntohs(msg->announce.stepsRemoved); uint16_t sourcePortID = ntohs(msg->header.sourcePortID); clock_private_info->grandmasterIdentity = grandmaster_clock_id; clock_private_info->grandmasterPriority1 = msg->announce.grandmasterPriority1; clock_private_info->grandmasterQuality = clockQuality; clock_private_info->grandmasterClass = clockClass; clock_private_info->grandmasterAccuracy = clockAccuracy; clock_private_info->grandmasterVariance = offsetScaledLogVariance; clock_private_info->grandmasterPriority2 = msg->announce.grandmasterPriority2; clock_private_info->stepsRemoved = stepsRemoved; clock_private_info->clock_port_number = sourcePortID; if (clock_private_info->wakeup_sent == 0) { send_awakening_announcement_sequence( clock_private_info->clock_id, clock_private_info->ip, clock_private_info->family, clock_private_info->grandmasterPriority1, clock_private_info->grandmasterPriority2); clock_private_info->wakeup_sent = 1; } } } void handle_sync(char *buf, ssize_t recv_len, clock_source_private_data *clock_private_info, __attribute__((unused)) uint64_t reception_time) { /* // diagnostic -- decide whether to delay the processing of the follow_up to simulate a noisy network if (drand48() < 0.015) { // generate a random delay between 10 and 3500 milliseconds int delay = (int)((3000 - 60) * drand48()) + 60; debug(1,"Delay sync processing by %u milliseconds.", delay); usleep(delay * 1000); reception_time = get_time_now(); } */ if (clock_private_info->clock_id == 0) { debug(2, "Sync received before announcement -- discarded."); } else { if ((recv_len >= 0) && ((size_t)recv_len >= sizeof(struct ptp_sync_message))) { // debug_print_buffer(1, buf, recv_len); struct ptp_sync_message *msg = (struct ptp_sync_message *)buf; // clang-format off // actually the precision timestamp needs to be corrected by the Follow_Up Correction_Field contents. // According to IEEE Std 802.1AS-2020, paragraph 11.4.4.2.1: /* The value of the preciseOriginTimestamp field is the sourceTime of the ClockMaster entity of the Grandmaster PTP Instance, when the associated Sync message was sent by that Grandmaster PTP Instance, with any fractional nanoseconds truncated (see 10.2.9). The sum of the correctionFields in the Follow_Up and associated Sync messages, added to the preciseOriginTimestamp field of the Follow_Up message, is the value of the synchronized time corresponding to the syncEventEgressTimestamp at the PTP Instance that sent the associated Sync message, including any fractional nanoseconds. */ // clang-format on int64_t correction_field = ntoh64(msg->header.correctionField); if (correction_field != 0) debug(1, "Sync correction field is non-zero: %" PRId64 " ns.", correction_field); correction_field = correction_field / 65536; // might be signed } else { debug(1, "Sync message is too small to be valid."); } } } void handle_follow_up(char *buf, ssize_t recv_len, clock_source_private_data *clock_private_info, uint64_t reception_time) { if (clock_private_info->clock_id == 0) { debug(2, "Follow_Up received before announcement -- discarded."); } else { clock_private_info->announcements_without_followups = 0; if ((recv_len >= 0) && ((size_t)recv_len >= sizeof(struct ptp_follow_up_message))) { // debug_print_buffer(1, buf, recv_len); struct ptp_follow_up_message *msg = (struct ptp_follow_up_message *)buf; uint16_t seconds_hi = nctohs(&msg->follow_up.preciseOriginTimestamp[0]); uint32_t seconds_low = nctohl(&msg->follow_up.preciseOriginTimestamp[2]); uint32_t nanoseconds = nctohl(&msg->follow_up.preciseOriginTimestamp[6]); uint64_t preciseOriginTimestamp = seconds_hi; preciseOriginTimestamp = preciseOriginTimestamp << 32; preciseOriginTimestamp = preciseOriginTimestamp + seconds_low; preciseOriginTimestamp = preciseOriginTimestamp * 1000000000L; preciseOriginTimestamp = preciseOriginTimestamp + nanoseconds; // update our sample information int grandmasterClockIsStopped = 0; if ((clock_private_info->previous_preciseOriginTimestamp == preciseOriginTimestamp) && (clock_private_info->clock_id == clock_private_info->grandmasterIdentity)) { clock_private_info->identical_previous_preciseOriginTimestamp_count++; grandmasterClockIsStopped = 1; if (clock_private_info->identical_previous_preciseOriginTimestamp_count == 8 * 60) { int64_t duration_of_mastership = reception_time - clock_private_info->mastership_start_time; if (clock_private_info->mastership_start_time == 0) duration_of_mastership = 0; debug(2, "Clock %" PRIx64 "'s grandmaster clock has stopped after %f seconds of mastership.", clock_private_info->clock_id, 0.000000001 * duration_of_mastership); int64_t wait_limit = 62; wait_limit = wait_limit * 1000000000; // only try to restart a grandmaster clock on the clock itself. if ((duration_of_mastership <= wait_limit) && (clock_private_info->clock_id == clock_private_info->grandmasterIdentity)) { debug(2, "Attempt to start a stopped clock %" PRIx64 ", at follow_up_number %u at IP %s.", clock_private_info->clock_id, clock_private_info->follow_up_number, clock_private_info->ip); send_awakening_announcement_sequence( clock_private_info->clock_id, clock_private_info->ip, clock_private_info->family, clock_private_info->grandmasterPriority1, clock_private_info->grandmasterPriority2); } } } else { clock_private_info->identical_previous_preciseOriginTimestamp_count = 0; } clock_private_info->previous_preciseOriginTimestamp = preciseOriginTimestamp; // clang-format off // actually the precision timestamp needs to be corrected by the Follow_Up Correction_Field contents. // According to IEEE Std 802.1AS-2020, paragraph 11.4.4.2.1: /* The value of the preciseOriginTimestamp field is the sourceTime of the ClockMaster entity of the Grandmaster PTP Instance, when the associated Sync message was sent by that Grandmaster PTP Instance, with any fractional nanoseconds truncated (see 10.2.9). The sum of the correctionFields in the Follow_Up and associated Sync messages, added to the preciseOriginTimestamp field of the Follow_Up message, is the value of the synchronized time corresponding to the syncEventEgressTimestamp at the PTP Instance that sent the associated Sync message, including any fractional nanoseconds. */ // clang-format on int64_t correction_field = ntoh64(msg->header.correctionField); // debug(1," Check ntoh64: in: %" PRIx64 ", out: %" PRIx64 ".", msg->header.correctionField, // correction_field); correction_field = correction_field / 65536; // might be signed uint64_t correctedPreciseOriginTimestamp = preciseOriginTimestamp + correction_field; if (clock_private_info->follow_up_number < 100) clock_private_info->follow_up_number++; // if (clock_private_info->announcements_without_followups < 4) // if we haven't signalled // already clock_private_info->announcements_without_followups = 0; // we've seen a followup debug(2, "FOLLOWUP from %" PRIx64 ", %s.", clock_private_info->clock_id, &clock_private_info->ip); uint64_t offset = correctedPreciseOriginTimestamp - reception_time; int64_t jitter = 0; int64_t time_since_previous_offset = 0; uint64_t smoothed_offset = offset; // This is a bit hacky. // Basically, the idea is that if the grandmaster has changed, then acceptance checking and // smoothing should start as it it's a new clock. This is because the // correctedPreciseOriginTimestamp, which is part of the data that is being smoothed, refers // to the grandmaster, so when the grandmaster changes any previous calculations are no // longer valid. The hacky bit is to signal this condition by zeroing the // previous_offset_time. if (clock_private_info->previous_offset_grandmaster != clock_private_info->grandmasterIdentity) { clock_private_info->previous_offset_time = 0; if (clock_private_info->previous_offset_grandmaster == 0) debug(1, "grandmaster is %" PRIx64 ".", clock_private_info->grandmasterIdentity); else debug(1, "grandmaster has changed from %" PRIx64 " to %" PRIx64 ".", clock_private_info->previous_offset_grandmaster, clock_private_info->grandmasterIdentity); } // Do acceptance checking and smoothing. // Positive changes in the offset are much more likely to be // legitimate, since they could only occur due to a shorter // propagation time or less of a delay sending or receiving the packet. // When the clock is new, we give preferential weighting to // positive changes in the offset to allow the clock to sync up quickly. // If the new offset is greater, by any amount, than the old offset, // or if it is less by up to the clamping_limit, accept it. // This seems to be quite stable if (reset_clock_smoothing == 0) { if (clock_private_info->previous_offset_time != 0) { time_since_previous_offset = reception_time - clock_private_info->previous_offset_time; jitter = offset - clock_private_info->previous_offset; } // We take any positive or a limited negative jitter as a sync event in // a continuous synchronisation sequence. // The full value of a positive offset jitter is accepted for a // number of follow_ups at the start. // After that, the weight of the jitter is reduced. // Follow-ups don't always come in at 125 ms intervals, especially after a discontinuity // Delays makes the offsets smaller than they should be, which is quickly // allowed for. const int64_t clamping_limit = -2500000; // nanoseconds int64_t mastership_time = reception_time - clock_private_info->mastership_start_time; if (clock_private_info->mastership_start_time == 0) mastership_time = 0; // if ((clock_private_info->previous_offset_time != 0) && // (clock_private_info->identical_previous_preciseOriginTimestamp_count <= 1)) { if (clock_private_info->previous_offset_time != 0) { if (jitter < 0) { int64_t clamped_jitter = jitter; if (clamped_jitter < clamping_limit) { clamped_jitter = clamping_limit; // 0 means ignore a clamped value completely } // if (mastership_time < 1000000000) // at the beginning, if jitter is negative // smoothed_offset = clock_private_info->previous_offset + clamped_jitter / 16; // else // ignore negative jitter at first... 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 smoothed_offset = clock_private_info->previous_offset + jitter / 1; // at the beginning, if jitter is positive -- accept positive changes quickly } else { smoothed_offset = clock_private_info->previous_offset + jitter / 16; // later, if jitter is positive } } else { if (clock_private_info->previous_offset_time == 0) debug(2, "Clock %" PRIx64 " record (re)starting at %s.", clock_private_info->clock_id, clock_private_info->ip); else debug(2, "Timing discontinuity on clock %" PRIx64 " at %s: time_since_previous_offset: %.3f seconds%s.", clock_private_info->clock_id, clock_private_info->ip, 0.000000001 * time_since_previous_offset, grandmasterClockIsStopped != 0 ? ", grandmaster clock stopped" : ""); smoothed_offset = offset; // clock_private_info->follow_up_number = 0; clock_private_info->mastership_start_time = reception_time; // mastership is reset to this time... } int64_t delta = smoothed_offset - offset; debug(2, "Clock %" PRIx64 ", grandmaster %" PRIx64 ". Offset: %" PRIx64 ", smoothed offset: %" PRIx64 ". Smoothed Offset - Offset: %10.3f. Raw Precise Origin Timestamp: %" PRIx64 "%s correction_field: %" PRIx64 ". Time since previous offset: %8.3f milliseconds. ID: %5u, Follow_Up Number: " "%u. Source: %s", clock_private_info->clock_id, clock_private_info->grandmasterIdentity, offset, smoothed_offset, 0.000001 * delta, preciseOriginTimestamp, clock_is_active != 0 ? ". " : "*.", correction_field, 0.000001 * time_since_previous_offset, ntohs(msg->header.sequenceId), clock_private_info->follow_up_number, clock_private_info->ip); if (clock_is_active) { update_master_clock_info(clock_private_info->grandmasterIdentity, (const char *)&clock_private_info->ip, reception_time, smoothed_offset, clock_private_info->mastership_start_time); } else { update_master_clock_info(0, NULL, 0, 0, 0); // the SMI may have obsolete stuff in it } clock_private_info->previous_offset = smoothed_offset; clock_private_info->previous_offset_time = reception_time; } else { reset_clock_smoothing = 0; clock_private_info->mastership_start_time = 0; clock_private_info->previous_offset = 0; clock_private_info->previous_offset_time = 0; // so that the first non-stopped sample will be taken as the first one in a sequence } clock_private_info->previous_offset_grandmaster = clock_private_info->grandmasterIdentity; // now do some quick calculations on the possible "Universal Time" // debug_print_buffer(1, "", buf, recv_len); uint8_t *tlv = (uint8_t *)&msg->follow_up.tlvs[0]; uint8_t *lastGmPhaseChange = tlv + 16; uint64_t lpt = nctoh64(lastGmPhaseChange + 4); uint64_t last_tlv_clock = nctoh64((uint8_t *)buf + 86); uint64_t huh = offset - lpt; debug_print_buffer(2, buf, (size_t)recv_len); debug(2, "%" PRIx64 ", %" PRIx64 ", %s, Origin: %016" PRIx64 ", LPT: %016" PRIx64 ", Offset: %016" PRIx64 ", Universal Offset: %016" PRIx64 ", packet length: %u.", clock_private_info->clock_id, last_tlv_clock, hex_string(lastGmPhaseChange, 12), preciseOriginTimestamp, lpt, offset, huh, recv_len); // debug(1,"Clock: %" PRIx64 ", UT: %016" PRIx64 ", correctedPOT: %016" PRIx64 ", part of // lastGMPhaseChange: %016" PRIx64 ".", packet_clock_id, correctedPOT - lpt, correctedPOT, // lpt); } else { debug(1, "Follow_Up message is too small to be valid."); } } } nqptp-1.2.6/nqptp-message-handlers.h000066400000000000000000000030241514334334200174160ustar00rootroot00000000000000/* * This file is part of the nqptp distribution (https://github.com/mikebrady/nqptp). * Copyright (c) 2021-2022 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 * the Free Software Foundation, version 2. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Commercial licensing is also available. */ #ifndef NQPTP_MESSAGE_HANDLERS_H #define NQPTP_MESSAGE_HANDLERS_H #include "general-utilities.h" #include "nqptp-clock-sources.h" #include void handle_announce(char *buf, ssize_t recv_len, clock_source_private_data *clock_private_info, uint64_t reception_time); void handle_sync(char *buf, ssize_t recv_len, clock_source_private_data *clock_private_info, uint64_t reception_time); void handle_follow_up(char *buf, ssize_t recv_len, clock_source_private_data *clock_private_info, uint64_t reception_time); void handle_control_port_messages(char *buf, ssize_t recv_len, clock_source_private_data *clock_private_info, uint64_t reception_time); #endifnqptp-1.2.6/nqptp-ptp-definitions.h000066400000000000000000000170271514334334200173200ustar00rootroot00000000000000/* * This file is part of the nqptp distribution (https://github.com/mikebrady/nqptp). * Copyright (c) 2021-2022 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 * the Free Software Foundation, version 2. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Commercial licensing is also available. */ #ifndef NQPTP_PTP_DEFINITIONS_H #define NQPTP_PTP_DEFINITIONS_H #include // This is for definitions and stuff that flows more or less directly // from external sources. // They may not be used. Yet. // Derived from // https://github.com/rroussel/OpenAvnu/blob/ArtAndLogic-aPTP-changes/daemons/gptp/gptp_cfg.ini: #define aPTPpriority1 248 #define aPTPpriority2 248 #define aPTPaccuracy 254 // "Per the Apple Vendor PTP profile" // these seem to be log2 of seconds, thus 0 is 2^0 or 1 sec, -3 to 2^-3 or 0.125 sec // see 7.7.7.2 #define aPTPinitialLogAnnounceInterval 0 // see 7.7.2.3 #define aPTPinitialLogSyncInterval -3 // This doesn't seem to be used in OpenAvnu // but see 7.7.3.1, so it looks like they are units of the announceInterval, so seconds here #define aPTPannounceReceiptTimeout 120 // "Per the Apple Vendor PTP profile (8*announceReceiptTimeout)" // This doesn't seem to be used in OpenAvnu // Guess it's the same idea, but based on aPTPinitialLogSyncInterval // but it could be based on aPTPinitialLogAnnounceInterval, of course. #define aPTPsyncReceiptTimeout 960 // "Neighbor propagation delay threshold in nanoseconds" #define aPTPneighborPropDelayThresh 800 // "Sync Receipt Threshold // This value defines the number of syncs with wrong seqID that will trigger // the ptp slave to become master (it will start announcing) // Normally sync messages are sent every 125ms, so setting it to 8 will allow // up to 1 second of wrong messages before switching" #define aPTPsyncReceiptThresh 8 // References from the IEEE Document ISBN 978-0-7381-5400-8 STD95773. // "IEEE Standard for a Precision Clock Synchronization Protocol for Networked Measurement and // Control Systems" The IEEE Std 1588-2008 (Revision of IEEE Std 1588-2002) // See 9.3.2.4.4 FOREIGN_MASTER_TIME_WINDOW and FOREIGN_MASTER_THRESHOLD // units are the announceInterval #define FOREIGN_MASTER_TIME_WINDOW 4 #define FOREIGN_MASTER_THRESHOLD 2 // See also 9.3.2.5 Qualification of Announce messages // Table 19 enum messageType { Sync, Delay_Req, Pdelay_Req, Pdelay_Resp, Reserved_4, Reserved_5, Reserved_6, Reserved_7, Follow_Up, Delay_Resp, Pdelay_Resp_Follow_Up, Announce, Signaling, Management, Reserved_E, Reserved_F }; // Table 34, part of enum tlvTypeValue { Reserved, // Standard TLVs MANAGEMENT, MANAGEMENT_ERROR_STATUS, ORGANIZATION_EXTENSION, // Optional unicast message negotiation TLVs REQUEST_UNICAST_TRANSMISSION, GRANT_UNICAST_TRANSMISSION, CANCEL_UNICAST_TRANSMISSION, ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION, // Optional path trace mechanism TLV PATH_TRACE, // Optional alternate timescale TLV ALTERNATE_TIME_OFFSET_INDICATOR // there are more, but not needed yet }; // Table 23 enum controlFieldValue { Control_Field_Value_Sync, Control_Field_Value_Delay_Req, Control_Field_Value_Follow_Up, Control_Field_Value_Delay_Resp, Control_Field_Value_Management, Control_Field_Value_Other }; // this is the structure of a PATH_TRACE TLV (16.2, Table 78, pp 164) without any space for the data struct __attribute__((__packed__)) ptp_path_trace_tlv { uint16_t tlvType; uint16_t lengthField; uint8_t pathSequence[0]; }; // this is the structure of a TLV (14.3, Table 35, pp 135) without any space for the data struct __attribute__((__packed__)) ptp_tlv { uint16_t tlvType; uint16_t lengthField; uint8_t organizationId[3]; uint8_t organizationSubType[3]; uint8_t dataField[0]; }; // this is the Common Message Header struct __attribute__((__packed__)) ptp_common_message_header { uint8_t transportSpecificAndMessageID; // 0x11 uint8_t reservedAndVersionPTP; // 0x02 uint16_t messageLength; uint8_t domainNumber; // 0 uint8_t reserved_b; // 0 uint16_t flags; // 0x0608 uint64_t correctionField; // 0 uint32_t reserved_l; // 0 uint8_t clockIdentity[8]; // MAC uint16_t sourcePortID; // 1 uint16_t sequenceId; // increments uint8_t controlField; // 5 uint8_t logMessagePeriod; // 0 }; // this is the extra part for an Announce message struct __attribute__((__packed__)) ptp_announce { uint8_t originTimestamp[10]; uint16_t currentUtcOffset; uint8_t reserved1; uint8_t grandmasterPriority1; uint32_t grandmasterClockQuality; uint8_t grandmasterPriority2; uint8_t grandmasterIdentity[8]; uint16_t stepsRemoved; uint8_t timeSource; struct ptp_path_trace_tlv path_trace[0]; }; // this is the extra part for a Sync or Delay_Req message struct __attribute__((__packed__)) ptp_sync { uint8_t originTimestamp[10]; }; // this is the extra part for a Sync or Delay_Req message struct __attribute__((__packed__)) ptp_delay_req { uint8_t originTimestamp[10]; }; // this is the extra part for a Follow_Up message struct __attribute__((__packed__)) ptp_follow_up { uint8_t preciseOriginTimestamp[10]; // to be followed by zero or more TLVs struct ptp_tlv tlvs[0]; }; // this is the extra part for a Delay_Resp message struct __attribute__((__packed__)) ptp_delay_resp { uint8_t receiveTimestamp[10]; uint8_t requestingPortIdentity[10]; }; // this is the extra part for a Pdelay_Req message (13.9, pp 131) struct __attribute__((__packed__)) ptp_pdelay_req { uint8_t originTimestamp[10]; uint8_t reserved[10]; // to make it the same length as a Pdelay_Resp message }; // this is the extra part for a Pdelay_Resp message (13.10, pp 131) struct __attribute__((__packed__)) ptp_pdelay_resp { uint8_t requestReceiptTimestamp[10]; uint8_t requestingPortIdentity[10]; }; // this is the extra part for a Signaling message (13.12, pp 132) without any TLVs struct __attribute__((__packed__)) ptp_signaling { uint8_t targetPortIdentity[10]; // to be followed by _one_ or more TLVs struct ptp_tlv tlvs[0]; }; struct __attribute__((__packed__)) ptp_pdelay_req_message { struct ptp_common_message_header header; struct ptp_pdelay_req pdelay_req; }; struct __attribute__((__packed__)) ptp_pdelay_resp_message { struct ptp_common_message_header header; struct ptp_pdelay_resp pdelay_resp; }; struct __attribute__((__packed__)) ptp_sync_message { struct ptp_common_message_header header; struct ptp_sync sync; }; struct __attribute__((__packed__)) ptp_delay_req_message { struct ptp_common_message_header header; struct ptp_delay_req delay_req; }; struct __attribute__((__packed__)) ptp_follow_up_message { struct ptp_common_message_header header; struct ptp_follow_up follow_up; }; struct __attribute__((__packed__)) ptp_delay_resp_message { struct ptp_common_message_header header; struct ptp_delay_resp delay_resp; }; struct __attribute__((__packed__)) ptp_announce_message { struct ptp_common_message_header header; struct ptp_announce announce; }; struct __attribute__((__packed__)) ptp_signaling_message { struct ptp_common_message_header header; struct ptp_signaling signaling; }; #endifnqptp-1.2.6/nqptp-shm-structures.h000066400000000000000000000070431514334334200172110ustar00rootroot00000000000000/* * This file is part of the nqptp distribution (https://github.com/mikebrady/nqptp). * 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 * the Free Software Foundation, version 2. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Commercial licensing is also available. */ #ifndef NQPTP_SHM_STRUCTURES_H #define NQPTP_SHM_STRUCTURES_H #define NQPTP_INTERFACE_NAME "/nqptp" #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 // and the rest being any arguments, the whole not to exceed 4096 characters. // The "T" command, must followed by nothing or by // a space and a space-delimited list of IPv4 or IPv6 numbers. // The IPs, if provided, will become the new list of timing peers, replacing any // previous list. The first IP number is the clock that NQPTP will listen to. // The remaining IP address are the addresses of all the timing peers. The timing peers // are not used in this version of NQPTP. // If no timing list is provided, the existing timing list is deleted. // The "B" command is a message that the client -- which generates the clock -- // is about to start playing. // NQPTP uses it to determine that the clock is active and will not sleep. // The "E" command signifies that the client has stopped playing and that // the clock may shortly sleep. // The "P" command signifies that SPS has paused play (buffered audio only). // The clock seems to stay running in this state. // When the clock is active, it is assumed that any decreases in the offset // between the local and remote clocks are due to delays in the network. // NQPTP smooths the offset by clamping any decreases to a small value. // In this way, it can follow clock drift but ignore network delays. // 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. #include #include typedef struct { uint64_t master_clock_id; // the current master clock 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 nqptp-1.2.6/nqptp-utilities.c000066400000000000000000000170061514334334200162070ustar00rootroot00000000000000/* * This file is part of the nqptp distribution (https://github.com/mikebrady/nqptp). * Copyright (c) 2021-2022 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 * the Free Software Foundation, version 2. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Commercial licensing is also available. */ #include "nqptp-utilities.h" #include "general-utilities.h" #include #include // fcntl etc. #include // getifaddrs #include #ifdef CONFIG_FOR_LINUX #include // sockaddr_ll #endif #if defined(CONFIG_FOR_FREEBSD) || defined(CONFIG_FOR_OPENBSD) #include #include #include #include #include #endif #include // getaddrinfo etc. #include // snprintf #include // malloc, free #include // memset strcpy, etc. #include "debug.h" void open_sockets_at_port(const char *node, uint16_t port, sockets_open_bundle *sockets_open_stuff) { // open up sockets for UDP ports 319 and 320 // will try IPv6 and IPv4 if IPV6_V6ONLY is not defined struct addrinfo hints, *info, *p; int ret; int sockets_opened = 0; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_PASSIVE; char portstr[20]; snprintf(portstr, 20, "%d", port); ret = getaddrinfo(node, portstr, &hints, &info); if (ret) { die("getifaddrs: %s", gai_strerror(ret)); } for (p = info; p; p = p->ai_next) { ret = 0; int fd = socket(p->ai_family, p->ai_socktype, IPPROTO_UDP); int yes = 1; // Handle socket open failures if protocol unavailable (or IPV6 not handled) if (fd != -1) { #ifdef IPV6_V6ONLY // some systems don't support v4 access on v6 sockets, but some do. // since we need to account for two sockets we might as well // always. if (p->ai_family == AF_INET6) { ret |= setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)); } #endif if (!ret) ret = bind(fd, p->ai_addr, p->ai_addrlen); int flags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, flags | O_NONBLOCK); // one of the address families will fail on some systems that // report its availability. Do not complain. if (ret == 0) { // debug(1, "socket %d is listening on %s port %d.", fd, // p->ai_family == AF_INET6 ? "IPv6" : "IPv4", port); sockets_open_stuff->sockets[sockets_open_stuff->sockets_open].number = fd; sockets_open_stuff->sockets[sockets_open_stuff->sockets_open].port = port; sockets_open_stuff->sockets[sockets_open_stuff->sockets_open].family = p->ai_family; sockets_open_stuff->sockets_open++; sockets_opened++; } } } freeaddrinfo(info); if (sockets_opened == 0) { 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) start as root.", port); } else { die("nqptp is unable to listen on port %u. The error is: %d, \"%s\".", port, errno, strerror(errno)); } } } void debug_print_buffer(int level, char *buf, size_t buf_len) { if (debug_level() >= level) { // printf("Received %u bytes in a packet from %s:%d\n", buf_len, inet_ntoa(si_other.sin_addr), // ntohs(si_other.sin_port)); char *obf = malloc(buf_len * 4 + 1); // to be on the safe side -- 4 characters on average for each byte if (obf != NULL) { char *obfp = obf; unsigned int obfc; for (obfc = 0; obfc < buf_len; obfc++) { snprintf(obfp, 3, "%02X", buf[obfc]); obfp += 2; if (obfc != buf_len - 1) { if (obfc % 32 == 31) { snprintf(obfp, 5, " || "); obfp += 4; } else if (obfc % 16 == 15) { snprintf(obfp, 4, " | "); obfp += 3; } else if (obfc % 4 == 3) { snprintf(obfp, 2, " "); obfp += 1; } } }; *obfp = 0; switch (buf[0]) { case 0x10: debug(level, "SYNC: \"%s\".", obf); break; case 0x18: debug(level, "FLUP: \"%s\".", obf); break; case 0x19: debug(level, "DRSP: \"%s\".", obf); break; case 0x1B: debug(level, "ANNC: \"%s\".", obf); break; case 0x1C: debug(level, "SGNL: \"%s\".", obf); break; default: debug(1, "XXXX \"%s\".", obf); // output this at level 1 break; } free(obf); } } } // pass in an array of bytes and a max_length, or a max length of 0 for unlimited // the actual size will be returned int get_device_id(uint8_t *id, int *int_length) { int max_length = *int_length; int response = -1; struct ifaddrs *ifaddr = NULL; struct ifaddrs *ifa = NULL; int i = 0; uint8_t *t = id; // clear the buffer if non zero length passed in for (i = 0; i < max_length; i++) { *t++ = 0; } // look for a useful MAC address if (getifaddrs(&ifaddr) != -1) { t = id; int found = 0; for (ifa = ifaddr; ((ifa != NULL) && (found == 0)); ifa = ifa->ifa_next) { #ifdef AF_PACKET if ((ifa->ifa_addr) && (ifa->ifa_addr->sa_family == AF_PACKET)) { struct sockaddr_ll *s = (struct sockaddr_ll *)ifa->ifa_addr; if ((strcmp(ifa->ifa_name, "lo") != 0)) { found = 1; if ((max_length == 0) || (s->sll_halen < max_length)) { max_length = s->sll_halen; *int_length = max_length; } for (i = 0; i < max_length; i++) { *t++ = s->sll_addr[i]; } } } #else #ifdef AF_LINK struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifa->ifa_addr; if ((sdl) && (sdl->sdl_family == AF_LINK)) { if (sdl->sdl_type == IFT_ETHER) { found = 1; if ((max_length == 0) || (sdl->sdl_alen < max_length)) { max_length = sdl->sdl_alen; *int_length = max_length; } uint8_t *s = (uint8_t *)LLADDR(sdl); for (i = 0; i < max_length; i++) { *t++ = *s++; } } } #endif #endif } if (found != 0) response = 0; freeifaddrs(ifaddr); } return response; } uint64_t get_self_clock_id() { // make up a clock ID based on an interface's MAC int local_clock_id_size = 8; // don't exceed this uint8_t local_clock_id[local_clock_id_size]; memset(local_clock_id, 0, local_clock_id_size); if (get_device_id(local_clock_id, &local_clock_id_size) == 0) { // if the length of the MAC address is 6 we need to doctor it a little // See Section 7.5.2.2.2 IEEE EUI-64 clockIdentity values, NOTE 2 if (local_clock_id_size == 6) { // i.e. an EUI-48 MAC Address local_clock_id[7] = local_clock_id[5]; local_clock_id[6] = local_clock_id[4]; local_clock_id[5] = local_clock_id[3]; local_clock_id[3] = 0xFF; local_clock_id[4] = 0xFE; } } // convert to host byte order return nctoh64(local_clock_id); } nqptp-1.2.6/nqptp-utilities.h000066400000000000000000000027221514334334200162130ustar00rootroot00000000000000/* * This file is part of the nqptp distribution (https://github.com/mikebrady/nqptp). * Copyright (c) 2021-2022 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 * the Free Software Foundation, version 2. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Commercial licensing is also available. */ #ifndef NQPTP_UTILITIES_H #define NQPTP_UTILITIES_H #include "nqptp.h" #include #include // functions that are specific to NQPTP // general stuff should go in the general-utilities typedef struct { int number; uint16_t port; int family; // AF_INET or AF_INET6 } socket_info; typedef struct { unsigned int sockets_open; // also doubles as where to put next one, as sockets are never closed. socket_info sockets[MAX_OPEN_SOCKETS]; } sockets_open_bundle; void open_sockets_at_port(const char *node, uint16_t port, sockets_open_bundle *sockets_open_stuff); void debug_print_buffer(int level, char *buf, size_t buf_len); uint64_t get_self_clock_id(); // a clock ID based on a MAC address #endifnqptp-1.2.6/nqptp.c000066400000000000000000000503561514334334200142030ustar00rootroot00000000000000/* * This file is part of the nqptp distribution (https://github.com/mikebrady/nqptp). * Copyright (c) 2021-2022 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 * the Free Software Foundation, version 2. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Commercial licensing is also available. */ #include "nqptp.h" #include "config.h" #include "debug.h" #include "general-utilities.h" #include "nqptp-clock-sources.h" #include "nqptp-message-handlers.h" #include "nqptp-ptp-definitions.h" #include "nqptp-utilities.h" #ifdef CONFIG_USE_GIT_VERSION_STRING #include "gitversion.h" #endif #include // inet_ntop #include // fprint #include // malloc; #include // memset #include #include // close #include /* For O_* constants */ #include // for shared memory stuff #include // for fd_set #include // umask #include // for timeval #include // SIGTERM and stuff like that #include #include #if defined(CONFIG_FOR_FREEBSD) || defined(CONFIG_FOR_OPENBSD) #include #include #endif #ifdef CONFIG_FOR_OPENBSD #include #include #include #endif #ifndef FIELD_SIZEOF #define FIELD_SIZEOF(t, f) (sizeof(((t *)0)->f)) #endif // 8 samples per second #define BUFLEN 4096 // Max length of buffer sockets_open_bundle sockets_open_stuff; typedef struct { uint64_t trigger_time; uint64_t (*task)(uint64_t nominal_call_time, void *private_data); void *private_data; } timed_task_t; #define TIMED_TASKS 1 timed_task_t timed_tasks[TIMED_TASKS]; /* uint64_t sample_task(uint64_t call_time, __attribute__((unused)) void *private_data) { debug(1,"sample_task called."); uint64_t next_time = call_time; next_time = next_time + 1000000000; return next_time; } */ int epoll_fd; void goodbye(void) { // close any open sockets unsigned int i; for (i = 0; i < sockets_open_stuff.sockets_open; i++) close(sockets_open_stuff.sockets[i].number); // close off shared memory interface delete_clients(); // 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)); } // shm_open cleanup if (shm_unlink(NQPTP_INTERFACE_NAME) == -1) { debug(1, "error unlinking shared memory \"%s\": \"%s\".", NQPTP_INTERFACE_NAME, strerror(errno)); } if (shm_fd != -1) close(shm_fd); if (epoll_fd != -1) close(epoll_fd); debug(1, "goodbye"); } void intHandler(__attribute__((unused)) int k) { debug(1, "exit on SIGINT"); exit(EXIT_SUCCESS); } void termHandler(__attribute__((unused)) int k) { debug(1, "exit on SIGTERM"); exit(EXIT_SUCCESS); } int main(int argc, char **argv) { #ifdef CONFIG_FOR_OPENBSD if (pledge("stdio rpath tmppath inet dns id", NULL) == -1) { die("pledge: %s", strerror(errno)); } #endif int debug_level = 0; int i; for (i = 1; i < argc; ++i) { if (argv[i][0] == '-') { 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); else #endif fprintf(stdout, "Version: %s. Shared Memory Interface Version: smi%u.\n", VERSION, NQPTP_SHM_STRUCTURES_VERSION); exit(EXIT_SUCCESS); } else if (strcmp(argv[i] + 1, "vvv") == 0) { debug_level = 3; } else if (strcmp(argv[i] + 1, "vv") == 0) { debug_level = 2; } else if (strcmp(argv[i] + 1, "v") == 0) { debug_level = 1; } else if (strcmp(argv[i] + 1, "h") == 0) { fprintf(stdout, " -V print version,\n" " -v verbose log,\n" " -vv more verbose log,\n" " -vvv very verbose log,\n" " -h this help text.\n"); exit(EXIT_SUCCESS); } else { fprintf(stdout, "%s -- unknown option. Program terminated.\n", argv[0]); exit(EXIT_FAILURE); } } } debug_init(debug_level, 0, 1, 1); #ifdef CONFIG_USE_GIT_VERSION_STRING if (git_version_string[0] != '\0') debug(1, "Version: %s, smi%u. Clock ID: \"%" PRIx64 "\".", git_version_string, NQPTP_SHM_STRUCTURES_VERSION, get_self_clock_id()); else #endif debug(1, "Version: %s, smi%u. Clock ID: \"%" PRIx64 "\".", VERSION, NQPTP_SHM_STRUCTURES_VERSION, get_self_clock_id()); // debug(1, "size of a clock entry is %u bytes.", sizeof(clock_source_private_data)); atexit(goodbye); // try to set a real time scheduling policy with a priority of -6 int policy = SCHED_FIFO; struct sched_param param; param.sched_priority = 5; int s = pthread_setschedparam(pthread_self(), policy, ¶m); if (s != 0) debug(1, "pthread_setschedparam failed: %d -- \"%s\".", s, strerror(s)); sockets_open_stuff.sockets_open = 0; // open PTP sockets open_sockets_at_port(NULL, 319, &sockets_open_stuff); open_sockets_at_port(NULL, 320, &sockets_open_stuff); epoll_fd = -1; // control-c (SIGINT) cleanly struct sigaction act; memset(&act, 0, sizeof(struct sigaction)); act.sa_handler = intHandler; sigaction(SIGINT, &act, NULL); // terminate (SIGTERM) struct sigaction act2; memset(&act2, 0, sizeof(struct sigaction)); act2.sa_handler = termHandler; sigaction(SIGTERM, &act2, NULL); #ifdef CONFIG_FOR_OPENBSD // shm_open(3) prohibits sharing between different UIDs, so nqptp must run as // the same user shairport-sync does. struct passwd *pw; const char *shairport_user = "_shairport"; pw = getpwnam(shairport_user); if (pw == NULL) { die("unknown user %s", shairport_user); } if (setgroups(1, &pw->pw_gid) == -1 || setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 || setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1) { die("cannot drop privileges to %s", shairport_user); } if (pledge("stdio tmppath inet dns", NULL) == -1) { die("pledge: %s", strerror(errno)); } #endif // open the SMI shm_fd = -1; mode_t oldumask = umask(0); shm_fd = shm_open(NQPTP_INTERFACE_NAME, O_RDWR | O_CREAT, 0644); if (shm_fd == -1) { 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); if (ftruncate(shm_fd, sizeof(struct shm_structure)) == -1) { die("failed to set size of shared memory \"%s\".", NQPTP_INTERFACE_NAME); } #if defined(CONFIG_FOR_FREEBSD) || defined(CONFIG_FOR_OPENBSD) shared_memory = (struct shm_structure *)mmap(NULL, sizeof(struct shm_structure), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); #endif #ifdef CONFIG_FOR_LINUX shared_memory = (struct shm_structure *)mmap(NULL, sizeof(struct shm_structure), PROT_READ | PROT_WRITE, MAP_LOCKED | MAP_SHARED, shm_fd, 0); #endif if (shared_memory == (struct shm_structure *)-1) { die("failed to mmap shared memory \"%s\".", NQPTP_INTERFACE_NAME); } if (shm_fd == -1) { warn("error closing \"%s\" after mapping.", shm_fd); } // zero it memset(shared_memory, 0, sizeof(struct shm_structure)); shared_memory->version = NQPTP_SHM_STRUCTURES_VERSION; ssize_t recv_len; char buf[BUFLEN]; // open control socket open_sockets_at_port("localhost", NQPTP_CONTROL_PORT, &sockets_open_stuff); // this for messages from the client // start the timed tasks uint64_t broadcasting_task(uint64_t call_time, void *private_data); timed_tasks[0].trigger_time = get_time_now() + 100000000; // start after 100 ms timed_tasks[0].private_data = (void *)&clocks_private; timed_tasks[0].task = broadcasting_task; // now, get down to business if (sockets_open_stuff.sockets_open > 0) { while (1) { fd_set readSockSet; struct timeval timeout; FD_ZERO(&readSockSet); int smax = -1; unsigned int s; for (s = 0; s < sockets_open_stuff.sockets_open; s++) { if (sockets_open_stuff.sockets[s].number > smax) smax = sockets_open_stuff.sockets[s].number; FD_SET(sockets_open_stuff.sockets[s].number, &readSockSet); } timeout.tv_sec = 0; timeout.tv_usec = 10000; // timeout after ten milliseconds int retval = select(smax + 1, &readSockSet, NULL, NULL, &timeout); uint64_t reception_time = get_time_now(); // use this if other methods fail if (retval > 0) { unsigned t; for (t = 0; t < sockets_open_stuff.sockets_open; t++) { int socket_number = sockets_open_stuff.sockets[t].number; if (FD_ISSET(socket_number, &readSockSet)) { SOCKADDR from_sock_addr; memset(&from_sock_addr, 0, sizeof(SOCKADDR)); struct { struct cmsghdr cm; char control[512]; } control; struct msghdr msg; struct iovec iov[1]; memset(iov, 0, sizeof(iov)); memset(&msg, 0, sizeof(msg)); memset(&control, 0, sizeof(control)); iov[0].iov_base = buf; iov[0].iov_len = BUFLEN; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_name = &from_sock_addr; msg.msg_namelen = sizeof(from_sock_addr); msg.msg_control = &control; msg.msg_controllen = sizeof(control); uint16_t receiver_port = 0; // int msgsize = recv(udpsocket_fd, &msg_buffer, 4, 0); recv_len = recvmsg(socket_number, &msg, MSG_DONTWAIT); if (recv_len != -1) { // get the receiver port unsigned int jp; for (jp = 0; jp < sockets_open_stuff.sockets_open; jp++) { if (socket_number == sockets_open_stuff.sockets[jp].number) receiver_port = sockets_open_stuff.sockets[jp].port; } } if (recv_len == -1) { if (errno == EAGAIN) { usleep(1000); // this can happen, it seems... } else { debug(1, "recvmsg() error %d", errno); } // check if it's a control port message before checking for the length of the // message. } else if (receiver_port == NQPTP_CONTROL_PORT) { handle_control_port_messages( buf, recv_len, (clock_source_private_data *)&clocks_private, reception_time); } else if (recv_len >= (ssize_t)sizeof(struct ptp_common_message_header)) { debug_print_buffer(2, buf, recv_len); // check its credentials // the sending and receiving ports must be the same (i.e. 319 -> 319 or 320 -> 320) // initialise the connection info void *sender_addr = NULL; uint16_t sender_port = 0; sa_family_t connection_ip_family = from_sock_addr.SAFAMILY; #ifdef AF_INET6 if (connection_ip_family == AF_INET6) { struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&from_sock_addr; sender_addr = &(sa6->sin6_addr); sender_port = ntohs(sa6->sin6_port); } #endif if (connection_ip_family == AF_INET) { struct sockaddr_in *sa4 = (struct sockaddr_in *)&from_sock_addr; sender_addr = &(sa4->sin_addr); sender_port = ntohs(sa4->sin_port); } if (sender_port == receiver_port) { char sender_string[256]; memset(sender_string, 0, sizeof(sender_string)); inet_ntop(connection_ip_family, sender_addr, sender_string, sizeof(sender_string)); // now, find the record for this ip int the_clock = find_clock_source_record( sender_string, (clock_source_private_data *)&clocks_private); // not sure about requiring a Sync before creating it... // if ((the_clock == -1) && ((buf[0] & 0xF) == Sync)) { /* if (the_clock == -1) { the_clock = create_clock_source_record( sender_string, (clock_source_private_data *)&clocks_private); } */ if (the_clock != -1) { clocks_private[the_clock].time_of_last_use = reception_time; // for garbage collection switch (buf[0] & 0xF) { case Announce: handle_announce(buf, recv_len, &clocks_private[the_clock], reception_time); break; case Follow_Up: handle_follow_up(buf, recv_len, &clocks_private[the_clock], reception_time); break; case Sync: handle_sync(buf, recv_len, &clocks_private[the_clock], reception_time); break; default: debug_print_buffer(2, buf, recv_len); // unusual messages will have debug level 1. break; } } // otherwise, just forget it } } } } } // if (retval >= 0) // manage_clock_sources(reception_time, (clock_source_private_data *)&clocks_private); int i; for (i = 0; i < TIMED_TASKS; i++) { if (timed_tasks[i].trigger_time != 0) { int64_t time_to_wait = timed_tasks[i].trigger_time - reception_time; if (time_to_wait <= 0) { timed_tasks[i].trigger_time = timed_tasks[i].task(reception_time, timed_tasks[i].private_data); } } } } } // should never get to here, unless no sockets were ever opened. return 0; } void send_awakening_announcement_sequence(const uint64_t clock_id, const char *clock_ip, const int ip_family, const uint8_t priority1, const uint8_t priority2) { struct ptp_announce_message *msg; size_t msg_length = sizeof(struct ptp_announce_message); msg = malloc(msg_length); memset((void *)msg, 0, msg_length); uint64_t my_clock_id = get_self_clock_id(); msg->header.transportSpecificAndMessageID = 0x10 + Announce; msg->header.reservedAndVersionPTP = 0x02; msg->header.messageLength = htons(sizeof(struct ptp_announce_message)); msg->header.flags = htons(0x0408); hcton64(my_clock_id, &msg->header.clockIdentity[0]); msg->header.sourcePortID = htons(32776); msg->header.controlField = 0x05; msg->header.logMessagePeriod = 0xFE; msg->announce.currentUtcOffset = htons(37); hcton64(my_clock_id, &msg->announce.grandmasterIdentity[0]); uint32_t my_clock_quality = 0xf8fe436a; msg->announce.grandmasterClockQuality = htonl(my_clock_quality); if (priority1 > 2) { msg->announce.grandmasterPriority1 = priority1 - 1; // make this announcement seem better than the clock we are about to ping msg->announce.grandmasterPriority2 = priority2; } else { warn("Cannot select a suitable priority for pinging clock %" PRIx64 " at %s.", clock_id, clock_ip); msg->announce.grandmasterPriority1 = 248; msg->announce.grandmasterPriority2 = 248; } msg->announce.timeSource = 160; // Internal Oscillator // get the socket for the correct port -- 320 -- and family -- IPv4 or IPv6 -- to send it // from. int s = 0; unsigned t; for (t = 0; t < sockets_open_stuff.sockets_open; t++) { if ((sockets_open_stuff.sockets[t].port == 320) && (sockets_open_stuff.sockets[t].family == ip_family)) s = sockets_open_stuff.sockets[t].number; } if (s == 0) { debug(1, "sending socket not found for clock %" PRIx64 " at %s, family %s.", clock_id, clock_ip, ip_family == AF_INET ? "IPv4" : ip_family == AF_INET6 ? "IPv6" : "Unknown"); } else { // debug(1, "Send message from socket %d.", s); const char *portname = "320"; struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = 0; hints.ai_flags = AI_ADDRCONFIG; struct addrinfo *res = NULL; int err = getaddrinfo(clock_ip, portname, &hints, &res); if (err != 0) { debug(1, "failed to resolve remote socket address (err=%d)", err); } else { // here, we have the destination, so send it // debug_print_buffer(1, (char *)msg, msg_length); int ret = sendto(s, msg, msg_length, 0, res->ai_addr, res->ai_addrlen); if (ret == -1) debug(1, "result of sendto is %d.", ret); debug(2, "Send awaken Announce message to clock \"%" PRIx64 "\" at %s on %s.", clock_id, clock_ip, ip_family == AF_INET6 ? "IPv6" : "IPv4"); if (priority1 < 254) { msg->announce.grandmasterPriority1 = priority1 + 1; // make this announcement seem worse than the clock we about to ping } else { warn("Cannot select a suitable priority for second ping of clock %" PRIx64 " at %s.", clock_id, clock_ip); msg->announce.grandmasterPriority1 = 250; } msg->announce.grandmasterPriority2 = priority2; usleep(150000); ret = sendto(s, msg, msg_length, 0, res->ai_addr, res->ai_addrlen); if (ret == -1) debug(1, "result of second sendto is %d.", ret); freeaddrinfo(res); } } free(msg); } uint64_t broadcasting_task(uint64_t call_time, __attribute__((unused)) void *private_data) { clock_source_private_data *clocks_private = (clock_source_private_data *)private_data; int i; for (i = 0; i < MAX_CLOCKS; i++) { /* int is_a_master = 0; int temp_client_id; for (temp_client_id = 0; temp_client_id < MAX_CLIENTS; temp_client_id++) if ((clocks_private->client_flags[temp_client_id] & (1 << clock_is_master)) != 0) is_a_master = 1; // only process it if it's a master somewhere... if ((is_a_master != 0) && (clocks_private[i].announcements_without_followups == 3)) { */ if (clocks_private[i].announcements_without_followups == 3) { if (clocks_private[i].follow_up_number == 0) { debug(1, "Attempt to awaken a silent clock %" PRIx64 ", index %u, at follow_up_number %u at IP %s.", clocks_private[i].clock_id, i, clocks_private[i].follow_up_number, clocks_private[i].ip); // send an Announce message to attempt to waken this silent PTP clock by // getting it to negotiate with an apparently better clock // that then immediately sends another Announce message indicating that it's inferior clocks_private[i].announcements_without_followups++; // set to 4 to indicate done/parked send_awakening_announcement_sequence( clocks_private[i].clock_id, clocks_private[i].ip, clocks_private[i].family, clocks_private[i].grandmasterPriority1, clocks_private[i].grandmasterPriority2); } else { debug(1, "Silent clock %" PRIx64 " detected, index %u, at follow_up_number %u at IP %s. No attempt to awaken it.", clocks_private[i].clock_id, i, clocks_private[i].follow_up_number, clocks_private[i].ip); } } } /* uint64_t announce_interval = 1; announce_interval = announce_interval << (8 + aPTPinitialLogAnnounceInterval); announce_interval = announce_interval * 1000000000; announce_interval = announce_interval >> 8; // nanoseconds return call_time + announce_interval; */ return call_time + 50000000; } nqptp-1.2.6/nqptp.freebsd000066400000000000000000000005601514334334200153630ustar00rootroot00000000000000#!/bin/sh # # PROVIDE: nqptp # REQUIRE: FILESYSTEMS DAEMON hostname . /etc/rc.subr name="nqptp" rcvar="nqptp_enable" pidfile="/var/run/${name}.pid" apptodaemonise="/usr/local/bin/nqptp" command="/usr/sbin/daemon" # -S log to syslog; -P store the supervisor PID command_args="-S -T nqptp -P ${pidfile} ${apptodaemonise}" load_rc_config $name run_rc_command "$1" nqptp-1.2.6/nqptp.h000066400000000000000000000034011514334334200141750ustar00rootroot00000000000000/* * This file is part of the nqptp distribution (https://github.com/mikebrady/nqptp). * Copyright (c) 2021-2022 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 * the Free Software Foundation, version 2. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Commercial licensing is also available. */ #ifndef NQPTP_H #define NQPTP_H #include #include #include "nqptp-shm-structures.h" #define MAX_CLOCKS 64 #define MAX_CLIENTS 16 #define MAX_OPEN_SOCKETS 16 // When a new timing peer group is created, one of the clocks in the // group may become the master and its native time becomes the "master time". // This is what is provided to the client. // An NQPTP client interface communicates through a shared memory interface named by the // shm_interface_name It provides the shm_interface_name at the start of every control message it // sends through port 9000. Following the name, the client can specify the members -- the "PTP // Instances" -- of a "PTP Network" it wishes to monitor. This is a "timing group" in AirPlay 2 // parlance, it seems. void send_awakening_announcement_sequence(const uint64_t clock_id, const char *clock_ip, const int ip_family, const uint8_t priority1, const uint8_t priority2); #endif nqptp-1.2.6/nqptp.service.in000066400000000000000000000004441514334334200160170ustar00rootroot00000000000000[Unit] Description=NQPTP -- Not Quite PTP Wants=network-online.target After=network.target network-online.target Before=shairport-sync.service [Service] ExecStart=@prefix@/bin/nqptp DynamicUser=yes LimitRTPRIO=6 AmbientCapabilities=CAP_NET_BIND_SERVICE [Install] WantedBy=multi-user.target