Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Backports:SLE-15-SP4:FactoryCandidates
clknetsim
clknetsim-0+git.20240610.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File clknetsim-0+git.20240610.obscpio of Package clknetsim
07070100000000000081A40000000000000000000000016666F04D0000002D000000000000000000000000000000000000002400000000clknetsim-0+git.20240610/.gitignore/*.o /.deps /clknetsim /clknetsim.so /tests* 07070100000001000081A40000000000000000000000016666F04D00004643000000000000000000000000000000000000002100000000clknetsim-0+git.20240610/COPYING 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. <one line to give the program's name and a brief idea of what it does.> Copyright (C) <year> <name of author> 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. <signature of Ty Coon>, 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. 07070100000002000081A40000000000000000000000016666F04D000002FC000000000000000000000000000000000000002200000000clknetsim-0+git.20240610/MakefileCFLAGS += -O2 -Wall -g -fPIC CXXFLAGS += $(CFLAGS) CPPFLAGS += $(apiflags) all: clknetsim.so clknetsim apiflags := $(shell echo -e '\x23include <sys/time.h>' | $(CC) -x c -E - | \ grep __timezone_ptr_t > /dev/null || echo -DGETTIMEOFDAY_VOID) clientobjs = client.o serverobjs = $(patsubst %.cc,%.o,$(wildcard *.cc)) clknetsim.so: $(clientobjs) $(CC) $(CFLAGS) -shared -o $@ $^ $(LDFLAGS) -ldl -lm clknetsim: $(serverobjs) $(CXX) $(CFLAGS) -o $@ $^ $(LDFLAGS) clean: rm -rf clknetsim *.so *.o core.* .deps .deps: @mkdir .deps .deps/%.d: %.c .deps @$(CC) -MM $(CPPFLAGS) -MT '$(<:%.c=%.o) $@' $< -o $@ .deps/%.D: %.cc .deps @$(CXX) -MM $(CPPFLAGS) -MT '$(<:%.cc=%.o) $@' $< -o $@ -include $(clientobjs:%.o=.deps/%.d) $(serverobjs:%.o=.deps/%.D) 07070100000003000081A40000000000000000000000016666F04D00002635000000000000000000000000000000000000002000000000clknetsim-0+git.20240610/READMEClock and Network Simulator (clknetsim) ======================================= clknetsim is a tool designed to test programs which synchronize the system clock, either over network or from a hardware reference clock. It simulates a system or a number of systems connected to each other in a network and the tested programs discipline the simulated system clocks. It can be used to quickly test how well the programs control the system clocks in various conditions or to test the network protocols. The tested programs are not modified in order to be included in the simulation, but they have some system calls redirected by a clknetsim library, which is loaded by the LD_PRELOAD feature of the dynamic linker, to a clknetsim server, which runs the simulation and collects several statistics about each client. The server and the clients run on a single host, they communicate via a UNIX domain socket. The simulation runs as fast as the host system is capable of, with two simulated systems it is usually three or four orders of magnitude faster than real time. Supported programs: - chronyd and chronyc from chrony (http://chrony.tuxfamily.org/) - ntpd, ntpdate, sntp and ntpq from ntp (http://www.ntp.org/) - ntpd from busybox (http://www.busybox.net/) - ptp4l, phc2sys, pmc and nsm from linuxptp (http://linuxptp.sourceforge.net/) Limitations: - only Linux is supported - the fake system calls implement only a minimal functionality required to keep the supported clients working - the simulated system clock advances only on select(), poll() or usleep() calls, this means the client sees the CPU as infinitely fast - adjtimex() frequency and tick changes happen immediately, the kernel has infinite HZ - adjtime() and PLL updates happen in one second intervals in the simulated time instead of the uncorrected simulated system time, all clocks are updated at the same time Usage ----- The clknetsim server is started with two required arguments, the first one is path to a configuration file describing the network and clocks and the second argument is the number of simulated nodes. The simulation is started when all clients are connected. The clients are started under a non-root user, with preloaded clknetsim.so and the environment variable CLKNETSIM_NODE set to the number of the client. Optionally, the environment variable CLKNETSIM_SOCKET can be set to the path of the UNIX domain socket which is used to connect to the server, clknetsim.sock in current directory is used by default. The CLKNETSIM_START_DATE variable can be used to specify in seconds since 1970 when should the simulated time start, 1262304000 by default (2010-01-01 0:00 UTC). The CLKNETSIM_CONNECT_TIMEOUT variable sets the server connection timeout, 10 seconds by default. The simulated network is available to the clients as one or more Ethernet networks with IPv4 addressing. All nodes have interfaces to all networks. Their addresses are 192.168.122+s.n, where n is the number of the node (starting at 1) and s is the number of the network (starting at 1). The broadcast addresses are 192.168.122+s.255. At the end of the simulation clock and network statistics are printed. clknetsim has options which can be used to control for how long the simulation should run, or if the frequency, offset or network log should be written. clknetsim -h prints a complete list of available options. A minimal example how to start a simulation: $ LD_PRELOAD=./clknetsim.so CLKNETSIM_NODE=1 chronyd -d -f chrony.conf & $ LD_PRELOAD=./clknetsim.so CLKNETSIM_NODE=2 ntpd -n -c ntp.conf & $ ./clknetsim -o log.offset -l 100000 clknetsim.conf 2 clknetsim.conf: node2_freq = (sum (* 1e-8 (normal))) node1_delay2 = (+ 1e-1 (* 1e-3 (exponential))) node2_delay1 = (+ 1e-1 (* 1e-3 (exponential))) chrony.conf: pidfile chronyd.pid local stratum 1 allow ntp.conf: pidfile ntpd.pid server 192.168.123.1 The clknetsim.bash file contains bash functions which can create the configuration in several network settings, start the simulation, stop the clients and process the results. The examples subdirectory contains an example script for each supported client. The above example can be written in a bash script as: CLKNETSIM_PATH=. . ./clknetsim.bash generate_config1 2 0.0 "(sum (* 1e-8 (normal)))" "(+ 1e-1 (* 1e-3 (exponential)))" start_client 1 chrony "local stratum 1" start_client 2 ntp "server 192.168.123.1" start_server 2 -o log.offset -l 100000 cat tmp/stats Configuration file ------------------ The configuration file is a text file containing a list of assignments, each specified on a separate line, and comments using # as delimiter. Each node has several variables, which configure the system clock, the reference clock and the network delays to other nodes in the network. They can be set either to an integer value, a floating-point value or a number generating expression written in a Lisp-style syntax. Variables: - nodeX_freq = float | expr the system clock frequency error in terms of gained seconds per second of simulated time, if an expression is specified, the expression is evaluated and frequency updated once per simulated second (or at the rate specified with the -R option), the allowed range is (-0.2, 0.2), the default is 0 - nodeX_delayY = expr the network delay for packets sent from node X to node Y in seconds, the expression is evaluated for each sent packet, a negative value means the packet will be dropped, there is no default (packets are dropped) - nodeX_delay_correctionY = expr the correction written to PTP packets (as a one-step E2E transparent clock) sent from node X to node Y in seconds, no correction is written by default - nodeX_offset = float the initial time error of the system clock in seconds, the default is 0 - nodeX_start = float the time in seconds when will be the node started, the default is 0 - nodeX_refclock = expr the reference clock time error in seconds, the clock can be accessed by the client via shared memory (NTP SHM protocol) or as a PTP hardware clock (PHC) via the clock_gettime() function, there is no default (the clock is disabled) - nodeX_refclock_base = nodeX the base of the reference clock, the default is the network time - nodeX_step = expr the extra time step applied once per second (or at the rate specified with the -R option) in seconds, there is no default (no extra steps are applied) - nodeX_shift_pll = integer kernel PLL parameter, the default is 2 - nodeX_pll_clamp = 1 | 0 kernel PLL parameter, the default is 0 - nodeX_fll_mode2 = 1 | 0 kernel FLL parameter, the default is 0 Functions and their parameters supported in the expressions: (* [expr | float] ...) - multiplication (+ [expr | float] ...) - addition (% [expr | float] ...) - modulo (sum [expr | float] ...) - summation over consecutive evaluation of parameters (uniform) - random number generator with standard uniform distribution (normal) - random number generator with standard normal distribution (exponential) - random number generator with exponential distribution (lambda = 1) (poisson lambda) - random number generator with poisson distribution (file "datafile") - number generator reading floating-point values from the specified file in an inifinite loop (pulse high low) - pulse wave generator (sine period) - sine wave generator (cosine period) - cosine wave generator (triangle period) - triangle wave generator (equal epsilon [expr | float] ...) - returns 1.0 if the values of all parameters are equal within epsilon, 0.0 otherwise (max [expr | float] ...) - returns maximum value (min [expr | float] ...) - returns minimum value Variables available in network delay expressions: time - current network time from - number of the sending node to - number of the receiving node port - receiving port number length - length of the packet subnet - number of the Ethernet network in which the packet was sent Variables available in delay correction expressions: delay - delay of the packet length - length of the packet (layer 4) An example: # node1 is an NTP server, it has an accurate and absolutely stable clock node1_offset = 0 node1_freq = 0 # node2 is an NTP client, it starts with 0.1s offset and has # 0.01ppm/s frequency wander node2_offset = 0.1 node2_freq = (sum (* 1e-8 (normal))) # network delays between the two nodes have 10ms mean and 100us # jitter in both directions node1_delay2 = (+ 9.9e-3 (* 100e-6 (exponential))) node2_delay1 = (+ 9.9e-3 (* 100e-6 (exponential))) Author ------ Miroslav Lichvar <mlichvar@redhat.com> License ------- Copyright (C) 2010, 2011, 2012 Miroslav Lichvar <mlichvar@redhat.com> 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, see <http://www.gnu.org/licenses/>. 07070100000004000081A40000000000000000000000016666F04D00013986000000000000000000000000000000000000002200000000clknetsim-0+git.20240610/client.c/* * Copyright (C) 2010 Miroslav Lichvar <mlichvar@redhat.com> * * 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, see <http://www.gnu.org/licenses/>. */ #define _GNU_SOURCE /* avoid redirection in glibc headers */ #define adjtimex adjtimex_off #include <sys/timex.h> #undef adjtimex #define fopen fopen_off #include <stdio.h> #undef fopen #include <sys/utsname.h> #include <sys/time.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/syscall.h> #include <sys/timerfd.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/sysmacros.h> #include <netinet/in.h> #include <arpa/inet.h> #include <time.h> #include <dlfcn.h> #include <fcntl.h> #include <sys/un.h> #include <unistd.h> #include <assert.h> #include <stddef.h> #include <stdlib.h> #include <errno.h> #include <sys/ioctl.h> #include <net/if.h> #include <netdb.h> #include <pwd.h> #include <stdarg.h> #include <signal.h> #include <ifaddrs.h> #include <linux/types.h> #include <linux/ethtool.h> #include <linux/limits.h> #include <linux/pps.h> #include <linux/rtc.h> #include <linux/sockios.h> #ifdef SO_TIMESTAMPING #include <linux/ptp_clock.h> #include <linux/net_tstamp.h> #endif #include "protocol.h" #include "client_fuzz.c" /* first node in first subnet is 192.168.123.1 */ #define BASE_ADDR 0xc0a87b00 #define NETMASK 0xffffff00 #define NODE_ADDR(subnet, node) (BASE_ADDR + 0x100 * (subnet) + (node) + 1) #define BROADCAST_ADDR(subnet) (NODE_ADDR(subnet, 0) | 0xff) #define NODE_FROM_ADDR(addr) (((addr) & ~NETMASK) - 1) #define SUBNET_FROM_ADDR(addr) ((((addr) & NETMASK) - BASE_ADDR) / 0x100) #define PTP_PRIMARY_MCAST_ADDR 0xe0000181 /* 224.0.1.129 */ #define PTP_PDELAY_MCAST_ADDR 0xe000006b /* 224.0.0.107 */ #define REFCLK_FD 1000 #define REFCLK_ID ((clockid_t)(((unsigned int)~REFCLK_FD << 3) | 3)) #define REFCLK_PHC_INDEX 0 #define SYSCLK_FD 1001 #define SYSCLK_CLOCKID ((clockid_t)(((unsigned int)~SYSCLK_FD << 3) | 3)) #define SYSCLK_PHC_INDEX 1 #define PPS_FD 1002 #define RTC_FD 1003 #define URANDOM_FD 1010 #define MAX_SOCKETS 20 #define BASE_SOCKET_FD 100 #define BASE_SOCKET_DEFAULT_PORT 60000 #define MAX_TIMERS 80 #define BASE_TIMER_ID 0xC1230123 #define BASE_TIMER_FD 200 #define URANDOM_FILE (void *)0xD1230123 #if !defined(__GLIBC_PREREQ) || __GLIBC_PREREQ(2, 33) #define HAVE_STAT #endif static FILE *(*_fopen)(const char *path, const char *mode); static FILE *(*_fdopen)(int fd, const char *mode); static size_t (*_fread)(void *ptr, size_t size, size_t nmemb, FILE *stream); static int (*_fileno)(FILE *stream); static int (*_fclose)(FILE *fp); static int (*_fcntl)(int fd, int cmd, ...); #ifdef HAVE_STAT static int (*_fstat)(int fd, struct stat *statbuf); static int (*_stat)(const char *pathname, struct stat *statbuf); #else static int (*_fxstat)(int ver, int fd, struct stat *statbuf); static int (*_xstat)(int ver, const char *pathname, struct stat *statbuf); #endif static char *(*_realpath)(const char *path, char *resolved_path); static int (*_open)(const char *pathname, int flags, ...); static ssize_t (*_read)(int fd, void *buf, size_t count); static int (*_close)(int fd); static int (*_socket)(int domain, int type, int protocol); static int (*_connect)(int sockfd, const struct sockaddr *addr, socklen_t addrlen); static ssize_t (*_recvmsg)(int sockfd, struct msghdr *msg, int flags); static ssize_t (*_send)(int sockfd, const void *buf, size_t len, int flags); static int (*_usleep)(useconds_t usec); static void (*_srandom)(unsigned int seed); static int (*_shmget)(key_t key, size_t size, int shmflg); static void *(*_shmat)(int shmid, const void *shmaddr, int shmflg); static unsigned int node; static int initializing = 0; static int initialized_symbols = 0; static int initialized = 0; static int clknetsim_fd; static int precision_hack = 1; static unsigned int random_seed = 0; static int recv_multiply = 1; static int timestamping = 1; static double phc_delay = 0.0; static double phc_jitter = 0.0; static double phc_jitter_asym = 0.0; static int phc_swap = 0; /* Ethernet speed in Mb/s */ static int link_speed = 100000; static double rtc_offset = 0.0; static int rtc_timerfd = 0; enum { IFACE_UNIX, IFACE_LO, IFACE_ALL, IFACE_ETH0, }; struct message { char data[MAX_PACKET_SIZE]; unsigned int len; unsigned int subnet; unsigned int to_from; unsigned int port; }; struct socket { int used; int domain; int type; int port; int iface; int remote_node; int remote_port; int listening; int connected; int broadcast; int pkt_info; int time_stamping; struct message last_ts_msg; struct message buffer; }; static struct socket sockets[MAX_SOCKETS]; static int subnets; static int unix_subnet = -1; static double real_time = 0.0; static double monotonic_time = 0.0; static double network_time = 0.0; static double freq_error = 0.0; static int local_time_valid = 0; static time_t system_time_offset = 1262304000; /* 2010-01-01 0:00 UTC */ #define TIMER_TYPE_SIGNAL 1 #define TIMER_TYPE_FD 2 struct timer { int used; int armed; int type; int fd_flags; uint64_t expired; clockid_t clock_id; double timeout; double interval; }; static struct timer timers[MAX_TIMERS]; static timer_t itimer_real_id; #define SHM_KEY 0x4e545030 #define SHM_REFCLOCKS 4 static struct shmTime { int mode; int count; time_t clockTimeStampSec; int clockTimeStampUSec; time_t receiveTimeStampSec; int receiveTimeStampUSec; int leap; int precision; int nsamples; int valid; int clockTimeStampNSec; int receiveTimeStampNSec; int dummy[8]; } shm_time[SHM_REFCLOCKS]; static int shm_refclocks = 0; static double shm_refclock_time = 0.0; static struct Reply_getrefoffsets refclock_offsets; static int refclock_offsets_used = 0; static int pps_fds = 0; static FILE *pcap = NULL; static void write_pcap_header(void); static void make_request(int request_id, const void *request_data, int reqlen, void *reply, int replylen); static void init_symbols(void) { if (initialized_symbols) return; _fopen = (FILE *(*)(const char *path, const char *mode))dlsym(RTLD_NEXT, "fopen"); _fdopen = (FILE *(*)(int fd, const char *mode))dlsym(RTLD_NEXT, "fdopen"); _fread = (size_t (*)(void *ptr, size_t size, size_t nmemb, FILE *stream))dlsym(RTLD_NEXT, "fread"); _fileno = (int (*)(FILE *stream))dlsym(RTLD_NEXT, "fileno"); _fclose = (int (*)(FILE *fp))dlsym(RTLD_NEXT, "fclose"); _fcntl = (int (*)(int fd, int cmd, ...))dlsym(RTLD_NEXT, "fcntl"); #ifdef HAVE_STAT #if defined(__USE_TIME_BITS64) && __USE_TIME_BITS64 && __TIMESIZE == 32 _fstat = (int (*)(int fd, struct stat *statbuf))dlsym(RTLD_NEXT, "__fstat64_time64"); _stat = (int (*)(const char *pathname, struct stat *statbuf))dlsym(RTLD_NEXT, "__stat64_time64"); #else _fstat = (int (*)(int fd, struct stat *statbuf))dlsym(RTLD_NEXT, "fstat"); _stat = (int (*)(const char *pathname, struct stat *statbuf))dlsym(RTLD_NEXT, "stat"); #endif #else _fxstat = (int (*)(int ver, int fd, struct stat *statbuf))dlsym(RTLD_NEXT, "__fxstat"); _xstat = (int (*)(int ver, const char *pathname, struct stat *statbuf))dlsym(RTLD_NEXT, "__xstat"); #endif _realpath = (char *(*)(const char *path, char *resolved_path))dlsym(RTLD_NEXT, "realpath"); _open = (int (*)(const char *pathname, int flags, ...))dlsym(RTLD_NEXT, "open"); _read = (ssize_t (*)(int fd, void *buf, size_t count))dlsym(RTLD_NEXT, "read"); _close = (int (*)(int fd))dlsym(RTLD_NEXT, "close"); _socket = (int (*)(int domain, int type, int protocol))dlsym(RTLD_NEXT, "socket"); _connect = (int (*)(int sockfd, const struct sockaddr *addr, socklen_t addrlen))dlsym(RTLD_NEXT, "connect"); _recvmsg = (ssize_t (*)(int sockfd, struct msghdr *msg, int flags))dlsym(RTLD_NEXT, "recvmsg"); _send = (ssize_t (*)(int sockfd, const void *buf, size_t len, int flags))dlsym(RTLD_NEXT, "send"); _usleep = (int (*)(useconds_t usec))dlsym(RTLD_NEXT, "usleep"); _srandom = (void (*)(unsigned int seed))dlsym(RTLD_NEXT, "srandom"); _shmget = (int (*)(key_t key, size_t size, int shmflg))dlsym(RTLD_NEXT, "shmget"); _shmat = (void *(*)(int shmid, const void *shmaddr, int shmflg))dlsym(RTLD_NEXT, "shmat"); initialized_symbols = 1; } __attribute__((constructor)) static void init(void) { unsigned int connect_retries = 100; /* 10 seconds */ struct sockaddr_un s = {AF_UNIX, "clknetsim.sock"}; struct Request_register req; struct Reply_register rep; const char *env; char command[64]; FILE *f; if (initializing || initialized) return; initializing = 1; init_symbols(); env = getenv("CLKNETSIM_START_DATE"); if (env) system_time_offset = atoll(env); env = getenv("CLKNETSIM_RANDOM_SEED"); if (env) random_seed = atoi(env); env = getenv("CLKNETSIM_RECV_MULTIPLY"); if (env) recv_multiply = atoi(env); env = getenv("CLKNETSIM_TIMESTAMPING"); if (env) timestamping = atoi(env); env = getenv("CLKNETSIM_LINK_SPEED"); if (env) link_speed = atoi(env); env = getenv("CLKNETSIM_PHC_DELAY"); if (env) phc_delay = atof(env); env = getenv("CLKNETSIM_PHC_JITTER"); if (env) phc_jitter = atof(env); env = getenv("CLKNETSIM_PHC_JITTER_ASYM"); if (env) phc_jitter_asym = atof(env); env = getenv("CLKNETSIM_PHC_SWAP"); if (env) phc_swap = atoi(env); env = getenv("CLKNETSIM_RTC_OFFSET"); if (env) rtc_offset = atof(env); f = _fopen("/proc/self/comm", "r"); if (f) { command[0] = '\0'; if (!fgets(command, sizeof (command), f)) ; fclose(f); if (strncmp(command, "valgrind", 8) == 0 || strncmp(command, "strace", 6) == 0) { /* don't connect to the server */ initialized = 1; return; } } env = getenv("CLKNETSIM_PCAP_DUMP"); if (env) { pcap = _fopen(env, "w"); write_pcap_header(); } if (fuzz_init()) { node = 0; subnets = 2; unix_subnet = 1; initialized = 1; return; } env = getenv("CLKNETSIM_NODE"); if (!env) { fprintf(stderr, "clknetsim: CLKNETSIM_NODE variable not set.\n"); exit(1); } node = atoi(env) - 1; env = getenv("CLKNETSIM_UNIX_SUBNET"); if (env) unix_subnet = atoi(env) - 1; env = getenv("CLKNETSIM_SOCKET"); if (env) snprintf(s.sun_path, sizeof (s.sun_path), "%s", env); env = getenv("CLKNETSIM_CONNECT_TIMEOUT"); if (env) connect_retries = 10 * atoi(env); clknetsim_fd = _socket(AF_UNIX, SOCK_SEQPACKET, 0); assert(clknetsim_fd >= 0); while (_connect(clknetsim_fd, (struct sockaddr *)&s, sizeof (s)) < 0) { if (!--connect_retries) { fprintf(stderr, "clknetsim: could not connect to server.\n"); exit(1); } _usleep(100000); } /* this requires the node variable to be already set */ srandom(0); initializing = 0; initialized = 1; req.node = node; make_request(REQ_REGISTER, &req, sizeof (req), &rep, sizeof (rep)); subnets = rep.subnets; } __attribute__((destructor)) static void fini(void) { if (initialized) make_request(REQ_DEREGISTER, NULL, 0, NULL, 0); if (pcap) fclose(pcap); } static void make_request(int request_id, const void *request_data, int reqlen, void *reply, int replylen) { struct Request_packet request; int sent, received = 0; init(); if (fuzz_mode) { fuzz_process_request(request_id, request_data, reply, replylen); return; } request.header.request = request_id; request.header._pad = 0; assert(offsetof(struct Request_packet, data) + reqlen <= sizeof (request)); if (request_data) memcpy(&request.data, request_data, reqlen); reqlen += offsetof(struct Request_packet, data); if ((sent = _send(clknetsim_fd, &request, reqlen, 0)) <= 0 || (reply && (received = recv(clknetsim_fd, reply, replylen, 0)) <= 0)) { fprintf(stderr, "clknetsim: server connection closed.\n"); initialized = 0; exit(1); } assert(sent == reqlen); if (!reply) return; /* check reply length */ switch (request_id) { case REQ_RECV: /* reply with variable length */ assert(received >= offsetof(struct Reply_recv, data)); assert(offsetof(struct Reply_recv, data) + ((struct Reply_recv *)reply)->len <= received); break; case REQ_GETREFOFFSETS: /* reply with variable length */ assert(received >= offsetof(struct Reply_getrefoffsets, offsets)); assert(offsetof(struct Reply_getrefoffsets, offsets) + (sizeof ((struct Reply_getrefoffsets *)reply)->offsets[0]) * ((struct Reply_getrefoffsets *)reply)->size == received); break; default: assert(received == replylen); } } static void fetch_time(void) { struct Reply_gettime r; if (!local_time_valid) { make_request(REQ_GETTIME, NULL, 0, &r, sizeof (r)); real_time = r.real_time; monotonic_time = r.monotonic_time; network_time = r.network_time; freq_error = r.freq_error; local_time_valid = 1; } } static double get_real_time(void) { fetch_time(); return real_time; } static double get_monotonic_time(void) { fetch_time(); return monotonic_time; } static double get_refclock_offset(void) { if (refclock_offsets_used >= refclock_offsets.size) { make_request(REQ_GETREFOFFSETS, NULL, 0, &refclock_offsets, sizeof (refclock_offsets)); assert(refclock_offsets.size > 0); refclock_offsets_used = 0; } return refclock_offsets.offsets[refclock_offsets_used++]; } static double get_refclock_time(void) { fetch_time(); return network_time - get_refclock_offset(); } static double get_rtc_time(void) { return get_monotonic_time() + rtc_offset; } static void settime(double time) { struct Request_settime req; req.time = time; make_request(REQ_SETTIME, &req, sizeof (req), NULL, 0); local_time_valid = 0; } static void fill_refclock_sample(void) { struct Reply_getrefsample r; double clock_time, receive_time, round_corr; int i; if (shm_refclocks == 0 && pps_fds == 0) return; make_request(REQ_GETREFSAMPLE, NULL, 0, &r, sizeof (r)); if (r.time == shm_refclock_time || !r.valid) return; shm_refclock_time = r.time; for (i = 0; i < shm_refclocks; i++) { if (shm_refclocks == 1) { clock_time = r.time - r.offset; receive_time = r.time; } else { clock_time = get_refclock_time(); receive_time = get_real_time(); } round_corr = (clock_time * 1e6 - floor(clock_time * 1e6) + 0.5) / 1e6; clock_time -= round_corr; receive_time -= round_corr; shm_time[i].count++; shm_time[i].clockTimeStampSec = floor(clock_time); shm_time[i].clockTimeStampUSec = (clock_time - shm_time[i].clockTimeStampSec) * 1e6; shm_time[i].clockTimeStampNSec = (clock_time - shm_time[i].clockTimeStampSec) * 1e9; shm_time[i].clockTimeStampSec += system_time_offset; shm_time[i].receiveTimeStampSec = floor(receive_time); shm_time[i].receiveTimeStampUSec = (receive_time - shm_time[i].receiveTimeStampSec) * 1e6; shm_time[i].receiveTimeStampNSec = (receive_time - shm_time[i].receiveTimeStampSec) * 1e9; shm_time[i].receiveTimeStampSec += system_time_offset; shm_time[i].leap = 0; shm_time[i].valid = 1; } } static int socket_in_subnet(int socket, int subnet) { switch (sockets[socket].iface) { case IFACE_LO: return 0; case IFACE_UNIX: return subnet == unix_subnet; case IFACE_ALL: return subnet != unix_subnet; default: return sockets[socket].iface - IFACE_ETH0 == subnet && subnet != unix_subnet; } } static void get_target(int socket, uint32_t addr, unsigned int *subnet, unsigned int *node) { if (addr == PTP_PRIMARY_MCAST_ADDR || addr == PTP_PDELAY_MCAST_ADDR) { assert(sockets[socket].iface >= IFACE_ETH0); *subnet = sockets[socket].iface - IFACE_ETH0; *node = -1; /* multicast as broadcast */ } else { *subnet = SUBNET_FROM_ADDR(addr); if (fuzz_mode && (*subnet >= subnets || *subnet == unix_subnet)) *subnet = 0; assert(*subnet >= 0 && *subnet < subnets); assert(socket_in_subnet(socket, *subnet)); if (addr == BROADCAST_ADDR(*subnet)) *node = -1; /* broadcast */ else *node = NODE_FROM_ADDR(addr); } } static int get_network_from_iface(const char *iface) { if (strncmp(iface, "eth", 3)) return -1; return atoi(iface + 3); } static int get_free_socket(void) { int i; for (i = 0; i < MAX_SOCKETS; i++) { if (!sockets[i].used) return i; } return -1; } static int get_socket_from_fd(int fd) { int s = fd - BASE_SOCKET_FD; if (s >= 0 && s < MAX_SOCKETS && sockets[s].used) return s; return -1; } static int get_socket_fd(int s) { return s + BASE_SOCKET_FD; } static int find_recv_socket(struct Reply_select *rep) { int i, s = -1; for (i = 0; i < MAX_SOCKETS; i++) { if (!sockets[i].used) continue; if (rep == NULL) return i; if (!socket_in_subnet(i, rep->subnet) || (rep->dst_port && sockets[i].port != rep->dst_port) || (sockets[i].remote_node >= 0 && sockets[i].remote_node != rep->from) || (sockets[i].remote_port >= 0 && sockets[i].remote_port != rep->src_port)) continue; switch (rep->type) { case MSG_TYPE_NO_MSG: break; case MSG_TYPE_UDP_DATA: if (sockets[i].type != SOCK_DGRAM) continue; break; case MSG_TYPE_TCP_CONNECT: if (sockets[i].type != SOCK_STREAM || sockets[i].connected) continue; break; case MSG_TYPE_TCP_DATA: case MSG_TYPE_TCP_DISCONNECT: if (sockets[i].type != SOCK_STREAM || sockets[i].listening || !sockets[i].connected) continue; break; default: assert(0); } if (s < 0 || sockets[s].iface < sockets[i].iface || (rep->ret == REPLY_SELECT_BROADCAST && sockets[i].broadcast) || (rep->ret != REPLY_SELECT_BROADCAST && sockets[s].broadcast && !sockets[i].broadcast)) s = i; } return s; } static void send_msg_to_peer(int s, int type) { struct Request_send req; assert(sockets[s].domain == AF_INET); assert(sockets[s].type == SOCK_STREAM); if (sockets[s].remote_node == -1) return; req.type = type; req.subnet = sockets[s].iface - IFACE_ETH0; req.to = sockets[s].remote_node; req.src_port = sockets[s].port; req.dst_port = sockets[s].remote_port; req.len = 0; make_request(REQ_SEND, &req, offsetof(struct Request_send, data), NULL, 0); } static int get_free_timer(void) { int i; for (i = 0; i < MAX_TIMERS; i++) { if (!timers[i].used) return i; } return -1; } static timer_t get_timerid(int timer) { return (timer_t)((long)timer + BASE_TIMER_ID); } static int get_timer_from_id(timer_t timerid) { int t = (long)timerid - BASE_TIMER_ID; if (t >= 0 && t < MAX_TIMERS && timers[t].used) return t; return -1; } static int get_timerfd(int timer) { return timer + BASE_TIMER_FD; } static int get_timer_from_fd(int fd) { int t = fd - BASE_TIMER_FD; if (t >= 0 && t < MAX_TIMERS && timers[t].used) return t; return -1; } static int get_first_timer(fd_set *timerfds) { int i, r = -1; for (i = 0; i < MAX_TIMERS; i++) { if (!timers[i].used || !timers[i].armed) continue; if (timers[i].type == TIMER_TYPE_FD && !(timerfds && FD_ISSET(get_timerfd(i), timerfds))) continue; if (r < 0 || timers[r].timeout > timers[i].timeout) r = i; } return r; } static void rearm_timer(int timer) { assert(timers[timer].armed); if (timers[timer].interval > 0.0) timers[timer].timeout += timers[timer].interval; else timers[timer].armed = 0; timers[timer].expired++; } static void time_to_timeval(double d, struct timeval *tv) { tv->tv_sec = floor(d); tv->tv_usec = (d - tv->tv_sec) * 1e6; } static void time_to_timespec(double d, struct timespec *tp) { tp->tv_sec = floor(d); tp->tv_nsec = (d - tp->tv_sec) * 1e9; } static double timeval_to_time(const struct timeval *tv, time_t offset) { return tv->tv_sec + offset + tv->tv_usec / 1e6; } static double timespec_to_time(const struct timespec *tp, time_t offset) { return tp->tv_sec + offset + tp->tv_nsec / 1e9; } static void normalize_timespec(struct timespec *tp) { while (tp->tv_nsec >= 1000000000) { tp->tv_nsec -= 1000000000; tp->tv_sec++; } while (tp->tv_nsec < 0) { tp->tv_nsec += 1000000000; tp->tv_sec--; } } static void add_to_timespec(struct timespec *tp, double offset) { tp->tv_sec += floor(offset); tp->tv_nsec += (offset - floor(offset)) * 1e9; normalize_timespec(tp); } static double get_random_double(void) { return (double)random() / ((1U << 31) - 1); } static double get_phc_delay(int dir) { double L, p, delay = 0.0; int k, lambda = 5; /* Poisson with uniform steps */ if (phc_jitter > 0.0) { for (L = exp(-lambda), p = 1.0, k = 0; k < 100 && p > L; k++) p *= get_random_double(); delay += (k + get_random_double()) / (lambda + 0.5) * phc_jitter * (0.5 + dir * phc_jitter_asym); } return (delay + phc_delay / 2.0) * (freq_error + 1.0); } static int generate_eth_frame(unsigned int type, unsigned int subnet, unsigned int from, unsigned int to, unsigned int src_port, unsigned int dst_port, char *data, unsigned int data_len, char *frame, unsigned int buf_len) { uint16_t port1, port2, ip_len, udp_len; uint32_t addr1, addr2; assert(type == SOCK_DGRAM || type == SOCK_STREAM); if ((type == SOCK_DGRAM && data_len + 42 > buf_len) || (type == SOCK_STREAM && data_len + 54 > buf_len)) return 0; addr1 = htonl(NODE_ADDR(subnet, from)); addr2 = htonl(NODE_ADDR(subnet, to)); port1 = htons(src_port); port2 = htons(dst_port); memset(frame, 0, buf_len); frame[12] = 0x08; frame[14] = 0x45; memcpy(frame + 26, &addr1, sizeof (addr1)); memcpy(frame + 30, &addr2, sizeof (addr2)); memcpy(frame + 34, &port1, sizeof (port1)); memcpy(frame + 36, &port2, sizeof (port2)); if (type == SOCK_DGRAM) { ip_len = htons(data_len + 28); udp_len = htons(data_len + 8); memcpy(frame + 16, &ip_len, sizeof (ip_len)); frame[23] = 17; memcpy(frame + 38, &udp_len, sizeof (udp_len)); memcpy(frame + 42, data, data_len); return data_len + 42; } else { ip_len = htons(data_len + 40); memcpy(frame + 16, &ip_len, sizeof (ip_len)); frame[23] = 6; frame[46] = 5 << 4; memcpy(frame + 54, data, data_len); return data_len + 54; } } static void write_pcap_header(void) { /* Big-endian nanosecond pcap with DLT_EN10MB */ const char header[] = "\xa1\xb2\x3c\x4d\x00\x02\x00\x04\x00\x00\x00\x00" "\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x01"; if (!pcap) return; if (fwrite(header, sizeof (header) - 1, 1, pcap) != 1) return; } static void write_pcap_packet(unsigned int type, unsigned int subnet, unsigned int from, unsigned int to, unsigned int src_port, unsigned int dst_port, char *data, unsigned int len) { char frame[64 + MAX_PACKET_SIZE]; unsigned int frame_len; struct timespec ts; uint32_t v; if (!pcap) return; clock_gettime(CLOCK_REALTIME, &ts); frame_len = generate_eth_frame(type, subnet, from, to, src_port, dst_port, data, len, frame, sizeof (frame)); v = htonl(ts.tv_sec); if (fwrite(&v, sizeof (v), 1, pcap) != 1) return; v = htonl(ts.tv_nsec); if (fwrite(&v, sizeof (v), 1, pcap) != 1) return; v = htonl(frame_len); if (fwrite(&v, sizeof (v), 1, pcap) != 1 || fwrite(&v, sizeof (v), 1, pcap) != 1) return; if (fwrite(frame, frame_len, 1, pcap) != 1) return; } int gettimeofday(struct timeval *tv, #if !defined(__GLIBC_PREREQ) || __GLIBC_PREREQ(2, 31) || defined(GETTIMEOFDAY_VOID) void *tz #else struct timezone *tz #endif ) { double time; time = get_real_time() + 0.5e-6; time_to_timeval(time, tv); tv->tv_sec += system_time_offset; /* old chrony clock precision routine hack */ if (precision_hack) tv->tv_usec += random() % 2; return 0; } int clock_gettime(clockid_t which_clock, struct timespec *tp) { double time; /* try to allow reading of the clock from other constructors, but prevent a recursive call (e.g. due to a special memory allocator) */ init(); if (!initialized) { errno = EINVAL; return -1; } switch (which_clock) { case CLOCK_REALTIME: case CLOCK_REALTIME_COARSE: case SYSCLK_CLOCKID: time = get_real_time(); break; case CLOCK_MONOTONIC: case CLOCK_MONOTONIC_COARSE: time = get_monotonic_time(); break; case REFCLK_ID: time = get_refclock_time(); break; default: assert(0); } time += 0.5e-9; time_to_timespec(time, tp); if (which_clock != CLOCK_MONOTONIC && which_clock != CLOCK_MONOTONIC_COARSE) tp->tv_sec += system_time_offset; /* chrony and ntpd clock precision routine hack */ if (precision_hack) { static int x = 0; tp->tv_nsec += x++ * 101; } return 0; } time_t time(time_t *t) { time_t time; time = floor(get_real_time()); time += system_time_offset; if (t) *t = time; return time; } int settimeofday(const struct timeval *tv, const struct timezone *tz) { struct timespec ts; assert(tv); ts.tv_sec = tv->tv_sec; ts.tv_nsec = 1000 * tv->tv_usec; return clock_settime(CLOCK_REALTIME, &ts); } int clock_settime(clockid_t which_clock, const struct timespec *tp) { assert(tp && which_clock == CLOCK_REALTIME); if (tp->tv_sec < 0 || tp->tv_sec > ((1LLU << 63) / 1000000000)) { errno = EINVAL; return -1; } settime(timespec_to_time(tp, -system_time_offset)); return 0; } int adjtimex(struct timex *buf) { struct Request_adjtimex req; struct Reply_adjtimex rep; if (buf->modes & ADJ_SETOFFSET) local_time_valid = 0; memset(&req, 0, sizeof (req)); req.timex.modes = buf->modes; if (buf->modes & ADJ_FREQUENCY) req.timex.freq = buf->freq; if (buf->modes & ADJ_MAXERROR) req.timex.maxerror = buf->maxerror; if (buf->modes & ADJ_STATUS) req.timex.status = buf->status; if ((buf->modes & ADJ_TIMECONST) || (buf->modes & ADJ_TAI)) req.timex.constant = buf->constant; if (buf->modes & ADJ_TICK) req.timex.tick = buf->tick; if (buf->modes & ADJ_OFFSET) req.timex.offset = buf->offset; if (buf->modes & ADJ_SETOFFSET) req.timex.time = buf->time; make_request(REQ_ADJTIMEX, &req, sizeof (req), &rep, sizeof (rep)); *buf = rep.timex; if (rep.ret < 0) errno = EINVAL; return rep.ret; } int ntp_adjtime(struct timex *buf) { return adjtimex(buf); } int clock_adjtime(clockid_t id, struct timex *tx) { assert(id == CLOCK_REALTIME || id == SYSCLK_CLOCKID || id == REFCLK_ID); if (id == SYSCLK_CLOCKID) { /* allow large frequency adjustment by setting ticks */ long hz, base_tick, scaled_ppm_per_tick; int r; hz = sysconf(_SC_CLK_TCK); assert(hz > 0); base_tick = (1000000 + hz / 2) / hz; scaled_ppm_per_tick = 65536 * hz; if (tx->modes & ADJ_FREQUENCY && !(tx->modes & ADJ_TICK)) tx->tick = base_tick, tx->modes |= ADJ_TICK; tx->tick += tx->freq / scaled_ppm_per_tick; tx->freq = tx->freq % scaled_ppm_per_tick; r = adjtimex(tx); tx->freq += (tx->tick - base_tick) * scaled_ppm_per_tick; tx->tick = base_tick; return r; } else if (id == REFCLK_ID) { if (tx->modes) { errno = EINVAL; return -1; } memset(tx, 0, sizeof (*tx)); return 0; } return adjtimex(tx); } int adjtime(const struct timeval *delta, struct timeval *olddelta) { struct Request_adjtime req; struct Reply_adjtime rep; if (delta) req.tv = *delta; else time_to_timeval(0.0, &req.tv); make_request(REQ_ADJTIME, &req, sizeof (req), &rep, sizeof (rep)); if (olddelta) *olddelta = rep.tv; if (!delta) { req.tv = rep.tv; make_request(REQ_ADJTIME, &req, sizeof (req), &rep, sizeof (rep)); } return 0; } int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) { struct Request_select req; struct Reply_select rep; int i, timer, s, recv_fd = -1; double elapsed = 0.0; if (writefds) { for (i = 0; i < nfds; i++) { if (!FD_ISSET(i, writefds)) continue; s = get_socket_from_fd(i); if (s < 0 || (sockets[s].type == SOCK_STREAM && !sockets[s].connected)) continue; FD_ZERO(writefds); FD_SET(i, writefds); if (exceptfds) FD_ZERO(exceptfds); if (readfds) FD_ZERO(readfds); return 1; } FD_ZERO(writefds); } if (exceptfds) { /* TX timestamp available in the error queue */ for (i = 0; i < nfds; i++) { if (!FD_ISSET(i, exceptfds) || get_socket_from_fd(i) < 0 || !sockets[get_socket_from_fd(i)].last_ts_msg.len) continue; if (readfds) FD_ZERO(readfds); FD_ZERO(exceptfds); FD_SET(i, exceptfds); return 1; } FD_ZERO(exceptfds); } req.read = 0; req._pad = 0; /* unknown reading fds are always ready (e.g. chronyd waiting for name resolving notification, or OpenSSL waiting for /dev/urandom) */ if (readfds) { for (i = 0; i < nfds; i++) { if (!FD_ISSET(i, readfds)) continue; if (i == RTC_FD) { if (rtc_timerfd > 0) FD_SET(rtc_timerfd, readfds); continue; } s = get_socket_from_fd(i); if ((s < 0 && get_timer_from_fd(i) < 0) || (s >= 0 && sockets[s].buffer.len > 0)) { FD_ZERO(readfds); FD_SET(i, readfds); return 1; } req.read = 1; } } timer = get_first_timer(readfds); assert((timeout && (timeout->tv_sec > 0 || timeout->tv_usec > 0)) || timer >= 0 || find_recv_socket(NULL) >= 0); fetch_time(); if (timeout) req.timeout = timeout->tv_sec + (timeout->tv_usec + 1) / 1e6; else req.timeout = 1e20; try_again: if (timer >= 0 && timers[timer].timeout <= monotonic_time) { /* avoid unnecessary requests */ rep.ret = REPLY_SELECT_TIMEOUT; } else { if (timer >= 0 && monotonic_time + req.timeout > timers[timer].timeout) req.timeout = timers[timer].timeout - monotonic_time; make_request(REQ_SELECT, &req, sizeof (req), &rep, sizeof (rep)); elapsed += rep.time.monotonic_time - monotonic_time; req.timeout -= rep.time.monotonic_time - monotonic_time; real_time = rep.time.real_time; monotonic_time = rep.time.monotonic_time; network_time = rep.time.network_time; freq_error = rep.time.freq_error; local_time_valid = 1; fill_refclock_sample(); if (monotonic_time >= 0.1 || timer >= 0 || rep.ret != REPLY_SELECT_TIMEOUT) precision_hack = 0; } switch (rep.ret) { case REPLY_SELECT_TERMINATE: kill(getpid(), SIGTERM); errno = EINTR; return -1; case REPLY_SELECT_TIMEOUT: if (timer >= 0 && monotonic_time >= timers[timer].timeout) { rearm_timer(timer); switch (timers[timer].type) { case TIMER_TYPE_SIGNAL: kill(getpid(), SIGALRM); errno = EINTR; return -1; case TIMER_TYPE_FD: recv_fd = get_timerfd(timer); break; default: assert(0); } } else recv_fd = 0; break; case REPLY_SELECT_NORMAL: case REPLY_SELECT_BROADCAST: s = find_recv_socket(&rep); if (s >= 0 && rep.type == MSG_TYPE_TCP_CONNECT && !sockets[s].listening && !sockets[s].connected) { struct Reply_recv recv_rep; /* drop the connection packet and let the client repeat the call in order to see that the socket is ready for writing */ make_request(REQ_RECV, NULL, 0, &recv_rep, sizeof (recv_rep)); assert(recv_rep.type == MSG_TYPE_TCP_CONNECT); assert(sockets[s].type == SOCK_STREAM); sockets[s].connected = 1; errno = EINTR; return -1; } recv_fd = s >= 0 ? get_socket_fd(s) : 0; /* fetch and drop the packet if no fd is waiting for it */ if (!readfds || !recv_fd || !FD_ISSET(recv_fd, readfds)) { struct Reply_recv recv_rep; make_request(REQ_RECV, NULL, 0, &recv_rep, sizeof (recv_rep)); if (rep.ret != REPLY_SELECT_BROADCAST) { if (s >= 0 && sockets[s].buffer.len == 0) { sockets[s].buffer.len = recv_rep.len; assert(sockets[s].buffer.len <= sizeof (sockets[s].buffer.data)); memcpy(sockets[s].buffer.data, recv_rep.data, sockets[s].buffer.len); sockets[s].buffer.subnet = recv_rep.subnet; sockets[s].buffer.to_from = recv_rep.from; sockets[s].buffer.port = recv_rep.src_port; } else { fprintf(stderr, "clknetsim: dropped packet of type %d from " "node %d on port %d in subnet %d\n", recv_rep.type, recv_rep.from + 1, recv_rep.dst_port, recv_rep.subnet + 1); } } goto try_again; } break; default: assert(0); return 0; } assert(!recv_fd || (readfds && FD_ISSET(recv_fd, readfds))); assert(!recv_fd || (recv_fd >= BASE_SOCKET_FD && recv_fd < BASE_SOCKET_FD + MAX_SOCKETS) || (recv_fd >= BASE_TIMER_FD && recv_fd < BASE_TIMER_FD + MAX_TIMERS)); if (readfds) { FD_ZERO(readfds); if (recv_fd) { if (recv_fd == rtc_timerfd) recv_fd = RTC_FD; FD_SET(recv_fd, readfds); } } if (timeout) { time_to_timeval(timeval_to_time(timeout, 0) - elapsed, timeout); if (timeout->tv_sec < 0) { timeout->tv_sec = 0; timeout->tv_usec = 0; } } return recv_fd ? 1 : 0; } #ifndef CLKNETSIM_DISABLE_POLL int poll(struct pollfd *fds, nfds_t nfds, int timeout) { struct timeval tv, *ptv = NULL; fd_set rfds, wfds, efds; int r, maxfd = 0; nfds_t i; FD_ZERO(&rfds); FD_ZERO(&wfds); FD_ZERO(&efds); for (i = 0; i < nfds; i++) { if (fds[i].fd < 0) continue; assert(fds[i].fd < FD_SETSIZE); if (fds[i].events & POLLIN) FD_SET(fds[i].fd, &rfds); if (fds[i].events & POLLOUT) FD_SET(fds[i].fd, &wfds); if (fds[i].events & POLLPRI) FD_SET(fds[i].fd, &efds); if (maxfd < fds[i].fd) maxfd = fds[i].fd; } if (timeout >= 0) { tv.tv_sec = timeout / 1000; tv.tv_usec = (timeout % 1000) * 1000; ptv = &tv; } r = select(maxfd + 1, &rfds, &wfds, &efds, ptv); if (r < 0) return r; for (i = 0, r = 0; i < nfds; i++) { fds[i].revents = 0; if (fds[i].fd < 0) continue; if (FD_ISSET(fds[i].fd, &rfds)) fds[i].revents |= POLLIN; if (FD_ISSET(fds[i].fd, &wfds)) fds[i].revents |= POLLOUT; if (FD_ISSET(fds[i].fd, &efds)) fds[i].revents |= POLLPRI; if (fds[i].revents) r++; } return r; } int __poll_chk(struct pollfd *fds, nfds_t nfds, int timeout, size_t fdslen) { return poll(fds, nfds, timeout); } #endif int usleep(useconds_t usec) { struct timeval tv; int r; tv.tv_sec = usec / 1000000; tv.tv_usec = usec % 1000000; r = select(0, NULL, NULL, NULL, &tv); assert(r <= 0); return 0; } int nanosleep(const struct timespec *req, struct timespec *rem) { struct timeval tv; int r; tv.tv_sec = req->tv_sec; tv.tv_usec = req->tv_nsec / 1000 + 1; r = select(0, NULL, NULL, NULL, &tv); assert(r <= 0); if (r < 0) { assert(!rem); return r; } if (rem) rem->tv_sec = rem->tv_nsec = 0; return 0; } int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *request, struct timespec *remain) { assert(clock_id == CLOCK_MONOTONIC || clock_id == CLOCK_REALTIME); return nanosleep(request, remain); } int eventfd(unsigned int initval, int flags) { /* dummy file descriptor to disable libevent thread notification */ return timerfd_create(CLOCK_REALTIME, 0); } FILE *fopen(const char *path, const char *mode) { if (!strcmp(path, "/proc/net/if_inet6")) { errno = ENOENT; return NULL; } else if (!strcmp(path, "/dev/urandom")) { return URANDOM_FILE; } /* make sure _fopen is initialized in case it is called from another constructor (e.g. OpenSSL's libcrypto) */ init_symbols(); return _fopen(path, mode); } FILE *fopen64(const char *path, const char *mode) { return fopen(path, mode); } FILE *fdopen(int fd, const char *mode) { if (fd == URANDOM_FD) return URANDOM_FILE; return _fdopen(fd, mode); } size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) { if (stream == URANDOM_FILE) { if (read(URANDOM_FD, ptr, size * nmemb) != size * nmemb) assert(0); return nmemb; } return _fread(ptr, size, nmemb, stream); } size_t __fread_chk(void *ptr, size_t ptrlen, size_t size, size_t nmemb, FILE *stream) { return fread(ptr, size, nmemb, stream); } int fileno(FILE *stream) { if (stream == URANDOM_FILE) return -1; return _fileno(stream); } int fclose(FILE *fp) { if (fp == URANDOM_FILE) return 0; return _fclose(fp); } char *realpath(const char *path, char *resolved_path) { if (!strncmp(path, "/dev/ptp", 8)) { snprintf(resolved_path, PATH_MAX, "%s", path); return resolved_path; } return _realpath(path, resolved_path); } char *__realpath_chk(const char *name, char *resolved_path, size_t buflen) { return realpath(name, resolved_path); } int open(const char *pathname, int flags, ...) { int r, mode_arg = 0; mode_t mode = 0; va_list ap; mode_arg = flags & O_CREAT || (flags & O_TMPFILE) == O_TMPFILE; if (mode_arg) { va_start(ap, flags); mode = va_arg(ap, mode_t); va_end(ap); } assert(REFCLK_PHC_INDEX == 0 && SYSCLK_PHC_INDEX == 1); if (!strcmp(pathname, "/dev/ptp0")) return phc_swap ? SYSCLK_FD : REFCLK_FD; else if (!strcmp(pathname, "/dev/ptp1")) return phc_swap ? REFCLK_FD : SYSCLK_FD; else if (!strcmp(pathname, "/dev/pps0")) return pps_fds++, PPS_FD; else if (!strcmp(pathname, "/dev/rtc")) return RTC_FD; else if (!strcmp(pathname, "/dev/urandom")) return URANDOM_FD; init_symbols(); if (mode_arg) r = _open(pathname, flags, mode); else r = _open(pathname, flags); assert(r < 0 || (r < BASE_SOCKET_FD && r < BASE_TIMER_FD)); return r; } ssize_t read(int fd, void *buf, size_t count) { int t; if (fd == URANDOM_FD) { size_t i; long r; assert(RAND_MAX >= 0xffffff); for (i = r = 0; i < count; i++) { if (i % 3) r >>= 8; else r = random(); ((unsigned char *)buf)[i] = r; } return count; } else if (fd == RTC_FD) { unsigned long d = RTC_UF | 1 << 8; if (count < sizeof (d)) { errno = EINVAL; return -1; } memcpy(buf, &d, sizeof (d)); return sizeof (d); } else if ((t = get_timer_from_fd(fd)) >= 0) { if (count < sizeof (timers[t].expired)) { errno = EINVAL; return -1; } assert(timers[t].expired > 0); memcpy(buf, &timers[t].expired, sizeof (timers[t].expired)); timers[t].expired = 0; return sizeof (timers[t].expired); } return _read(fd, buf, count); } int close(int fd) { int t, s; if (fd == REFCLK_FD || fd == SYSCLK_FD || fd == RTC_FD || fd == URANDOM_FD) { return 0; } else if (fd == PPS_FD) { pps_fds--; return 0; } else if ((t = get_timer_from_fd(fd)) >= 0) { return timer_delete(get_timerid(t)); } else if ((s = get_socket_from_fd(fd)) >= 0) { if (sockets[s].type == SOCK_STREAM) shutdown(fd, SHUT_RDWR); sockets[s].used = 0; return 0; } return _close(fd); } int socket(int domain, int type, int protocol) { int s; if ((domain != AF_INET && (domain != AF_UNIX || unix_subnet < 0)) || (type != SOCK_DGRAM && type != SOCK_STREAM)) { errno = EINVAL; return -1; } s = get_free_socket(); if (s < 0) { errno = ENOMEM; return -1; } memset(sockets + s, 0, sizeof (struct socket)); sockets[s].used = 1; sockets[s].domain = domain; sockets[s].type = type; sockets[s].port = BASE_SOCKET_DEFAULT_PORT + s; sockets[s].iface = domain == AF_UNIX ? IFACE_UNIX : IFACE_ALL; sockets[s].remote_node = -1; sockets[s].remote_port = -1; return get_socket_fd(s); } int listen(int sockfd, int backlog) { int s = get_socket_from_fd(sockfd); if (s < 0 || sockets[s].domain != AF_INET || sockets[s].type != SOCK_STREAM) { errno = EINVAL; return -1; } sockets[s].listening = 1; return 0; } int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { int s = get_socket_from_fd(sockfd), r; struct sockaddr_in *in; struct Reply_recv rep; if (s < 0 || sockets[s].domain != AF_INET || sockets[s].type != SOCK_STREAM) { errno = EINVAL; return -1; } make_request(REQ_RECV, NULL, 0, &rep, sizeof (rep)); assert(rep.type == MSG_TYPE_TCP_CONNECT); r = socket(AF_INET, SOCK_STREAM, 0); s = get_socket_from_fd(r); assert(s >= 0); sockets[s].port = rep.dst_port; sockets[s].iface = IFACE_ETH0 + rep.subnet; sockets[s].remote_node = rep.from; sockets[s].remote_port = rep.src_port; sockets[s].connected = 1; in = (struct sockaddr_in *)addr; assert(*addrlen >= sizeof (*in)); *addrlen = sizeof (*in); in->sin_family = AF_INET; in->sin_port = htons(sockets[s].remote_port); in->sin_addr.s_addr = htonl(NODE_ADDR(sockets[s].iface - IFACE_ETH0, node)); send_msg_to_peer(s, MSG_TYPE_TCP_CONNECT); return r; } int shutdown(int sockfd, int how) { int s = get_socket_from_fd(sockfd); if (s < 0) { assert(0); errno = EINVAL; return -1; } assert(sockets[s].domain == AF_INET); assert(sockets[s].type == SOCK_STREAM); if (sockets[s].connected) { send_msg_to_peer(s, MSG_TYPE_TCP_DISCONNECT); sockets[s].connected = 0; } return 0; } int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { /* ntpd uses connect() and getsockname() to find the interface which will be used to send packets to an address */ int s = get_socket_from_fd(sockfd); unsigned int node, subnet; struct sockaddr_in *sin; struct sockaddr_un *sun; if (s < 0) { errno = EINVAL; return -1; } switch (addr->sa_family) { case AF_INET: sin = (struct sockaddr_in *)addr; assert(addrlen >= sizeof (*sin)); get_target(s, ntohl(sin->sin_addr.s_addr), &subnet, &node); if (node == -1) { errno = EINVAL; return -1; } sockets[s].iface = IFACE_ETH0 + subnet; sockets[s].remote_node = node; sockets[s].remote_port = ntohs(sin->sin_port); break; case AF_UNIX: sun = (struct sockaddr_un *)addr; assert(addrlen >= sizeof (*sun)); assert(sockets[s].iface == IFACE_UNIX); if (sscanf(sun->sun_path, "/clknetsim/unix/%d:%d", &sockets[s].remote_node, &sockets[s].remote_port) != 2) { errno = EINVAL; return -1; } sockets[s].remote_node--; break; default: errno = EINVAL; return -1; } if (sockets[s].type == SOCK_STREAM) send_msg_to_peer(s, MSG_TYPE_TCP_CONNECT); return 0; } int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { int s = get_socket_from_fd(sockfd), port; struct sockaddr_in *sin; struct sockaddr_un *sun; uint32_t a; static int unix_sockets = 0; if (s < 0) { errno = EINVAL; return -1; } switch (addr->sa_family) { case AF_INET: assert(addrlen >= sizeof (*sin)); sin = (struct sockaddr_in *)addr; port = ntohs(sin->sin_port); if (port) sockets[s].port = port; a = ntohl(sin->sin_addr.s_addr); if (a == INADDR_ANY) { sockets[s].iface = IFACE_ALL; } else if (a == INADDR_LOOPBACK) { sockets[s].iface = IFACE_LO; } else { int subnet = SUBNET_FROM_ADDR(a); assert(subnet >= 0 && subnet < subnets); if (a == NODE_ADDR(subnet, node)) { sockets[s].iface = IFACE_ETH0 + subnet; } else if (a == BROADCAST_ADDR(subnet)) { sockets[s].iface = IFACE_ETH0 + subnet; sockets[s].broadcast = 1; } else { assert(0); } } break; case AF_UNIX: assert(addrlen >= sizeof (*sun)); sun = (struct sockaddr_un *)addr; assert(sockets[s].iface == IFACE_UNIX); sockets[s].port = ++unix_sockets; break; default: errno = EINVAL; return -1; } return 0; } int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { int s = get_socket_from_fd(sockfd); uint32_t a; if (s < 0 || sockets[s].domain != AF_INET) { errno = EINVAL; return -1; } struct sockaddr_in *in; in = (struct sockaddr_in *)addr; assert(*addrlen >= sizeof (*in)); *addrlen = sizeof (*in); in->sin_family = AF_INET; in->sin_port = htons(sockets[s].port); switch (sockets[s].iface) { case IFACE_ALL: a = INADDR_ANY; break; case IFACE_UNIX: assert(0); case IFACE_LO: a = INADDR_LOOPBACK; break; default: assert(sockets[s].iface - IFACE_ETH0 < subnets); a = sockets[s].broadcast ? BROADCAST_ADDR(sockets[s].iface - IFACE_ETH0) : NODE_ADDR(sockets[s].iface - IFACE_ETH0, node); } in->sin_addr.s_addr = htonl(a); return 0; } int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) { int subnet, s = get_socket_from_fd(sockfd); if (s < 0 || sockets[s].domain != AF_INET) { errno = EINVAL; return -1; } if (level == SOL_SOCKET && optname == SO_BINDTODEVICE) { if (!strcmp(optval, "lo")) sockets[s].iface = IFACE_LO; else if ((subnet = get_network_from_iface(optval)) >= 0) sockets[s].iface = IFACE_ETH0 + subnet; else { errno = EINVAL; return -1; } } else if (level == IPPROTO_IP && optname == IP_PKTINFO && optlen == sizeof (int)) sockets[s].pkt_info = !!(int *)optval; #ifdef SO_TIMESTAMPING else if (level == SOL_SOCKET && optname == SO_TIMESTAMPING && optlen >= sizeof (int)) { if (!timestamping) { errno = EINVAL; return -1; } sockets[s].time_stamping = *(int *)optval; } #endif /* unhandled options succeed too */ return 0; } int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen) { int s = get_socket_from_fd(sockfd); if (s < 0 || sockets[s].domain != AF_INET) { errno = EINVAL; return -1; } if (level == SOL_SOCKET && optname == SO_ERROR && *optlen == sizeof (int)) { *(int *)optval = 0; } else { errno = EINVAL; return -1; } return 0; } int fcntl(int fd, int cmd, ...) { int i, s = get_socket_from_fd(fd); va_list ap; if (fd == RTC_FD) return 0; if (s < 0) { switch (cmd) { /* including fcntl.h breaks open() declaration */ case 0: /* F_DUPFD */ case 3: /* F_GETFL */ case 4: /* F_SETFL */ va_start(ap, cmd); i = va_arg(ap, int); va_end(ap); return _fcntl(fd, cmd, i); } } return 0; } int fstat(int fd, struct stat *statbuf) { if (fd == URANDOM_FD) return stat("/dev/urandom", statbuf); if (fd == REFCLK_FD || fd == SYSCLK_FD) { memset(statbuf, 0, sizeof (*statbuf)); statbuf->st_mode = S_IFCHR | 0660; statbuf->st_rdev = makedev(247, fd == REFCLK_FD ? 0 : 1); return 0; } #ifdef HAVE_STAT assert(_fstat); return _fstat(fd, statbuf); #else assert(_fxstat); return _fxstat(_STAT_VER, fd, statbuf); #endif } int __fxstat(int ver, int fd, struct stat *stat_buf) { return fstat(fd, stat_buf); } int stat(const char *pathname, struct stat *statbuf) { if (strcmp(pathname, "/clknetsim") == 0 || strcmp(pathname, "/clknetsim/unix") == 0) { memset(statbuf, 0, sizeof (*statbuf)); statbuf->st_mode = S_IFDIR | 0750; return 0; } init_symbols(); #ifdef HAVE_STAT assert(_stat); return _stat(pathname, statbuf); #else assert(_xstat); return _xstat(_STAT_VER, pathname, statbuf); #endif } int __xstat(int ver, const char *pathname, struct stat *statbuf) { return stat(pathname, statbuf); } int chmod(const char *pathname, mode_t mode) { return 0; } int ioctl(int fd, unsigned long request, ...) { int i, j, n, subnet, ret = 0, s = get_socket_from_fd(fd); va_list ap; struct ifconf *conf; struct ifreq *req; va_start(ap, request); if (request == SIOCGIFCONF) { conf = va_arg(ap, struct ifconf *); n = 1 + subnets - (unix_subnet >= 0 ? 1 : 0); assert(conf->ifc_len >= sizeof (struct ifreq) * n); conf->ifc_len = sizeof (struct ifreq) * n; sprintf(conf->ifc_req[0].ifr_name, "lo"); ((struct sockaddr_in*)&conf->ifc_req[0].ifr_addr)->sin_addr.s_addr = htonl(INADDR_LOOPBACK); conf->ifc_req[0].ifr_addr.sa_family = AF_INET; for (i = 0, j = 1; i < subnets && j < n; i++) { if (i == unix_subnet) continue; sprintf(conf->ifc_req[j].ifr_name, "eth%d", i); ((struct sockaddr_in *)&conf->ifc_req[j].ifr_addr)->sin_addr.s_addr = htonl(NODE_ADDR(i, node)); conf->ifc_req[j].ifr_addr.sa_family = AF_INET; j++; } } else if (request == SIOCGIFINDEX) { req = va_arg(ap, struct ifreq *); if (!strcmp(req->ifr_name, "lo")) req->ifr_ifindex = 0; else if ((subnet = get_network_from_iface(req->ifr_name)) >= 0) req->ifr_ifindex = subnet + 1; else ret = -1, errno = EINVAL; } else if (request == SIOCGIFFLAGS) { req = va_arg(ap, struct ifreq *); if (!strcmp(req->ifr_name, "lo")) req->ifr_flags = IFF_UP | IFF_LOOPBACK; else if (get_network_from_iface(req->ifr_name) >= 0) req->ifr_flags = IFF_UP | IFF_BROADCAST; else ret = -1, errno = EINVAL; } else if (request == SIOCGIFBRDADDR) { req = va_arg(ap, struct ifreq *); if ((subnet = get_network_from_iface(req->ifr_name)) >= 0) ((struct sockaddr_in*)&req->ifr_broadaddr)->sin_addr.s_addr = htonl(BROADCAST_ADDR(subnet)); else ret = -1, errno = EINVAL; req->ifr_broadaddr.sa_family = AF_INET; } else if (request == SIOCGIFNETMASK) { req = va_arg(ap, struct ifreq *); if (!strcmp(req->ifr_name, "lo")) ((struct sockaddr_in*)&req->ifr_netmask)->sin_addr.s_addr = htonl(0xff000000); else if (get_network_from_iface(req->ifr_name) >= 0) ((struct sockaddr_in*)&req->ifr_netmask)->sin_addr.s_addr = htonl(NETMASK); else ret = -1, errno = EINVAL; req->ifr_netmask.sa_family = AF_INET; } else if (request == SIOCGIFHWADDR) { req = va_arg(ap, struct ifreq *); if (!strcmp(req->ifr_name, "lo")) memset((&req->ifr_hwaddr)->sa_data, 0, IFHWADDRLEN); else if ((subnet = get_network_from_iface(req->ifr_name)) >= 0) { char mac[IFHWADDRLEN] = {0x12, 0x34, 0x56, 0x78, subnet + 1, node + 1}; memcpy((&req->ifr_hwaddr)->sa_data, mac, sizeof (mac)); } else ret = -1, errno = EINVAL; req->ifr_netmask.sa_family = AF_UNSPEC; } else if (request == SIOCETHTOOL) { struct ethtool_cmd *cmd; req = va_arg(ap, struct ifreq *); cmd = (struct ethtool_cmd *)req->ifr_data; if (cmd->cmd == ETHTOOL_GSET) { memset(cmd, 0, sizeof (*cmd)); ethtool_cmd_speed_set(cmd, link_speed); #ifdef ETHTOOL_GET_TS_INFO } else if (cmd->cmd == ETHTOOL_GET_TS_INFO) { struct ethtool_ts_info *info; info = (struct ethtool_ts_info *)req->ifr_data; memset(info, 0, sizeof (*info)); if (get_network_from_iface(req->ifr_name) >= 0) { info->phc_index = timestamping > 1 ? REFCLK_PHC_INDEX : SYSCLK_PHC_INDEX; info->so_timestamping = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE | SOF_TIMESTAMPING_RAW_HARDWARE | SOF_TIMESTAMPING_TX_HARDWARE | SOF_TIMESTAMPING_RX_HARDWARE; info->tx_types = HWTSTAMP_TX_ON; info->rx_filters = 1 << HWTSTAMP_FILTER_NONE | 1 << HWTSTAMP_FILTER_ALL; } else ret = -1, errno = EINVAL; #endif } else ret = -1, errno = EINVAL; #ifdef PTP_CLOCK_GETCAPS } else if (request == PTP_CLOCK_GETCAPS && (fd == REFCLK_FD || fd == SYSCLK_FD)) { struct ptp_clock_caps *caps = va_arg(ap, struct ptp_clock_caps *); memset(caps, 0, sizeof (*caps)); /* maximum frequency in 32-bit timex.freq */ caps->max_adj = 32767999; #endif #ifdef PTP_SYS_OFFSET } else if (request == PTP_SYS_OFFSET && fd == REFCLK_FD) { struct ptp_sys_offset *sys_off = va_arg(ap, struct ptp_sys_offset *); struct timespec ts, ts1, ts2; double delay; int i; if (sys_off->n_samples > PTP_MAX_SAMPLES) sys_off->n_samples = PTP_MAX_SAMPLES; clock_gettime(CLOCK_REALTIME, &ts); sys_off->ts[sys_off->n_samples * 2].sec = ts.tv_sec; sys_off->ts[sys_off->n_samples * 2].nsec = ts.tv_nsec; for (delay = 0.0, i = sys_off->n_samples - 1; i >= 0; i--) { delay += get_phc_delay(1); clock_gettime(REFCLK_ID, &ts1); add_to_timespec(&ts1, -delay); ts2 = ts; delay += get_phc_delay(-1); add_to_timespec(&ts2, -delay); sys_off->ts[2 * i + 1].sec = ts1.tv_sec; sys_off->ts[2 * i + 1].nsec = ts1.tv_nsec; sys_off->ts[2 * i + 0].sec = ts2.tv_sec; sys_off->ts[2 * i + 0].nsec = ts2.tv_nsec; } #endif #ifdef PTP_SYS_OFFSET_EXTENDED } else if (request == PTP_SYS_OFFSET_EXTENDED && fd == REFCLK_FD) { struct ptp_sys_offset_extended *sys_off = va_arg(ap, struct ptp_sys_offset_extended *); struct timespec ts, ts1, ts2; int i; if (sys_off->n_samples > PTP_MAX_SAMPLES) sys_off->n_samples = PTP_MAX_SAMPLES; for (i = 0; i < sys_off->n_samples; i++) { clock_gettime(REFCLK_ID, &ts); sys_off->ts[i][1].sec = ts.tv_sec; sys_off->ts[i][1].nsec = ts.tv_nsec; } clock_gettime(CLOCK_REALTIME, &ts); for (i = 0; i < sys_off->n_samples; i++) { ts1 = ts, ts2 = ts; add_to_timespec(&ts1, -get_phc_delay(-1)); add_to_timespec(&ts2, get_phc_delay(1)); sys_off->ts[i][0].sec = ts1.tv_sec; sys_off->ts[i][0].nsec = ts1.tv_nsec; sys_off->ts[i][2].sec = ts2.tv_sec; sys_off->ts[i][2].nsec = ts2.tv_nsec; } #endif #ifdef PTP_SYS_OFFSET_PRECISE } else if (request == PTP_SYS_OFFSET_PRECISE && fd == REFCLK_FD) { struct ptp_sys_offset_precise *sys_off = va_arg(ap, struct ptp_sys_offset_precise *); struct timespec ts; clock_gettime(REFCLK_ID, &ts); sys_off->device.sec = ts.tv_sec; sys_off->device.nsec = ts.tv_nsec; clock_gettime(CLOCK_REALTIME, &ts); sys_off->sys_realtime.sec = ts.tv_sec; sys_off->sys_realtime.nsec = ts.tv_nsec; #endif #ifdef SIOCSHWTSTAMP } else if (request == SIOCSHWTSTAMP && s >= 0) { #endif #ifdef SIOCGHWTSTAMP } else if (request == SIOCGHWTSTAMP && s >= 0) { struct hwtstamp_config *ts_config; req = va_arg(ap, struct ifreq *); ts_config = (struct hwtstamp_config *)req->ifr_data; ts_config->flags = 0; ts_config->tx_type = HWTSTAMP_TX_ON; ts_config->rx_filter = HWTSTAMP_FILTER_ALL; #endif } else if (request == PPS_GETCAP && fd == PPS_FD) { int *mode = va_arg(ap, int *); *mode = PPS_CAPTUREASSERT | PPS_TSFMT_TSPEC; } else if (request == PPS_GETPARAMS && fd == PPS_FD) { struct pps_kparams *params = va_arg(ap, struct pps_kparams *); memset(params, 0, sizeof (*params)); params->mode = PPS_CAPTUREASSERT | PPS_TSFMT_TSPEC; } else if (request == PPS_SETPARAMS && fd == PPS_FD) { struct pps_kparams *params = va_arg(ap, struct pps_kparams *); if (params->mode != (PPS_CAPTUREASSERT | PPS_TSFMT_TSPEC)) ret = -1, errno = EINVAL; } else if (request == PPS_FETCH && fd == PPS_FD) { static double last_refclock_time = 0.0; static unsigned long seq = 0; struct pps_fdata *data = va_arg(ap, struct pps_fdata *); memset(&data->info, 0, sizeof (data->info)); if (data->timeout.flags & PPS_TIME_INVALID || data->timeout.sec > 0 || data->timeout.nsec > 0) { double d, prev_shm_time = shm_refclock_time; while (prev_shm_time == shm_refclock_time) { d = ceil(network_time) - network_time + 0.001; usleep((d > 0.2 ? d : 0.2) * 1e6); } } if (shm_refclock_time > 0.0) { if (shm_refclock_time != last_refclock_time) seq++; last_refclock_time = shm_refclock_time; data->info.assert_sequence = seq; data->info.assert_tu.sec = shm_refclock_time; data->info.assert_tu.nsec = (shm_refclock_time - data->info.assert_tu.sec) * 1e9; data->info.assert_tu.sec += system_time_offset; } } else if (request == RTC_UIE_ON && fd == RTC_FD) { struct itimerspec it; if (rtc_timerfd) close(rtc_timerfd); rtc_timerfd = timerfd_create(CLOCK_MONOTONIC, 0); it.it_interval.tv_sec = 1; it.it_interval.tv_nsec = 0; it.it_value.tv_sec = 0; it.it_value.tv_nsec = (ceil(get_rtc_time()) - get_rtc_time() + 1e-6) * 1e9; normalize_timespec(&it.it_value); timerfd_settime(rtc_timerfd, 0, &it, NULL); } else if (request == RTC_UIE_OFF && fd == RTC_FD) { close(rtc_timerfd); rtc_timerfd = 0; } else if (request == RTC_RD_TIME && fd == RTC_FD) { struct rtc_time *rtc = va_arg(ap, struct rtc_time *); time_t t = (time_t)get_rtc_time() + system_time_offset; struct tm *tm = gmtime(&t); rtc->tm_sec = tm->tm_sec; rtc->tm_min = tm->tm_min; rtc->tm_hour = tm->tm_hour; rtc->tm_mday = tm->tm_mday; rtc->tm_mon = tm->tm_mon; rtc->tm_year = tm->tm_year; rtc->tm_wday = tm->tm_wday; rtc->tm_yday = tm->tm_yday; rtc->tm_isdst = tm->tm_isdst; } else if (request == RTC_SET_TIME && fd == RTC_FD) { struct rtc_time *rtc = va_arg(ap, struct rtc_time *); struct tm tm; tm.tm_sec = rtc->tm_sec; tm.tm_min = rtc->tm_min; tm.tm_hour = rtc->tm_hour; tm.tm_mday = rtc->tm_mday; tm.tm_mon = rtc->tm_mon; tm.tm_year = rtc->tm_year; tm.tm_isdst = 0; rtc_offset -= get_rtc_time() + system_time_offset - (timegm(&tm) + 0.5); } else { ret = -1; errno = EINVAL; } va_end(ap); return ret; } int getifaddrs(struct ifaddrs **ifap) { struct iface { struct ifaddrs ifaddrs; struct sockaddr_in addr, netmask, broadaddr; char name[16]; } *ifaces; int i, j; ifaces = malloc(sizeof (struct iface) * (1 + subnets)); ifaces[0].ifaddrs = (struct ifaddrs){ .ifa_next = &ifaces[1].ifaddrs, .ifa_name = "lo", .ifa_flags = IFF_UP | IFF_LOOPBACK | IFF_RUNNING, .ifa_addr = (struct sockaddr *)&ifaces[0].addr, .ifa_netmask = (struct sockaddr *)&ifaces[0].netmask, .ifa_broadaddr = (struct sockaddr *)&ifaces[0].broadaddr }; ifaces[0].addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); ifaces[0].netmask.sin_addr.s_addr = htonl(0xff000000); ifaces[0].broadaddr.sin_addr.s_addr = 0; for (i = 0, j = 1; i < subnets && j < 1 + subnets; i++) { if (i == unix_subnet) continue; ifaces[j].ifaddrs = (struct ifaddrs){ .ifa_next = &ifaces[j + 1].ifaddrs, .ifa_flags = IFF_UP | IFF_BROADCAST | IFF_RUNNING, .ifa_addr = (struct sockaddr *)&ifaces[j].addr, .ifa_netmask = (struct sockaddr *)&ifaces[j].netmask, .ifa_broadaddr = (struct sockaddr *)&ifaces[j].broadaddr }; ifaces[j].ifaddrs.ifa_name = ifaces[j].name; snprintf(ifaces[j].name, sizeof (ifaces[j].name), "eth%d", i); ifaces[j].addr.sin_addr.s_addr = htonl(NODE_ADDR(i, node)); ifaces[j].netmask.sin_addr.s_addr = htonl(NETMASK); ifaces[j].broadaddr.sin_addr.s_addr = htonl(BROADCAST_ADDR(i)); j++; } ifaces[j - 1].ifaddrs.ifa_next = NULL; for (i = 0; i < 1 + subnets; i++) { ifaces[i].addr.sin_family = AF_INET; ifaces[i].netmask.sin_family = AF_INET; ifaces[i].broadaddr.sin_family = AF_INET; } *ifap = (struct ifaddrs *)ifaces; return 0; } void freeifaddrs(struct ifaddrs *ifa) { free(ifa); } ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags) { struct Request_send req; struct sockaddr_in *sin; struct sockaddr_un *sun; struct cmsghdr *cmsg; int i, s = get_socket_from_fd(sockfd), timestamping; if (s < 0) { assert(0); errno = EINVAL; return -1; } if (sockets[s].remote_node >= 0) { if (msg->msg_name) { errno = EISCONN; return -1; } req.subnet = sockets[s].iface >= IFACE_ETH0 ? sockets[s].iface - IFACE_ETH0 : unix_subnet; req.to = sockets[s].remote_node; assert(sockets[s].remote_port >= 0); req.dst_port = sockets[s].remote_port; } else { switch (sockets[s].domain) { case AF_INET: sin = msg->msg_name; assert(sin && msg->msg_namelen >= sizeof (*sin)); assert(sin->sin_family == AF_INET); get_target(s, ntohl(sin->sin_addr.s_addr), &req.subnet, &req.to); req.dst_port = ntohs(sin->sin_port); break; case AF_UNIX: sun = msg->msg_name; assert(sun && msg->msg_namelen >= sizeof (*sun)); assert(sun->sun_family == AF_UNIX); req.subnet = unix_subnet; if (sscanf(sun->sun_path, "/clknetsim/unix/%u:%u", &req.to, &req.dst_port) != 2) { errno = EINVAL; return -1; } req.to--; break; default: assert(0); } } switch (sockets[s].type) { case SOCK_DGRAM: req.type = MSG_TYPE_UDP_DATA; break; case SOCK_STREAM: assert(sockets[s].connected); req.type = MSG_TYPE_TCP_DATA; break; default: assert(0); } assert(req.src_port >= 0); req.src_port = sockets[s].port; assert(socket_in_subnet(s, req.subnet)); for (req.len = 0, i = 0; i < msg->msg_iovlen; i++) { assert(req.len + msg->msg_iov[i].iov_len <= sizeof (req.data)); memcpy(req.data + req.len, msg->msg_iov[i].iov_base, msg->msg_iov[i].iov_len); req.len += msg->msg_iov[i].iov_len; } make_request(REQ_SEND, &req, offsetof(struct Request_send, data) + req.len, NULL, 0); write_pcap_packet(sockets[s].type, req.subnet, node, req.to, req.src_port, req.dst_port, req.data, req.len); timestamping = sockets[s].time_stamping; for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR((struct msghdr *)msg, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING) memcpy(×tamping, CMSG_DATA(cmsg), sizeof (timestamping)); } if (timestamping & (SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_TX_HARDWARE)) { struct message *last_ts_msg = &sockets[s].last_ts_msg; assert(req.len <= sizeof (last_ts_msg->data)); memcpy(last_ts_msg->data, req.data, req.len); last_ts_msg->len = req.len; last_ts_msg->subnet = req.subnet; last_ts_msg->to_from = req.to; last_ts_msg->port = req.dst_port; } return req.len; } ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) { struct msghdr msg; struct iovec iov; iov.iov_base = (void *)buf; iov.iov_len = len; msg.msg_name = (void *)dest_addr; msg.msg_namelen = addrlen; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = NULL; msg.msg_controllen = 0; msg.msg_flags = 0; return sendmsg(sockfd, &msg, flags); } ssize_t send(int sockfd, const void *buf, size_t len, int flags) { return sendto(sockfd, buf, len, flags, NULL, 0); } int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, int flags, #if !defined(__GLIBC_PREREQ) || !(__GLIBC_PREREQ(2, 20)) const #endif struct timespec *timeout) { ssize_t len; int i, n; assert(vlen > 0); len = recvmsg(sockfd, &msgvec[0].msg_hdr, flags); if (len < 0) return -1; msgvec[0].msg_len = len; if (recv_multiply <= 1 || vlen <= 1) return 1; n = random() % recv_multiply + 1; if (n > vlen) n = vlen; for (i = 1; i < n; i++) { struct msghdr *src = &msgvec[0].msg_hdr, *dst = &msgvec[i].msg_hdr; if (dst->msg_name) { memcpy(dst->msg_name, src->msg_name, src->msg_namelen); dst->msg_namelen = src->msg_namelen; } assert(dst->msg_iovlen == 1 && dst->msg_iov[0].iov_len >= len); memcpy(dst->msg_iov[0].iov_base, src->msg_iov[0].iov_base, len); if (dst->msg_control) { assert(dst->msg_controllen >= src->msg_controllen); memcpy(dst->msg_control, src->msg_control, src->msg_controllen); dst->msg_controllen = src->msg_controllen; } dst->msg_flags = src->msg_flags; msgvec[i].msg_len = msgvec[0].msg_len; } return n; } ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags) { struct message *last_ts_msg = NULL; struct Reply_recv rep; struct sockaddr_in *sin; struct sockaddr_un *sun; struct cmsghdr *cmsg; int msglen, cmsglen, s = get_socket_from_fd(sockfd); if (sockfd == clknetsim_fd) return _recvmsg(sockfd, msg, flags); assert(s >= 0); if (sockets[s].last_ts_msg.len && flags & MSG_ERRQUEUE) { /* last message looped back to the error queue */ last_ts_msg = &sockets[s].last_ts_msg; msg->msg_flags = MSG_ERRQUEUE; assert(sockets[s].type == SOCK_DGRAM); rep.type = MSG_TYPE_UDP_DATA; rep.subnet = last_ts_msg->subnet; rep.from = last_ts_msg->to_from; rep.src_port = last_ts_msg->port; rep.dst_port = sockets[s].port; /* put the message in an Ethernet frame */ rep.len = generate_eth_frame(sockets[s].type, last_ts_msg->subnet, node, last_ts_msg->to_from, sockets[s].port, last_ts_msg->port, last_ts_msg->data, last_ts_msg->len, rep.data, sizeof (rep.data)); rep.len = 42 + last_ts_msg->len; last_ts_msg->len = 0; } else if (sockets[s].buffer.len > 0) { switch (sockets[s].type) { case SOCK_STREAM: assert(sockets[s].remote_node != -1); rep.type = MSG_TYPE_TCP_DATA; break; case SOCK_DGRAM: rep.type = MSG_TYPE_UDP_DATA; break; default: assert(0); } assert(sockets[s].buffer.len <= sizeof (rep.data)); memcpy(rep.data, sockets[s].buffer.data, sockets[s].buffer.len); rep.subnet = sockets[s].buffer.subnet; rep.from = sockets[s].buffer.to_from; rep.src_port = sockets[s].buffer.port; rep.dst_port = sockets[s].port; rep.len = sockets[s].buffer.len; sockets[s].buffer.len = 0; } else { make_request(REQ_RECV, NULL, 0, &rep, sizeof (rep)); switch (rep.type) { case MSG_TYPE_NO_MSG: errno = EWOULDBLOCK; return -1; case MSG_TYPE_UDP_DATA: assert(sockets[s].type == SOCK_DGRAM); break; case MSG_TYPE_TCP_DATA: case MSG_TYPE_TCP_DISCONNECT: assert(sockets[s].type == SOCK_STREAM); assert(sockets[s].remote_node >= 0); assert(sockets[s].remote_port >= 0); if (!sockets[s].connected) { errno = ENOTCONN; return -1; } if (rep.type == MSG_TYPE_TCP_DISCONNECT) { assert(rep.len == 0); sockets[s].connected = 0; } break; default: assert(0); } write_pcap_packet(sockets[s].type, rep.subnet, rep.from, node, rep.src_port, rep.dst_port, rep.data, rep.len); } assert(socket_in_subnet(s, rep.subnet)); assert(sockets[s].port == rep.dst_port); assert(sockets[s].remote_port == -1 || sockets[s].remote_port == rep.src_port); if (msg->msg_name) { switch (sockets[s].domain) { case AF_INET: assert(msg->msg_namelen >= sizeof (*sin)); sin = msg->msg_name; sin->sin_family = AF_INET; sin->sin_port = htons(rep.src_port); sin->sin_addr.s_addr = htonl(NODE_ADDR(rep.subnet, rep.from)); msg->msg_namelen = sizeof (struct sockaddr_in); break; case AF_UNIX: assert(msg->msg_namelen >= sizeof (*sun)); sun = msg->msg_name; sun->sun_family = AF_UNIX; snprintf(sun->sun_path, sizeof (sun->sun_path), "/clknetsim/unix/%d:%d", rep.from + 1, rep.src_port); break; default: assert(0); } } assert(msg->msg_iovlen == 1); msglen = msg->msg_iov[0].iov_len < rep.len ? msg->msg_iov[0].iov_len : rep.len; memcpy(msg->msg_iov[0].iov_base, rep.data, msglen); if (sockets[s].type == SOCK_STREAM) { if (msglen < rep.len) { sockets[s].buffer.len = rep.len - msglen; assert(sockets[s].buffer.len <= sizeof (sockets[s].buffer.data)); memcpy(sockets[s].buffer.data, rep.data + msglen, rep.len - msglen); sockets[s].buffer.subnet = rep.subnet; sockets[s].buffer.to_from = rep.from; sockets[s].buffer.port = rep.src_port; } else { sockets[s].buffer.len = 0; } } cmsglen = 0; if (sockets[s].pkt_info) { struct in_pktinfo ipi; cmsglen = CMSG_SPACE(sizeof (ipi)); assert(msg->msg_control && msg->msg_controllen >= cmsglen); cmsg = CMSG_FIRSTHDR(msg); memset(cmsg, 0, sizeof (*cmsg)); cmsg->cmsg_level = IPPROTO_IP; cmsg->cmsg_type = IP_PKTINFO; cmsg->cmsg_len = CMSG_LEN(sizeof (ipi)); memset(&ipi, 0, sizeof (ipi)); ipi.ipi_spec_dst.s_addr = htonl(NODE_ADDR(rep.subnet, node)); ipi.ipi_addr.s_addr = ipi.ipi_spec_dst.s_addr; ipi.ipi_ifindex = rep.subnet + 1; memcpy(CMSG_DATA(cmsg), &ipi, sizeof (ipi)); } #ifdef SO_TIMESTAMPING if (last_ts_msg || (sockets[s].time_stamping & (SOF_TIMESTAMPING_RX_SOFTWARE | SOF_TIMESTAMPING_RX_HARDWARE) && !(flags & MSG_ERRQUEUE))) { struct timespec ts; /* don't use CMSG_NXTHDR as it's buggy in glibc */ cmsg = (struct cmsghdr *)((char *)CMSG_FIRSTHDR(msg) + cmsglen); cmsglen += CMSG_SPACE(3 * sizeof (ts)); assert(msg->msg_control && msg->msg_controllen >= cmsglen); memset(cmsg, 0, CMSG_SPACE(3 * sizeof (ts))); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_TIMESTAMPING; cmsg->cmsg_len = CMSG_LEN(3 * sizeof (ts)); if (sockets[s].time_stamping & SOF_TIMESTAMPING_SOFTWARE) { clock_gettime(CLOCK_REALTIME, &ts); memcpy((struct timespec *)CMSG_DATA(cmsg), &ts, sizeof (ts)); } if (sockets[s].time_stamping & SOF_TIMESTAMPING_RAW_HARDWARE) { clock_gettime(timestamping > 1 ? REFCLK_ID : CLOCK_REALTIME, &ts); if (!(flags & MSG_ERRQUEUE)) add_to_timespec(&ts, -(8 * (msglen + 42 + 4) / (1e6 * link_speed))); memcpy((struct timespec *)CMSG_DATA(cmsg) + 2, &ts, sizeof (ts)); #ifdef SCM_TIMESTAMPING_PKTINFO if (!(flags & MSG_ERRQUEUE) && (sockets[s].time_stamping & SOF_TIMESTAMPING_OPT_PKTINFO) == SOF_TIMESTAMPING_OPT_PKTINFO) { struct scm_ts_pktinfo tpi; cmsg = (struct cmsghdr *)((char *)CMSG_FIRSTHDR(msg) + cmsglen); cmsglen += CMSG_SPACE(sizeof (tpi)); assert(msg->msg_control && msg->msg_controllen >= cmsglen); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_TIMESTAMPING_PKTINFO; cmsg->cmsg_len = CMSG_LEN(sizeof (tpi)); memset(&tpi, 0, sizeof (tpi)); tpi.if_index = rep.subnet + 1; tpi.pkt_length = msglen + 42; memcpy(CMSG_DATA(cmsg), &tpi, sizeof (tpi)); } #endif } } #endif msg->msg_controllen = cmsglen; return msglen; } ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) { ssize_t ret; struct msghdr msg; struct iovec iov; iov.iov_base = (void *)buf; iov.iov_len = len; /* needed for compatibility with old glibc recvmsg() */ memset(&msg, 0, sizeof (msg)); msg.msg_name = (void *)src_addr; msg.msg_namelen = *addrlen; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = NULL; msg.msg_controllen = 0; msg.msg_flags = 0; ret = recvmsg(sockfd, &msg, flags); *addrlen = msg.msg_namelen; return ret; } ssize_t recv(int sockfd, void *buf, size_t len, int flags) { union { struct sockaddr_in sin; struct sockaddr_un sun; } sa; socklen_t addrlen = sizeof (sa); return recvfrom(sockfd, buf, len, flags, (struct sockaddr *)&sa, &addrlen); } ssize_t __recv_chk(int fd, void *buf, size_t len, size_t buflen, int flags) { return recv(fd, buf, len, flags); } int timer_create(clockid_t which_clock, struct sigevent *timer_event_spec, timer_t *created_timer_id) { int t; assert(which_clock == CLOCK_REALTIME && timer_event_spec == NULL); t = get_free_timer(); if (t < 0) { assert(0); errno = ENOMEM; return -1; } timers[t].used = 1; timers[t].armed = 0; timers[t].type = TIMER_TYPE_SIGNAL; timers[t].fd_flags = 0; timers[t].expired = 0; timers[t].clock_id = which_clock; *created_timer_id = get_timerid(t); return 0; } int timer_delete(timer_t timerid) { int t = get_timer_from_id(timerid); if (t < 0) { errno = EINVAL; return -1; } timers[t].used = 0; return 0; } int timer_settime(timer_t timerid, int flags, const struct itimerspec *value, struct itimerspec *ovalue) { int t = get_timer_from_id(timerid); if (t < 0) { errno = EINVAL; return -1; } assert(value && ovalue == NULL && (flags == 0 || (flags == TIMER_ABSTIME && timers[t].clock_id == CLOCK_MONOTONIC))); if (value->it_value.tv_sec || value->it_value.tv_nsec) { timers[t].armed = 1; timers[t].expired = 0; timers[t].timeout = timespec_to_time(&value->it_value, 0); if (!(flags & TIMER_ABSTIME)) timers[t].timeout += get_monotonic_time(); timers[t].interval = timespec_to_time(&value->it_interval, 0); } else { timers[t].armed = 0; } return 0; } int timer_gettime(timer_t timerid, struct itimerspec *value) { double timeout; int t = get_timer_from_id(timerid); if (t < 0) { errno = EINVAL; return -1; } if (timers[t].armed) { timeout = timers[t].timeout - get_monotonic_time(); time_to_timespec(timeout, &value->it_value); } else { value->it_value.tv_sec = 0; value->it_value.tv_nsec = 0; } time_to_timespec(timers[t].interval, &value->it_interval); return 0; } #ifndef CLKNETSIM_DISABLE_ITIMER int setitimer(__itimer_which_t which, const struct itimerval *new_value, struct itimerval *old_value) { struct itimerspec timerspec; assert(which == ITIMER_REAL && old_value == NULL); if (get_timer_from_id(itimer_real_id) < 0) timer_create(CLOCK_REALTIME, NULL, &itimer_real_id); timerspec.it_interval.tv_sec = new_value->it_interval.tv_sec; timerspec.it_interval.tv_nsec = new_value->it_interval.tv_usec * 1000; timerspec.it_value.tv_sec = new_value->it_value.tv_sec; timerspec.it_value.tv_nsec = new_value->it_value.tv_usec * 1000; return timer_settime(itimer_real_id, 0, &timerspec, NULL); } int getitimer(__itimer_which_t which, struct itimerval *curr_value) { struct itimerspec timerspec; assert(which == ITIMER_REAL); if (timer_gettime(itimer_real_id, &timerspec)) return -1; curr_value->it_interval.tv_sec = timerspec.it_interval.tv_sec; curr_value->it_interval.tv_usec = timerspec.it_interval.tv_nsec / 1000; curr_value->it_value.tv_sec = timerspec.it_value.tv_sec; curr_value->it_value.tv_usec = timerspec.it_value.tv_nsec / 1000; return 0; } #endif int timerfd_create(int clockid, int flags) { int t; assert((clockid == CLOCK_REALTIME || clockid == CLOCK_MONOTONIC) && !(flags & ~TFD_NONBLOCK)); t = get_free_timer(); if (t < 0) { assert(0); errno = ENOMEM; return -1; } timers[t].used = 1; timers[t].armed = 0; timers[t].type = TIMER_TYPE_FD; timers[t].fd_flags = flags; timers[t].expired = 0; timers[t].clock_id = clockid; return get_timerfd(t); } int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value) { if (flags == TFD_TIMER_ABSTIME) flags = TIMER_ABSTIME; else assert(!flags); return timer_settime(get_timerid(get_timer_from_fd(fd)), flags, new_value, old_value); } int timerfd_gettime(int fd, struct itimerspec *curr_value) { return timer_gettime(get_timerid(get_timer_from_fd(fd)), curr_value); } int shmget(key_t key, size_t size, int shmflg) { if (fuzz_mode) return _shmget(key, size, shmflg); if (key >= SHM_KEY && key < SHM_KEY + SHM_REFCLOCKS) return key; return -1; } void *shmat(int shmid, const void *shmaddr, int shmflg) { if (fuzz_mode) return _shmat(shmid, shmaddr, shmflg); assert(shmid >= SHM_KEY && shmid < SHM_KEY + SHM_REFCLOCKS); if (shm_refclocks < shmid - SHM_KEY + 1) shm_refclocks = shmid - SHM_KEY + 1; memset(&shm_time[shmid - SHM_KEY], 0, sizeof (shm_time[0])); shm_time[shmid - SHM_KEY].mode = 1; shm_time[shmid - SHM_KEY].precision = -20; /* don't wait for select() with starting of the refclock generator */ fill_refclock_sample(); return &shm_time[shmid - SHM_KEY]; } int shmdt(const void *shmaddr) { assert(shmaddr >= (void *)&shm_time[0] && shmaddr < (void *)&shm_time[SHM_REFCLOCKS]); return 0; } int uname(struct utsname *buf) { memset(buf, 0, sizeof (*buf)); sprintf(buf->sysname, "Linux (clknetsim)"); sprintf(buf->release, "4.19"); return 0; } int gethostname(char *name, size_t len) { snprintf(name, len, "clknetsim-node%d", node + 1); return 0; } void openlog(const char *ident, int option, int facility) { } void __syslog_chk(int priority, int flag, const char *format, ...) { va_list ap; va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); fprintf(stderr, "\n"); } void __syslog_chkieee128(int priority, int flag, const char *format, ...) { va_list ap; va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); fprintf(stderr, "\n"); } void syslog(int priority, const char *format, ...) { va_list ap; va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); fprintf(stderr, "\n"); } void closelog(void) { } #ifndef CLKNETSIM_DISABLE_SYSCALL long syscall(long number, ...) { va_list ap; long r; struct timex *timex; clockid_t clock_id; va_start(ap, number); switch (number) { #ifdef __NR_clock_adjtime case __NR_clock_adjtime: clock_id = va_arg(ap, clockid_t); timex = va_arg(ap, struct timex *); r = clock_adjtime(clock_id, timex); break; #endif #ifdef __NR_getrandom case __NR_getrandom: if (1) { void *buf = va_arg(ap, void *); size_t length = va_arg(ap, size_t); r = read(URANDOM_FD, buf, length); } break; #endif default: assert(0); } va_end(ap); return r; } #endif ssize_t getrandom(void *buf, size_t length, unsigned int flags) { return read(URANDOM_FD, buf, length); } void srandom(unsigned int seed) { FILE *f; /* override the seed to the fixed seed if set or make it truly random in case it's based on the simulated time */ if (random_seed) { seed = random_seed + node; } else if ((f = _fopen("/dev/urandom", "r"))) { if (fread(&seed, sizeof (seed), 1, f) != 1) ; fclose(f); } _srandom(seed); } struct passwd *getpwnam(const char *name) { static struct passwd pw = { .pw_name = "", .pw_passwd = "", .pw_uid = 0, .pw_gid = 0, .pw_gecos = "", .pw_dir = "", .pw_shell = "" }; return &pw; } int initgroups(const char *user, gid_t group) { return 0; } int setgroups(size_t size, const gid_t *list) { return 0; } uid_t getuid(void) { return 0; } uid_t geteuid(void) { return 0; } gid_t getgid(void) { return 0; } gid_t getegid(void) { return 0; } int setegid(gid_t gid) { return 0; } int setgid(gid_t gid) { return 0; } int seteuid(uid_t uid) { return 0; } int setuid(uid_t uid) { return 0; } int cap_set_proc() { return 0; } static struct addrinfo *get_addrinfo(uint32_t addr, int port, int type, struct addrinfo *next) { struct sockaddr_in *sin; struct addrinfo *r; sin = malloc(sizeof *sin); sin->sin_family = AF_INET; sin->sin_port = htons(port); sin->sin_addr.s_addr = htonl(addr); r = malloc(sizeof *r); memset(r, 0, sizeof *r); r->ai_family = AF_INET; r->ai_socktype = type; r->ai_addrlen = sizeof *sin; r->ai_addr = (struct sockaddr *)sin; r->ai_next = next; return r; } int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) { int port = 0, type = SOCK_DGRAM; struct in_addr addr; if (hints) { if ((hints->ai_family != AF_UNSPEC && hints->ai_family != AF_INET) || (hints->ai_socktype != SOCK_STREAM && hints->ai_socktype != SOCK_DGRAM && hints->ai_socktype != 0)) return EAI_NONAME; if (hints->ai_socktype == SOCK_STREAM) type = SOCK_STREAM; } if (service) { if (strcmp(service, "ntp") == 0) port = 123; else if (service[0] >= '0' && service[0] <= '9') port = atoi(service); else return EAI_NONAME; } if (node == NULL) { *res = get_addrinfo(INADDR_ANY, port, type, NULL); } else if (inet_aton(node, &addr)) { *res = get_addrinfo(ntohl(addr.s_addr), port, type, NULL); } else if ((strlen(node) > 4 && strcmp(node + strlen(node) - 4, ".clk") == 0) || (strlen(node) > 5 && strcmp(node + strlen(node) - 5, ".clk.") == 0)) { const char *s = strstr(node, ".net"); int subnet; if (s == NULL) return EAI_NONAME; subnet = atoi(s + 4) - 1; if (strncmp(node, "nodes-", 6) == 0) { s = node + 5; *res = NULL; do { *res = get_addrinfo(NODE_ADDR(subnet, atoi(s + 1) - 1), port, type, *res); s = strchr(s + 1, '-'); } while (s); } else if (strncmp(node, "node", 4) == 0) { *res = get_addrinfo(NODE_ADDR(subnet, atoi(node + 4) - 1), port, type, NULL); } else { return EAI_NONAME; } } else { return EAI_NONAME; } return 0; } void freeaddrinfo(struct addrinfo *res) { if (res->ai_next) freeaddrinfo(res->ai_next); free(res->ai_addr); free(res); } int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags) { const struct sockaddr_in *sin = (const struct sockaddr_in *)addr; int node, subnet; if (addrlen < sizeof *sin || sin->sin_family != AF_INET) return EAI_NONAME; assert(!(flags & NI_NOFQDN)); if (host && hostlen > 0) { if (flags & NI_NUMERICHOST) { assert(addr->sa_family == AF_INET); if (!inet_ntop(AF_INET, &((struct sockaddr_in *)addr)->sin_addr, host, hostlen)) return EAI_OVERFLOW; } else { node = NODE_FROM_ADDR(ntohl(sin->sin_addr.s_addr)); subnet = SUBNET_FROM_ADDR(ntohl(sin->sin_addr.s_addr)); if (subnet < 0 || subnet > 100) { assert(flags & NI_NAMEREQD); return EAI_NONAME; } if (snprintf(host, hostlen, "node%d.net%d.clk", node + 1, subnet + 1) >= hostlen) return EAI_OVERFLOW; } } if (serv && servlen > 0) { if (flags & NI_NUMERICSERV) { assert(addr->sa_family == AF_INET); if (snprintf(serv, servlen, "%d", ntohs(((struct sockaddr_in *)addr)->sin_port)) >= servlen) return EAI_OVERFLOW; } else { switch (ntohs(sin->sin_port)) { case 123: if (snprintf(serv, servlen, "ntp") >= servlen) return EAI_OVERFLOW; break; default: if (snprintf(serv, servlen, "%u", ntohs(sin->sin_port)) >= servlen) return EAI_OVERFLOW; } } } return 0; } struct hostent *gethostbyname(const char *name) { h_errno = HOST_NOT_FOUND; return NULL; } struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type) { h_errno = HOST_NOT_FOUND; return NULL; } 07070100000005000081A40000000000000000000000016666F04D00001D37000000000000000000000000000000000000002700000000clknetsim-0+git.20240610/client_fuzz.c/* * Copyright (C) 2015 Miroslav Lichvar <mlichvar@redhat.com> * * 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, see <http://www.gnu.org/licenses/>. */ /* This is a minimal replacement for the clknetsim server to allow fuzz testing. There is no clock control or networking. When the time reaches fuzz_start, a packet read from stdin is forwarded to the fuzz_port port of the client, and the client is terminated. Packets sent by the client from the port are written to stdout. */ enum { FUZZ_MODE_DISABLED = 0, FUZZ_MODE_ONESHOT = 1, FUZZ_MODE_BURST = 2, FUZZ_MODE_REPLY = 3, FUZZ_MODE_NONE = 4, }; #define FUZZ_FLAG_TIMEOUT 1024 #define MAX_FUZZ_PORTS 16 static int fuzz_mode; static int fuzz_msg_type; static int fuzz_ports[MAX_FUZZ_PORTS]; static int fuzz_port_index, fuzz_ports_n; static int fuzz_subnet; static int fuzz_timeout; static double fuzz_start; static int fuzz_init(void) { const char *env; env = getenv("CLKNETSIM_FUZZ_MODE"); if (!env) return 0; fuzz_mode = atoi(env); if (fuzz_mode & FUZZ_FLAG_TIMEOUT) { fuzz_timeout = 1; fuzz_mode &= ~FUZZ_FLAG_TIMEOUT; } if (fuzz_mode == FUZZ_MODE_DISABLED) return 0; if (fuzz_mode < FUZZ_MODE_ONESHOT || fuzz_mode > FUZZ_MODE_NONE) { fprintf(stderr, "clknetsim: unknown fuzz mode.\n"); exit(1); } env = getenv("CLKNETSIM_FUZZ_MSG_TYPE"); fuzz_msg_type = env ? atoi(env) : MSG_TYPE_UDP_DATA; env = getenv("CLKNETSIM_FUZZ_PORT"); for (fuzz_ports_n = 0; env && fuzz_ports_n < MAX_FUZZ_PORTS; fuzz_ports_n++) { fuzz_ports[fuzz_ports_n] = atoi(env); if (!fuzz_ports[fuzz_ports_n]) break; env = strchr(env, ','); if (env) env++; } if (!fuzz_ports_n) { fprintf(stderr, "clknetsim: CLKNETSIM_FUZZ_PORT variable not set or invalid.\n"); exit(1); } fuzz_port_index = 0; env = getenv("CLKNETSIM_FUZZ_SUBNET"); fuzz_subnet = env ? atoi(env) - 1 : 0; env = getenv("CLKNETSIM_FUZZ_START"); fuzz_start = env ? atof(env) : 0.1; return 1; } static int fuzz_is_fuzz_port(int port) { int i; for (i = 0; i < fuzz_ports_n; i++) if (fuzz_ports[i] == port) return 1; return 0; } static int fuzz_get_fuzz_port(void) { return fuzz_ports[fuzz_port_index]; } static void fuzz_switch_fuzz_port(void) { fuzz_port_index = (fuzz_port_index + 1) % fuzz_ports_n; } static int fuzz_read_packet(char *data, int maxlen, int *rlen) { int len; uint16_t slen; if (fuzz_mode > FUZZ_MODE_ONESHOT) { if (fread(&slen, 1, sizeof (slen), stdin) != sizeof (slen)) return 0; len = ntohs(slen); if (len > maxlen) len = maxlen; } else { len = maxlen; } *rlen = fread(data, 1, len, stdin); return !len || rlen; } static void fuzz_write_packet(const char *data, int len) { uint16_t slen; if (fuzz_mode > FUZZ_MODE_ONESHOT) { slen = htons(len); fwrite(&slen, 1, sizeof (slen), stdout); } fwrite(data, 1, len, stdout); } static void get_recv_data(int valid_packet, int received, int last_tx_src_port, unsigned int *type, unsigned int *subnet, unsigned int *from, unsigned int *src_port, unsigned int *dst_port) { if (valid_packet) { if (fuzz_msg_type == MSG_TYPE_TCP_DATA && received == 0) *type = MSG_TYPE_TCP_CONNECT; else *type = fuzz_msg_type; *from = 1; } else { *type = MSG_TYPE_NO_MSG; *from = -1; } *subnet = fuzz_subnet; *src_port = fuzz_get_fuzz_port(); *dst_port = last_tx_src_port ? last_tx_src_port : fuzz_get_fuzz_port(); } static void fuzz_process_request(int request_id, const union Request_data *request, union Reply_data *reply, int replylen) { static double network_time = 0.0; static int received = 0; static int sent = 0; static int last_tx_src_port = 0; static int packet_len = 0; static int valid_packet = 0; static char packet[MAX_PACKET_SIZE]; if (reply) memset(reply, 0, replylen); switch (request_id) { case REQ_GETTIME: reply->gettime.real_time = network_time; reply->gettime.monotonic_time = network_time; reply->gettime.network_time = network_time; break; case REQ_SELECT: if (fuzz_mode == FUZZ_MODE_NONE) { network_time += request->select.timeout; reply->select.ret = REPLY_SELECT_TIMEOUT; reply->select.time.real_time = network_time; reply->select.time.monotonic_time = network_time; reply->select.time.network_time = network_time; return; } if (!valid_packet && (!received || fuzz_mode != FUZZ_MODE_ONESHOT)) valid_packet = fuzz_read_packet(packet, sizeof (packet), &packet_len); if (!valid_packet) { reply->select.ret = REPLY_SELECT_TERMINATE; } else if (!packet_len && fuzz_timeout) { network_time += request->select.timeout; reply->select.ret = REPLY_SELECT_TIMEOUT; valid_packet = 0; } else { if (fuzz_mode == FUZZ_MODE_REPLY) { if (sent > received) { reply->select.ret = REPLY_SELECT_NORMAL; } else { network_time += request->select.timeout; reply->select.ret = REPLY_SELECT_TIMEOUT; } } else { if (network_time < fuzz_start && !sent) { network_time += request->select.timeout; if (network_time >= fuzz_start) { network_time = fuzz_start; reply->select.ret = REPLY_SELECT_NORMAL; } else { reply->select.ret = REPLY_SELECT_TIMEOUT; } } else { reply->select.ret = REPLY_SELECT_NORMAL; } } } get_recv_data(valid_packet, received, last_tx_src_port, &reply->select.type, &reply->select.subnet, &reply->select.from, &reply->select.src_port, &reply->select.dst_port); reply->select.time.real_time = network_time; reply->select.time.monotonic_time = network_time; reply->select.time.network_time = network_time; break; case REQ_SEND: if (request->send.to != 1 && request->send.to != -1) break; if (fuzz_mode == FUZZ_MODE_REPLY) { if (!fuzz_is_fuzz_port(request->send.dst_port)) break; last_tx_src_port = request->send.src_port; } else if (!fuzz_is_fuzz_port(request->send.src_port)) break; fuzz_write_packet(request->send.data, request->send.len); sent++; break; case REQ_RECV: network_time += 1e-5; get_recv_data(valid_packet, received, last_tx_src_port, &reply->recv.type, &reply->recv.subnet, &reply->recv.from, &reply->recv.src_port, &reply->recv.dst_port); received++; if (reply->recv.type != fuzz_msg_type) { reply->recv.len = 0; break; } memcpy(reply->recv.data, packet, packet_len); reply->recv.len = packet_len; valid_packet = 0; packet_len = 0; fuzz_switch_fuzz_port(); break; case REQ_SETTIME: network_time = request->settime.time; break; case REQ_GETREFOFFSETS: reply->getrefoffsets.size = MAX_GETREFOFFSETS_SIZE; break; case REQ_ADJTIME: case REQ_GETREFSAMPLE: case REQ_DEREGISTER: break; case REQ_ADJTIMEX: reply->adjtimex.timex.tick = 10000; break; case REQ_REGISTER: default: assert(0); } } 07070100000006000081A40000000000000000000000016666F04D00001C80000000000000000000000000000000000000002800000000clknetsim-0+git.20240610/clknetsim.bash# Copyright (C) 2010, 2011 Miroslav Lichvar <mlichvar@redhat.com> # # 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, see <http://www.gnu.org/licenses/>. [ -n "$CLKNETSIM_TMPDIR" ] || CLKNETSIM_TMPDIR=tmp client_pids="" start_client() { local node=$1 client=$2 config=$3 suffix=$4 opts=$5 local args=() line lastpid wrapper_options="" rm -f $CLKNETSIM_TMPDIR/log.$node $CLKNETSIM_TMPDIR/conf.$node [ $client = chrony ] && client=chronyd [ $client = ntp ] && client=ntpd if ! which $client$suffix &> /dev/null; then echo "can't find $client$suffix in PATH" return 1 fi case $client in chronyd) cat > $CLKNETSIM_TMPDIR/conf.$node <<-EOF pidfile $CLKNETSIM_TMPDIR/pidfile.$node allow cmdallow bindcmdaddress 0.0.0.0 bindcmdaddress /clknetsim/unix/chronyd.sock $config EOF args=(-d -f $CLKNETSIM_TMPDIR/conf.$node $opts) ;; ntpd) cat > $CLKNETSIM_TMPDIR/conf.$node <<-EOF pidfile $CLKNETSIM_TMPDIR/pidfile.$node restrict default $config EOF args=(-n -c $CLKNETSIM_TMPDIR/conf.$node $opts) ;; ptp4l) cat > $CLKNETSIM_TMPDIR/conf.$node <<-EOF [global] $config EOF args=(-f $CLKNETSIM_TMPDIR/conf.$node $opts) ;; chronyc) args=($opts -m) while read line; do args+=("$line"); done <<< "$config" ;; pmc) args=($opts) while read line; do args+=("$line"); done <<< "$config" ;; ntpq) while read line; do args+=(-c "$line"); done <<< "$config" args+=($opts) ;; sntp) args=(-K /dev/null $opts $config) ;; ntpdate) args=($opts $config) ;; busybox) args=(ntpd -ddd -n) while read line; do args+=(-p "$line"); done <<< "$config" args+=($opts) ;; phc2sys) args=($opts $config) ;; nsm) args=($opts) while read line; do args+=("$line"); done <<< "$config" ;; *) echo "unknown client $client" exit 1 ;; esac if [[ $CLKNETSIM_CLIENT_WRAPPER == *valgrind* ]]; then unset DEBUGINFOD_URLS wrapper_options="--log-file=$CLKNETSIM_TMPDIR/valgrind.$node" fi LD_PRELOAD=${CLKNETSIM_PRELOAD:+$CLKNETSIM_PRELOAD:}$CLKNETSIM_PATH/clknetsim.so \ CLKNETSIM_NODE=$node CLKNETSIM_SOCKET=$CLKNETSIM_TMPDIR/sock \ $CLKNETSIM_CLIENT_WRAPPER $wrapper_options \ $client$suffix "${args[@]}" &> $CLKNETSIM_TMPDIR/log.$node & lastpid=$! disown $lastpid client_pids="$client_pids $lastpid" } start_server() { local nodes=$1 ret=0 wrapper_options="" shift if [[ $CLKNETSIM_SERVER_WRAPPER == *valgrind* ]]; then wrapper_options="--log-file=$CLKNETSIM_TMPDIR/valgrind.0" fi $CLKNETSIM_SERVER_WRAPPER $wrapper_options \ $CLKNETSIM_PATH/clknetsim "$@" -s $CLKNETSIM_TMPDIR/sock \ $CLKNETSIM_TMPDIR/conf $nodes > $CLKNETSIM_TMPDIR/stats 2> $CLKNETSIM_TMPDIR/log if [ $? -ne 0 ]; then echo clknetsim failed 1>&2 ret=1 fi kill $client_pids &> /dev/null client_pids=" " if ls tmp/valgrind.* &> /dev/null; then if grep -q 'ERROR SUMMARY: [^0]' tmp/valgrind.*; then echo " valgrind error" 1>&2 ret=1 fi sed -i '/^ERROR: ld.so: object.*from LD_PRELOAD cannot/d' tmp/log.[0-9]* fi return $ret } generate_seq() { $CLKNETSIM_PATH/clknetsim -G "$@" } generate_config1() { local nodes=$1 offset=$2 freqexpr=$3 delayexprup=$4 delayexprdown=$5 refclockexpr=$6 i for i in `seq 2 $nodes`; do echo "node${i}_offset = $offset" echo "node${i}_freq = $freqexpr" echo "node${i}_delay1 = $delayexprup" if [ -n "$delayexprdown" ]; then echo "node1_delay${i} = $delayexprdown" else echo "node1_delay${i} = $delayexprup" fi [ -n "$refclockexpr" ] && echo "node${i}_refclock = $refclockexpr" done > $CLKNETSIM_TMPDIR/conf } generate_config2() { local nodes=$1 offset=$2 freqexpr=$3 delayexpr=$4 i j for i in `seq 2 $nodes`; do echo "node${i}_offset = $offset" echo "node${i}_freq = $freqexpr" for j in `seq 1 $nodes`; do [ $i -eq $j ] && continue echo "node${i}_delay${j} = $delayexpr" echo "node${j}_delay${i} = $delayexpr" done done > $CLKNETSIM_TMPDIR/conf } generate_config3() { local topnodes=$1 nodes=$2 offset=$3 freqexpr=$4 delayexprup=$5 delayexprdown=$6 i j for i in `seq $[$topnodes + 1] $nodes`; do echo "node${i}_offset = $offset" echo "node${i}_freq = $freqexpr" for j in `seq 1 $topnodes`; do [ $i -eq $j ] && continue echo "node${i}_delay${j} = $delayexprup" if [ -n "$delayexprdown" ]; then echo "node${j}_delay${i} = $delayexprdown" else echo "node${j}_delay${i} = $delayexprup" fi done done > $CLKNETSIM_TMPDIR/conf } generate_config4() { local stablenodes=$1 subnets=$2 offset=$3 freqexpr=$4 delayexpr=$5 local subnet i j added echo "$subnets" | tr '|' '\n' | while read subnet; do for i in $subnet; do if ! [[ " $stablenodes $added " =~ [^0-9]$i[^0-9] ]]; then echo "node${i}_offset = $offset" echo "node${i}_freq = $freqexpr" fi for j in $subnet; do [ $i -eq $j ] && continue echo "node${i}_delay${j} = $delayexpr" done added="$added $i" done done > $CLKNETSIM_TMPDIR/conf } find_sync() { local offlog=$1 freqlog=$2 index=$3 offsync=$4 freqsync=$5 smooth=$6 [ -z "$smooth" ] && smooth=0.05 paste <(cut -f $index $1) <(cut -f $index $2) | awk ' BEGIN { lastnonsync = -1 time = 0 } { off = $1 < 0 ? -$1 : $1 freq = $2 < 0 ? -$2 : $2 if (avgoff == 0.0 && avgfreq == 0.0) { avgoff = off avgfreq = freq } else { avgoff += '$smooth' * (off - avgoff) avgfreq += '$smooth' * (freq - avgfreq) } if (avgoff > '$offsync' || avgfreq > '$freqsync') { lastnonsync = time } time++ } END { if (lastnonsync < time) { print lastnonsync + 1 } else { print -1 } }' } get_stat() { local statname=$1 index=$2 if [ -z "$index" ]; then echo $(cat $CLKNETSIM_TMPDIR/stats | grep "^$statname:" | cut -f 2) else cat $CLKNETSIM_TMPDIR/stats | grep "^$statname:" | cut -f 2 | head -n $index | tail -n 1 fi } check_stat() { local value=$1 min=$2 max=$3 tolerance=$4 [ -z "$tolerance" ] && tolerance=0.0 awk " BEGIN { eq = (\"$value\" == \"inf\" || $value + $value / 1e6 + $tolerance >= $min) && (\"$max\" == \"inf\" || (\"$value\" != \"inf\" && $value - $value / 1e6 - $tolerance <= $max)) exit !eq }" } if [ -z "$CLKNETSIM_PATH" ]; then echo CLKNETSIM_PATH not set 2>&1 exit 1 fi if [ ! -x "$CLKNETSIM_PATH/clknetsim" -o ! -e "$CLKNETSIM_PATH/clknetsim.so" ]; then echo "can't find clknetsim or clknetsim.so in $CLKNETSIM_PATH" exit 1 fi [ -d $CLKNETSIM_TMPDIR ] || mkdir $CLKNETSIM_TMPDIR 07070100000007000081A40000000000000000000000016666F04D000025FB000000000000000000000000000000000000002200000000clknetsim-0+git.20240610/clock.cc/* * Copyright (C) 2010 Miroslav Lichvar <mlichvar@redhat.com> * * 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, see <http://www.gnu.org/licenses/>. */ #include "clock.h" #define MINSEC 256 #define MAXSEC 2048 #define MAXTIMECONST 10 #define MAXMAXERROR 16000000 #define SHIFT_FLL 2 #define SCALE_FREQ 65536.0e6 #define MAXFREQ_SCALED 32768000 #define MAX_SLEWRATE 500 #define MAX_TICK(base_tick) ((base_tick) * 11 / 10) #define MIN_TICK(base_tick) ((base_tick) * 9 / 10) #define MIN_FREQ 0.8 #define MAX_FREQ 1.2 Clock::Clock() { time = 0.0; mono_time = 0.0; freq = 1.0; freq_generator = NULL; step_generator = NULL; base_tick = sysconf(_SC_CLK_TCK); assert(base_tick > 0); base_tick = (1000000 + base_tick / 2) / base_tick; memset(&ntp_timex, 0, sizeof(ntp_timex)); ntp_timex.tick = base_tick; ntp_timex.tolerance = MAXFREQ_SCALED; ntp_timex.precision = 1; ntp_state = TIME_OK; /* in Linux kernel SHIFT_PLL is 2 since 2.6.31 */ ntp_shift_pll = 2; ntp_flags = 0; ntp_update_interval = 0; ntp_offset = 0.0; ntp_slew = 0.0; ss_offset = 0; ss_slew = 0; } Clock::~Clock() { if (freq_generator) delete freq_generator; if (step_generator) delete step_generator; } double Clock::get_real_time() const { return time; } double Clock::get_monotonic_time() const { return mono_time; } double Clock::get_total_freq() const { double timex_freq, adjtime_freq; timex_freq = (double)ntp_timex.tick / base_tick + ntp_timex.freq / SCALE_FREQ + ntp_slew; adjtime_freq = ss_slew / 1e6; return freq * (timex_freq + adjtime_freq); } double Clock::get_raw_freq() const { double timex_freq; timex_freq = (double)ntp_timex.tick / base_tick + ntp_timex.freq / SCALE_FREQ; return freq * timex_freq; } double Clock::get_true_interval(double local_interval) const { return local_interval / get_total_freq(); } double Clock::get_local_interval(double true_interval) const { return true_interval * get_total_freq(); } void Clock::set_freq_generator(Generator *gen) { if (freq_generator) delete freq_generator; freq_generator = gen; } void Clock::set_step_generator(Generator *gen) { if (step_generator) delete step_generator; step_generator = gen; } void Clock::set_freq(double freq) { this->freq = freq + 1.0; if (!(this->freq > MIN_FREQ && this->freq < MAX_FREQ)) { fprintf(stderr, "frequency %e outside allowed range (%.2f, %.2f)\n", this->freq - 1.0, MIN_FREQ - 1.0, MAX_FREQ - 1.0); exit(1); } } void Clock::set_time(double time) { this->time = time; } void Clock::step_time(double step) { this->time += step; } void Clock::set_ntp_shift_pll(int shift) { ntp_shift_pll = shift; } void Clock::set_ntp_flag(int enable, int flag) { ntp_flags &= ~flag; if (enable) ntp_flags |= flag; } void Clock::advance(double real_interval) { double local_interval = get_local_interval(real_interval); time += local_interval; mono_time += local_interval; } void Clock::update(bool second) { if (freq_generator) set_freq(freq_generator->generate(NULL)); if (step_generator) step_time(step_generator->generate(NULL)); if (!second) return; if (ntp_timex.status & STA_PLL) { ntp_update_interval++; ntp_slew = ntp_offset / (1 << (ntp_shift_pll + ntp_timex.constant)); #if 0 if (ntp_slew > MAX_SLEWRATE / 1e6) ntp_slew = MAX_SLEWRATE / 1e6; else if (ntp_slew < -MAX_SLEWRATE / 1e6) ntp_slew = -MAX_SLEWRATE / 1e6; #endif ntp_offset -= ntp_slew; if (ntp_timex.status & STA_NANO) ntp_timex.offset = ntp_offset * 1e9; else ntp_timex.offset = ntp_offset * 1e6; } if (ss_offset) { if (ss_offset > 0) { if (ss_offset > MAX_SLEWRATE) { ss_slew = MAX_SLEWRATE; ss_offset -= MAX_SLEWRATE; } else { ss_slew = ss_offset; ss_offset = 0; } } else { if (ss_offset < -MAX_SLEWRATE) { ss_slew = -MAX_SLEWRATE; ss_offset -= -MAX_SLEWRATE; } else { ss_slew = ss_offset; ss_offset = 0; } } } else ss_slew = 0; switch (ntp_state) { case TIME_OK: if (ntp_timex.status & STA_INS) ntp_state = TIME_INS; else if (ntp_timex.status & STA_DEL) ntp_state = TIME_DEL; break; case TIME_INS: if ((time_t)(time + 0.5) % (24 * 3600) <= 1) { time -= 1.0; ntp_timex.tai += 1.0; ntp_state = TIME_OOP; } else if (!(ntp_timex.status & STA_INS)) { ntp_state = TIME_OK; } break; case TIME_DEL: if ((time_t)(time + 1.0 + 0.5) % (24 * 3600) <= 1) { time += 1.0; ntp_timex.tai -= 1.0; ntp_state = TIME_WAIT; } else if (!(ntp_timex.status & STA_DEL)) { ntp_state = TIME_OK; } break; case TIME_OOP: ntp_state = TIME_WAIT; break; case TIME_WAIT: if (!(ntp_timex.status & (STA_INS | STA_DEL))) ntp_state = TIME_OK; break; default: assert(0); } } void Clock::update_ntp_offset(long offset) { double fll_adj, pll_adj, new_offset, old_offset, tc, t; if (ntp_timex.status & STA_FREQHOLD) ntp_update_interval = 0; if (ntp_timex.status & STA_NANO) new_offset = offset / 1e9; else new_offset = offset / 1e6; tc = 1 << ntp_timex.constant; ntp_timex.offset = offset; old_offset = ntp_offset; ntp_offset = new_offset; if (!(ntp_timex.status & STA_PLL)) return; if (old_offset && ntp_update_interval >= MINSEC && (ntp_timex.status & STA_FLL || ntp_update_interval > MAXSEC)) { ntp_timex.status |= STA_MODE; if (ntp_flags & CLOCK_NTP_FLL_MODE2) fll_adj = (new_offset - old_offset) / (ntp_update_interval * (1 << SHIFT_FLL)); else fll_adj = new_offset / (ntp_update_interval * (1 << SHIFT_FLL)); } else { ntp_timex.status &= ~STA_MODE; fll_adj = 0.0; } if (ntp_flags & CLOCK_NTP_PLL_CLAMP) { if (ntp_update_interval > MAXSEC) ntp_update_interval = MAXSEC; if (ntp_update_interval > tc * (1 << (ntp_shift_pll + 1))) ntp_update_interval = tc * (1 << (ntp_shift_pll + 1)); } t = 4 * (1 << ntp_shift_pll) * tc; pll_adj = new_offset * ntp_update_interval / (t * t); ntp_timex.freq += (fll_adj + pll_adj) * SCALE_FREQ; if (ntp_timex.freq > MAXFREQ_SCALED) ntp_timex.freq = MAXFREQ_SCALED; else if (ntp_timex.freq < -MAXFREQ_SCALED) ntp_timex.freq = -MAXFREQ_SCALED; ntp_update_interval = 0; } int Clock::adjtimex(struct timex *buf) { int r = ntp_state; struct timex t; if (buf->modes & ADJ_FREQUENCY) { ntp_timex.freq = buf->freq; if (ntp_timex.freq > MAXFREQ_SCALED) ntp_timex.freq = MAXFREQ_SCALED; else if (ntp_timex.freq < -MAXFREQ_SCALED) ntp_timex.freq = -MAXFREQ_SCALED; } if (buf->modes & ADJ_MAXERROR) ntp_timex.maxerror = buf->maxerror; if (buf->modes & ADJ_STATUS) { if ((buf->status & STA_PLL) && !(ntp_timex.status & STA_PLL)) ntp_update_interval = 0; ntp_timex.status = buf->status & 0xff; } if (buf->modes & ADJ_MICRO) ntp_timex.status &= ~STA_NANO; if (buf->modes & ADJ_NANO) ntp_timex.status |= STA_NANO; if (buf->modes & ADJ_TIMECONST) { ntp_timex.constant = buf->constant; if (!(ntp_timex.status & STA_NANO)) ntp_timex.constant += 4; if (ntp_timex.constant > MAXTIMECONST) ntp_timex.constant = MAXTIMECONST; if (ntp_timex.constant < 0) ntp_timex.constant = 0; } if (buf->modes & ADJ_TICK) { if (buf->tick > MAX_TICK(base_tick) || buf->tick < MIN_TICK(base_tick)) { r = -1; } else ntp_timex.tick = buf->tick; } if ((buf->modes & ADJ_OFFSET_SINGLESHOT) != ADJ_OFFSET_SINGLESHOT) { if (buf->modes & ADJ_OFFSET) { update_ntp_offset(buf->offset); } } if (buf->modes & ADJ_SETOFFSET) { if (ntp_timex.status & STA_NANO) time += buf->time.tv_sec + buf->time.tv_usec * 1e-9; else time += buf->time.tv_sec + buf->time.tv_usec * 1e-6; ntp_timex.maxerror = MAXMAXERROR; } if (buf->modes & ADJ_TAI) { ntp_timex.tai = buf->constant; } t = ntp_timex; if ((buf->modes & ADJ_OFFSET_SINGLESHOT) == ADJ_OFFSET_SINGLESHOT) { if ((buf->modes & ADJ_OFFSET_SS_READ) == ADJ_OFFSET_SINGLESHOT) { t.offset = ss_offset; ss_offset = buf->offset; } else { t.offset = ss_offset; } } *buf = t; return r; } int Clock::adjtime(const struct timeval *delta, struct timeval *olddelta) { if (olddelta) { olddelta->tv_sec = ss_offset / 1000000; olddelta->tv_usec = ss_offset % 1000000; } if (delta) ss_offset = delta->tv_sec * 1000000 + delta->tv_usec; return 0; } Refclock::Refclock() { time = 0.0; offset = 0.0; generate = false; valid = false; offset_generator = NULL; } Refclock::~Refclock() { if (offset_generator) delete offset_generator; } void Refclock::set_offset_generator(Generator *gen) { if (offset_generator) delete offset_generator; offset_generator = gen; } void Refclock::set_generation(bool enable) { generate = enable; } void Refclock::update(double time, const Clock *clock) { if (!generate || !offset_generator) return; this->time = clock->get_real_time(); offset = this->time - time + offset_generator->generate(NULL); valid = true; } bool Refclock::get_sample(double *time, double *offset) const { *time = this->time; *offset = this->offset; return valid; } void Refclock::get_offsets(double *offsets, int size) { int i; for (i = 0; i < size; i++) offsets[i] = offset_generator ? offset_generator->generate(NULL) : 0.0; } 07070100000008000081A40000000000000000000000016666F04D000008F5000000000000000000000000000000000000002100000000clknetsim-0+git.20240610/clock.h/* * Copyright (C) 2010 Miroslav Lichvar <mlichvar@redhat.com> * * 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, see <http://www.gnu.org/licenses/>. */ #ifndef CLOCK_H #define CLOCK_H #include "sysheaders.h" #include "generator.h" #define CLOCK_NTP_FLL_MODE2 0x1 #define CLOCK_NTP_PLL_CLAMP 0x2 class Clock { double time; double mono_time; double freq; Generator *freq_generator; Generator *step_generator; long base_tick; struct timex ntp_timex; int ntp_state; int ntp_shift_pll; int ntp_flags; long ntp_update_interval; double ntp_offset; double ntp_slew; long ss_offset; long ss_slew; public: Clock(); ~Clock(); double get_real_time() const; double get_monotonic_time() const; double get_total_freq() const; double get_raw_freq() const; double get_true_interval(double local_interval) const; double get_local_interval(double true_interval) const; void set_freq_generator(Generator *gen); void set_step_generator(Generator *gen); void set_freq(double freq); void set_time(double time); void step_time(double step); void set_ntp_shift_pll(int shift); void set_ntp_flag(int enable, int flag); void advance(double real_interval); void update(bool second); void update_ntp_offset(long offset); int adjtimex(struct timex *buf); int adjtime(const struct timeval *delta, struct timeval *olddelta); }; class Refclock { double time; double offset; bool generate; bool valid; Generator *offset_generator; public: Refclock(); ~Refclock(); void set_offset_generator(Generator *gen); void update(double time, const Clock *clock); void set_generation(bool enable); bool get_sample(double *time, double *offset) const; void get_offsets(double *offsets, int size); }; #endif 07070100000009000041ED0000000000000000000000026666F04D00000000000000000000000000000000000000000000002200000000clknetsim-0+git.20240610/examples0707010000000A000081ED0000000000000000000000016666F04D0000014F000000000000000000000000000000000000002F00000000clknetsim-0+git.20240610/examples/busybox.test#!/bin/bash CLKNETSIM_PATH=.. . ../clknetsim.bash generate_config1 2 0.01 "(+ 1e-6 (sum (* 1e-9 (normal))))" "(+ 1e-3 (* 1e-3 (exponential)))" start_client 1 chrony "local stratum 1" start_client 2 busybox "192.168.123.1" start_server 2 -v 2 -o log.offset -f log.freq -g log.rawfreq -p log.packets -r 2000 -l 400000 cat tmp/stats 0707010000000B000081ED0000000000000000000000016666F04D0000018D000000000000000000000000000000000000002F00000000clknetsim-0+git.20240610/examples/chronyc.test#!/bin/bash CLKNETSIM_PATH=.. . ../clknetsim.bash generate_config4 "1" "1 2 3" 0.01 "(sum (* 1e-9 (normal)))" "(+ 1e-3 (* 1e-3 (exponential)))" echo "node3_start = 10000" >> tmp/conf start_client 1 chronyd "local stratum 1" start_client 2 chronyd "server 192.168.123.1" start_client 3 chronyc "tracking sources -n sourcestats" "" "-h 192.168.123.2" start_server 3 -v 2 -l 10001 cat tmp/log.3 0707010000000C000081ED0000000000000000000000016666F04D00000185000000000000000000000000000000000000002F00000000clknetsim-0+git.20240610/examples/chronyd.test#!/bin/bash CLKNETSIM_PATH=.. . ../clknetsim.bash generate_config1 2 0.01 "(sum (* 1e-9 (normal)))" "(+ 1e-3 (* 1e-3 (exponential)))" "" "(* 1e-4 (normal))" start_client 1 chrony "local stratum 1" start_client 2 chrony "server 192.168.123.1 minpoll 6 maxpoll 6 refclock SHM 0" start_server 2 -v 2 -o log.offset -f log.freq -g log.rawfreq -p log.packets -r 2000 -l 40000 cat tmp/stats 0707010000000D000081ED0000000000000000000000016666F04D000001A5000000000000000000000000000000000000002B00000000clknetsim-0+git.20240610/examples/nsm.test#!/bin/bash CLKNETSIM_PATH=.. . ../clknetsim.bash generate_config4 "1 3" "1 2 3" 0.01 "(sum (* 1e-9 (normal)))" "(* 1e-8 (exponential))" echo 'node3_start = 50' >> tmp/conf start_client 1 ptp4l "hybrid_e2e 1 net_sync_monitor 1" "" "-i eth0" start_client 2 ptp4l "hybrid_e2e 1 net_sync_monitor 1" "" "-i eth0" start_client 3 nsm "NSM 192.168.123.1 NSM 192.168.123.2" "" "-i eth0" start_server 3 -l 110 cat tmp/log.3 0707010000000E000081ED0000000000000000000000016666F04D000001CC000000000000000000000000000000000000002C00000000clknetsim-0+git.20240610/examples/ntpd.test#!/bin/bash CLKNETSIM_PATH=.. . ../clknetsim.bash generate_config1 3 0.01 "(sum (* 1e-9 (normal)))" "(+ 1e-3 (* 1e-3 (exponential)))" echo "node2_shift_pll = 2" >> tmp/conf start_client 1 ntp "server 127.127.1.0" start_client 2 ntp "server 192.168.123.1 minpoll 6 maxpoll 6" start_client 3 ntp "server 192.168.123.1 minpoll 6 maxpoll 6" start_server 3 -v 2 -o log.offset -r 2000 -l 40000 cat tmp/stats echo get_stat 'RMS offset' get_stat 'RMS frequency' 0707010000000F000081ED0000000000000000000000016666F04D00000191000000000000000000000000000000000000002F00000000clknetsim-0+git.20240610/examples/ntpdate.test#!/bin/bash CLKNETSIM_PATH=.. . ../clknetsim.bash generate_config1 3 10.0 "(sum (* 1e-9 (normal)))" "(+ 1e-3 (* 1e-3 (exponential)))" echo "node2_start = 330" >> tmp/conf echo "node3_start = 330" >> tmp/conf start_client 1 ntpd "server 127.127.1.0" start_client 2 ntpdate "-B 192.168.123.1" start_client 3 ntpdate "-b 192.168.123.1" start_server 3 -v 2 -o log.offset -r 340 -l 350 cat tmp/stats 07070100000010000081ED0000000000000000000000016666F04D00000178000000000000000000000000000000000000002C00000000clknetsim-0+git.20240610/examples/ntpq.test#!/bin/bash CLKNETSIM_PATH=.. . ../clknetsim.bash generate_config4 "1" "1 2 | 2 3" 0.01 "(sum (* 1e-9 (normal)))" "(* 1e-8 (exponential))" echo 'node3_start = 1000' >> tmp/conf start_client 1 ntpd "server 127.127.1.0" start_client 2 ntpd "server 192.168.123.1 minpoll 6 maxpoll 6" start_client 3 ntpq "rv 0 peers" "" "192.168.123.2" start_server 3 -l 1010 cat tmp/log.3 07070100000011000081ED0000000000000000000000016666F04D0000010A000000000000000000000000000000000000002F00000000clknetsim-0+git.20240610/examples/phc2sys.test#!/bin/bash CLKNETSIM_PATH=.. . ../clknetsim.bash echo "node1_freq = (sum (* 1e-9 (normal)))" > tmp/conf echo "node1_refclock = (* 1e-6 (normal))" >> tmp/conf start_client 1 phc2sys "" start_server 1 -v 2 -o log.offset -f log.freq -r 1000 -l 4000 cat tmp/stats 07070100000012000081ED0000000000000000000000016666F04D00000178000000000000000000000000000000000000002B00000000clknetsim-0+git.20240610/examples/pmc.test#!/bin/bash CLKNETSIM_PATH=.. . ../clknetsim.bash generate_config4 "1" "1 2 3" 0.01 "(sum (* 1e-9 (normal)))" "(* 1e-8 (exponential))" echo 'node3_start = 100' >> tmp/conf start_client 1 ptp4l "" "" "-i eth0" start_client 2 ptp4l "" "" "-i eth0" start_client 3 pmc " GET TIME_STATUS_NP GET TIME_PROPERTIES_DATA_SET GET PORT_DATA_SET" start_server 3 -l 110 cat tmp/log.3 07070100000013000081ED0000000000000000000000016666F04D0000025D000000000000000000000000000000000000003500000000clknetsim-0+git.20240610/examples/ptp4l-phc2sys.test#!/bin/bash CLKNETSIM_PATH=.. . ../clknetsim.bash export CLKNETSIM_UNIX_SUBNET=2 generate_config4 "1" "1 2 | 2 3" 0.01 "(sum (* 1e-9 (normal)))" "(* 1e-8 (exponential))" start_client 1 ptp4l "clockClass 6" "" "-i eth0" start_client 2 ptp4l " first_step_threshold 0.0 max_frequency 10000" "" "-i eth0" CLKNETSIM_PHC_SWAP=1 \ start_client 3 phc2sys "-a -r -z /clknetsim/unix/2:1" echo "node3_refclock = (+ -37 (* 1e-6 (normal)))" >> tmp/conf echo "node3_refclock_base = node2" >> tmp/conf start_server 3 -v 2 -n 2 -o log.offset -f log.freq -g log.rawfreq -p log.packets -r 1000 -l 4000 cat tmp/stats 07070100000014000081ED0000000000000000000000016666F04D0000019E000000000000000000000000000000000000002D00000000clknetsim-0+git.20240610/examples/ptp4l.test#!/bin/bash CLKNETSIM_PATH=.. . ../clknetsim.bash generate_config4 "1" "1 2 3 | 3 4" 0.01 "(sum (* 1e-9 (normal)))" "(* 1e-8 (exponential))" start_client 1 ptp4l "clockClass 6" "" "-i eth0" start_client 2 ptp4l "time_stamping software" "" "-i eth0" start_client 3 ptp4l "" "" "-i eth0 -i eth1" start_client 4 ptp4l "" "" "-i eth1" start_server 4 -n 2 -o log.offset -p log.packets -r 100 -l 1000 cat tmp/stats 07070100000015000081ED0000000000000000000000016666F04D000001A3000000000000000000000000000000000000002C00000000clknetsim-0+git.20240610/examples/sntp.test#!/bin/bash export EVENT_NOEPOLL=1 CLKNETSIM_PATH=.. . ../clknetsim.bash generate_config1 3 10.0 "(sum (* 1e-9 (normal)))" "(+ 1e-3 (* 1e-3 (exponential)))" echo "node2_start = 330" >> tmp/conf echo "node3_start = 330" >> tmp/conf start_client 1 ntpd "server 127.127.1.0" start_client 2 sntp "-s 192.168.123.1" start_client 3 sntp "-S 192.168.123.1" start_server 3 -v 2 -o log.offset -r 340 -l 350 cat tmp/stats 07070100000016000081ED0000000000000000000000016666F04D00000322000000000000000000000000000000000000003100000000clknetsim-0+git.20240610/examples/visclocks.test#!/bin/bash CLKNETSIM_PATH=.. . ../clknetsim.bash export CLKNETSIM_RANDOM_SEED CLKNETSIM_RANDOM_SEED=1011 generate_seq '(sum (* 2e-9 (normal)))' 50000 > freq.input CLKNETSIM_RANDOM_SEED=1012 generate_seq '(+ 1e-5 (* 1e-4 (exponential)))' 10000 > delay_up.input CLKNETSIM_RANDOM_SEED=1013 generate_seq '(+ 1e-5 (* 1e-4 (exponential)))' 10000 > delay_down.input CLKNETSIM_RANDOM_SEED=1014 generate_config1 3 0.0 "(file \"freq.input\")" \ "(file \"delay_up.input\")" "(file \"delay_down.input\")" start_client 1 chrony "local stratum 1" start_client 2 chrony "server 192.168.123.1 minpoll 6 maxpoll 6 minsamples 32" start_client 3 chrony "server 192.168.123.1 minpoll 6 maxpoll 6 maxsamples 16" start_server 3 -o log.offset -p log.packets -l 50000 ../visclocks.py freq.input log.offset log.packets 07070100000017000081A40000000000000000000000016666F04D00002AF8000000000000000000000000000000000000002600000000clknetsim-0+git.20240610/generator.cc/* * Copyright (C) 2010 Miroslav Lichvar <mlichvar@redhat.com> * * 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, see <http://www.gnu.org/licenses/>. */ #include "generator.h" static void syntax_assert(bool condition) { if (!condition) { fprintf(stderr, "syntax error\n"); exit(1); } } Generator::Generator(const vector<Generator *> *input) { if (input) this->input = *input; constant = false; } Generator::~Generator() { while (!input.empty()) { delete input.back(); input.pop_back(); } } bool Generator::is_constant() const { return constant; } Generator_float::Generator_float(double f): Generator(NULL) { this->f = f; constant = true; } double Generator_float::generate(const Generator_variables *variables) { return f; } Generator_variable::Generator_variable(string name): Generator(NULL) { this->name = name; } double Generator_variable::generate(const Generator_variables *variables) { Generator_variables::const_iterator iter; syntax_assert(variables); iter = variables->find(name); syntax_assert(iter != variables->end()); return iter->second; } Generator_random_uniform::Generator_random_uniform(const vector<Generator *> *input): Generator(NULL) { syntax_assert(!input || input->size() == 0); } double Generator_random_uniform::generate(const Generator_variables *variables) { double x; x = ((random() & 0x7fffffff) + 1) / 2147483649.0; x = ((random() & 0x7fffffff) + x) / 2147483648.0; return x; } Generator_random_normal::Generator_random_normal(const vector<Generator *> *input): Generator(NULL), uniform(NULL) { syntax_assert(!input || input->size() == 0); } double Generator_random_normal::generate(const Generator_variables *variables) { /* Marsaglia polar method */ double x, y, s; do { x = 2.0 * uniform.generate(variables) - 1.0; y = 2.0 * uniform.generate(variables) - 1.0; s = x * x + y * y; } while (s >= 1.0); x *= sqrt(-2.0 * log(s) / s); return x; } Generator_random_exponential::Generator_random_exponential(const vector<Generator *> *input): Generator(NULL), uniform(NULL) { syntax_assert(!input || input->size() == 0); } double Generator_random_exponential::generate(const Generator_variables *variables) { return -log(uniform.generate(variables)); } Generator_random_poisson::Generator_random_poisson(const vector<Generator *> *input): Generator(NULL), uniform(NULL) { double lambda; syntax_assert(input && input->size() == 1 && (*input)[0]->is_constant()); lambda = (*input)[0]->generate(NULL); syntax_assert(lambda >= 1 && lambda <= 20); L = exp(-lambda); } double Generator_random_poisson::generate(const Generator_variables *variables) { double p; int k; for (p = 1.0, k = 0; k < 100; k++) { p *= uniform.generate(variables); if (p <= L) break; } return k; } Generator_file::Generator_file(const char *file): Generator(NULL) { input = fopen(file, "r"); if (!input) { fprintf(stderr, "can't open %s\n", file); exit(1); } } Generator_file::~Generator_file() { fclose(input); } double Generator_file::generate(const Generator_variables *variables) { double x; while (1) { if (fscanf(input, "%lf", &x) != 1) { if (feof(input)) { fseek(input, 0, SEEK_SET); continue; } assert(0); } break; } return x; } Generator_wave_pulse::Generator_wave_pulse(const vector<Generator *> *input): Generator(NULL) { syntax_assert(input && input->size() == 2 && (*input)[0]->is_constant() && (*input)[1]->is_constant()); high = (*input)[0]->generate(NULL); low = (*input)[1]->generate(NULL); counter = 0; } double Generator_wave_pulse::generate(const Generator_variables *variables) { counter++; if (counter > high + low) counter = 1; if (counter <= high) return 1.0; return -1.0; } Generator_wave_sine::Generator_wave_sine(const vector<Generator *> *input): Generator(NULL) { syntax_assert(input && input->size() == 1 && (*input)[0]->is_constant()); length = (*input)[0]->generate(NULL); counter = 0; } double Generator_wave_sine::generate(const Generator_variables *variables) { return sin(counter++ / length * 2 * M_PI); } Generator_wave_cosine::Generator_wave_cosine(const vector<Generator *> *input): Generator(NULL) { syntax_assert(input && input->size() == 1 && (*input)[0]->is_constant()); length = (*input)[0]->generate(NULL); counter = 0; } double Generator_wave_cosine::generate(const Generator_variables *variables) { return cos(counter++ / length * 2 * M_PI); } Generator_wave_triangle::Generator_wave_triangle(const vector<Generator *> *input): Generator(NULL) { syntax_assert(input && input->size() == 1 && (*input)[0]->is_constant()); length = (*input)[0]->generate(NULL); counter = 0; } double Generator_wave_triangle::generate(const Generator_variables *variables) { double phase; phase = counter / length - floor(counter / length); counter++; return -4.0 * (fabs(phase - 0.5) - 0.25); } Generator_sum::Generator_sum(const vector<Generator *> *input): Generator(input) { sum = 0.0; } double Generator_sum::generate(const Generator_variables *variables) { unsigned int i; for (i = 0; i < input.size(); i++) sum += input[i]->generate(variables); return sum; } Generator_multiply::Generator_multiply(const vector<Generator *> *input): Generator(input) { } double Generator_multiply::generate(const Generator_variables *variables) { unsigned int i; double x = 1.0; for (i = 0; i < input.size(); i++) x *= input[i]->generate(variables); return x; } Generator_add::Generator_add(const vector<Generator *> *input): Generator(input) { } double Generator_add::generate(const Generator_variables *variables) { unsigned int i; double x = 0.0; for (i = 0; i < input.size(); i++) x += input[i]->generate(variables); return x; } Generator_modulo::Generator_modulo(const vector<Generator *> *input): Generator(input) { syntax_assert(input && input->size() > 0); } double Generator_modulo::generate(const Generator_variables *variables) { unsigned int i; double x = input[0]->generate(variables); for (i = 1; i < input.size(); i++) x = fmod(x, input[i]->generate(variables)); return x; } Generator_equal::Generator_equal(const vector<Generator *> *input): Generator(input) { syntax_assert(input && input->size() > 0); } double Generator_equal::generate(const Generator_variables *variables) { unsigned int i; double x, min = 0.0, max = 0.0, epsilon = input[0]->generate(variables); for (i = 1; i < input.size(); i++) { x = input[i]->generate(variables); if (i == 1 || min > x) min = x; if (i == 1 || max < x) max = x; } return max - min <= epsilon ? 1.0 : 0.0; } Generator_max::Generator_max(const vector<Generator *> *input): Generator(input) { syntax_assert(input && input->size() > 0); } double Generator_max::generate(const Generator_variables *variables) { unsigned int i; double x, max = 0.0; for (i = 0; i < input.size(); i++) { x = input[i]->generate(variables); if (!i || max < x) max = x; } return max; } Generator_min::Generator_min(const vector<Generator *> *input): Generator(input) { syntax_assert(input && input->size() > 0); } double Generator_min::generate(const Generator_variables *variables) { unsigned int i; double x, min = 0.0; for (i = 0; i < input.size(); i++) { x = input[i]->generate(variables); if (!i || min > x) min = x; } return min; } Generator_generator::Generator_generator() { } Generator_generator::~Generator_generator() { } Generator *Generator_generator::generate(char *code) const { const char *ws = " \t\n\r", *wsp = " \t\n\r()"; int len, paren; Generator *ret; vector<Generator *> generators; char *arg, *name, *end, *string = NULL; //printf("code: |%s|\n", code); len = strlen(code); end = code + len; if (code[0] == '(') { syntax_assert(len > 2 && code[len - 1] == ')'); code[len - 1] = '\0'; code++; end = code + len - 2; } code += strspn(code, ws); name = code; code += strcspn(code, wsp); code[0] = '\0'; code++; code += strspn(code, ws); while (code < end) { arg = code; if (arg[0] == '(') { code = ++arg; for (paren = 1; code < end; code++) { if (code[0] == '(') paren++; else if (code[0] == ')') paren--; if (paren == 0) break; } syntax_assert(paren == 0 && code[0] == ')'); code[0] = '\0'; code++; //printf("generator: %s\n", arg); generators.push_back(generate(arg)); syntax_assert(generators.back()); } else if (arg[0] == '"') { string = code = ++arg; code += strcspn(code, "\""); syntax_assert(code[0] == '"'); code[0] = '\0'; code++; //printf("string: |%s|\n", string); } else { code += strcspn(code, wsp); syntax_assert(code[0] != ')' && code[0] != '('); code[0] = '\0'; code++; if (isalpha(arg[0])) { generators.push_back(new Generator_variable(arg)); //printf("variable: %s\n", arg); } else { generators.push_back(new Generator_float(atof(arg))); //printf("float: %f\n", generators.back()->generate()); } } code += strspn(code, ws); } if (strcmp(name, "*") == 0) ret = new Generator_multiply(&generators); else if (strcmp(name, "+") == 0) ret = new Generator_add(&generators); else if (strcmp(name, "%") == 0) ret = new Generator_modulo(&generators); else if (strcmp(name, "sum") == 0) ret = new Generator_sum(&generators); else if (strcmp(name, "uniform") == 0) ret = new Generator_random_uniform(&generators); else if (strcmp(name, "normal") == 0) ret = new Generator_random_normal(&generators); else if (strcmp(name, "exponential") == 0) ret = new Generator_random_exponential(&generators); else if (strcmp(name, "poisson") == 0) ret = new Generator_random_poisson(&generators); else if (strcmp(name, "file") == 0) ret = new Generator_file(string); else if (strcmp(name, "pulse") == 0) ret = new Generator_wave_pulse(&generators); else if (strcmp(name, "sine") == 0) ret = new Generator_wave_sine(&generators); else if (strcmp(name, "cosine") == 0) ret = new Generator_wave_cosine(&generators); else if (strcmp(name, "triangle") == 0) ret = new Generator_wave_triangle(&generators); else if (strcmp(name, "equal") == 0) ret = new Generator_equal(&generators); else if (strcmp(name, "max") == 0) ret = new Generator_max(&generators); else if (strcmp(name, "min") == 0) ret = new Generator_min(&generators); else { ret = NULL; syntax_assert(0); } return ret; } 07070100000018000081A40000000000000000000000016666F04D000012A0000000000000000000000000000000000000002500000000clknetsim-0+git.20240610/generator.h/* * Copyright (C) 2010 Miroslav Lichvar <mlichvar@redhat.com> * * 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, see <http://www.gnu.org/licenses/>. */ #ifndef GENERATOR_H #define GENERATOR_H #include "sysheaders.h" #include <vector> #include <map> #include <string> using namespace std; typedef map<string, double> Generator_variables; class Generator { protected: vector<Generator *> input; bool constant; public: Generator(const vector<Generator *> *input); virtual ~Generator(); virtual double generate(const Generator_variables *variables) = 0; bool is_constant() const; }; class Generator_float: public Generator { double f; public: Generator_float(double f); virtual double generate(const Generator_variables *variables); }; class Generator_variable: public Generator { string name; public: Generator_variable(string name); virtual double generate(const Generator_variables *variables); }; class Generator_random_uniform: public Generator { public: Generator_random_uniform(const vector<Generator *> *input); virtual double generate(const Generator_variables *variables); }; class Generator_random_normal: public Generator { Generator_random_uniform uniform; public: Generator_random_normal(const vector<Generator *> *input); virtual double generate(const Generator_variables *variables); }; class Generator_random_exponential: public Generator { Generator_random_uniform uniform; public: Generator_random_exponential(const vector<Generator *> *input); virtual double generate(const Generator_variables *variables); }; class Generator_random_poisson: public Generator { Generator_random_uniform uniform; double L; public: Generator_random_poisson(const vector<Generator *> *input); virtual double generate(const Generator_variables *variables); }; class Generator_file: public Generator { FILE *input; public: Generator_file(const char *file); virtual ~Generator_file(); virtual double generate(const Generator_variables *variables); }; class Generator_wave_pulse: public Generator { int high; int low; int counter; public: Generator_wave_pulse(const vector<Generator *> *input); virtual double generate(const Generator_variables *variables); }; class Generator_wave_sine: public Generator { double length; int counter; public: Generator_wave_sine(const vector<Generator *> *input); virtual double generate(const Generator_variables *variables); }; class Generator_wave_cosine: public Generator { double length; int counter; public: Generator_wave_cosine(const vector<Generator *> *input); virtual double generate(const Generator_variables *variables); }; class Generator_wave_triangle: public Generator { double length; int counter; public: Generator_wave_triangle(const vector<Generator *> *input); virtual double generate(const Generator_variables *variables); }; class Generator_sum: public Generator { double sum; public: Generator_sum(const vector<Generator *> *input); virtual double generate(const Generator_variables *variables); }; class Generator_multiply: public Generator { public: Generator_multiply(const vector<Generator *> *input); virtual double generate(const Generator_variables *variables); }; class Generator_add: public Generator { public: Generator_add(const vector<Generator *> *input); virtual double generate(const Generator_variables *variables); }; class Generator_modulo: public Generator { public: Generator_modulo(const vector<Generator *> *input); virtual double generate(const Generator_variables *variables); }; class Generator_equal: public Generator { public: Generator_equal(const vector<Generator *> *input); virtual double generate(const Generator_variables *variables); }; class Generator_max: public Generator { public: Generator_max(const vector<Generator *> *input); virtual double generate(const Generator_variables *variables); }; class Generator_min: public Generator { public: Generator_min(const vector<Generator *> *input); virtual double generate(const Generator_variables *variables); }; class Generator_generator { public: Generator_generator(); ~Generator_generator(); Generator *generate(char *code) const; }; #endif 07070100000019000081A40000000000000000000000016666F04D00002BAB000000000000000000000000000000000000002400000000clknetsim-0+git.20240610/network.cc/* * Copyright (C) 2010 Miroslav Lichvar <mlichvar@redhat.com> * * 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, see <http://www.gnu.org/licenses/>. */ #include "sysheaders.h" #include "network.h" #define CONNECT_TIMEOUT 10 Packet_queue::Packet_queue() { } Packet_queue::~Packet_queue() { while (!queue.empty()) { delete queue.back(); queue.pop_back(); } } void Packet_queue::insert(struct Packet *packet) { deque<struct Packet *>::iterator i; for (i = queue.begin(); i < queue.end(); i++) if (packet->receive_time < (*i)->receive_time) break; queue.insert(i, packet); } struct Packet *Packet_queue::dequeue() { struct Packet *ret; assert(!queue.empty()); ret = queue.front(); queue.pop_front(); return ret; } double Packet_queue::get_timeout(double time) const { if (!queue.empty()) { return queue[0]->receive_time - time; } return 1e20; } Network::Network(const char *socket, unsigned int n, unsigned int subnets, unsigned int rate) { time = 0.0; this->subnets = subnets; socket_name = socket; update_rate = rate; update_count = 0; offset_log = NULL; freq_log = NULL; rawfreq_log = NULL; packet_log = NULL; assert(n > 0); while (nodes.size() < n) nodes.push_back(new Node(nodes.size(), this)); stats.resize(n); link_delays.resize(n * n); link_corrections.resize(n * n); } Network::~Network() { while (!nodes.empty()) { delete nodes.back(); nodes.pop_back(); } while (!link_delays.empty()) { delete link_delays.back(); link_delays.pop_back(); } while (!link_corrections.empty()) { delete link_corrections.back(); link_corrections.pop_back(); } unlink(socket_name); if (offset_log) fclose(offset_log); if (freq_log) fclose(freq_log); if (rawfreq_log) fclose(rawfreq_log); if (packet_log) fclose(packet_log); } bool Network::prepare_clients() { struct sockaddr_un s; struct timeval tv; int sockfd, fd; unsigned int i; s.sun_family = AF_UNIX; snprintf(s.sun_path, sizeof (s.sun_path), "%s", socket_name); sockfd = socket(AF_UNIX, SOCK_SEQPACKET, 0); if (sockfd < 0) { fprintf(stderr, "socket() failed: %s\n", strerror(errno)); return false; } unlink(socket_name); if (bind(sockfd, (struct sockaddr *)&s, sizeof (s)) < 0) { fprintf(stderr, "bind() failed: %s\n", strerror(errno)); return false; } if (listen(sockfd, nodes.size()) < 0) { fprintf(stderr, "listen() failed: %s\n", strerror(errno)); return false; } tv.tv_sec = CONNECT_TIMEOUT; tv.tv_usec = 0; if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof (tv))) { fprintf(stderr, "setsockopt() failed: %s\n", strerror(errno)); return false; } for (i = 0; i < nodes.size(); i++) { Request_packet req; unsigned int node; fprintf(stderr, "\rWaiting for %u clients...", (unsigned int)nodes.size() - i); fd = accept(sockfd, NULL, NULL); if (fd < 0) { fprintf(stderr, "\naccept() failed: %s\n", strerror(errno)); return false; } if (recv(fd, &req, sizeof (req), 0) != offsetof(Request_packet, data) + sizeof (Request_register) || req.header.request != REQ_REGISTER) { fprintf(stderr, "client didn't register correctly.\n"); return false; } node = req.data._register.node; assert(node < nodes.size() && nodes[node]->get_fd() < 0); nodes[node]->set_fd(fd); } fprintf(stderr, "done\n"); close(sockfd); update(); return true; } Node *Network::get_node(unsigned int node) { assert(node < nodes.size()); return nodes[node]; } void Network::set_link_delay_generator(unsigned int from, unsigned int to, Generator *generator) { unsigned int i; assert(from < nodes.size() && to < nodes.size()); i = from * nodes.size() + to; if (link_delays[i]) delete link_delays[i]; link_delays[i] = generator; } void Network::set_link_correction_generator(unsigned int from, unsigned int to, Generator *generator) { unsigned int i; assert(from < nodes.size() && to < nodes.size()); i = from * nodes.size() + to; if (link_corrections[i]) delete link_corrections[i]; link_corrections[i] = generator; } bool Network::run(double time_limit) { int i, n = nodes.size(), waiting; bool pending_update; double min_timeout, timeout, next_update; while (time < time_limit) { for (i = 0, waiting = 0; i < n; i++) if (nodes[i]->waiting()) waiting++; else stats[i].update_wakeup_stats(); while (waiting < n) { for (i = 0; i < n; i++) { if (nodes[i]->waiting()) continue; if (!nodes[i]->process_fd()) { fprintf(stderr, "client %d failed.\n", i + 1); return false; } if (nodes[i]->waiting()) waiting++; } } do { min_timeout = nodes[0]->get_timeout(); for (i = 1; i < n; i++) { timeout = nodes[i]->get_timeout(); if (min_timeout > timeout) min_timeout = timeout; } timeout = packet_queue.get_timeout(time); if (timeout <= min_timeout) min_timeout = timeout; next_update = floor(time) + (double)(update_count + 1) / update_rate; timeout = next_update - time; if (timeout <= min_timeout) { min_timeout = timeout; pending_update = true; } else pending_update = false; //min_timeout += 1e-12; assert(min_timeout >= 0.0); if (pending_update) time = next_update; else time += min_timeout; for (i = 0; i < n; i++) nodes[i]->get_clock()->advance(min_timeout); if (pending_update) update(); } while (pending_update && time < time_limit); for (i = 0; i < n; i++) nodes[i]->resume(); while (packet_queue.get_timeout(time) <= 0) { assert(packet_queue.get_timeout(time) > -1e-10); struct Packet *packet = packet_queue.dequeue(); stats[packet->to].update_packet_stats(true, time, packet->delay); nodes[packet->to]->receive(packet); } } return true; } void Network::update() { int i, n = nodes.size(); update_count++; update_count %= update_rate; for (i = 0; i < n; i++) { nodes[i]->get_clock()->update(update_count == 0); nodes[i]->get_refclock()->update(time, nodes[i]->get_clock()); } update_clock_stats(); } void Network::update_clock_stats() { int i, n = nodes.size(); if (offset_log) { for (i = 0; i < n; i++) fprintf(offset_log, "%.9f%c", nodes[i]->get_clock()->get_real_time() - time, i + 1 < n ? '\t' : '\n'); } if (freq_log) { for (i = 0; i < n; i++) fprintf(freq_log, "%e%c", nodes[i]->get_clock()->get_total_freq() - 1.0, i + 1 < n ? '\t' : '\n'); } if (rawfreq_log) { for (i = 0; i < n; i++) fprintf(rawfreq_log, "%e%c", nodes[i]->get_clock()->get_raw_freq() - 1.0, i + 1 < n ? '\t' : '\n'); } for (i = 0; i < n; i++) stats[i].update_clock_stats(nodes[i]->get_clock()->get_real_time() - time, nodes[i]->get_clock()->get_total_freq() - 1.0, nodes[i]->get_clock()->get_raw_freq() - 1.0); } void Network::write_correction(struct Packet *packet, double correction) { uint64_t c; /* one-step transparent end-to-end PTP clock */ if (packet->src_port != 319 || packet->dst_port != 319 || packet->len < 34 || ((packet->data[0] & 0xf) != 0 && (packet->data[0] & 0xf) != 1) || (packet->data[1] & 0xf) != 2) return; c = ((uint64_t)ntohl(*(uint32_t *)(packet->data + 8)) << 32) | ntohl(*(uint32_t *)(packet->data + 12)); c += (uint64_t)(correction * ((1 << 16) * 1.0e9)); *(uint32_t *)(packet->data + 8) = htonl(c >> 32); *(uint32_t *)(packet->data + 12) = htonl(c); } void Network::open_offset_log(const char *log) { offset_log = fopen(log, "w"); } void Network::open_freq_log(const char *log) { freq_log = fopen(log, "w"); } void Network::open_rawfreq_log(const char *log) { rawfreq_log = fopen(log, "w"); } void Network::open_packet_log(const char *log) { packet_log = fopen(log, "w"); } void Network::print_stats(int verbosity) const { int i, n = nodes.size(); if (verbosity <= 0) return; for (i = 0; i < n; i++) { if (verbosity > 1) printf("\n---------------------- Node %d ----------------------\n\n", i + 1); stats[i].print(verbosity); } if (verbosity == 1) printf("\n"); } void Network::reset_stats() { int i, n = nodes.size(); for (i = 0; i < n; i++) stats[i].reset(); } void Network::reset_clock_stats() { int i, n = nodes.size(); for (i = 0; i < n; i++) stats[i].reset_clock_stats(); } void Network::send(struct Packet *packet) { double delay = -1.0; unsigned int i; /* broadcast */ if (packet->to == (unsigned int)-1) { for (i = 0; i < nodes.size(); i++) { struct Packet *p; if (i == packet->from) continue; p = new struct Packet; memcpy(p, packet, sizeof (struct Packet)); p->to = i; send(p); } delete packet; return; } if (packet->to >= nodes.size() || packet->from >= nodes.size() || packet->subnet >= subnets) { #ifdef DEBUG printf("dropping packet of type %d from %d to %d:%d:%d at %f\n", packet->type, packet->from, packet->subnet, packet->to, packet->dst_port, time); #endif delete packet; return; } i = packet->from * nodes.size() + packet->to; if (packet->type != MSG_TYPE_UDP_DATA) { /* constant delay to not break the order of TCP packets */ delay = 1.0e-3; } else if (link_delays[i]) { link_delay_variables["time"] = time; link_delay_variables["from"] = packet->from + 1; link_delay_variables["to"] = packet->to + 1; link_delay_variables["subnet"] = packet->subnet + 1; link_delay_variables["port"] = packet->dst_port; link_delay_variables["length"] = packet->len; delay = link_delays[i]->generate(&link_delay_variables); } if (delay > 0.0 && link_corrections[i]) { link_correction_variables["delay"] = delay; link_correction_variables["length"] = packet->len; write_correction(packet, link_corrections[i]->generate(&link_correction_variables)); } stats[packet->from].update_packet_stats(false, time, delay); if (packet_log) fprintf(packet_log, "%e\t%d\t%d\t%e\t%d\t%d\t%d\t%d\t%d\n", time, packet->from + 1, packet->to + 1, delay, packet->src_port, packet->dst_port, packet->subnet + 1, packet->len, packet->type); if (delay > 0.0) { packet->receive_time = time + delay; packet->delay = delay; packet_queue.insert(packet); #ifdef DEBUG printf("sending packet of type %d from %d to %d:%d:%d at %f delay %f \n", packet->type, packet->from, packet->subnet, packet->to, packet->dst_port, time, delay); #endif } else { #ifdef DEBUG printf("dropping packet of type %d from %d to %d:%d:%d at %f\n", packet->type, packet->from, packet->subnet, packet->to, packet->dst_port, time); #endif delete packet; } } double Network::get_time() const { return time; } unsigned int Network::get_subnets() const { return subnets; } 0707010000001A000081A40000000000000000000000016666F04D00000A26000000000000000000000000000000000000002300000000clknetsim-0+git.20240610/network.h/* * Copyright (C) 2010 Miroslav Lichvar <mlichvar@redhat.com> * * 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, see <http://www.gnu.org/licenses/>. */ #ifndef NETWORK_H #define NETWORK_H #include <vector> #include <deque> using namespace std; #include "node.h" #include "stats.h" struct Packet { double receive_time; double delay; int broadcast; unsigned int type; unsigned int subnet; unsigned int from; unsigned int to; unsigned int src_port; unsigned int dst_port; unsigned int len; char data[MAX_PACKET_SIZE]; }; class Packet_queue { deque<Packet *> queue; public: Packet_queue(); ~Packet_queue(); void insert(struct Packet *packet); struct Packet *dequeue(); double get_timeout(double time) const; }; class Network { double time; unsigned int subnets; unsigned int update_rate; unsigned int update_count; const char *socket_name; vector<Node *> nodes; vector<Generator *> link_delays; vector<Generator *> link_corrections; vector<Stats> stats; Generator_variables link_delay_variables; Generator_variables link_correction_variables; Packet_queue packet_queue; FILE *offset_log; FILE *freq_log; FILE *rawfreq_log; FILE *packet_log; void update(); void update_clock_stats(); void write_correction(struct Packet *packet, double correction); public: Network(const char *socket, unsigned int n, unsigned int s, unsigned int rate); ~Network(); bool prepare_clients(); Node *get_node(unsigned int node); void set_link_delay_generator(unsigned int from, unsigned int to, Generator *generator); void set_link_correction_generator(unsigned int from, unsigned int to, Generator *generator); bool run(double time_limit); void open_offset_log(const char *log); void open_freq_log(const char *log); void open_rawfreq_log(const char *log); void open_packet_log(const char *log); void print_stats(int verbosity) const; void reset_stats(); void reset_clock_stats(); void send(struct Packet *packet); double get_time() const; unsigned int get_subnets() const; }; #endif 0707010000001B000081A40000000000000000000000016666F04D0000239D000000000000000000000000000000000000002100000000clknetsim-0+git.20240610/node.cc/* * Copyright (C) 2010 Miroslav Lichvar <mlichvar@redhat.com> * * 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, see <http://www.gnu.org/licenses/>. */ #include "node.h" #include "network.h" #include "protocol.h" #include "sysheaders.h" Node::Node(int index, Network *network) { this->refclock_base = NULL; this->network = network; this->index = index; fd = -1; pending_request = REQ_REGISTER; start_time = 0.0; terminate = false; } Node::~Node() { while (!incoming_packets.empty()) { delete incoming_packets.back(); incoming_packets.pop_back(); } terminate = true; do { if (waiting()) resume(); } while (process_fd()); if (fd >= 0) close(fd); } void Node::set_fd(int fd) { this->fd = fd; } int Node::get_fd() const { return fd; } void Node::set_start_time(double time) { start_time = time; } bool Node::process_fd() { Request_packet request; int received, reqlen; received = recv(fd, &request, sizeof (request), 0); if (received < 0) fprintf(stderr, "recv() failed: %s\n", strerror(errno)); if (received < (int)sizeof (request.header)) return false; reqlen = received - (int)offsetof(Request_packet, data); assert(pending_request == 0); pending_request = request.header.request; #ifdef DEBUG printf("received request %d in node %d at %f\n", pending_request, index, clock.get_real_time()); #endif switch (pending_request) { case REQ_GETTIME: assert(reqlen == 0); process_gettime(); break; case REQ_SETTIME: assert(reqlen == sizeof (Request_settime)); process_settime(&request.data.settime); break; case REQ_ADJTIMEX: assert(reqlen == sizeof (Request_adjtimex)); process_adjtimex(&request.data.adjtimex); break; case REQ_ADJTIME: assert(reqlen == sizeof (Request_adjtime)); process_adjtime(&request.data.adjtime); break; case REQ_SELECT: assert(reqlen == sizeof (Request_select)); process_select(&request.data.select); break; case REQ_SEND: /* request with variable length */ assert(reqlen >= (int)offsetof(Request_send, data) && reqlen <= (int)sizeof (Request_send)); assert(request.data.send.len <= sizeof (request.data.send.data)); assert((int)(request.data.send.len + offsetof(Request_send, data)) <= reqlen); process_send(&request.data.send); break; case REQ_RECV: assert(reqlen == 0); process_recv(); break; case REQ_GETREFSAMPLE: assert(reqlen == 0); process_getrefsample(); break; case REQ_GETREFOFFSETS: assert(reqlen == 0); process_getrefoffsets(); break; case REQ_DEREGISTER: assert(reqlen == 0); break; default: assert(0); } return true; } void Node::reply(void *data, int len, int request) { int sent; assert(request == pending_request); pending_request = 0; if (data) { sent = send(fd, data, len, 0); assert(sent == len); } } void Node::process_gettime() { Reply_gettime r; r.real_time = clock.get_real_time(); r.monotonic_time = clock.get_monotonic_time(); r.network_time = network->get_time(); r.freq_error = clock.get_total_freq() - 1.0; reply(&r, sizeof (r), REQ_GETTIME); } void Node::process_settime(Request_settime *req) { clock.set_time(req->time); reply(NULL, 0, REQ_SETTIME); } void Node::process_adjtimex(Request_adjtimex *req) { Reply_adjtimex rep; struct timex *buf = &req->timex; rep.ret = clock.adjtimex(buf); rep.timex = *buf; rep._pad = 0; reply(&rep, sizeof (rep), REQ_ADJTIMEX); } void Node::process_adjtime(Request_adjtime *req) { Reply_adjtime rep; clock.adjtime(&req->tv, &rep.tv); reply(&rep, sizeof (rep), REQ_ADJTIME); } void Node::try_select() { Reply_select rep = {-1, 0, 0}; if (terminate) { rep.ret = REPLY_SELECT_TERMINATE; #ifdef DEBUG printf("select returned on termination in %d at %f\n", index, clock.get_real_time()); #endif } else if (select_timeout - clock.get_monotonic_time() <= 0.0) { assert(select_timeout - clock.get_monotonic_time() > -1e-10); rep.ret = REPLY_SELECT_TIMEOUT; #ifdef DEBUG printf("select returned on timeout in %d at %f\n", index, clock.get_real_time()); #endif } else if (select_read && incoming_packets.size() > 0) { rep.ret = incoming_packets.back()->broadcast ? REPLY_SELECT_BROADCAST : REPLY_SELECT_NORMAL; rep.type = incoming_packets.back()->type; rep.subnet = incoming_packets.back()->subnet; rep.from = incoming_packets.back()->from; rep.src_port = incoming_packets.back()->src_port; rep.dst_port = incoming_packets.back()->dst_port; #ifdef DEBUG printf("select returned for packet in %d at %f\n", index, clock.get_real_time()); #endif } if (rep.ret >= 0) { rep.time.real_time = clock.get_real_time(); rep.time.monotonic_time = clock.get_monotonic_time(); rep.time.network_time = network->get_time(); rep.time.freq_error = clock.get_total_freq() - 1.0; reply(&rep, sizeof (rep), REQ_SELECT); } } void Node::process_select(Request_select *req) { if (req->timeout < 0.0) req->timeout = 0.0; select_timeout = clock.get_monotonic_time() + req->timeout; select_read = req->read; #ifdef DEBUG printf("select called with timeout %f read %d in %d at %f\n", req->timeout, req->read, index, clock.get_real_time()); #endif try_select(); } void Node::process_send(Request_send *req) { struct Packet *packet; if (!terminate) { packet = new struct Packet; packet->type = req->type; packet->broadcast = req->to == (unsigned int)-1; packet->subnet = req->subnet; packet->from = index; packet->to = req->to; packet->src_port = req->src_port; packet->dst_port = req->dst_port; packet->len = req->len; memcpy(packet->data, req->data, req->len); network->send(packet); } reply(NULL, 0, REQ_SEND); } void Node::process_recv() { Reply_recv rep; struct Packet *packet; if (incoming_packets.empty()) { rep.type = MSG_TYPE_NO_MSG; rep.subnet = 0; rep.from = -1; rep.src_port = 0; rep.dst_port = 0; rep.len = 0; reply(&rep, offsetof (Reply_recv, data), REQ_RECV); return; } packet = incoming_packets.back(); rep.type = packet->type; rep.subnet = packet->subnet; rep.from = packet->from; rep.src_port = packet->src_port; rep.dst_port = packet->dst_port; rep.len = packet->len; assert(packet->len <= sizeof (rep.data)); memcpy(rep.data, packet->data, packet->len); delete packet; reply(&rep, offsetof (Reply_recv, data) + rep.len, REQ_RECV); incoming_packets.pop_back(); #ifdef DEBUG printf("received packet in %d at %f\n", index, clock.get_real_time()); #endif } void Node::receive(struct Packet *packet) { if (pending_request == REQ_REGISTER || pending_request == REQ_DEREGISTER) { delete packet; return; } incoming_packets.insert(incoming_packets.begin(), packet); if (pending_request == REQ_SELECT) try_select(); } void Node::process_getrefsample() { Reply_getrefsample r; refclock.set_generation(true); r.valid = refclock.get_sample(&r.time, &r.offset); assert(!refclock_base); r._pad = 0; reply(&r, sizeof (r), REQ_GETREFSAMPLE); } void Node::process_getrefoffsets() { Reply_getrefoffsets r; if (refclock_base) { r.size = 1; refclock.get_offsets(r.offsets, r.size); r.offsets[0] += network->get_time() - refclock_base->get_real_time(); } else { r.size = MAX_GETREFOFFSETS_SIZE; refclock.get_offsets(r.offsets, r.size); } r._pad = 0; reply(&r, offsetof(Reply_getrefoffsets, offsets) + sizeof (r.offsets[0]) * r.size, REQ_GETREFOFFSETS); } void Node::resume() { switch (pending_request) { case REQ_SELECT: try_select(); break; case REQ_REGISTER: if (start_time - network->get_time() <= 0.0 || terminate) { Reply_register rep; rep.subnets = network->get_subnets(); reply(&rep, sizeof (rep), REQ_REGISTER); #ifdef DEBUG printf("starting %d at %f\n", index, network->get_time()); #endif } break; case REQ_DEREGISTER: break; default: assert(0); } } bool Node::waiting() const { return pending_request == REQ_SELECT || pending_request == REQ_REGISTER || pending_request == REQ_DEREGISTER; } bool Node::finished() const { return pending_request == REQ_DEREGISTER; } double Node::get_timeout() const { switch (pending_request) { case REQ_SELECT: return clock.get_true_interval(select_timeout - clock.get_monotonic_time()); case REQ_REGISTER: return start_time - network->get_time(); case REQ_DEREGISTER: return 10.0; default: assert(0); } } Clock *Node::get_clock() { return &clock; } Refclock *Node::get_refclock() { return &refclock; } void Node::set_refclock_base(Clock *clock) { refclock_base = clock; } 0707010000001C000081A40000000000000000000000016666F04D0000075E000000000000000000000000000000000000002000000000clknetsim-0+git.20240610/node.h/* * Copyright (C) 2010 Miroslav Lichvar <mlichvar@redhat.com> * * 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, see <http://www.gnu.org/licenses/>. */ #ifndef NODE_H #define NODE_H #include "protocol.h" #include "clock.h" #include <vector> using namespace std; class Network; class Node { Clock clock; Refclock refclock; Clock *refclock_base; Network *network; int index; int fd; int pending_request; double start_time; double select_timeout; bool select_read; bool terminate; vector<struct Packet *> incoming_packets; public: Node(int index, Network *network); ~Node(); void set_fd(int fd); int get_fd() const; void set_start_time(double time); bool process_fd(); void reply(void *data, int len, int request); void process_gettime(); void process_settime(Request_settime *req); void process_adjtimex(Request_adjtimex *req); void process_adjtime(Request_adjtime *req); void try_select(); void process_select(Request_select *req); void process_send(Request_send *req); void process_recv(); void process_getrefsample(); void process_getrefoffsets(); void receive(struct Packet *packet); void resume(); bool waiting() const; bool finished() const; double get_timeout() const; Clock *get_clock(); Refclock *get_refclock(); void set_refclock_base(Clock *clock); }; #endif 0707010000001D000081A40000000000000000000000016666F04D00000DA5000000000000000000000000000000000000002400000000clknetsim-0+git.20240610/protocol.h/* * Copyright (C) 2010 Miroslav Lichvar <mlichvar@redhat.com> * * 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, see <http://www.gnu.org/licenses/>. */ #ifndef PROTOCOL_H #define PROTOCOL_H #include "sysheaders.h" #define REQ_REGISTER 1 #define REQ_GETTIME 2 #define REQ_SETTIME 3 #define REQ_ADJTIMEX 4 #define REQ_ADJTIME 5 #define REQ_SELECT 6 #define REQ_SEND 7 #define REQ_RECV 8 #define REQ_GETREFSAMPLE 9 #define REQ_GETREFOFFSETS 10 #define REQ_DEREGISTER 11 struct Request_header { int request; int _pad; }; struct Request_register { unsigned int node; }; struct Reply_register { unsigned int subnets; }; struct Reply_gettime { double real_time; double monotonic_time; double network_time; double freq_error; }; struct Request_settime { double time; }; struct Request_adjtimex { struct timex timex; }; struct Reply_adjtimex { int ret; int _pad; struct timex timex; }; struct Request_adjtime { struct timeval tv; }; struct Reply_adjtime { struct timeval tv; }; struct Request_select { double timeout; int read; int _pad; }; #define REPLY_SELECT_TIMEOUT 0 #define REPLY_SELECT_NORMAL 1 #define REPLY_SELECT_BROADCAST 2 #define REPLY_SELECT_TERMINATE 3 struct Reply_select { int ret; unsigned int type; /* for NORMAL */ unsigned int subnet; /* for NORMAL or BROADCAST */ unsigned int from; /* for NORMAL or BROADCAST */ unsigned int src_port; /* for NORMAL or BROADCAST */ unsigned int dst_port; /* for NORMAL or BROADCAST */ struct Reply_gettime time; }; #define MAX_PACKET_SIZE 4000 #define MSG_TYPE_NO_MSG 0 #define MSG_TYPE_UDP_DATA 1 #define MSG_TYPE_TCP_CONNECT 2 #define MSG_TYPE_TCP_DATA 3 #define MSG_TYPE_TCP_DISCONNECT 4 struct Request_send { unsigned int type; unsigned int subnet; unsigned int to; unsigned int src_port; unsigned int dst_port; unsigned int len; char data[MAX_PACKET_SIZE]; }; struct Reply_recv { unsigned int type; unsigned int subnet; unsigned int from; unsigned int src_port; unsigned int dst_port; unsigned int len; char data[MAX_PACKET_SIZE]; }; struct Reply_getrefsample { double time; double offset; int valid; int _pad; }; #define MAX_GETREFOFFSETS_SIZE 1024 struct Reply_getrefoffsets { unsigned int size; int _pad; double offsets[MAX_GETREFOFFSETS_SIZE]; }; union Request_data { struct Request_register _register; struct Request_settime settime; struct Request_adjtimex adjtimex; struct Request_adjtime adjtime; struct Request_select select; struct Request_send send; }; union Reply_data { struct Reply_register _register; struct Reply_gettime gettime; struct Reply_adjtimex adjtimex; struct Reply_adjtime adjtime; struct Reply_select select; struct Reply_recv recv; struct Reply_getrefsample getrefsample; struct Reply_getrefoffsets getrefoffsets; }; struct Request_packet { struct Request_header header; union Request_data data; }; struct Reply_packet { union Reply_data data; }; #endif 0707010000001E000081A40000000000000000000000016666F04D00001A4F000000000000000000000000000000000000002300000000clknetsim-0+git.20240610/server.cc/* * Copyright (C) 2010 Miroslav Lichvar <mlichvar@redhat.com> * * 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, see <http://www.gnu.org/licenses/>. */ #include "sysheaders.h" #include "network.h" bool load_config(const char *file, Network *network, unsigned int nodes) { Generator_generator generator; FILE *f; const char *ws = " \t\n\r"; char line[1000], *var, *arg, *end; unsigned int node, node2; f = fopen(file, "r"); if (!f) return false; while (fgets(line, sizeof (line), f)) { end = line + strlen(line); var = line + strspn(line, ws); arg = line + strcspn(line, "="); *arg++ = '\0'; if (var >= end || *var == '#') continue; if (arg >= end) return false; while (end > line && (end[-1] == '\r' || end[-1] == '\n' || end[-1] == '\t' || end[-1] == ' ')) *--end = '\0'; arg += strspn(arg, ws); if (strncmp(var, "node", 4)) return false; var += 4; node = atoi(var) - 1; if (node >= nodes) continue; var += strcspn(var, "_") + 1; if (var >= end) return false; if (strncmp(var, "offset", 6) == 0) network->get_node(node)->get_clock()->set_time(atof(arg)); else if (strncmp(var, "start", 5) == 0) network->get_node(node)->set_start_time(atof(arg)); else if (strncmp(var, "freq", 4) == 0) { if (arg[0] == '(') network->get_node(node)->get_clock()->set_freq_generator(generator.generate(arg)); else network->get_node(node)->get_clock()->set_freq(atof(arg)); } else if (strncmp(var, "step", 4) == 0) network->get_node(node)->get_clock()->set_step_generator(generator.generate(arg)); else if (strncmp(var, "shift_pll", 9) == 0) network->get_node(node)->get_clock()->set_ntp_shift_pll(atoi(arg)); else if (strncmp(var, "fll_mode2", 9) == 0) network->get_node(node)->get_clock()->set_ntp_flag(atoi(arg), CLOCK_NTP_FLL_MODE2); else if (strncmp(var, "pll_clamp", 9) == 0) network->get_node(node)->get_clock()->set_ntp_flag(atoi(arg), CLOCK_NTP_PLL_CLAMP); else if (strncmp(var, "delay_correction", 16) == 0) { var += 16; node2 = atoi(var) - 1; if (node2 >= nodes) continue; network->set_link_correction_generator(node, node2, generator.generate(arg)); } else if (strncmp(var, "delay", 5) == 0) { var += 5; node2 = atoi(var) - 1; if (node2 >= nodes) continue; network->set_link_delay_generator(node, node2, generator.generate(arg)); } else if (strncmp(var, "refclock_base", 13) == 0) { if (strncmp(arg, "node", 4) != 0) return false; node2 = atoi(arg + 4) - 1; if (node2 >= nodes) return false; network->get_node(node)->set_refclock_base(network->get_node(node2)->get_clock()); } else if (strncmp(var, "refclock", 8) == 0) network->get_node(node)->get_refclock()->set_offset_generator(generator.generate(arg)); else return false; } fclose(f); return true; } void run_generator(char *expr, int num) { Generator_generator gen_generator; Generator *generator; generator = gen_generator.generate(expr); while (num--) printf("%.9e\n", generator->generate(NULL)); delete generator; } int main(int argc, char **argv) { int nodes, subnets = 1, help = 0, verbosity = 2, generate_only = 0, rate = 1; double limit = 10000.0, reset = 0.0; const char *offset_log = NULL, *freq_log = NULL, *rawfreq_log = NULL, *packet_log = NULL, *config, *socket = "clknetsim.sock", *env; struct timeval tv; int r, opt; Network *network; while ((opt = getopt(argc, argv, "l:r:R:n:o:f:Gg:p:s:v:h")) != -1) { switch (opt) { case 'l': limit = atof(optarg); break; case 'r': reset = atof(optarg); break; case 'R': rate = atoi(optarg); break; case 'n': subnets = atoi(optarg); break; case 'o': offset_log = optarg; break; case 'f': freq_log = optarg; break; case 'g': rawfreq_log = optarg; break; case 'p': packet_log = optarg; break; case 's': socket = optarg; break; case 'G': generate_only = 1; break; case 'v': verbosity = atoi(optarg); break; case 'h': default: help = 1; } } if (optind + 2 != argc || help) { printf("usage: clknetsim [options] config nodes\n"); printf(" or: clknetsim -G expr num\n"); printf(" -l secs set time limit to secs (default 10000)\n"); printf(" -r secs reset clock stats after secs (default 0)\n"); printf(" -R rate set freq/log/stats update rate (default 1 per second)\n"); printf(" -n subnets set number of subnetworks (default 1)\n"); printf(" -o file log time offsets to file\n"); printf(" -f file log frequency offsets to file\n"); printf(" -g file log raw (w/o slew) frequency offsets to file\n"); printf(" -p file log packet delays to file\n"); printf(" -s socket set server socket name (default clknetsim.sock)\n"); printf(" -v level set verbosity level (default 2)\n"); printf(" -G print num numbers generated by expr\n"); printf(" -h print usage\n"); return 1; } config = argv[optind]; nodes = atoi(argv[optind + 1]); env = getenv("CLKNETSIM_RANDOM_SEED"); if (env) { srandom(atoi(env)); } else { gettimeofday(&tv, NULL); srandom(tv.tv_sec ^ tv.tv_usec); } if (generate_only) { run_generator(argv[optind], nodes); return 0; } network = new Network(socket, nodes, subnets, rate); if (offset_log) network->open_offset_log(offset_log); if (freq_log) network->open_freq_log(freq_log); if (rawfreq_log) network->open_rawfreq_log(rawfreq_log); if (packet_log) network->open_packet_log(packet_log); if (!load_config(config, network, nodes)) { fprintf(stderr, "Couldn't parse config %s\n", config); return 1; } if (!network->prepare_clients()) return 1; fprintf(stderr, "Running simulation..."); if (reset && reset < limit) { r = network->run(reset); network->reset_clock_stats(); } else r = true; if (r) r = network->run(limit); if (r) { fprintf(stderr, "done\n\n"); network->print_stats(verbosity); } else fprintf(stderr, "failed\n"); delete network; return !r; } 0707010000001F000081A40000000000000000000000016666F04D0000144F000000000000000000000000000000000000002200000000clknetsim-0+git.20240610/stats.cc/* * Copyright (C) 2010 Miroslav Lichvar <mlichvar@redhat.com> * * 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, see <http://www.gnu.org/licenses/>. */ #include "stats.h" #include "sysheaders.h" Stats::Stats() { reset(); } Stats::~Stats() { } void Stats::reset() { reset_clock_stats(); packets_in_sum2 = 0.0; packets_out_sum2 = 0.0; packets_in = 0; packets_out = 0; packets_in_time_last = 0.0; packets_out_time_last = 0.0; packets_in_int_sum = 0.0; packets_out_int_sum = 0.0; packets_in_int_min = 0.0; packets_out_int_min = 0.0; wakeups_int_sum = 0; wakeups = 0; } void Stats::reset_clock_stats() { offset_sum2 = 0.0; offset_abs_sum = 0.0; offset_sum = 0.0; offset_abs_max = 0.0; freq_sum2 = 0.0; freq_abs_sum = 0.0; freq_sum = 0.0; freq_abs_max = 0.0; rawfreq_sum2 = 0.0; rawfreq_abs_sum = 0.0; rawfreq_sum = 0.0; rawfreq_abs_max = 0.0; samples = 0; } void Stats::update_clock_stats(double offset, double freq, double rawfreq) { offset_sum2 += offset * offset; offset_abs_sum += fabs(offset); offset_sum += offset; if (offset_abs_max < fabs(offset)) offset_abs_max = fabs(offset); freq_sum2 += freq * freq; freq_abs_sum += fabs(freq); freq_sum += freq; if (freq_abs_max < fabs(freq)) freq_abs_max = fabs(freq); rawfreq_sum2 += rawfreq * rawfreq; rawfreq_abs_sum += fabs(rawfreq); rawfreq_sum += rawfreq; if (rawfreq_abs_max < fabs(rawfreq)) rawfreq_abs_max = fabs(rawfreq); samples++; wakeups_int_sum++; } void Stats::update_packet_stats(bool incoming, double time, double delay) { if (delay < 0.0) delay = 0.0; if (incoming) { packets_in++; packets_in_sum2 += delay * delay; if (packets_in >= 2) { packets_in_int_sum += time - packets_in_time_last; if (packets_in == 2 || packets_in_int_min > time - packets_in_time_last) packets_in_int_min = time - packets_in_time_last; } packets_in_time_last = time; } else { packets_out++; packets_out_sum2 += delay * delay; if (packets_out >= 2) { packets_out_int_sum += time - packets_out_time_last; if (packets_out == 2 || packets_out_int_min > time - packets_out_time_last) packets_out_int_min = time - packets_out_time_last; } packets_out_time_last = time; } } void Stats::update_wakeup_stats() { wakeups++; } void Stats::print(int verbosity) const { if (verbosity <= 0) return; if (verbosity <= 1) { printf("%e ", sqrt(offset_sum2 / samples)); return; } printf("RMS offset: \t%e\n", sqrt(offset_sum2 / samples)); printf("Maximum absolute offset: \t%e\n", offset_abs_max); printf("Mean absolute offset: \t%e\n", offset_abs_sum / samples); printf("Mean offset: \t%e\n", offset_sum / samples); printf("RMS frequency: \t%e\n", sqrt(freq_sum2 / samples)); printf("Maximum absolute frequency: \t%e\n", freq_abs_max); printf("Mean absolute frequency: \t%e\n", freq_abs_sum / samples); printf("Mean frequency: \t%e\n", freq_sum / samples); printf("RMS raw frequency: \t%e\n", sqrt(rawfreq_sum2 / samples)); printf("Maximum absolute raw frequency: \t%e\n", rawfreq_abs_max); printf("Mean absolute raw frequency: \t%e\n", rawfreq_abs_sum / samples); printf("Mean raw frequency: \t%e\n", rawfreq_sum / samples); if (packets_in) { printf("RMS incoming packet delay: \t%e\n", (double)sqrt(packets_in_sum2 / packets_in)); } else { printf("RMS incoming packet delay: \tinf\n"); } if (packets_in >= 2) { printf("Mean incoming packet interval: \t%e\n", packets_in_int_sum / (packets_in - 1)); printf("Minimum incoming packet interval: \t%e\n", packets_in_int_min); } else { printf("Mean incoming packet interval: \tinf\n"); printf("Minimum incoming packet interval: \tinf\n"); } if (packets_out) { printf("RMS outgoing packet delay: \t%e\n", (double)sqrt(packets_out_sum2 / packets_out)); } else { printf("RMS outgoing packet delay: \tinf\n"); } if (packets_out >= 2) { printf("Mean outgoing packet interval: \t%e\n", packets_out_int_sum / (packets_out - 1)); printf("Minimum outgoing packet interval: \t%e\n", packets_out_int_min); } else { printf("Mean outgoing packet interval: \tinf\n"); printf("Minimum outgoing packet interval: \tinf\n"); } if (wakeups) printf("Mean wakeup interval: \t%e\n", (double)wakeups_int_sum / wakeups); else printf("Mean wakeup interval: \tinf\n"); } 07070100000020000081A40000000000000000000000016666F04D000006A5000000000000000000000000000000000000002100000000clknetsim-0+git.20240610/stats.h/* * Copyright (C) 2010 Miroslav Lichvar <mlichvar@redhat.com> * * 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, see <http://www.gnu.org/licenses/>. */ #ifndef STATS_H #define STATS_H #include "clock.h" class Stats { double offset_sum2; double offset_abs_sum; double offset_sum; double offset_abs_max; double freq_sum2; double freq_abs_sum; double freq_sum; double freq_abs_max; double rawfreq_sum2; double rawfreq_abs_sum; double rawfreq_sum; double rawfreq_abs_max; unsigned long samples; double packets_in_sum2; double packets_out_sum2; unsigned long packets_in; unsigned long packets_out; double packets_in_time_last; double packets_out_time_last; double packets_in_int_sum; double packets_out_int_sum; double packets_in_int_min; double packets_out_int_min; unsigned long wakeups_int_sum; unsigned long wakeups; public: Stats(); ~Stats(); void reset(); void reset_clock_stats(); void update_clock_stats(double offset, double freq, double rawfreq); void update_packet_stats(bool incoming, double time, double delay); void update_wakeup_stats(); void print(int verbosity) const; }; #endif 07070100000021000081A40000000000000000000000016666F04D000003D8000000000000000000000000000000000000002600000000clknetsim-0+git.20240610/sysheaders.h#ifndef SYSTEM_H #include <arpa/inet.h> #include <errno.h> #include <stdint.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <poll.h> #include <time.h> #include <sys/time.h> #include <sys/timex.h> #include <assert.h> #include <math.h> #include <stddef.h> #include <stdlib.h> #ifdef __linux__ #ifndef ADJ_SETOFFSET #define ADJ_SETOFFSET 0x0100 /* add 'time' to current time */ #endif #ifndef ADJ_MICRO #define ADJ_MICRO 0x1000 /* select microsecond resolution */ #endif #ifndef ADJ_NANO #define ADJ_NANO 0x2000 /* select nanosecond resolution */ #endif #ifndef ADJ_OFFSET_SS_READ #define ADJ_OFFSET_SS_READ 0xa001 /* read-only adjtime */ #endif #ifndef STA_NANO #define STA_NANO 0x2000 /* resolution (0 = us, 1 = ns) (ro) */ #endif #ifndef STA_MODE #define STA_MODE 0x4000 /* mode (0 = PLL, 1 = FLL) (ro) */ #endif #endif #endif 07070100000022000081ED0000000000000000000000016666F04D00001970000000000000000000000000000000000000002600000000clknetsim-0+git.20240610/visclocks.py#!/usr/bin/python # # Copyright (C) 2012 Miroslav Lichvar <mlichvar@redhat.com> # # 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, see <http://www.gnu.org/licenses/>. import sys, pygame import collections import math freq_file = open(sys.argv[1], 'r') offset_file = open(sys.argv[2], 'r') delay_file = open(sys.argv[3], 'r') (maxx, maxy) = (640, 480) pygame.init() window = pygame.display.set_mode((maxx, maxy)) font = pygame.font.SysFont("monospace", 12) pygame.time.set_timer(pygame.USEREVENT, 1000 // 60) white = (255, 255, 255) black = (0, 0, 0) blue = (50, 50, 255) lightblue = (150, 150, 255) red = (255, 0, 0) green = (0, 255, 0) freqs = collections.deque() offsets = collections.deque() delays = [] freq_avg = 0.0 time = -1 xscale = 2e-1 yscale = 1e6 frame_skip = 10 offset_rms = [ 0, 0, 0, 0 ] offset_lock = 0 delays_shown = 2 eof = False paused = False game_mode = False delay_lines = [] while True: line = delay_file.readline() if line == "": break line = line.split() if line[2] == "1": idx = int(line[1]) elif line[1] == "1": idx = int(line[2]) else: continue while len(delay_lines) < idx + 1: delay_lines.append([]) delay_lines[idx].append(line) delay_file.close() for i in range(len(delay_lines)): delays.append([]) last_line = [] for line in delay_lines[i]: if len(last_line) == 9 and len(line) == 9 and last_line[2] == "1" and line[1] == "1": delay1 = float(last_line[3]) delay2 = float(line[3]) delays[i].append((int(float(line[0])), (delay1 - delay2) / 2, (delay1 + delay2) / 2)) last_line = line while True: event = pygame.event.wait() if event.type == pygame.QUIT: break if event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE or event.key == pygame.K_p: paused = not paused elif event.key == pygame.K_q: break elif event.key == pygame.K_g: game_mode = not game_mode pygame.event.set_grab(game_mode) pygame.mouse.set_visible(not game_mode) elif event.key == pygame.K_l: offset_lock += 1 offset_lock %= len(offset_rms) delays_shown = offset_lock + 1 if delays_shown == 1: delays_shown = 3 elif event.key == pygame.K_PAGEUP: frame_skip *= 2 elif event.key == pygame.K_PAGEDOWN: frame_skip /= 2 if frame_skip <= 5: frame_skip = 5 elif event.key == pygame.K_UP: yscale *= 2 elif event.key == pygame.K_DOWN: yscale /= 2 elif event.key == pygame.K_LEFT: xscale *= 2 elif event.key == pygame.K_RIGHT: xscale /= 2 elif event.type == pygame.MOUSEMOTION: rel = pygame.mouse.get_rel() if game_mode and rel != (0, 0): freq_avg += rel[1] / 1e9 pygame.event.clear(pygame.USEREVENT) if event.type != pygame.USEREVENT or paused: continue while not eof: histsize = maxx / xscale freq = freq_file.readline() if freq == "": eof = True break freqs.appendleft(float(freq)) while len(freqs) > histsize: freqs.pop() offset = offset_file.readline() if offset == "": eof = True break offsets.appendleft(list(map(float, offset.split()))) while len(offsets) > histsize: offsets.pop() if not game_mode: freq_avg += 0.001 * (freqs[0] - freq_avg) else: buttons = pygame.mouse.get_pressed() if buttons == (1, 0, 0): slew = 1e-6 elif buttons == (0, 0, 1): slew = -1e-6 else: slew = 0.0 offsets[0][0] = offsets[1][0] - (freq_avg - freqs[0] + slew) offset_rms = [r + 0.001 * (o * o - r) for r, o in zip(offset_rms, offsets[0])] time += 1 if time % frame_skip == 0: break if len(offsets) == 0: continue window.fill(black) last_off = [] x = maxx y = maxy / 2 + offsets[0][offset_lock] * yscale def get_delays(time): d = delays[delays_shown] idx = len(d) - 1 while time >= 0 and idx >= 0: while d[idx][0] > time and idx > 0: idx -= 1 if d[idx][0] != time: yield (False, 0, 0) else: yield (True, d[idx][1], d[idx][2]) time -= 1 for freq, offset, (delay_valid, delay_center, delay_size) in zip(freqs, offsets, get_delays(time)): x -= xscale y -= (freq - freq_avg) * yscale if int(x + xscale) != int(x): for i, (off, col) in enumerate(zip(offset, [white, red, green, blue])): oy = y - off * yscale if len(last_off) > i: pygame.draw.aaline(window, col, last_off[i], (x, oy)) else: last_off.append(()) last_off[i] = (x, oy) if game_mode: break if delay_valid: pygame.draw.line(window, blue, (x, y - (delay_center - delay_size) * yscale), (x, y - (delay_center + delay_size) * yscale)) pygame.draw.line(window, lightblue, (x - 3, y - delay_center * yscale), (x + 3, y - delay_center * yscale)) window.blit(font.render("time = %d rms = %s xscale = %.1e yscale = %.1e" % (time, ["%1.6f" % math.sqrt(o) for o in offset_rms], xscale, yscale), False, white, black), (5, 0)) window.blit(font.render("q:Quit p:Pause PgDn:Slow down PgUp:Speed up g:Game mode l:Switch lock Arrows:Scale", False, white, black), (5, maxy - 15)) pygame.display.flip() #pygame.image.save(window, "visclocks%06d.png" % time) freq_file.close() offset_file.close() 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!414 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor