├── .gitignore ├── examples ├── phc2sys.test ├── busybox.test ├── chronyd.test ├── ntpq.test ├── pmc.test ├── chronyc.test ├── ntpdate.test ├── ptp4l.test ├── sntp.test ├── nsm.test ├── ntpd.test ├── ptp4l-phc2sys.test ├── visclocks.test └── clkmgr.test ├── Makefile ├── sysheaders.h ├── stats.h ├── node.h ├── clock.h ├── network.h ├── protocol.h ├── generator.h ├── stats.cc ├── visclocks.py ├── server.cc ├── client_fuzz.c ├── clknetsim.bash ├── README ├── node.cc ├── clock.cc ├── generator.cc ├── network.cc └── COPYING /.gitignore: -------------------------------------------------------------------------------- 1 | /*.o 2 | /.deps 3 | /clknetsim 4 | /clknetsim.so 5 | /tests* 6 | -------------------------------------------------------------------------------- /examples/phc2sys.test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CLKNETSIM_PATH=.. 4 | . ../clknetsim.bash 5 | 6 | echo "node1_freq = (sum (* 1e-9 (normal)))" > tmp/conf 7 | echo "node1_refclock = (* 1e-6 (normal))" >> tmp/conf 8 | 9 | start_client 1 phc2sys "" 10 | 11 | start_server 1 -v 2 -o log.offset -f log.freq -r 1000 -l 4000 12 | 13 | cat tmp/stats 14 | -------------------------------------------------------------------------------- /examples/busybox.test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CLKNETSIM_PATH=.. 4 | . ../clknetsim.bash 5 | 6 | generate_config1 2 0.01 "(+ 1e-6 (sum (* 1e-9 (normal))))" "(+ 1e-3 (* 1e-3 (exponential)))" 7 | 8 | start_client 1 chrony "local stratum 1" 9 | start_client 2 busybox "192.168.123.1" 10 | 11 | start_server 2 -v 2 -o log.offset -f log.freq -g log.rawfreq -p log.packets -r 2000 -l 400000 12 | 13 | cat tmp/stats 14 | -------------------------------------------------------------------------------- /examples/chronyd.test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CLKNETSIM_PATH=.. 4 | . ../clknetsim.bash 5 | 6 | generate_config1 2 0.01 "(sum (* 1e-9 (normal)))" "(+ 1e-3 (* 1e-3 (exponential)))" "" "(* 1e-4 (normal))" 7 | 8 | start_client 1 chrony "local stratum 1" 9 | start_client 2 chrony "server 192.168.123.1 minpoll 6 maxpoll 6 10 | refclock SHM 0" 11 | 12 | start_server 2 -v 2 -o log.offset -f log.freq -g log.rawfreq -p log.packets -r 2000 -l 40000 13 | 14 | cat tmp/stats 15 | -------------------------------------------------------------------------------- /examples/ntpq.test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CLKNETSIM_PATH=.. 4 | . ../clknetsim.bash 5 | 6 | generate_config4 "1" "1 2 | 2 3" 0.01 "(sum (* 1e-9 (normal)))" "(* 1e-8 (exponential))" 7 | 8 | echo 'node3_start = 1000' >> tmp/conf 9 | 10 | start_client 1 ntpd "server 127.127.1.0" 11 | start_client 2 ntpd "server 192.168.123.1 minpoll 6 maxpoll 6" 12 | start_client 3 ntpq "rv 0 13 | peers" "" "192.168.123.2" 14 | 15 | start_server 3 -l 1010 16 | 17 | cat tmp/log.3 18 | -------------------------------------------------------------------------------- /examples/pmc.test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CLKNETSIM_PATH=.. 4 | . ../clknetsim.bash 5 | 6 | generate_config4 "1" "1 2 3" 0.01 "(sum (* 1e-9 (normal)))" "(* 1e-8 (exponential))" 7 | 8 | echo 'node3_start = 100' >> tmp/conf 9 | 10 | start_client 1 ptp4l "" "" "-i eth0" 11 | start_client 2 ptp4l "" "" "-i eth0" 12 | start_client 3 pmc " 13 | GET TIME_STATUS_NP 14 | GET TIME_PROPERTIES_DATA_SET 15 | GET PORT_DATA_SET" 16 | 17 | start_server 3 -l 110 18 | 19 | cat tmp/log.3 20 | -------------------------------------------------------------------------------- /examples/chronyc.test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CLKNETSIM_PATH=.. 4 | . ../clknetsim.bash 5 | 6 | generate_config4 "1" "1 2 3" 0.01 "(sum (* 1e-9 (normal)))" "(+ 1e-3 (* 1e-3 (exponential)))" 7 | 8 | echo "node3_start = 10000" >> tmp/conf 9 | start_client 1 chronyd "local stratum 1" 10 | start_client 2 chronyd "server 192.168.123.1" 11 | start_client 3 chronyc "tracking 12 | sources -n 13 | sourcestats" "" "-h 192.168.123.2" 14 | 15 | start_server 3 -v 2 -l 10001 16 | 17 | cat tmp/log.3 18 | -------------------------------------------------------------------------------- /examples/ntpdate.test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CLKNETSIM_PATH=.. 4 | . ../clknetsim.bash 5 | 6 | generate_config1 3 10.0 "(sum (* 1e-9 (normal)))" "(+ 1e-3 (* 1e-3 (exponential)))" 7 | 8 | echo "node2_start = 330" >> tmp/conf 9 | echo "node3_start = 330" >> tmp/conf 10 | 11 | start_client 1 ntpd "server 127.127.1.0" 12 | start_client 2 ntpdate "-B 192.168.123.1" 13 | start_client 3 ntpdate "-b 192.168.123.1" 14 | 15 | start_server 3 -v 2 -o log.offset -r 340 -l 350 16 | 17 | cat tmp/stats 18 | -------------------------------------------------------------------------------- /examples/ptp4l.test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CLKNETSIM_PATH=.. 4 | . ../clknetsim.bash 5 | 6 | generate_config4 "1" "1 2 3 | 3 4" 0.01 "(sum (* 1e-9 (normal)))" "(* 1e-8 (exponential))" 7 | 8 | start_client 1 ptp4l "clockClass 6" "" "-i eth0" 9 | start_client 2 ptp4l "time_stamping software" "" "-i eth0" 10 | start_client 3 ptp4l "" "" "-i eth0 -i eth1" 11 | start_client 4 ptp4l "" "" "-i eth1" 12 | 13 | start_server 4 -n 2 -o log.offset -p log.packets -r 100 -l 1000 14 | 15 | cat tmp/stats 16 | -------------------------------------------------------------------------------- /examples/sntp.test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export EVENT_NOEPOLL=1 4 | 5 | CLKNETSIM_PATH=.. 6 | . ../clknetsim.bash 7 | 8 | generate_config1 3 10.0 "(sum (* 1e-9 (normal)))" "(+ 1e-3 (* 1e-3 (exponential)))" 9 | 10 | echo "node2_start = 330" >> tmp/conf 11 | echo "node3_start = 330" >> tmp/conf 12 | 13 | start_client 1 ntpd "server 127.127.1.0" 14 | start_client 2 sntp "-s 192.168.123.1" 15 | start_client 3 sntp "-S 192.168.123.1" 16 | 17 | start_server 3 -v 2 -o log.offset -r 340 -l 350 18 | 19 | cat tmp/stats 20 | -------------------------------------------------------------------------------- /examples/nsm.test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CLKNETSIM_PATH=.. 4 | . ../clknetsim.bash 5 | 6 | generate_config4 "1 3" "1 2 3" 0.01 "(sum (* 1e-9 (normal)))" "(* 1e-8 (exponential))" 7 | 8 | echo 'node3_start = 50' >> tmp/conf 9 | 10 | start_client 1 ptp4l "hybrid_e2e 1 11 | net_sync_monitor 1" "" "-i eth0" 12 | start_client 2 ptp4l "hybrid_e2e 1 13 | net_sync_monitor 1" "" "-i eth0" 14 | start_client 3 nsm "NSM 192.168.123.1 15 | NSM 192.168.123.2" "" "-i eth0" 16 | 17 | start_server 3 -l 110 18 | 19 | cat tmp/log.3 20 | -------------------------------------------------------------------------------- /examples/ntpd.test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CLKNETSIM_PATH=.. 4 | . ../clknetsim.bash 5 | 6 | generate_config1 3 0.01 "(sum (* 1e-9 (normal)))" "(+ 1e-3 (* 1e-3 (exponential)))" 7 | echo "node2_shift_pll = 2" >> tmp/conf 8 | 9 | start_client 1 ntp "server 127.127.1.0" 10 | start_client 2 ntp "server 192.168.123.1 minpoll 6 maxpoll 6" 11 | start_client 3 ntp "server 192.168.123.1 minpoll 6 maxpoll 6" 12 | 13 | start_server 3 -v 2 -o log.offset -r 2000 -l 40000 14 | 15 | cat tmp/stats 16 | 17 | echo 18 | get_stat 'RMS offset' 19 | get_stat 'RMS frequency' 20 | -------------------------------------------------------------------------------- /examples/ptp4l-phc2sys.test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CLKNETSIM_PATH=.. 4 | . ../clknetsim.bash 5 | 6 | export CLKNETSIM_UNIX_SUBNET=2 7 | 8 | generate_config4 "1" "1 2 | 2 3" 0.01 "(sum (* 1e-9 (normal)))" "(* 1e-8 (exponential))" 9 | 10 | start_client 1 ptp4l "clockClass 6" "" "-i eth0" 11 | start_client 2 ptp4l " 12 | first_step_threshold 0.0 13 | max_frequency 10000" "" "-i eth0" 14 | CLKNETSIM_PHC_SWAP=1 \ 15 | start_client 3 phc2sys "-a -r -z /clknetsim/unix/2:1" 16 | 17 | echo "node3_refclock = (+ -37 (* 1e-6 (normal)))" >> tmp/conf 18 | echo "node3_refclock_base = node2" >> tmp/conf 19 | 20 | start_server 3 -v 2 -n 2 -o log.offset -f log.freq -g log.rawfreq -p log.packets -r 1000 -l 4000 21 | 22 | cat tmp/stats 23 | -------------------------------------------------------------------------------- /examples/visclocks.test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CLKNETSIM_PATH=.. 4 | . ../clknetsim.bash 5 | 6 | export CLKNETSIM_RANDOM_SEED 7 | 8 | CLKNETSIM_RANDOM_SEED=1011 9 | generate_seq '(sum (* 2e-9 (normal)))' 50000 > freq.input 10 | CLKNETSIM_RANDOM_SEED=1012 11 | generate_seq '(+ 1e-5 (* 1e-4 (exponential)))' 10000 > delay_up.input 12 | CLKNETSIM_RANDOM_SEED=1013 13 | generate_seq '(+ 1e-5 (* 1e-4 (exponential)))' 10000 > delay_down.input 14 | CLKNETSIM_RANDOM_SEED=1014 15 | 16 | generate_config1 3 0.0 "(file \"freq.input\")" \ 17 | "(file \"delay_up.input\")" "(file \"delay_down.input\")" 18 | 19 | start_client 1 chrony "local stratum 1" 20 | start_client 2 chrony "server 192.168.123.1 minpoll 6 maxpoll 6 minsamples 32" 21 | start_client 3 chrony "server 192.168.123.1 minpoll 6 maxpoll 6 maxsamples 16" 22 | start_server 3 -o log.offset -p log.packets -l 50000 23 | 24 | ../visclocks.py freq.input log.offset log.packets 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS += -O2 -Wall -g -fPIC 2 | CXXFLAGS += $(CFLAGS) 3 | CPPFLAGS += $(sysflags) 4 | 5 | all: clknetsim.so clknetsim 6 | 7 | sysflags := $(shell echo -e '\x23include ' | $(CC) -x c -E - | \ 8 | grep -q __timezone_ptr_t || echo -DGETTIMEOFDAY_VOID) 9 | sysflags += $(shell echo -e '\x23include ' | $(CC) -x c -E - | \ 10 | grep -q '[^_]SOF_TIMESTAMPING_OPT_ID ' && echo -DHAVE_SOF_TS_OPT_ID) 11 | 12 | clientobjs = client.o 13 | serverobjs = $(patsubst %.cc,%.o,$(wildcard *.cc)) 14 | 15 | clknetsim.so: $(clientobjs) 16 | $(CC) $(CFLAGS) -shared -o $@ $^ $(LDFLAGS) -ldl -lm 17 | 18 | clknetsim: $(serverobjs) 19 | $(CXX) $(CFLAGS) -o $@ $^ $(LDFLAGS) 20 | 21 | clean: 22 | rm -rf clknetsim *.so *.o core.* .deps 23 | 24 | .deps: 25 | @mkdir .deps 26 | 27 | .deps/%.d: %.c .deps 28 | @$(CC) -MM $(CPPFLAGS) -MT '$(<:%.c=%.o) $@' $< -o $@ 29 | 30 | .deps/%.D: %.cc .deps 31 | @$(CXX) -MM $(CPPFLAGS) -MT '$(<:%.cc=%.o) $@' $< -o $@ 32 | 33 | -include $(clientobjs:%.o=.deps/%.d) $(serverobjs:%.o=.deps/%.D) 34 | -------------------------------------------------------------------------------- /sysheaders.h: -------------------------------------------------------------------------------- 1 | #ifndef SYSTEM_H 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #ifdef __linux__ 24 | #ifndef ADJ_SETOFFSET 25 | #define ADJ_SETOFFSET 0x0100 /* add 'time' to current time */ 26 | #endif 27 | #ifndef ADJ_MICRO 28 | #define ADJ_MICRO 0x1000 /* select microsecond resolution */ 29 | #endif 30 | #ifndef ADJ_NANO 31 | #define ADJ_NANO 0x2000 /* select nanosecond resolution */ 32 | #endif 33 | #ifndef ADJ_OFFSET_SS_READ 34 | #define ADJ_OFFSET_SS_READ 0xa001 /* read-only adjtime */ 35 | #endif 36 | #ifndef STA_NANO 37 | #define STA_NANO 0x2000 /* resolution (0 = us, 1 = ns) (ro) */ 38 | #endif 39 | #ifndef STA_MODE 40 | #define STA_MODE 0x4000 /* mode (0 = PLL, 1 = FLL) (ro) */ 41 | #endif 42 | #endif 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /stats.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Miroslav Lichvar 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #ifndef STATS_H 19 | #define STATS_H 20 | 21 | #include "clock.h" 22 | 23 | class Stats { 24 | double offset_sum2; 25 | double offset_abs_sum; 26 | double offset_sum; 27 | double offset_abs_max; 28 | double freq_sum2; 29 | double freq_abs_sum; 30 | double freq_sum; 31 | double freq_abs_max; 32 | double rawfreq_sum2; 33 | double rawfreq_abs_sum; 34 | double rawfreq_sum; 35 | double rawfreq_abs_max; 36 | unsigned long samples; 37 | 38 | double packets_in_sum2; 39 | double packets_out_sum2; 40 | unsigned long packets_in; 41 | unsigned long packets_out; 42 | double packets_in_time_last; 43 | double packets_out_time_last; 44 | double packets_in_int_sum; 45 | double packets_out_int_sum; 46 | double packets_in_int_min; 47 | double packets_out_int_min; 48 | 49 | unsigned long wakeups_int_sum; 50 | unsigned long wakeups; 51 | 52 | public: 53 | Stats(); 54 | ~Stats(); 55 | void reset(); 56 | void reset_clock_stats(); 57 | void update_clock_stats(double offset, double freq, double rawfreq); 58 | void update_packet_stats(bool incoming, double time, double delay); 59 | void update_wakeup_stats(); 60 | void print(int verbosity) const; 61 | }; 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /node.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Miroslav Lichvar 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #ifndef NODE_H 19 | #define NODE_H 20 | 21 | #include "protocol.h" 22 | #include "clock.h" 23 | 24 | #include 25 | 26 | using namespace std; 27 | 28 | class Network; 29 | 30 | class Node { 31 | Clock clock; 32 | Refclock refclock; 33 | Clock *refclock_base; 34 | Network *network; 35 | int index; 36 | int fd; 37 | int pending_request; 38 | double start_time; 39 | double select_timeout; 40 | bool select_read; 41 | bool terminate; 42 | 43 | vector incoming_packets; 44 | 45 | public: 46 | Node(int index, Network *network); 47 | ~Node(); 48 | void set_fd(int fd); 49 | int get_fd() const; 50 | void set_start_time(double time); 51 | bool process_fd(); 52 | void reply(void *data, int len, int request); 53 | void process_gettime(); 54 | void process_settime(Request_settime *req); 55 | void process_adjtimex(Request_adjtimex *req); 56 | void process_adjtime(Request_adjtime *req); 57 | void try_select(); 58 | void process_select(Request_select *req); 59 | void process_send(Request_send *req); 60 | void process_recv(); 61 | void process_getrefsample(); 62 | void process_getrefoffsets(); 63 | 64 | void receive(struct Packet *packet); 65 | void resume(); 66 | bool waiting() const; 67 | bool finished() const; 68 | 69 | double get_timeout() const; 70 | Clock *get_clock(); 71 | Refclock *get_refclock(); 72 | void set_refclock_base(Clock *clock); 73 | }; 74 | 75 | #endif 76 | -------------------------------------------------------------------------------- /examples/clkmgr.test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Requirements: 4 | # A Clock Manager Proxy service 'clkmgr_proxy' and library 'libclkmgr.so' need 5 | # to be built and install into the system before running this test. Below shows 6 | # an example of how to build and install the Clock Manager components. Please 7 | # refer to https://github.com/erezgeva/libptpmgmt for details. 8 | # Example commands: 9 | # $ cd 10 | # $ git clone https://github.com/erezgeva/libptpmgmt 11 | # $ cd /libptpmgmt 12 | # $ autoreconf -i 13 | # $ ./configure 14 | # $ make 15 | # $ sudo make install 16 | # 17 | # A Clock Manager Client application that uses libclkmgr.so is needed for 18 | # testing. The name of the application should be in the format of 19 | # 'clkmgr', e.g. 'clkmgr_test'. A sample client application is 20 | # available at: 21 | # https://github.com/erezgeva/libptpmgmt/blob/master/clkmgr/sample/clkmgr_test.cpp 22 | # To build the sample application, follow the instructions below. 23 | # Example commands: 24 | # $ cd 25 | # $ cd /libptpmgmt/clkmgr/sample 26 | # $ make 27 | 28 | # Replace the following line with the path to the folder that contains Clock 29 | # Manager Client application. 30 | PATH+=:/libptpmgmt/clkmgr/sample 31 | 32 | CLKNETSIM_PATH=.. 33 | . ../clknetsim.bash 34 | 35 | export CLKNETSIM_UNIX_SUBNET=2 36 | 37 | generate_config4 '1 3 4' '1 2 | 2 3 | 3 4' 0.01 \ 38 | '(sum (* 1e-9 (normal)))' '(* 1e-8 (exponential))' 39 | 40 | # Start Clock Manager Proxy service, at 20th second 41 | echo 'node3_start = 20' >> $CLKNETSIM_TMPDIR/conf 42 | # Start Clock Manager Client application, at 30th second 43 | echo 'node4_start = 30' >> $CLKNETSIM_TMPDIR/conf 44 | 45 | start_client 1 ptp4l '' '' "-i eth0" 46 | start_client 2 ptp4l '' '' "-i eth0" 47 | start_client 3 clkmgr_proxy " 48 | { 49 | \"timeBases\": [{ 50 | \"timeBaseName\": \"Working Clock\", 51 | \"ptp4l\": { 52 | \"interfaceName\": \"eth0\", 53 | \"udsAddr\": \"/clknetsim/unix/2:1\", 54 | \"domainNumber\": 0, 55 | \"transportSpecific\": 0 56 | } 57 | }] 58 | }" '' '' 59 | 60 | # Replace the following line with the name of your Clock Manager Client 61 | # application. 62 | start_client 4 clkmgr '' '_test' '-t 1' 63 | 64 | start_server 4 -l 100 -n $CLKNETSIM_UNIX_SUBNET 65 | 66 | cat $CLKNETSIM_TMPDIR/log.4 | grep -v -E '^\s*$|Waiting|No event|sleep for' 67 | -------------------------------------------------------------------------------- /clock.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Miroslav Lichvar 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #ifndef CLOCK_H 19 | #define CLOCK_H 20 | 21 | #include "sysheaders.h" 22 | 23 | #include "generator.h" 24 | 25 | #define CLOCK_NTP_FLL_MODE2 0x1 26 | #define CLOCK_NTP_PLL_CLAMP 0x2 27 | 28 | class Clock { 29 | double time; 30 | double mono_time; 31 | double freq; 32 | 33 | Generator *freq_generator; 34 | Generator *step_generator; 35 | 36 | long base_tick; 37 | 38 | struct timex ntp_timex; 39 | int ntp_state; 40 | int ntp_shift_pll; 41 | int ntp_flags; 42 | long ntp_update_interval; 43 | double ntp_offset; 44 | double ntp_slew; 45 | 46 | long ss_offset; 47 | long ss_slew; 48 | 49 | public: 50 | Clock(); 51 | ~Clock(); 52 | double get_real_time() const; 53 | double get_monotonic_time() const; 54 | double get_total_freq() const; 55 | double get_raw_freq() const; 56 | double get_true_interval(double local_interval) const; 57 | double get_local_interval(double true_interval) const; 58 | 59 | void set_freq_generator(Generator *gen); 60 | void set_step_generator(Generator *gen); 61 | void set_freq(double freq); 62 | void set_time(double time); 63 | void step_time(double step); 64 | void set_ntp_shift_pll(int shift); 65 | void set_ntp_flag(int enable, int flag); 66 | 67 | void advance(double real_interval); 68 | void update(bool second); 69 | 70 | void update_ntp_offset(long offset); 71 | int adjtimex(struct timex *buf); 72 | int adjtime(const struct timeval *delta, struct timeval *olddelta); 73 | }; 74 | 75 | class Refclock { 76 | double time; 77 | double offset; 78 | bool generate; 79 | bool valid; 80 | 81 | Generator *offset_generator; 82 | public: 83 | Refclock(); 84 | ~Refclock(); 85 | void set_offset_generator(Generator *gen); 86 | void update(double time, const Clock *clock); 87 | void set_generation(bool enable); 88 | bool get_sample(double *time, double *offset) const; 89 | void get_offsets(double *offsets, int size); 90 | }; 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /network.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Miroslav Lichvar 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #ifndef NETWORK_H 19 | #define NETWORK_H 20 | 21 | #include 22 | #include 23 | 24 | using namespace std; 25 | 26 | #include "node.h" 27 | #include "stats.h" 28 | 29 | struct Packet { 30 | double receive_time; 31 | double delay; 32 | int broadcast; 33 | unsigned int type; 34 | unsigned int subnet; 35 | unsigned int from; 36 | unsigned int to; 37 | unsigned int src_port; 38 | unsigned int dst_port; 39 | unsigned int len; 40 | char data[MAX_PACKET_SIZE]; 41 | }; 42 | 43 | class Packet_queue { 44 | deque queue; 45 | public: 46 | Packet_queue(); 47 | ~Packet_queue(); 48 | void insert(struct Packet *packet); 49 | struct Packet *dequeue(); 50 | double get_timeout(double time) const; 51 | }; 52 | 53 | class Network { 54 | double time; 55 | unsigned int subnets; 56 | unsigned int update_rate; 57 | unsigned int update_count; 58 | 59 | const char *socket_name; 60 | const char *update_executable; 61 | vector nodes; 62 | vector link_delays; 63 | vector link_corrections; 64 | vector stats; 65 | 66 | Generator_variables link_delay_variables; 67 | Generator_variables link_correction_variables; 68 | 69 | Packet_queue packet_queue; 70 | 71 | FILE *offset_log; 72 | FILE *freq_log; 73 | FILE *rawfreq_log; 74 | FILE *packet_log; 75 | 76 | void update(); 77 | void update_clock_stats(); 78 | void write_correction(struct Packet *packet, double correction); 79 | 80 | public: 81 | Network(const char *socket, const char *executable, unsigned int n, unsigned int s, unsigned int rate); 82 | ~Network(); 83 | bool prepare_clients(); 84 | Node *get_node(unsigned int node); 85 | void set_link_delay_generator(unsigned int from, unsigned int to, Generator *generator); 86 | void set_link_correction_generator(unsigned int from, unsigned int to, Generator *generator); 87 | bool run(double time_limit); 88 | void open_offset_log(const char *log); 89 | void open_freq_log(const char *log); 90 | void open_rawfreq_log(const char *log); 91 | void open_packet_log(const char *log); 92 | void print_stats(int verbosity) const; 93 | void reset_stats(); 94 | void reset_clock_stats(); 95 | 96 | void send(struct Packet *packet); 97 | double get_time() const; 98 | unsigned int get_subnets() const; 99 | }; 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /protocol.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Miroslav Lichvar 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #ifndef PROTOCOL_H 19 | #define PROTOCOL_H 20 | 21 | #include "sysheaders.h" 22 | 23 | #define REQ_REGISTER 1 24 | #define REQ_GETTIME 2 25 | #define REQ_SETTIME 3 26 | #define REQ_ADJTIMEX 4 27 | #define REQ_ADJTIME 5 28 | #define REQ_SELECT 6 29 | #define REQ_SEND 7 30 | #define REQ_RECV 8 31 | #define REQ_GETREFSAMPLE 9 32 | #define REQ_GETREFOFFSETS 10 33 | #define REQ_DEREGISTER 11 34 | 35 | struct Request_header { 36 | int request; 37 | int _pad; 38 | }; 39 | 40 | struct Request_register { 41 | unsigned int node; 42 | }; 43 | 44 | struct Reply_register { 45 | unsigned int subnets; 46 | }; 47 | 48 | struct Reply_gettime { 49 | double real_time; 50 | double monotonic_time; 51 | double network_time; 52 | double freq_error; 53 | }; 54 | 55 | struct Request_settime { 56 | double time; 57 | }; 58 | 59 | struct Request_adjtimex { 60 | struct timex timex; 61 | }; 62 | 63 | struct Reply_adjtimex { 64 | int ret; 65 | int _pad; 66 | struct timex timex; 67 | }; 68 | 69 | struct Request_adjtime { 70 | struct timeval tv; 71 | }; 72 | 73 | struct Reply_adjtime { 74 | struct timeval tv; 75 | }; 76 | 77 | struct Request_select { 78 | double timeout; 79 | int read; 80 | int _pad; 81 | }; 82 | 83 | #define REPLY_SELECT_TIMEOUT 0 84 | #define REPLY_SELECT_NORMAL 1 85 | #define REPLY_SELECT_BROADCAST 2 86 | #define REPLY_SELECT_TERMINATE 3 87 | 88 | struct Reply_select { 89 | int ret; 90 | unsigned int type; /* for NORMAL */ 91 | unsigned int subnet; /* for NORMAL or BROADCAST */ 92 | unsigned int from; /* for NORMAL or BROADCAST */ 93 | unsigned int src_port; /* for NORMAL or BROADCAST */ 94 | unsigned int dst_port; /* for NORMAL or BROADCAST */ 95 | struct Reply_gettime time; 96 | }; 97 | 98 | #define MAX_PACKET_SIZE 4000 99 | 100 | #define MSG_TYPE_NO_MSG 0 101 | #define MSG_TYPE_UDP_DATA 1 102 | #define MSG_TYPE_TCP_CONNECT 2 103 | #define MSG_TYPE_TCP_DATA 3 104 | #define MSG_TYPE_TCP_DISCONNECT 4 105 | 106 | struct Request_send { 107 | unsigned int type; 108 | unsigned int subnet; 109 | unsigned int to; 110 | unsigned int src_port; 111 | unsigned int dst_port; 112 | unsigned int len; 113 | char data[MAX_PACKET_SIZE]; 114 | }; 115 | 116 | struct Reply_recv { 117 | unsigned int type; 118 | unsigned int subnet; 119 | unsigned int from; 120 | unsigned int src_port; 121 | unsigned int dst_port; 122 | unsigned int len; 123 | char data[MAX_PACKET_SIZE]; 124 | }; 125 | 126 | struct Reply_getrefsample { 127 | double time; 128 | double offset; 129 | int valid; 130 | int _pad; 131 | }; 132 | 133 | #define MAX_GETREFOFFSETS_SIZE 1024 134 | 135 | struct Reply_getrefoffsets { 136 | unsigned int size; 137 | int _pad; 138 | double offsets[MAX_GETREFOFFSETS_SIZE]; 139 | }; 140 | 141 | union Request_data { 142 | struct Request_register _register; 143 | struct Request_settime settime; 144 | struct Request_adjtimex adjtimex; 145 | struct Request_adjtime adjtime; 146 | struct Request_select select; 147 | struct Request_send send; 148 | }; 149 | 150 | union Reply_data { 151 | struct Reply_register _register; 152 | struct Reply_gettime gettime; 153 | struct Reply_adjtimex adjtimex; 154 | struct Reply_adjtime adjtime; 155 | struct Reply_select select; 156 | struct Reply_recv recv; 157 | struct Reply_getrefsample getrefsample; 158 | struct Reply_getrefoffsets getrefoffsets; 159 | }; 160 | 161 | struct Request_packet { 162 | struct Request_header header; 163 | union Request_data data; 164 | }; 165 | 166 | struct Reply_packet { 167 | union Reply_data data; 168 | }; 169 | 170 | #endif 171 | -------------------------------------------------------------------------------- /generator.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Miroslav Lichvar 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #ifndef GENERATOR_H 19 | #define GENERATOR_H 20 | 21 | #include "sysheaders.h" 22 | #include 23 | #include 24 | #include 25 | 26 | using namespace std; 27 | 28 | typedef map Generator_variables; 29 | 30 | class Generator { 31 | protected: 32 | vector input; 33 | bool constant; 34 | 35 | public: 36 | Generator(const vector *input); 37 | virtual ~Generator(); 38 | virtual double generate(const Generator_variables *variables) = 0; 39 | bool is_constant() const; 40 | }; 41 | 42 | class Generator_float: public Generator { 43 | double f; 44 | 45 | public: 46 | Generator_float(double f); 47 | virtual double generate(const Generator_variables *variables); 48 | }; 49 | 50 | class Generator_variable: public Generator { 51 | string name; 52 | 53 | public: 54 | Generator_variable(string name); 55 | virtual double generate(const Generator_variables *variables); 56 | }; 57 | 58 | class Generator_random_uniform: public Generator { 59 | char random_state[16]; 60 | 61 | public: 62 | Generator_random_uniform(const vector *input); 63 | virtual double generate(const Generator_variables *variables); 64 | }; 65 | 66 | class Generator_random_normal: public Generator { 67 | Generator_random_uniform uniform; 68 | 69 | public: 70 | Generator_random_normal(const vector *input); 71 | virtual double generate(const Generator_variables *variables); 72 | }; 73 | 74 | class Generator_random_exponential: public Generator { 75 | Generator_random_uniform uniform; 76 | 77 | public: 78 | Generator_random_exponential(const vector *input); 79 | virtual double generate(const Generator_variables *variables); 80 | }; 81 | 82 | class Generator_random_poisson: public Generator { 83 | Generator_random_uniform uniform; 84 | double L; 85 | 86 | public: 87 | Generator_random_poisson(const vector *input); 88 | virtual double generate(const Generator_variables *variables); 89 | }; 90 | 91 | class Generator_file: public Generator { 92 | FILE *input; 93 | 94 | public: 95 | Generator_file(const char *file); 96 | virtual ~Generator_file(); 97 | virtual double generate(const Generator_variables *variables); 98 | }; 99 | 100 | class Generator_wave_pulse: public Generator { 101 | int high; 102 | int low; 103 | int counter; 104 | 105 | public: 106 | Generator_wave_pulse(const vector *input); 107 | virtual double generate(const Generator_variables *variables); 108 | }; 109 | 110 | class Generator_wave_sine: public Generator { 111 | double length; 112 | int counter; 113 | 114 | public: 115 | Generator_wave_sine(const vector *input); 116 | virtual double generate(const Generator_variables *variables); 117 | }; 118 | 119 | class Generator_wave_cosine: public Generator { 120 | double length; 121 | int counter; 122 | 123 | public: 124 | Generator_wave_cosine(const vector *input); 125 | virtual double generate(const Generator_variables *variables); 126 | }; 127 | 128 | class Generator_wave_triangle: public Generator { 129 | double length; 130 | int counter; 131 | 132 | public: 133 | Generator_wave_triangle(const vector *input); 134 | virtual double generate(const Generator_variables *variables); 135 | }; 136 | 137 | class Generator_sum: public Generator { 138 | double sum; 139 | public: 140 | Generator_sum(const vector *input); 141 | virtual double generate(const Generator_variables *variables); 142 | }; 143 | 144 | class Generator_multiply: public Generator { 145 | public: 146 | Generator_multiply(const vector *input); 147 | virtual double generate(const Generator_variables *variables); 148 | }; 149 | 150 | class Generator_add: public Generator { 151 | public: 152 | Generator_add(const vector *input); 153 | virtual double generate(const Generator_variables *variables); 154 | }; 155 | 156 | class Generator_modulo: public Generator { 157 | public: 158 | Generator_modulo(const vector *input); 159 | virtual double generate(const Generator_variables *variables); 160 | }; 161 | 162 | class Generator_equal: public Generator { 163 | public: 164 | Generator_equal(const vector *input); 165 | virtual double generate(const Generator_variables *variables); 166 | }; 167 | 168 | class Generator_max: public Generator { 169 | public: 170 | Generator_max(const vector *input); 171 | virtual double generate(const Generator_variables *variables); 172 | }; 173 | 174 | class Generator_min: public Generator { 175 | public: 176 | Generator_min(const vector *input); 177 | virtual double generate(const Generator_variables *variables); 178 | }; 179 | 180 | class Generator_generator { 181 | public: 182 | Generator_generator(); 183 | ~Generator_generator(); 184 | Generator *generate(char *code) const; 185 | }; 186 | 187 | #endif 188 | -------------------------------------------------------------------------------- /stats.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Miroslav Lichvar 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "stats.h" 19 | 20 | #include "sysheaders.h" 21 | 22 | Stats::Stats() { 23 | reset(); 24 | } 25 | 26 | Stats::~Stats() { 27 | } 28 | 29 | void Stats::reset() { 30 | reset_clock_stats(); 31 | 32 | packets_in_sum2 = 0.0; 33 | packets_out_sum2 = 0.0; 34 | packets_in = 0; 35 | packets_out = 0; 36 | packets_in_time_last = 0.0; 37 | packets_out_time_last = 0.0; 38 | packets_in_int_sum = 0.0; 39 | packets_out_int_sum = 0.0; 40 | packets_in_int_min = 0.0; 41 | packets_out_int_min = 0.0; 42 | 43 | wakeups_int_sum = 0; 44 | wakeups = 0; 45 | } 46 | 47 | void Stats::reset_clock_stats() { 48 | offset_sum2 = 0.0; 49 | offset_abs_sum = 0.0; 50 | offset_sum = 0.0; 51 | offset_abs_max = 0.0; 52 | freq_sum2 = 0.0; 53 | freq_abs_sum = 0.0; 54 | freq_sum = 0.0; 55 | freq_abs_max = 0.0; 56 | rawfreq_sum2 = 0.0; 57 | rawfreq_abs_sum = 0.0; 58 | rawfreq_sum = 0.0; 59 | rawfreq_abs_max = 0.0; 60 | samples = 0; 61 | } 62 | 63 | void Stats::update_clock_stats(double offset, double freq, double rawfreq) { 64 | offset_sum2 += offset * offset; 65 | offset_abs_sum += fabs(offset); 66 | offset_sum += offset; 67 | if (offset_abs_max < fabs(offset)) 68 | offset_abs_max = fabs(offset); 69 | 70 | freq_sum2 += freq * freq; 71 | freq_abs_sum += fabs(freq); 72 | freq_sum += freq; 73 | if (freq_abs_max < fabs(freq)) 74 | freq_abs_max = fabs(freq); 75 | 76 | rawfreq_sum2 += rawfreq * rawfreq; 77 | rawfreq_abs_sum += fabs(rawfreq); 78 | rawfreq_sum += rawfreq; 79 | if (rawfreq_abs_max < fabs(rawfreq)) 80 | rawfreq_abs_max = fabs(rawfreq); 81 | 82 | samples++; 83 | wakeups_int_sum++; 84 | } 85 | 86 | void Stats::update_packet_stats(bool incoming, double time, double delay) { 87 | if (delay < 0.0) 88 | delay = 0.0; 89 | if (incoming) { 90 | packets_in++; 91 | packets_in_sum2 += delay * delay; 92 | if (packets_in >= 2) { 93 | packets_in_int_sum += time - packets_in_time_last; 94 | if (packets_in == 2 || packets_in_int_min > time - packets_in_time_last) 95 | packets_in_int_min = time - packets_in_time_last; 96 | } 97 | packets_in_time_last = time; 98 | } else { 99 | packets_out++; 100 | packets_out_sum2 += delay * delay; 101 | if (packets_out >= 2) { 102 | packets_out_int_sum += time - packets_out_time_last; 103 | if (packets_out == 2 || packets_out_int_min > time - packets_out_time_last) 104 | packets_out_int_min = time - packets_out_time_last; 105 | } 106 | packets_out_time_last = time; 107 | } 108 | } 109 | 110 | void Stats::update_wakeup_stats() { 111 | wakeups++; 112 | } 113 | 114 | void Stats::print(int verbosity) const { 115 | if (verbosity <= 0) 116 | return; 117 | if (verbosity <= 1) { 118 | printf("%e ", sqrt(offset_sum2 / samples)); 119 | return; 120 | } 121 | 122 | printf("RMS offset: \t%e\n", sqrt(offset_sum2 / samples)); 123 | printf("Maximum absolute offset: \t%e\n", offset_abs_max); 124 | printf("Mean absolute offset: \t%e\n", offset_abs_sum / samples); 125 | printf("Mean offset: \t%e\n", offset_sum / samples); 126 | printf("RMS frequency: \t%e\n", sqrt(freq_sum2 / samples)); 127 | printf("Maximum absolute frequency: \t%e\n", freq_abs_max); 128 | printf("Mean absolute frequency: \t%e\n", freq_abs_sum / samples); 129 | printf("Mean frequency: \t%e\n", freq_sum / samples); 130 | printf("RMS raw frequency: \t%e\n", sqrt(rawfreq_sum2 / samples)); 131 | printf("Maximum absolute raw frequency: \t%e\n", rawfreq_abs_max); 132 | printf("Mean absolute raw frequency: \t%e\n", rawfreq_abs_sum / samples); 133 | printf("Mean raw frequency: \t%e\n", rawfreq_sum / samples); 134 | if (packets_in) { 135 | printf("RMS incoming packet delay: \t%e\n", (double)sqrt(packets_in_sum2 / packets_in)); 136 | } else { 137 | printf("RMS incoming packet delay: \tinf\n"); 138 | } 139 | if (packets_in >= 2) { 140 | printf("Mean incoming packet interval: \t%e\n", packets_in_int_sum / (packets_in - 1)); 141 | printf("Minimum incoming packet interval: \t%e\n", packets_in_int_min); 142 | } else { 143 | printf("Mean incoming packet interval: \tinf\n"); 144 | printf("Minimum incoming packet interval: \tinf\n"); 145 | } 146 | if (packets_out) { 147 | printf("RMS outgoing packet delay: \t%e\n", (double)sqrt(packets_out_sum2 / packets_out)); 148 | } else { 149 | printf("RMS outgoing packet delay: \tinf\n"); 150 | } 151 | if (packets_out >= 2) { 152 | printf("Mean outgoing packet interval: \t%e\n", packets_out_int_sum / (packets_out - 1)); 153 | printf("Minimum outgoing packet interval: \t%e\n", packets_out_int_min); 154 | } else { 155 | printf("Mean outgoing packet interval: \tinf\n"); 156 | printf("Minimum outgoing packet interval: \tinf\n"); 157 | } 158 | if (wakeups) 159 | printf("Mean wakeup interval: \t%e\n", (double)wakeups_int_sum / wakeups); 160 | else 161 | printf("Mean wakeup interval: \tinf\n"); 162 | } 163 | -------------------------------------------------------------------------------- /visclocks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright (C) 2012 Miroslav Lichvar 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import sys, pygame 19 | import collections 20 | import math 21 | 22 | freq_file = open(sys.argv[1], 'r') 23 | offset_file = open(sys.argv[2], 'r') 24 | delay_file = open(sys.argv[3], 'r') 25 | 26 | (maxx, maxy) = (640, 480) 27 | 28 | pygame.init() 29 | window = pygame.display.set_mode((maxx, maxy)) 30 | font = pygame.font.SysFont("monospace", 12) 31 | 32 | pygame.time.set_timer(pygame.USEREVENT, 1000 // 60) 33 | 34 | white = (255, 255, 255) 35 | black = (0, 0, 0) 36 | blue = (50, 50, 255) 37 | lightblue = (150, 150, 255) 38 | red = (255, 0, 0) 39 | green = (0, 255, 0) 40 | 41 | freqs = collections.deque() 42 | offsets = collections.deque() 43 | delays = [] 44 | 45 | freq_avg = 0.0 46 | time = -1 47 | xscale = 2e-1 48 | yscale = 1e6 49 | frame_skip = 10 50 | 51 | offset_rms = [ 0, 0, 0, 0 ] 52 | offset_lock = 0 53 | delays_shown = 2 54 | eof = False 55 | paused = False 56 | game_mode = False 57 | 58 | delay_lines = [] 59 | while True: 60 | line = delay_file.readline() 61 | if line == "": 62 | break 63 | line = line.split() 64 | if line[2] == "1": 65 | idx = int(line[1]) 66 | elif line[1] == "1": 67 | idx = int(line[2]) 68 | else: 69 | continue 70 | while len(delay_lines) < idx + 1: 71 | delay_lines.append([]) 72 | delay_lines[idx].append(line) 73 | 74 | delay_file.close() 75 | 76 | for i in range(len(delay_lines)): 77 | delays.append([]) 78 | 79 | last_line = [] 80 | for line in delay_lines[i]: 81 | if len(last_line) == 9 and len(line) == 9 and last_line[2] == "1" and line[1] == "1": 82 | delay1 = float(last_line[3]) 83 | delay2 = float(line[3]) 84 | delays[i].append((int(float(line[0])), (delay1 - delay2) / 2, (delay1 + delay2) / 2)) 85 | last_line = line 86 | 87 | while True: 88 | event = pygame.event.wait() 89 | if event.type == pygame.QUIT: 90 | break 91 | if event.type == pygame.KEYDOWN: 92 | if event.key == pygame.K_SPACE or event.key == pygame.K_p: 93 | paused = not paused 94 | elif event.key == pygame.K_q: 95 | break 96 | elif event.key == pygame.K_g: 97 | game_mode = not game_mode 98 | pygame.event.set_grab(game_mode) 99 | pygame.mouse.set_visible(not game_mode) 100 | elif event.key == pygame.K_l: 101 | offset_lock += 1 102 | offset_lock %= len(offset_rms) 103 | delays_shown = offset_lock + 1 104 | if delays_shown == 1: 105 | delays_shown = 3 106 | elif event.key == pygame.K_PAGEUP: 107 | frame_skip *= 2 108 | elif event.key == pygame.K_PAGEDOWN: 109 | frame_skip /= 2 110 | if frame_skip <= 5: 111 | frame_skip = 5 112 | elif event.key == pygame.K_UP: 113 | yscale *= 2 114 | elif event.key == pygame.K_DOWN: 115 | yscale /= 2 116 | elif event.key == pygame.K_LEFT: 117 | xscale *= 2 118 | elif event.key == pygame.K_RIGHT: 119 | xscale /= 2 120 | elif event.type == pygame.MOUSEMOTION: 121 | rel = pygame.mouse.get_rel() 122 | if game_mode and rel != (0, 0): 123 | freq_avg += rel[1] / 1e9 124 | 125 | pygame.event.clear(pygame.USEREVENT) 126 | 127 | if event.type != pygame.USEREVENT or paused: 128 | continue 129 | 130 | while not eof: 131 | histsize = maxx / xscale 132 | 133 | freq = freq_file.readline() 134 | if freq == "": 135 | eof = True 136 | break 137 | freqs.appendleft(float(freq)) 138 | while len(freqs) > histsize: 139 | freqs.pop() 140 | 141 | offset = offset_file.readline() 142 | if offset == "": 143 | eof = True 144 | break 145 | offsets.appendleft(list(map(float, offset.split()))) 146 | while len(offsets) > histsize: 147 | offsets.pop() 148 | 149 | if not game_mode: 150 | freq_avg += 0.001 * (freqs[0] - freq_avg) 151 | else: 152 | buttons = pygame.mouse.get_pressed() 153 | if buttons == (1, 0, 0): 154 | slew = 1e-6 155 | elif buttons == (0, 0, 1): 156 | slew = -1e-6 157 | else: 158 | slew = 0.0 159 | offsets[0][0] = offsets[1][0] - (freq_avg - freqs[0] + slew) 160 | 161 | offset_rms = [r + 0.001 * (o * o - r) for r, o in zip(offset_rms, offsets[0])] 162 | 163 | time += 1 164 | if time % frame_skip == 0: 165 | break 166 | 167 | if len(offsets) == 0: 168 | continue 169 | 170 | window.fill(black) 171 | last_off = [] 172 | x = maxx 173 | y = maxy / 2 + offsets[0][offset_lock] * yscale 174 | 175 | def get_delays(time): 176 | d = delays[delays_shown] 177 | idx = len(d) - 1 178 | while time >= 0 and idx >= 0: 179 | while d[idx][0] > time and idx > 0: 180 | idx -= 1 181 | if d[idx][0] != time: 182 | yield (False, 0, 0) 183 | else: 184 | yield (True, d[idx][1], d[idx][2]) 185 | time -= 1 186 | 187 | for freq, offset, (delay_valid, delay_center, delay_size) in zip(freqs, offsets, get_delays(time)): 188 | x -= xscale 189 | y -= (freq - freq_avg) * yscale 190 | if int(x + xscale) != int(x): 191 | for i, (off, col) in enumerate(zip(offset, [white, red, green, blue])): 192 | oy = y - off * yscale 193 | if len(last_off) > i: 194 | pygame.draw.aaline(window, col, last_off[i], (x, oy)) 195 | else: 196 | last_off.append(()) 197 | last_off[i] = (x, oy) 198 | if game_mode: 199 | break 200 | 201 | if delay_valid: 202 | pygame.draw.line(window, blue, (x, y - (delay_center - delay_size) * yscale), (x, y - (delay_center + delay_size) * yscale)) 203 | pygame.draw.line(window, lightblue, (x - 3, y - delay_center * yscale), (x + 3, y - delay_center * yscale)) 204 | 205 | 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)) 206 | 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)) 207 | pygame.display.flip() 208 | 209 | #pygame.image.save(window, "visclocks%06d.png" % time) 210 | 211 | freq_file.close() 212 | offset_file.close() 213 | -------------------------------------------------------------------------------- /server.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Miroslav Lichvar 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "sysheaders.h" 19 | #include "network.h" 20 | 21 | bool load_config(const char *file, Network *network, unsigned int nodes) { 22 | Generator_generator generator; 23 | FILE *f; 24 | const char *ws = " \t\n\r"; 25 | char line[1000], *var, *arg, *end; 26 | unsigned int node, node2; 27 | 28 | f = fopen(file, "r"); 29 | if (!f) 30 | return false; 31 | 32 | while (fgets(line, sizeof (line), f)) { 33 | end = line + strlen(line); 34 | var = line + strspn(line, ws); 35 | arg = line + strcspn(line, "="); 36 | *arg++ = '\0'; 37 | 38 | if (var >= end || *var == '#') 39 | continue; 40 | 41 | if (arg >= end) 42 | return false; 43 | 44 | while (end > line && (end[-1] == '\r' || end[-1] == '\n' || end[-1] == '\t' || end[-1] == ' ')) 45 | *--end = '\0'; 46 | 47 | arg += strspn(arg, ws); 48 | 49 | if (strncmp(var, "node", 4)) 50 | return false; 51 | 52 | var += 4; 53 | node = atoi(var) - 1; 54 | if (node >= nodes) 55 | continue; 56 | 57 | var += strcspn(var, "_") + 1; 58 | if (var >= end) 59 | return false; 60 | 61 | if (strncmp(var, "offset", 6) == 0) 62 | network->get_node(node)->get_clock()->set_time(atof(arg)); 63 | else if (strncmp(var, "start", 5) == 0) 64 | network->get_node(node)->set_start_time(atof(arg)); 65 | else if (strncmp(var, "freq", 4) == 0) { 66 | if (arg[0] == '(') 67 | network->get_node(node)->get_clock()->set_freq_generator(generator.generate(arg)); 68 | else 69 | network->get_node(node)->get_clock()->set_freq(atof(arg)); 70 | } else if (strncmp(var, "step", 4) == 0) 71 | network->get_node(node)->get_clock()->set_step_generator(generator.generate(arg)); 72 | else if (strncmp(var, "shift_pll", 9) == 0) 73 | network->get_node(node)->get_clock()->set_ntp_shift_pll(atoi(arg)); 74 | else if (strncmp(var, "fll_mode2", 9) == 0) 75 | network->get_node(node)->get_clock()->set_ntp_flag(atoi(arg), CLOCK_NTP_FLL_MODE2); 76 | else if (strncmp(var, "pll_clamp", 9) == 0) 77 | network->get_node(node)->get_clock()->set_ntp_flag(atoi(arg), CLOCK_NTP_PLL_CLAMP); 78 | else if (strncmp(var, "delay_correction", 16) == 0) { 79 | var += 16; 80 | node2 = atoi(var) - 1; 81 | if (node2 >= nodes) 82 | continue; 83 | network->set_link_correction_generator(node, node2, generator.generate(arg)); 84 | } else if (strncmp(var, "delay", 5) == 0) { 85 | var += 5; 86 | node2 = atoi(var) - 1; 87 | if (node2 >= nodes) 88 | continue; 89 | network->set_link_delay_generator(node, node2, generator.generate(arg)); 90 | } else if (strncmp(var, "refclock_base", 13) == 0) { 91 | if (strncmp(arg, "node", 4) != 0) 92 | return false; 93 | node2 = atoi(arg + 4) - 1; 94 | if (node2 >= nodes) 95 | return false; 96 | network->get_node(node)->set_refclock_base(network->get_node(node2)->get_clock()); 97 | } else if (strncmp(var, "refclock", 8) == 0) 98 | network->get_node(node)->get_refclock()->set_offset_generator(generator.generate(arg)); 99 | else 100 | return false; 101 | } 102 | 103 | fclose(f); 104 | 105 | return true; 106 | } 107 | 108 | void run_generator(char *expr, int num) { 109 | Generator_generator gen_generator; 110 | Generator *generator; 111 | 112 | generator = gen_generator.generate(expr); 113 | while (num--) 114 | printf("%.9e\n", generator->generate(NULL)); 115 | delete generator; 116 | } 117 | 118 | int main(int argc, char **argv) { 119 | int nodes, subnets = 1, help = 0, verbosity = 2, generate_only = 0, rate = 1; 120 | double limit = 10000.0, reset = 0.0; 121 | const char *offset_log = NULL, *freq_log = NULL, *rawfreq_log = NULL, 122 | *packet_log = NULL, *config, *socket = "clknetsim.sock", *env, *executable = NULL; 123 | struct timeval tv; 124 | 125 | int r, opt; 126 | Network *network; 127 | 128 | while ((opt = getopt(argc, argv, "l:r:R:n:o:f:Gg:p:s:v:e:h")) != -1) { 129 | switch (opt) { 130 | case 'l': 131 | limit = atof(optarg); 132 | break; 133 | case 'r': 134 | reset = atof(optarg); 135 | break; 136 | case 'R': 137 | rate = atoi(optarg); 138 | break; 139 | case 'n': 140 | subnets = atoi(optarg); 141 | break; 142 | case 'o': 143 | offset_log = optarg; 144 | break; 145 | case 'f': 146 | freq_log = optarg; 147 | break; 148 | case 'g': 149 | rawfreq_log = optarg; 150 | break; 151 | case 'p': 152 | packet_log = optarg; 153 | break; 154 | case 's': 155 | socket = optarg; 156 | break; 157 | case 'G': 158 | generate_only = 1; 159 | break; 160 | case 'v': 161 | verbosity = atoi(optarg); 162 | break; 163 | case 'e': 164 | executable = optarg; 165 | break; 166 | case 'h': 167 | default: 168 | help = 1; 169 | } 170 | } 171 | 172 | if (optind + 2 != argc || help) { 173 | printf("usage: clknetsim [options] config nodes\n"); 174 | printf(" or: clknetsim -G expr num\n"); 175 | printf(" -l secs set time limit to secs (default 10000)\n"); 176 | printf(" -r secs reset clock stats after secs (default 0)\n"); 177 | printf(" -R rate set freq/log/stats update rate (default 1 per second)\n"); 178 | printf(" -n subnets set number of subnetworks (default 1)\n"); 179 | printf(" -o file log time offsets to file\n"); 180 | printf(" -f file log frequency offsets to file\n"); 181 | printf(" -g file log raw (w/o slew) frequency offsets to file\n"); 182 | printf(" -p file log packet delays to file\n"); 183 | printf(" -s socket set server socket name (default clknetsim.sock)\n"); 184 | printf(" -v level set verbosity level (default 2)\n"); 185 | printf(" -e file execute file on every freq/log/stats update\n"); 186 | printf(" -G print num numbers generated by expr\n"); 187 | printf(" -h print usage\n"); 188 | return 1; 189 | } 190 | 191 | config = argv[optind]; 192 | nodes = atoi(argv[optind + 1]); 193 | 194 | env = getenv("CLKNETSIM_RANDOM_SEED"); 195 | if (env) { 196 | srandom(atoi(env)); 197 | } else { 198 | gettimeofday(&tv, NULL); 199 | srandom(tv.tv_sec ^ tv.tv_usec); 200 | } 201 | 202 | if (generate_only) { 203 | run_generator(argv[optind], nodes); 204 | return 0; 205 | } 206 | 207 | network = new Network(socket, executable, nodes, subnets, rate); 208 | 209 | if (offset_log) 210 | network->open_offset_log(offset_log); 211 | if (freq_log) 212 | network->open_freq_log(freq_log); 213 | if (rawfreq_log) 214 | network->open_rawfreq_log(rawfreq_log); 215 | if (packet_log) 216 | network->open_packet_log(packet_log); 217 | 218 | if (!load_config(config, network, nodes)) { 219 | fprintf(stderr, "Couldn't parse config %s\n", config); 220 | return 1; 221 | } 222 | 223 | if (!network->prepare_clients()) 224 | return 1; 225 | 226 | fprintf(stderr, "Running simulation..."); 227 | 228 | if (reset && reset < limit) { 229 | r = network->run(reset); 230 | network->reset_clock_stats(); 231 | } else 232 | r = true; 233 | 234 | if (r) 235 | r = network->run(limit); 236 | 237 | if (r) { 238 | fprintf(stderr, "done\n\n"); 239 | network->print_stats(verbosity); 240 | } else 241 | fprintf(stderr, "failed\n"); 242 | 243 | delete network; 244 | 245 | return !r; 246 | } 247 | -------------------------------------------------------------------------------- /client_fuzz.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Miroslav Lichvar 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /* This is a minimal replacement for the clknetsim server to allow fuzz 19 | testing. There is no clock control or networking. When the time reaches 20 | fuzz_start, a packet read from stdin is forwarded to the fuzz_port port of 21 | the client, and the client is terminated. Packets sent by the client from 22 | the port are written to stdout. */ 23 | 24 | enum { 25 | FUZZ_MODE_DISABLED = 0, 26 | FUZZ_MODE_ONESHOT = 1, 27 | FUZZ_MODE_BURST = 2, 28 | FUZZ_MODE_REPLY = 3, 29 | FUZZ_MODE_NONE = 4, 30 | }; 31 | 32 | #define FUZZ_FLAG_TIMEOUT 1024 33 | 34 | #define MAX_FUZZ_PORTS 16 35 | 36 | static int fuzz_mode; 37 | static int fuzz_msg_type; 38 | static int fuzz_ports[MAX_FUZZ_PORTS]; 39 | static int fuzz_port_index, fuzz_ports_n; 40 | static int fuzz_subnet; 41 | static int fuzz_timeout; 42 | static double fuzz_start; 43 | 44 | static int fuzz_init(void) { 45 | const char *env; 46 | 47 | env = getenv("CLKNETSIM_FUZZ_MODE"); 48 | if (!env) 49 | return 0; 50 | 51 | fuzz_mode = atoi(env); 52 | 53 | if (fuzz_mode & FUZZ_FLAG_TIMEOUT) { 54 | fuzz_timeout = 1; 55 | fuzz_mode &= ~FUZZ_FLAG_TIMEOUT; 56 | } 57 | 58 | if (fuzz_mode == FUZZ_MODE_DISABLED) 59 | return 0; 60 | 61 | if (fuzz_mode < FUZZ_MODE_ONESHOT || fuzz_mode > FUZZ_MODE_NONE) { 62 | fprintf(stderr, "clknetsim: unknown fuzz mode.\n"); 63 | exit(1); 64 | } 65 | 66 | env = getenv("CLKNETSIM_FUZZ_MSG_TYPE"); 67 | fuzz_msg_type = env ? atoi(env) : MSG_TYPE_UDP_DATA; 68 | 69 | env = getenv("CLKNETSIM_FUZZ_PORT"); 70 | 71 | for (fuzz_ports_n = 0; env && fuzz_ports_n < MAX_FUZZ_PORTS; fuzz_ports_n++) { 72 | fuzz_ports[fuzz_ports_n] = atoi(env); 73 | if (!fuzz_ports[fuzz_ports_n]) 74 | break; 75 | env = strchr(env, ','); 76 | if (env) 77 | env++; 78 | } 79 | 80 | if (!fuzz_ports_n) { 81 | fprintf(stderr, "clknetsim: CLKNETSIM_FUZZ_PORT variable not set or invalid.\n"); 82 | exit(1); 83 | } 84 | fuzz_port_index = 0; 85 | 86 | env = getenv("CLKNETSIM_FUZZ_SUBNET"); 87 | fuzz_subnet = env ? atoi(env) - 1 : 0; 88 | 89 | env = getenv("CLKNETSIM_FUZZ_START"); 90 | fuzz_start = env ? atof(env) : 0.1; 91 | 92 | return 1; 93 | } 94 | 95 | static int fuzz_is_fuzz_port(int port) { 96 | int i; 97 | 98 | for (i = 0; i < fuzz_ports_n; i++) 99 | if (fuzz_ports[i] == port) 100 | return 1; 101 | return 0; 102 | } 103 | 104 | static int fuzz_get_fuzz_port(void) { 105 | return fuzz_ports[fuzz_port_index]; 106 | } 107 | 108 | static void fuzz_switch_fuzz_port(void) { 109 | fuzz_port_index = (fuzz_port_index + 1) % fuzz_ports_n; 110 | } 111 | 112 | static int fuzz_read_packet(char *data, int maxlen, int *rlen) { 113 | int len; 114 | uint16_t slen; 115 | 116 | if (fuzz_mode > FUZZ_MODE_ONESHOT) { 117 | if (fread(&slen, 1, sizeof (slen), stdin) != sizeof (slen)) 118 | return 0; 119 | len = ntohs(slen); 120 | if (len > maxlen) 121 | len = maxlen; 122 | } else { 123 | len = maxlen; 124 | } 125 | 126 | *rlen = fread(data, 1, len, stdin); 127 | 128 | return !len || rlen; 129 | } 130 | 131 | static void fuzz_write_packet(const char *data, int len) { 132 | uint16_t slen; 133 | 134 | if (fuzz_mode > FUZZ_MODE_ONESHOT) { 135 | slen = htons(len); 136 | fwrite(&slen, 1, sizeof (slen), stdout); 137 | } 138 | 139 | fwrite(data, 1, len, stdout); 140 | } 141 | 142 | static void get_recv_data(int valid_packet, int received, int last_tx_src_port, 143 | unsigned int *type, unsigned int *subnet, unsigned int *from, 144 | unsigned int *src_port, unsigned int *dst_port) { 145 | if (valid_packet) { 146 | if (fuzz_msg_type == MSG_TYPE_TCP_DATA && received == 0) 147 | *type = MSG_TYPE_TCP_CONNECT; 148 | else 149 | *type = fuzz_msg_type; 150 | *from = 1; 151 | } else { 152 | *type = MSG_TYPE_NO_MSG; 153 | *from = -1; 154 | } 155 | 156 | *subnet = fuzz_subnet; 157 | *src_port = fuzz_get_fuzz_port(); 158 | *dst_port = last_tx_src_port ? last_tx_src_port : fuzz_get_fuzz_port(); 159 | } 160 | 161 | static void fuzz_process_request(int request_id, const union Request_data *request, 162 | union Reply_data *reply, int replylen) { 163 | static double network_time = 0.0; 164 | static int received = 0; 165 | static int sent = 0; 166 | static int last_tx_src_port = 0; 167 | static int packet_len = 0; 168 | static int valid_packet = 0; 169 | static char packet[MAX_PACKET_SIZE]; 170 | 171 | if (reply) 172 | memset(reply, 0, replylen); 173 | 174 | switch (request_id) { 175 | case REQ_GETTIME: 176 | reply->gettime.real_time = network_time; 177 | reply->gettime.monotonic_time = network_time; 178 | reply->gettime.network_time = network_time; 179 | break; 180 | case REQ_SELECT: 181 | if (fuzz_mode == FUZZ_MODE_NONE) { 182 | network_time += request->select.timeout; 183 | reply->select.ret = REPLY_SELECT_TIMEOUT; 184 | reply->select.time.real_time = network_time; 185 | reply->select.time.monotonic_time = network_time; 186 | reply->select.time.network_time = network_time; 187 | return; 188 | } 189 | 190 | if (!valid_packet && (!received || fuzz_mode != FUZZ_MODE_ONESHOT)) 191 | valid_packet = fuzz_read_packet(packet, sizeof (packet), &packet_len); 192 | 193 | if (!valid_packet) { 194 | reply->select.ret = REPLY_SELECT_TERMINATE; 195 | } else if (!packet_len && fuzz_timeout) { 196 | network_time += request->select.timeout; 197 | reply->select.ret = REPLY_SELECT_TIMEOUT; 198 | valid_packet = 0; 199 | } else { 200 | if (fuzz_mode == FUZZ_MODE_REPLY) { 201 | if (sent > received) { 202 | reply->select.ret = REPLY_SELECT_NORMAL; 203 | } else { 204 | network_time += request->select.timeout; 205 | reply->select.ret = REPLY_SELECT_TIMEOUT; 206 | } 207 | } else { 208 | if (network_time < fuzz_start && !sent) { 209 | network_time += request->select.timeout; 210 | if (network_time >= fuzz_start) { 211 | network_time = fuzz_start; 212 | reply->select.ret = REPLY_SELECT_NORMAL; 213 | } else { 214 | reply->select.ret = REPLY_SELECT_TIMEOUT; 215 | } 216 | } else { 217 | reply->select.ret = REPLY_SELECT_NORMAL; 218 | } 219 | } 220 | } 221 | 222 | get_recv_data(valid_packet, received, last_tx_src_port, 223 | &reply->select.type, &reply->select.subnet, &reply->select.from, 224 | &reply->select.src_port, &reply->select.dst_port); 225 | reply->select.time.real_time = network_time; 226 | reply->select.time.monotonic_time = network_time; 227 | reply->select.time.network_time = network_time; 228 | break; 229 | case REQ_SEND: 230 | if (request->send.to != 1 && request->send.to != -1) 231 | break; 232 | 233 | if (fuzz_mode == FUZZ_MODE_REPLY) { 234 | if (!fuzz_is_fuzz_port(request->send.dst_port)) 235 | break; 236 | last_tx_src_port = request->send.src_port; 237 | } else if (!fuzz_is_fuzz_port(request->send.src_port)) 238 | break; 239 | 240 | fuzz_write_packet(request->send.data, request->send.len); 241 | sent++; 242 | break; 243 | case REQ_RECV: 244 | network_time += 1e-5; 245 | get_recv_data(valid_packet, received, last_tx_src_port, 246 | &reply->recv.type, &reply->recv.subnet, &reply->recv.from, 247 | &reply->recv.src_port, &reply->recv.dst_port); 248 | 249 | received++; 250 | 251 | if (reply->recv.type != fuzz_msg_type) { 252 | reply->recv.len = 0; 253 | break; 254 | } 255 | 256 | memcpy(reply->recv.data, packet, packet_len); 257 | reply->recv.len = packet_len; 258 | valid_packet = 0; 259 | packet_len = 0; 260 | fuzz_switch_fuzz_port(); 261 | break; 262 | case REQ_SETTIME: 263 | network_time = request->settime.time; 264 | break; 265 | case REQ_GETREFOFFSETS: 266 | reply->getrefoffsets.size = MAX_GETREFOFFSETS_SIZE; 267 | break; 268 | case REQ_ADJTIME: 269 | case REQ_GETREFSAMPLE: 270 | case REQ_DEREGISTER: 271 | break; 272 | case REQ_ADJTIMEX: 273 | reply->adjtimex.timex.tick = 10000; 274 | break; 275 | case REQ_REGISTER: 276 | default: 277 | assert(0); 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /clknetsim.bash: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2010, 2011 Miroslav Lichvar 2 | # 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation; either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | [ -n "$CLKNETSIM_TMPDIR" ] || CLKNETSIM_TMPDIR=tmp 17 | 18 | client_pids="" 19 | 20 | start_client() { 21 | local node=$1 client=$2 config=$3 suffix=$4 opts=$5 22 | local args=() line lastpid wrapper_options="" 23 | 24 | rm -f $CLKNETSIM_TMPDIR/log.$node $CLKNETSIM_TMPDIR/conf.$node 25 | 26 | [ $client = chrony ] && client=chronyd 27 | [ $client = ntp ] && client=ntpd 28 | 29 | if ! which $client$suffix &> /dev/null; then 30 | echo "can't find $client$suffix in PATH" 31 | return 1 32 | fi 33 | 34 | case $client in 35 | chronyd) 36 | cat > $CLKNETSIM_TMPDIR/conf.$node <<-EOF 37 | pidfile $CLKNETSIM_TMPDIR/pidfile.$node 38 | allow 39 | cmdallow 40 | bindcmdaddress 0.0.0.0 41 | bindcmdaddress /clknetsim/unix/chronyd.sock 42 | $config 43 | EOF 44 | args=(-d -f $CLKNETSIM_TMPDIR/conf.$node $opts) 45 | ;; 46 | ntpd) 47 | cat > $CLKNETSIM_TMPDIR/conf.$node <<-EOF 48 | pidfile $CLKNETSIM_TMPDIR/pidfile.$node 49 | restrict default 50 | $config 51 | EOF 52 | args=(-n -c $CLKNETSIM_TMPDIR/conf.$node $opts) 53 | ;; 54 | ptp4l) 55 | cat > $CLKNETSIM_TMPDIR/conf.$node <<-EOF 56 | [global] 57 | $config 58 | EOF 59 | args=(-f $CLKNETSIM_TMPDIR/conf.$node $opts) 60 | ;; 61 | chronyc) 62 | args=($opts -m) 63 | while read line; do args+=("$line"); done <<< "$config" 64 | ;; 65 | pmc) 66 | args=($opts) 67 | while read line; do args+=("$line"); done <<< "$config" 68 | ;; 69 | ntpq) 70 | while read line; do args+=(-c "$line"); done <<< "$config" 71 | args+=($opts) 72 | ;; 73 | sntp) 74 | args=(-K /dev/null $opts $config) 75 | ;; 76 | ntpdate) 77 | args=($opts $config) 78 | ;; 79 | busybox) 80 | args=(ntpd -ddd -n) 81 | while read line; do args+=(-p "$line"); done <<< "$config" 82 | args+=($opts) 83 | ;; 84 | phc2sys) 85 | args=($opts $config) 86 | ;; 87 | nsm) 88 | args=($opts) 89 | while read line; do args+=("$line"); done <<< "$config" 90 | ;; 91 | clkmgr_proxy) 92 | cat > $CLKNETSIM_TMPDIR/conf.$node <<-EOF 93 | $config 94 | EOF 95 | args=(-f $CLKNETSIM_TMPDIR/conf.$node $opts) 96 | ;; 97 | clkmgr) 98 | args=($opts) 99 | ;; 100 | *) 101 | echo "unknown client $client" 102 | exit 1 103 | ;; 104 | esac 105 | 106 | unset LISTEN_FDS NOTIFY_SOCKET 107 | 108 | if [[ $CLKNETSIM_CLIENT_WRAPPER == *valgrind* ]]; then 109 | wrapper_options="--log-file=$CLKNETSIM_TMPDIR/valgrind.$node --enable-debuginfod=no" 110 | fi 111 | 112 | LD_PRELOAD=${CLKNETSIM_PRELOAD:+$CLKNETSIM_PRELOAD:}$CLKNETSIM_PATH/clknetsim.so \ 113 | CLKNETSIM_NODE=$node CLKNETSIM_SOCKET=$CLKNETSIM_TMPDIR/sock \ 114 | $CLKNETSIM_CLIENT_WRAPPER $wrapper_options \ 115 | $client$suffix "${args[@]}" &> $CLKNETSIM_TMPDIR/log.$node & 116 | 117 | lastpid=$! 118 | disown $lastpid 119 | 120 | client_pids="$client_pids $lastpid" 121 | } 122 | 123 | start_server() { 124 | local nodes=$1 ret=0 wrapper_options="" i j 125 | shift 126 | 127 | if [[ $CLKNETSIM_SERVER_WRAPPER == *valgrind* ]]; then 128 | wrapper_options="--log-file=$CLKNETSIM_TMPDIR/valgrind.0 --enable-debuginfod=no" 129 | fi 130 | 131 | $CLKNETSIM_SERVER_WRAPPER $wrapper_options \ 132 | $CLKNETSIM_PATH/clknetsim "$@" -s $CLKNETSIM_TMPDIR/sock \ 133 | $CLKNETSIM_TMPDIR/conf $nodes > $CLKNETSIM_TMPDIR/stats 2> $CLKNETSIM_TMPDIR/log 134 | 135 | if [ $? -ne 0 ]; then 136 | echo clknetsim failed 1>&2 137 | ret=1 138 | fi 139 | 140 | kill $client_pids &> /dev/null 141 | 142 | i=0 143 | for pid in $client_pids; do 144 | i=$[i + 1] 145 | j=0 146 | while kill -0 $pid &> /dev/null; do 147 | j=$[j + 1] 148 | if [ $j -gt 30 ]; then 149 | echo " node $i did not terminate" 1>&2 150 | ret=1 151 | break 152 | fi 153 | sleep 0.1 154 | done 155 | done 156 | 157 | client_pids=" " 158 | 159 | if ls $CLKNETSIM_TMPDIR/valgrind.* &> /dev/null; then 160 | if grep -q 'ERROR SUMMARY: [^0]' $CLKNETSIM_TMPDIR/valgrind.*; then 161 | echo " valgrind error" 1>&2 162 | ret=1 163 | fi 164 | sed -i '/^ERROR: ld.so: object.*from LD_PRELOAD cannot/d' $CLKNETSIM_TMPDIR/log.[0-9]* 165 | fi 166 | 167 | return $ret 168 | } 169 | 170 | generate_seq() { 171 | $CLKNETSIM_PATH/clknetsim -G "$@" 172 | } 173 | 174 | generate_config1() { 175 | local nodes=$1 offset=$2 freqexpr=$3 delayexprup=$4 delayexprdown=$5 refclockexpr=$6 i 176 | 177 | for i in `seq 2 $nodes`; do 178 | echo "node${i}_offset = $offset" 179 | echo "node${i}_freq = $freqexpr" 180 | echo "node${i}_delay1 = $delayexprup" 181 | if [ -n "$delayexprdown" ]; then 182 | echo "node1_delay${i} = $delayexprdown" 183 | else 184 | echo "node1_delay${i} = $delayexprup" 185 | fi 186 | [ -n "$refclockexpr" ] && echo "node${i}_refclock = $refclockexpr" 187 | done > $CLKNETSIM_TMPDIR/conf 188 | } 189 | 190 | generate_config2() { 191 | local nodes=$1 offset=$2 freqexpr=$3 delayexpr=$4 i j 192 | 193 | for i in `seq 2 $nodes`; do 194 | echo "node${i}_offset = $offset" 195 | echo "node${i}_freq = $freqexpr" 196 | for j in `seq 1 $nodes`; do 197 | [ $i -eq $j ] && continue 198 | echo "node${i}_delay${j} = $delayexpr" 199 | echo "node${j}_delay${i} = $delayexpr" 200 | done 201 | done > $CLKNETSIM_TMPDIR/conf 202 | } 203 | 204 | generate_config3() { 205 | local topnodes=$1 nodes=$2 offset=$3 freqexpr=$4 delayexprup=$5 delayexprdown=$6 i j 206 | 207 | for i in `seq $[$topnodes + 1] $nodes`; do 208 | echo "node${i}_offset = $offset" 209 | echo "node${i}_freq = $freqexpr" 210 | for j in `seq 1 $topnodes`; do 211 | [ $i -eq $j ] && continue 212 | echo "node${i}_delay${j} = $delayexprup" 213 | if [ -n "$delayexprdown" ]; then 214 | echo "node${j}_delay${i} = $delayexprdown" 215 | else 216 | echo "node${j}_delay${i} = $delayexprup" 217 | fi 218 | done 219 | done > $CLKNETSIM_TMPDIR/conf 220 | } 221 | 222 | generate_config4() { 223 | local stablenodes=$1 subnets=$2 offset=$3 freqexpr=$4 delayexpr=$5 224 | local subnet i j added 225 | 226 | echo "$subnets" | tr '|' '\n' | while read subnet; do 227 | for i in $subnet; do 228 | if ! [[ " $stablenodes $added " =~ [^0-9]$i[^0-9] ]]; then 229 | echo "node${i}_offset = $offset" 230 | echo "node${i}_freq = $freqexpr" 231 | fi 232 | for j in $subnet; do 233 | [ $i -eq $j ] && continue 234 | echo "node${i}_delay${j} = $delayexpr" 235 | done 236 | added="$added $i" 237 | done 238 | done > $CLKNETSIM_TMPDIR/conf 239 | } 240 | 241 | find_sync() { 242 | local offlog=$1 freqlog=$2 index=$3 offsync=$4 freqsync=$5 smooth=$6 243 | 244 | [ -z "$smooth" ] && smooth=0.05 245 | 246 | paste <(cut -f $index $1) <(cut -f $index $2) | awk ' 247 | BEGIN { 248 | lastnonsync = -1 249 | time = 0 250 | } 251 | { 252 | off = $1 < 0 ? -$1 : $1 253 | freq = $2 < 0 ? -$2 : $2 254 | 255 | if (avgoff == 0.0 && avgfreq == 0.0) { 256 | avgoff = off 257 | avgfreq = freq 258 | } else { 259 | avgoff += '$smooth' * (off - avgoff) 260 | avgfreq += '$smooth' * (freq - avgfreq) 261 | } 262 | 263 | if (avgoff > '$offsync' || avgfreq > '$freqsync') { 264 | lastnonsync = time 265 | } 266 | time++ 267 | } END { 268 | if (lastnonsync < time) { 269 | print lastnonsync + 1 270 | } else { 271 | print -1 272 | } 273 | }' 274 | } 275 | 276 | get_stat() { 277 | local statname=$1 index=$2 278 | 279 | if [ -z "$index" ]; then 280 | echo $(cat $CLKNETSIM_TMPDIR/stats | grep "^$statname:" | cut -f 2) 281 | else 282 | cat $CLKNETSIM_TMPDIR/stats | grep "^$statname:" | cut -f 2 | 283 | head -n $index | tail -n 1 284 | fi 285 | } 286 | 287 | check_stat() { 288 | local value=$1 min=$2 max=$3 tolerance=$4 289 | [ -z "$tolerance" ] && tolerance=0.0 290 | awk " 291 | BEGIN { 292 | eq = (\"$value\" == \"inf\" || 293 | $value + $value / 1e6 + $tolerance >= $min) && 294 | (\"$max\" == \"inf\" || 295 | (\"$value\" != \"inf\" && 296 | $value - $value / 1e6 - $tolerance <= $max)) 297 | exit !eq 298 | }" 299 | } 300 | 301 | if [ -z "$CLKNETSIM_PATH" ]; then 302 | echo CLKNETSIM_PATH not set 2>&1 303 | exit 1 304 | fi 305 | 306 | if [ ! -x "$CLKNETSIM_PATH/clknetsim" -o ! -e "$CLKNETSIM_PATH/clknetsim.so" ]; then 307 | echo "can't find clknetsim or clknetsim.so in $CLKNETSIM_PATH" 308 | exit 1 309 | fi 310 | 311 | [ -d $CLKNETSIM_TMPDIR ] || mkdir $CLKNETSIM_TMPDIR 312 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Clock and Network Simulator (clknetsim) 2 | ======================================= 3 | 4 | clknetsim is a tool designed to test programs which synchronize the system 5 | clock, either over network or from a hardware reference clock. It simulates a 6 | system or a number of systems connected to each other in a network and the 7 | tested programs discipline the simulated system clocks. It can be used to 8 | quickly test how well the programs control the system clocks in various 9 | conditions or to test the network protocols. 10 | 11 | The tested programs are not modified in order to be included in the simulation, 12 | but they have some system calls redirected by a clknetsim library, which is 13 | loaded by the LD_PRELOAD feature of the dynamic linker, to a clknetsim server, 14 | which runs the simulation and collects several statistics about each client. 15 | The server and the clients run on a single host, they communicate via a UNIX 16 | domain socket. The simulation runs as fast as the host system is capable of, 17 | with two simulated systems it is usually three or four orders of magnitude 18 | faster than real time. 19 | 20 | Supported programs: 21 | - chronyd and chronyc from chrony (http://chrony.tuxfamily.org/) 22 | - ntpd, ntpdate, sntp and ntpq from ntp (http://www.ntp.org/) 23 | - ntpd from busybox (http://www.busybox.net/) 24 | - ptp4l, phc2sys, pmc and nsm from linuxptp (http://linuxptp.sourceforge.net/) 25 | 26 | Limitations: 27 | - only Linux is supported 28 | - the fake system calls implement only a minimal functionality required to 29 | keep the supported clients working 30 | - the simulated system clock advances only on select(), poll() or usleep() 31 | calls, this means the client sees the CPU as infinitely fast 32 | - adjtimex() frequency and tick changes happen immediately, the kernel has 33 | infinite HZ 34 | - adjtime() and PLL updates happen in one second intervals in the simulated 35 | time instead of the uncorrected simulated system time, all clocks are updated 36 | at the same time 37 | 38 | 39 | Usage 40 | ----- 41 | 42 | The clknetsim server is started with two required arguments, the first one is 43 | path to a configuration file describing the network and clocks and the second 44 | argument is the number of simulated nodes. The simulation is started when all 45 | clients are connected. 46 | 47 | The clients are started under a non-root user, with preloaded clknetsim.so and 48 | the environment variable CLKNETSIM_NODE set to the number of the client. 49 | Optionally, the environment variable CLKNETSIM_SOCKET can be set to the path of 50 | the UNIX domain socket which is used to connect to the server, clknetsim.sock 51 | in current directory is used by default. The CLKNETSIM_START_DATE variable can 52 | be used to specify in seconds since 1970 when should the simulated time start, 53 | 1262304000 by default (2010-01-01 0:00 UTC). The CLKNETSIM_CONNECT_TIMEOUT 54 | variable sets the server connection timeout, 10 seconds by default. 55 | 56 | The simulated network is available to the clients as one or more Ethernet 57 | networks with IPv4 addressing. All nodes have interfaces to all networks. 58 | Their addresses are 192.168.122+s.n, where n is the number of the node 59 | (starting at 1) and s is the number of the network (starting at 1). The 60 | broadcast addresses are 192.168.122+s.255. The CLKNETSIM_IP_FAMILY variable 61 | can be set to 6 to enable IPv6 and disable IPv4. The supported IPv6 addresses 62 | are fc00::123:SSNN, where SS is s-1 in hexadecimal and NN is n in hexadecimal. 63 | 64 | At the end of the simulation clock and network statistics are printed. 65 | clknetsim has options which can be used to control for how long the 66 | simulation should run, or if the frequency, offset or network log should be 67 | written. clknetsim -h prints a complete list of available options. 68 | 69 | A minimal example how to start a simulation: 70 | 71 | $ LD_PRELOAD=./clknetsim.so CLKNETSIM_NODE=1 chronyd -d -f chrony.conf & 72 | $ LD_PRELOAD=./clknetsim.so CLKNETSIM_NODE=2 ntpd -n -c ntp.conf & 73 | $ ./clknetsim -o log.offset -l 100000 clknetsim.conf 2 74 | 75 | clknetsim.conf: 76 | node2_freq = (sum (* 1e-8 (normal))) 77 | node1_delay2 = (+ 1e-1 (* 1e-3 (exponential))) 78 | node2_delay1 = (+ 1e-1 (* 1e-3 (exponential))) 79 | 80 | chrony.conf: 81 | pidfile chronyd.pid 82 | local stratum 1 83 | allow 84 | 85 | ntp.conf: 86 | pidfile ntpd.pid 87 | server 192.168.123.1 88 | 89 | The clknetsim.bash file contains bash functions which can create the 90 | configuration in several network settings, start the simulation, stop the 91 | clients and process the results. The examples subdirectory contains an example 92 | script for each supported client. The above example can be written in a bash 93 | script as: 94 | 95 | CLKNETSIM_PATH=. 96 | . ./clknetsim.bash 97 | 98 | generate_config1 2 0.0 "(sum (* 1e-8 (normal)))" "(+ 1e-1 (* 1e-3 (exponential)))" 99 | start_client 1 chrony "local stratum 1" 100 | start_client 2 ntp "server 192.168.123.1" 101 | start_server 2 -o log.offset -l 100000 102 | 103 | cat tmp/stats 104 | 105 | 106 | Configuration file 107 | ------------------ 108 | 109 | The configuration file is a text file containing a list of assignments, each 110 | specified on a separate line, and comments using # as delimiter. Each node has 111 | several variables, which configure the system clock, the reference clock and 112 | the network delays to other nodes in the network. They can be set either to an 113 | integer value, a floating-point value or a number generating expression written 114 | in a Lisp-style syntax. 115 | 116 | Variables: 117 | - nodeX_freq = float | expr 118 | the system clock frequency error in terms of gained seconds per second of 119 | simulated time, if an expression is specified, the expression is evaluated and 120 | frequency updated once per simulated second (or at the rate specified with 121 | the -R option), the allowed range is (-0.2, 0.2), the default is 0 122 | - nodeX_delayY = expr 123 | the network delay for packets sent from node X to node Y in seconds, the 124 | expression is evaluated for each sent packet, a negative value means the 125 | packet will be dropped, there is no default (packets are dropped) 126 | - nodeX_delay_correctionY = expr 127 | the correction written to PTP packets (as a one-step E2E transparent clock) 128 | sent from node X to node Y in seconds, no correction is written by default 129 | - nodeX_offset = float 130 | the initial time error of the system clock in seconds, the default is 0 131 | - nodeX_start = float 132 | the time in seconds when will be the node started, the default is 0 133 | - nodeX_refclock = expr 134 | the reference clock time error in seconds, the clock can be accessed by the 135 | client via shared memory (NTP SHM protocol) or as a PTP hardware clock (PHC) 136 | via the clock_gettime() function, there is no default (the clock is disabled) 137 | - nodeX_refclock_base = nodeX 138 | the base of the reference clock, the default is the network time 139 | - nodeX_step = expr 140 | the extra time step applied once per second (or at the rate specified with 141 | the -R option) in seconds, there is no default (no extra steps are applied) 142 | - nodeX_shift_pll = integer 143 | kernel PLL parameter, the default is 2 144 | - nodeX_pll_clamp = 1 | 0 145 | kernel PLL parameter, the default is 0 146 | - nodeX_fll_mode2 = 1 | 0 147 | kernel FLL parameter, the default is 0 148 | 149 | Functions and their parameters supported in the expressions: 150 | (* [expr | float] ...) - multiplication 151 | (+ [expr | float] ...) - addition 152 | (% [expr | float] ...) - modulo 153 | (sum [expr | float] ...) 154 | - summation over consecutive evaluation of parameters 155 | (uniform) - random number generator with standard uniform 156 | distribution 157 | (normal) - random number generator with standard normal 158 | distribution 159 | (exponential) - random number generator with exponential distribution 160 | (lambda = 1) 161 | (poisson lambda) - random number generator with poisson distribution 162 | (file "datafile") - number generator reading floating-point values from 163 | the specified file in an inifinite loop 164 | (pulse high low) - pulse wave generator 165 | (sine period) - sine wave generator 166 | (cosine period) - cosine wave generator 167 | (triangle period) - triangle wave generator 168 | (equal epsilon [expr | float] ...) 169 | - returns 1.0 if the values of all parameters are 170 | equal within epsilon, 0.0 otherwise 171 | (max [expr | float] ...) 172 | - returns maximum value 173 | (min [expr | float] ...) 174 | - returns minimum value 175 | 176 | Variables available in network delay expressions: 177 | time - current network time 178 | from - number of the sending node 179 | to - number of the receiving node 180 | port - receiving port number 181 | length - length of the packet 182 | subnet - number of the Ethernet network in which 183 | the packet was sent 184 | 185 | Variables available in delay correction expressions: 186 | delay - delay of the packet 187 | length - length of the packet (layer 4) 188 | 189 | An example: 190 | 191 | # node1 is an NTP server, it has an accurate and absolutely stable clock 192 | node1_offset = 0 193 | node1_freq = 0 194 | 195 | # node2 is an NTP client, it starts with 0.1s offset and has 196 | # 0.01ppm/s frequency wander 197 | node2_offset = 0.1 198 | node2_freq = (sum (* 1e-8 (normal))) 199 | 200 | # network delays between the two nodes have 10ms mean and 100us 201 | # jitter in both directions 202 | node1_delay2 = (+ 9.9e-3 (* 100e-6 (exponential))) 203 | node2_delay1 = (+ 9.9e-3 (* 100e-6 (exponential))) 204 | 205 | 206 | Author 207 | ------ 208 | 209 | Miroslav Lichvar 210 | 211 | 212 | License 213 | ------- 214 | 215 | Copyright (C) 2010, 2011, 2012 Miroslav Lichvar 216 | 217 | This program is free software; you can redistribute it and/or modify 218 | it under the terms of the GNU General Public License as published by 219 | the Free Software Foundation; either version 2 of the License, or 220 | (at your option) any later version. 221 | 222 | This program is distributed in the hope that it will be useful, 223 | but WITHOUT ANY WARRANTY; without even the implied warranty of 224 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 225 | GNU General Public License for more details. 226 | 227 | You should have received a copy of the GNU General Public License 228 | along with this program. If not, see . 229 | -------------------------------------------------------------------------------- /node.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Miroslav Lichvar 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "node.h" 19 | #include "network.h" 20 | #include "protocol.h" 21 | #include "sysheaders.h" 22 | 23 | Node::Node(int index, Network *network) { 24 | this->refclock_base = NULL; 25 | this->network = network; 26 | this->index = index; 27 | fd = -1; 28 | pending_request = REQ_REGISTER; 29 | start_time = 0.0; 30 | terminate = false; 31 | } 32 | 33 | Node::~Node() { 34 | while (!incoming_packets.empty()) { 35 | delete incoming_packets.back(); 36 | incoming_packets.pop_back(); 37 | } 38 | 39 | terminate = true; 40 | 41 | do { 42 | if (waiting()) 43 | resume(); 44 | } while (process_fd()); 45 | 46 | if (fd >= 0) 47 | close(fd); 48 | } 49 | 50 | void Node::set_fd(int fd) { 51 | this->fd = fd; 52 | } 53 | 54 | int Node::get_fd() const { 55 | return fd; 56 | } 57 | 58 | void Node::set_start_time(double time) { 59 | start_time = time; 60 | } 61 | 62 | bool Node::process_fd() { 63 | Request_packet request; 64 | int received, reqlen; 65 | 66 | received = recv(fd, &request, sizeof (request), 0); 67 | if (received < 0) 68 | fprintf(stderr, "recv() failed: %s\n", strerror(errno)); 69 | if (received < (int)sizeof (request.header)) 70 | return false; 71 | 72 | reqlen = received - (int)offsetof(Request_packet, data); 73 | 74 | assert(pending_request == 0); 75 | pending_request = request.header.request; 76 | 77 | #ifdef DEBUG 78 | printf("received request %d in node %d at %f\n", 79 | pending_request, index, clock.get_real_time()); 80 | #endif 81 | 82 | switch (pending_request) { 83 | case REQ_GETTIME: 84 | assert(reqlen == 0); 85 | process_gettime(); 86 | break; 87 | case REQ_SETTIME: 88 | assert(reqlen == sizeof (Request_settime)); 89 | process_settime(&request.data.settime); 90 | break; 91 | case REQ_ADJTIMEX: 92 | assert(reqlen == sizeof (Request_adjtimex)); 93 | process_adjtimex(&request.data.adjtimex); 94 | break; 95 | case REQ_ADJTIME: 96 | assert(reqlen == sizeof (Request_adjtime)); 97 | process_adjtime(&request.data.adjtime); 98 | break; 99 | case REQ_SELECT: 100 | assert(reqlen == sizeof (Request_select)); 101 | process_select(&request.data.select); 102 | break; 103 | case REQ_SEND: 104 | /* request with variable length */ 105 | assert(reqlen >= (int)offsetof(Request_send, data) && 106 | reqlen <= (int)sizeof (Request_send)); 107 | assert(request.data.send.len <= sizeof (request.data.send.data)); 108 | assert((int)(request.data.send.len + offsetof(Request_send, data)) <= reqlen); 109 | process_send(&request.data.send); 110 | break; 111 | case REQ_RECV: 112 | assert(reqlen == 0); 113 | process_recv(); 114 | break; 115 | case REQ_GETREFSAMPLE: 116 | assert(reqlen == 0); 117 | process_getrefsample(); 118 | break; 119 | case REQ_GETREFOFFSETS: 120 | assert(reqlen == 0); 121 | process_getrefoffsets(); 122 | break; 123 | case REQ_DEREGISTER: 124 | assert(reqlen == 0); 125 | break; 126 | default: 127 | assert(0); 128 | } 129 | 130 | return true; 131 | } 132 | 133 | void Node::reply(void *data, int len, int request) { 134 | int sent; 135 | 136 | assert(request == pending_request); 137 | pending_request = 0; 138 | 139 | if (data) { 140 | sent = send(fd, data, len, 0); 141 | assert(sent == len); 142 | } 143 | } 144 | 145 | 146 | void Node::process_gettime() { 147 | Reply_gettime r; 148 | 149 | r.real_time = clock.get_real_time(); 150 | r.monotonic_time = clock.get_monotonic_time(); 151 | r.network_time = network->get_time(); 152 | r.freq_error = clock.get_total_freq() - 1.0; 153 | reply(&r, sizeof (r), REQ_GETTIME); 154 | } 155 | 156 | void Node::process_settime(Request_settime *req) { 157 | clock.set_time(req->time); 158 | reply(NULL, 0, REQ_SETTIME); 159 | } 160 | 161 | void Node::process_adjtimex(Request_adjtimex *req) { 162 | Reply_adjtimex rep; 163 | struct timex *buf = &req->timex; 164 | 165 | rep.ret = clock.adjtimex(buf); 166 | rep.timex = *buf; 167 | rep._pad = 0; 168 | reply(&rep, sizeof (rep), REQ_ADJTIMEX); 169 | } 170 | 171 | void Node::process_adjtime(Request_adjtime *req) { 172 | Reply_adjtime rep; 173 | 174 | clock.adjtime(&req->tv, &rep.tv); 175 | reply(&rep, sizeof (rep), REQ_ADJTIME); 176 | } 177 | 178 | void Node::try_select() { 179 | Reply_select rep = {-1, 0, 0}; 180 | 181 | if (terminate) { 182 | rep.ret = REPLY_SELECT_TERMINATE; 183 | #ifdef DEBUG 184 | printf("select returned on termination in %d at %f\n", 185 | index, clock.get_real_time()); 186 | #endif 187 | } else if (select_timeout - clock.get_monotonic_time() <= 0.0) { 188 | assert(select_timeout - clock.get_monotonic_time() > -1e-10); 189 | rep.ret = REPLY_SELECT_TIMEOUT; 190 | #ifdef DEBUG 191 | printf("select returned on timeout in %d at %f\n", index, clock.get_real_time()); 192 | #endif 193 | } else if (select_read && incoming_packets.size() > 0) { 194 | rep.ret = incoming_packets.back()->broadcast ? 195 | REPLY_SELECT_BROADCAST : 196 | REPLY_SELECT_NORMAL; 197 | rep.type = incoming_packets.back()->type; 198 | rep.subnet = incoming_packets.back()->subnet; 199 | rep.from = incoming_packets.back()->from; 200 | rep.src_port = incoming_packets.back()->src_port; 201 | rep.dst_port = incoming_packets.back()->dst_port; 202 | #ifdef DEBUG 203 | printf("select returned for packet in %d at %f\n", index, clock.get_real_time()); 204 | #endif 205 | } 206 | 207 | if (rep.ret >= 0) { 208 | rep.time.real_time = clock.get_real_time(); 209 | rep.time.monotonic_time = clock.get_monotonic_time(); 210 | rep.time.network_time = network->get_time(); 211 | rep.time.freq_error = clock.get_total_freq() - 1.0; 212 | reply(&rep, sizeof (rep), REQ_SELECT); 213 | } 214 | } 215 | 216 | void Node::process_select(Request_select *req) { 217 | if (req->timeout < 0.0) 218 | req->timeout = 0.0; 219 | select_timeout = clock.get_monotonic_time() + req->timeout; 220 | select_read = req->read; 221 | #ifdef DEBUG 222 | printf("select called with timeout %f read %d in %d at %f\n", 223 | req->timeout, req->read, index, clock.get_real_time()); 224 | #endif 225 | try_select(); 226 | } 227 | 228 | void Node::process_send(Request_send *req) { 229 | struct Packet *packet; 230 | 231 | if (!terminate) { 232 | packet = new struct Packet; 233 | packet->type = req->type; 234 | packet->broadcast = req->to == (unsigned int)-1; 235 | packet->subnet = req->subnet; 236 | packet->from = index; 237 | packet->to = req->to; 238 | packet->src_port = req->src_port; 239 | packet->dst_port = req->dst_port; 240 | packet->len = req->len; 241 | memcpy(packet->data, req->data, req->len); 242 | network->send(packet); 243 | } 244 | 245 | reply(NULL, 0, REQ_SEND); 246 | } 247 | 248 | void Node::process_recv() { 249 | Reply_recv rep; 250 | struct Packet *packet; 251 | 252 | if (incoming_packets.empty()) { 253 | rep.type = MSG_TYPE_NO_MSG; 254 | rep.subnet = 0; 255 | rep.from = -1; 256 | rep.src_port = 0; 257 | rep.dst_port = 0; 258 | rep.len = 0; 259 | reply(&rep, offsetof (Reply_recv, data), REQ_RECV); 260 | 261 | return; 262 | } 263 | 264 | packet = incoming_packets.back(); 265 | 266 | rep.type = packet->type; 267 | rep.subnet = packet->subnet; 268 | rep.from = packet->from; 269 | rep.src_port = packet->src_port; 270 | rep.dst_port = packet->dst_port; 271 | rep.len = packet->len; 272 | 273 | assert(packet->len <= sizeof (rep.data)); 274 | memcpy(rep.data, packet->data, packet->len); 275 | 276 | delete packet; 277 | 278 | reply(&rep, offsetof (Reply_recv, data) + rep.len, REQ_RECV); 279 | 280 | incoming_packets.pop_back(); 281 | #ifdef DEBUG 282 | printf("received packet in %d at %f\n", index, clock.get_real_time()); 283 | #endif 284 | } 285 | 286 | void Node::receive(struct Packet *packet) { 287 | if (pending_request == REQ_REGISTER || pending_request == REQ_DEREGISTER) { 288 | delete packet; 289 | return; 290 | } 291 | 292 | incoming_packets.insert(incoming_packets.begin(), packet); 293 | 294 | if (pending_request == REQ_SELECT) 295 | try_select(); 296 | } 297 | 298 | void Node::process_getrefsample() { 299 | Reply_getrefsample r; 300 | 301 | refclock.set_generation(true); 302 | r.valid = refclock.get_sample(&r.time, &r.offset); 303 | assert(!refclock_base); 304 | r._pad = 0; 305 | reply(&r, sizeof (r), REQ_GETREFSAMPLE); 306 | } 307 | 308 | void Node::process_getrefoffsets() { 309 | Reply_getrefoffsets r; 310 | 311 | if (refclock_base) { 312 | r.size = 1; 313 | refclock.get_offsets(r.offsets, r.size); 314 | r.offsets[0] += network->get_time() - refclock_base->get_real_time(); 315 | } else { 316 | r.size = MAX_GETREFOFFSETS_SIZE; 317 | refclock.get_offsets(r.offsets, r.size); 318 | } 319 | r._pad = 0; 320 | reply(&r, offsetof(Reply_getrefoffsets, offsets) + 321 | sizeof (r.offsets[0]) * r.size, REQ_GETREFOFFSETS); 322 | } 323 | 324 | void Node::resume() { 325 | switch (pending_request) { 326 | case REQ_SELECT: 327 | try_select(); 328 | break; 329 | case REQ_REGISTER: 330 | if (start_time - network->get_time() <= 0.0 || terminate) { 331 | Reply_register rep; 332 | rep.subnets = network->get_subnets(); 333 | reply(&rep, sizeof (rep), REQ_REGISTER); 334 | #ifdef DEBUG 335 | printf("starting %d at %f\n", index, network->get_time()); 336 | #endif 337 | } 338 | break; 339 | case REQ_DEREGISTER: 340 | break; 341 | default: 342 | assert(0); 343 | 344 | } 345 | } 346 | 347 | bool Node::waiting() const { 348 | return pending_request == REQ_SELECT || 349 | pending_request == REQ_REGISTER || 350 | pending_request == REQ_DEREGISTER; 351 | } 352 | 353 | bool Node::finished() const { 354 | return pending_request == REQ_DEREGISTER; 355 | } 356 | 357 | double Node::get_timeout() const { 358 | switch (pending_request) { 359 | case REQ_SELECT: 360 | return clock.get_true_interval(select_timeout - clock.get_monotonic_time()); 361 | case REQ_REGISTER: 362 | return start_time - network->get_time(); 363 | case REQ_DEREGISTER: 364 | return 10.0; 365 | default: 366 | assert(0); 367 | } 368 | } 369 | 370 | Clock *Node::get_clock() { 371 | return &clock; 372 | } 373 | 374 | Refclock *Node::get_refclock() { 375 | return &refclock; 376 | } 377 | 378 | void Node::set_refclock_base(Clock *clock) { 379 | refclock_base = clock; 380 | } 381 | -------------------------------------------------------------------------------- /clock.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Miroslav Lichvar 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "clock.h" 19 | 20 | #define MINSEC 256 21 | #define MAXSEC 2048 22 | #define MAXTIMECONST 10 23 | #define MAXMAXERROR 16000000 24 | #define MAXERROR_RATE 500 25 | #define SHIFT_FLL 2 26 | #define SCALE_FREQ 65536.0e6 27 | #define MAXFREQ_SCALED 32768000 28 | #define MAX_SLEWRATE 500 29 | #define MAX_TICK(base_tick) ((base_tick) * 11 / 10) 30 | #define MIN_TICK(base_tick) ((base_tick) * 9 / 10) 31 | 32 | #define MIN_FREQ 0.8 33 | #define MAX_FREQ 1.2 34 | 35 | Clock::Clock() { 36 | time = 0.0; 37 | mono_time = 0.0; 38 | freq = 1.0; 39 | 40 | freq_generator = NULL; 41 | step_generator = NULL; 42 | 43 | base_tick = sysconf(_SC_CLK_TCK); 44 | assert(base_tick > 0); 45 | base_tick = (1000000 + base_tick / 2) / base_tick; 46 | 47 | memset(&ntp_timex, 0, sizeof(ntp_timex)); 48 | ntp_timex.tick = base_tick; 49 | ntp_timex.tolerance = MAXFREQ_SCALED; 50 | ntp_timex.precision = 1; 51 | ntp_timex.maxerror = MAXMAXERROR; 52 | ntp_timex.esterror = MAXMAXERROR; 53 | ntp_timex.status = STA_UNSYNC; 54 | 55 | ntp_state = TIME_OK; 56 | 57 | /* in Linux kernel SHIFT_PLL is 2 since 2.6.31 */ 58 | ntp_shift_pll = 2; 59 | ntp_flags = 0; 60 | ntp_update_interval = 0; 61 | ntp_offset = 0.0; 62 | ntp_slew = 0.0; 63 | 64 | ss_offset = 0; 65 | ss_slew = 0; 66 | } 67 | 68 | Clock::~Clock() { 69 | if (freq_generator) 70 | delete freq_generator; 71 | if (step_generator) 72 | delete step_generator; 73 | } 74 | 75 | double Clock::get_real_time() const { 76 | return time; 77 | } 78 | 79 | double Clock::get_monotonic_time() const { 80 | return mono_time; 81 | } 82 | 83 | double Clock::get_total_freq() const { 84 | double timex_freq, adjtime_freq; 85 | 86 | timex_freq = (double)ntp_timex.tick / base_tick + ntp_timex.freq / SCALE_FREQ + ntp_slew; 87 | adjtime_freq = ss_slew / 1e6; 88 | return freq * (timex_freq + adjtime_freq); 89 | } 90 | 91 | double Clock::get_raw_freq() const { 92 | double timex_freq; 93 | 94 | timex_freq = (double)ntp_timex.tick / base_tick + ntp_timex.freq / SCALE_FREQ; 95 | return freq * timex_freq; 96 | } 97 | 98 | double Clock::get_true_interval(double local_interval) const { 99 | return local_interval / get_total_freq(); 100 | } 101 | 102 | double Clock::get_local_interval(double true_interval) const { 103 | return true_interval * get_total_freq(); 104 | } 105 | 106 | void Clock::set_freq_generator(Generator *gen) { 107 | if (freq_generator) 108 | delete freq_generator; 109 | freq_generator = gen; 110 | } 111 | 112 | void Clock::set_step_generator(Generator *gen) { 113 | if (step_generator) 114 | delete step_generator; 115 | step_generator = gen; 116 | } 117 | 118 | void Clock::set_freq(double freq) { 119 | this->freq = freq + 1.0; 120 | if (!(this->freq > MIN_FREQ && this->freq < MAX_FREQ)) { 121 | fprintf(stderr, "frequency %e outside allowed range (%.2f, %.2f)\n", this->freq - 1.0, MIN_FREQ - 1.0, MAX_FREQ - 1.0); 122 | exit(1); 123 | } 124 | } 125 | 126 | void Clock::set_time(double time) { 127 | this->time = time; 128 | } 129 | 130 | void Clock::step_time(double step) { 131 | this->time += step; 132 | } 133 | 134 | void Clock::set_ntp_shift_pll(int shift) { 135 | ntp_shift_pll = shift; 136 | } 137 | 138 | void Clock::set_ntp_flag(int enable, int flag) { 139 | ntp_flags &= ~flag; 140 | if (enable) 141 | ntp_flags |= flag; 142 | } 143 | 144 | void Clock::advance(double real_interval) { 145 | double local_interval = get_local_interval(real_interval); 146 | 147 | time += local_interval; 148 | mono_time += local_interval; 149 | } 150 | 151 | void Clock::update(bool second) { 152 | if (freq_generator) 153 | set_freq(freq_generator->generate(NULL)); 154 | if (step_generator) 155 | step_time(step_generator->generate(NULL)); 156 | 157 | if (!second) 158 | return; 159 | 160 | if (ntp_timex.status & STA_PLL) { 161 | ntp_update_interval++; 162 | ntp_slew = ntp_offset / (1 << (ntp_shift_pll + 163 | ntp_timex.constant)); 164 | 165 | #if 0 166 | if (ntp_slew > MAX_SLEWRATE / 1e6) 167 | ntp_slew = MAX_SLEWRATE / 1e6; 168 | else if (ntp_slew < -MAX_SLEWRATE / 1e6) 169 | ntp_slew = -MAX_SLEWRATE / 1e6; 170 | #endif 171 | 172 | ntp_offset -= ntp_slew; 173 | 174 | if (ntp_timex.status & STA_NANO) 175 | ntp_timex.offset = ntp_offset * 1e9; 176 | else 177 | ntp_timex.offset = ntp_offset * 1e6; 178 | } 179 | 180 | if (ss_offset) { 181 | if (ss_offset > 0) { 182 | if (ss_offset > MAX_SLEWRATE) { 183 | ss_slew = MAX_SLEWRATE; 184 | ss_offset -= MAX_SLEWRATE; 185 | } else { 186 | ss_slew = ss_offset; 187 | ss_offset = 0; 188 | } 189 | } else { 190 | if (ss_offset < -MAX_SLEWRATE) { 191 | ss_slew = -MAX_SLEWRATE; 192 | ss_offset -= -MAX_SLEWRATE; 193 | } else { 194 | ss_slew = ss_offset; 195 | ss_offset = 0; 196 | } 197 | } 198 | } else 199 | ss_slew = 0; 200 | 201 | ntp_timex.maxerror += MAXERROR_RATE; 202 | if (ntp_timex.maxerror >= MAXMAXERROR) { 203 | ntp_timex.maxerror = MAXMAXERROR; 204 | ntp_timex.status |= STA_UNSYNC; 205 | } 206 | 207 | switch (ntp_state) { 208 | case TIME_OK: 209 | if (ntp_timex.status & STA_INS) 210 | ntp_state = TIME_INS; 211 | else if (ntp_timex.status & STA_DEL) 212 | ntp_state = TIME_DEL; 213 | break; 214 | case TIME_INS: 215 | if ((time_t)(time + 0.5) % (24 * 3600) <= 1) { 216 | time -= 1.0; 217 | ntp_timex.tai += 1.0; 218 | ntp_state = TIME_OOP; 219 | } else if (!(ntp_timex.status & STA_INS)) { 220 | ntp_state = TIME_OK; 221 | } 222 | break; 223 | case TIME_DEL: 224 | if ((time_t)(time + 1.0 + 0.5) % (24 * 3600) <= 1) { 225 | time += 1.0; 226 | ntp_timex.tai -= 1.0; 227 | ntp_state = TIME_WAIT; 228 | } else if (!(ntp_timex.status & STA_DEL)) { 229 | ntp_state = TIME_OK; 230 | } 231 | break; 232 | case TIME_OOP: 233 | ntp_state = TIME_WAIT; 234 | break; 235 | case TIME_WAIT: 236 | if (!(ntp_timex.status & (STA_INS | STA_DEL))) 237 | ntp_state = TIME_OK; 238 | break; 239 | default: 240 | assert(0); 241 | } 242 | } 243 | 244 | void Clock::update_ntp_offset(long offset) { 245 | double fll_adj, pll_adj, new_offset, old_offset, tc, t; 246 | 247 | if (ntp_timex.status & STA_FREQHOLD) 248 | ntp_update_interval = 0; 249 | 250 | if (ntp_timex.status & STA_NANO) 251 | new_offset = offset / 1e9; 252 | else 253 | new_offset = offset / 1e6; 254 | 255 | tc = 1 << ntp_timex.constant; 256 | ntp_timex.offset = offset; 257 | old_offset = ntp_offset; 258 | ntp_offset = new_offset; 259 | 260 | if (!(ntp_timex.status & STA_PLL)) 261 | return; 262 | 263 | if (old_offset && ntp_update_interval >= MINSEC && 264 | (ntp_timex.status & STA_FLL || ntp_update_interval > MAXSEC)) { 265 | ntp_timex.status |= STA_MODE; 266 | if (ntp_flags & CLOCK_NTP_FLL_MODE2) 267 | fll_adj = (new_offset - old_offset) / (ntp_update_interval * (1 << SHIFT_FLL)); 268 | else 269 | fll_adj = new_offset / (ntp_update_interval * (1 << SHIFT_FLL)); 270 | } else { 271 | ntp_timex.status &= ~STA_MODE; 272 | fll_adj = 0.0; 273 | } 274 | 275 | if (ntp_flags & CLOCK_NTP_PLL_CLAMP) { 276 | if (ntp_update_interval > MAXSEC) 277 | ntp_update_interval = MAXSEC; 278 | if (ntp_update_interval > tc * (1 << (ntp_shift_pll + 1))) 279 | ntp_update_interval = tc * (1 << (ntp_shift_pll + 1)); 280 | } 281 | 282 | t = 4 * (1 << ntp_shift_pll) * tc; 283 | pll_adj = new_offset * ntp_update_interval / (t * t); 284 | 285 | ntp_timex.freq += (fll_adj + pll_adj) * SCALE_FREQ; 286 | 287 | if (ntp_timex.freq > MAXFREQ_SCALED) 288 | ntp_timex.freq = MAXFREQ_SCALED; 289 | else if (ntp_timex.freq < -MAXFREQ_SCALED) 290 | ntp_timex.freq = -MAXFREQ_SCALED; 291 | 292 | ntp_update_interval = 0; 293 | } 294 | 295 | int Clock::adjtimex(struct timex *buf) { 296 | int r = ntp_state; 297 | struct timex t; 298 | 299 | if (buf->modes & ADJ_FREQUENCY) { 300 | ntp_timex.freq = buf->freq; 301 | if (ntp_timex.freq > MAXFREQ_SCALED) 302 | ntp_timex.freq = MAXFREQ_SCALED; 303 | else if (ntp_timex.freq < -MAXFREQ_SCALED) 304 | ntp_timex.freq = -MAXFREQ_SCALED; 305 | } 306 | if (buf->modes & ADJ_MAXERROR) { 307 | ntp_timex.maxerror = buf->maxerror; 308 | if (ntp_timex.maxerror > MAXMAXERROR) 309 | ntp_timex.maxerror = MAXMAXERROR; 310 | if (ntp_timex.maxerror < 0) 311 | ntp_timex.maxerror = 0; 312 | } 313 | if (buf->modes & ADJ_ESTERROR) { 314 | ntp_timex.esterror = buf->esterror; 315 | if (ntp_timex.esterror > MAXMAXERROR) 316 | ntp_timex.esterror = MAXMAXERROR; 317 | if (ntp_timex.esterror < 0) 318 | ntp_timex.esterror = 0; 319 | } 320 | if (buf->modes & ADJ_STATUS) { 321 | if ((buf->status & STA_PLL) && !(ntp_timex.status & STA_PLL)) 322 | ntp_update_interval = 0; 323 | ntp_timex.status = buf->status & 0xff; 324 | } 325 | if (buf->modes & ADJ_MICRO) 326 | ntp_timex.status &= ~STA_NANO; 327 | if (buf->modes & ADJ_NANO) 328 | ntp_timex.status |= STA_NANO; 329 | if (buf->modes & ADJ_TIMECONST) { 330 | ntp_timex.constant = buf->constant; 331 | if (!(ntp_timex.status & STA_NANO)) 332 | ntp_timex.constant += 4; 333 | if (ntp_timex.constant > MAXTIMECONST) 334 | ntp_timex.constant = MAXTIMECONST; 335 | if (ntp_timex.constant < 0) 336 | ntp_timex.constant = 0; 337 | } 338 | if (buf->modes & ADJ_TICK) { 339 | if (buf->tick > MAX_TICK(base_tick) || buf->tick < MIN_TICK(base_tick)) { 340 | r = -1; 341 | } else 342 | ntp_timex.tick = buf->tick; 343 | } 344 | if ((buf->modes & ADJ_OFFSET_SINGLESHOT) != ADJ_OFFSET_SINGLESHOT) { 345 | if (buf->modes & ADJ_OFFSET) { 346 | update_ntp_offset(buf->offset); 347 | } 348 | } 349 | if (buf->modes & ADJ_SETOFFSET) { 350 | if (ntp_timex.status & STA_NANO) 351 | time += buf->time.tv_sec + buf->time.tv_usec * 1e-9; 352 | else 353 | time += buf->time.tv_sec + buf->time.tv_usec * 1e-6; 354 | ntp_timex.maxerror = MAXMAXERROR; 355 | } 356 | if (buf->modes & ADJ_TAI) { 357 | ntp_timex.tai = buf->constant; 358 | } 359 | 360 | t = ntp_timex; 361 | t.modes = buf->modes; 362 | 363 | if ((buf->modes & ADJ_OFFSET_SINGLESHOT) == ADJ_OFFSET_SINGLESHOT) { 364 | if ((buf->modes & ADJ_OFFSET_SS_READ) == ADJ_OFFSET_SINGLESHOT) { 365 | t.offset = ss_offset; 366 | ss_offset = buf->offset; 367 | } else { 368 | t.offset = ss_offset; 369 | } 370 | } 371 | 372 | *buf = t; 373 | 374 | return r; 375 | } 376 | 377 | int Clock::adjtime(const struct timeval *delta, struct timeval *olddelta) { 378 | if (olddelta) { 379 | olddelta->tv_sec = ss_offset / 1000000; 380 | olddelta->tv_usec = ss_offset % 1000000; 381 | } 382 | if (delta) 383 | ss_offset = delta->tv_sec * 1000000 + delta->tv_usec; 384 | return 0; 385 | } 386 | 387 | Refclock::Refclock() { 388 | time = 0.0; 389 | offset = 0.0; 390 | generate = false; 391 | valid = false; 392 | offset_generator = NULL; 393 | } 394 | 395 | Refclock::~Refclock() { 396 | if (offset_generator) 397 | delete offset_generator; 398 | } 399 | 400 | void Refclock::set_offset_generator(Generator *gen) { 401 | if (offset_generator) 402 | delete offset_generator; 403 | offset_generator = gen; 404 | } 405 | 406 | void Refclock::set_generation(bool enable) { 407 | generate = enable; 408 | } 409 | 410 | void Refclock::update(double time, const Clock *clock) { 411 | if (!generate || !offset_generator) 412 | return; 413 | 414 | this->time = clock->get_real_time(); 415 | offset = this->time - time + offset_generator->generate(NULL); 416 | valid = true; 417 | } 418 | 419 | bool Refclock::get_sample(double *time, double *offset) const { 420 | *time = this->time; 421 | *offset = this->offset; 422 | return valid; 423 | } 424 | 425 | void Refclock::get_offsets(double *offsets, int size) { 426 | int i; 427 | 428 | for (i = 0; i < size; i++) 429 | offsets[i] = offset_generator ? offset_generator->generate(NULL) : 0.0; 430 | } 431 | -------------------------------------------------------------------------------- /generator.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Miroslav Lichvar 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "generator.h" 19 | 20 | static void syntax_assert(bool condition) { 21 | if (!condition) { 22 | fprintf(stderr, "syntax error\n"); 23 | exit(1); 24 | } 25 | } 26 | 27 | Generator::Generator(const vector *input) { 28 | if (input) 29 | this->input = *input; 30 | constant = false; 31 | } 32 | 33 | Generator::~Generator() { 34 | while (!input.empty()) { 35 | delete input.back(); 36 | input.pop_back(); 37 | } 38 | } 39 | 40 | bool Generator::is_constant() const { 41 | return constant; 42 | } 43 | 44 | Generator_float::Generator_float(double f): Generator(NULL) { 45 | this->f = f; 46 | constant = true; 47 | } 48 | 49 | double Generator_float::generate(const Generator_variables *variables) { 50 | return f; 51 | } 52 | 53 | Generator_variable::Generator_variable(string name): Generator(NULL) { 54 | this->name = name; 55 | } 56 | 57 | double Generator_variable::generate(const Generator_variables *variables) { 58 | Generator_variables::const_iterator iter; 59 | 60 | syntax_assert(variables); 61 | iter = variables->find(name); 62 | syntax_assert(iter != variables->end()); 63 | 64 | return iter->second; 65 | } 66 | 67 | Generator_random_uniform::Generator_random_uniform(const vector *input): 68 | Generator(NULL) { 69 | char *prev_state; 70 | 71 | syntax_assert(!input || input->size() == 0); 72 | 73 | prev_state = initstate(random(), random_state, sizeof (random_state)); 74 | setstate(prev_state); 75 | } 76 | 77 | double Generator_random_uniform::generate(const Generator_variables *variables) { 78 | char *prev_state; 79 | double x; 80 | 81 | prev_state = setstate(random_state); 82 | 83 | x = ((random() & 0x7fffffff) + 1) / 2147483649.0; 84 | x = ((random() & 0x7fffffff) + x) / 2147483648.0; 85 | 86 | setstate(prev_state); 87 | 88 | return x; 89 | } 90 | 91 | Generator_random_normal::Generator_random_normal(const vector *input): 92 | Generator(NULL), uniform(NULL) { 93 | syntax_assert(!input || input->size() == 0); 94 | } 95 | 96 | double Generator_random_normal::generate(const Generator_variables *variables) { 97 | /* Marsaglia polar method */ 98 | 99 | double x, y, s; 100 | 101 | do { 102 | x = 2.0 * uniform.generate(variables) - 1.0; 103 | y = 2.0 * uniform.generate(variables) - 1.0; 104 | s = x * x + y * y; 105 | } while (s >= 1.0); 106 | 107 | x *= sqrt(-2.0 * log(s) / s); 108 | 109 | return x; 110 | } 111 | 112 | Generator_random_exponential::Generator_random_exponential(const vector *input): 113 | Generator(NULL), uniform(NULL) { 114 | syntax_assert(!input || input->size() == 0); 115 | } 116 | 117 | double Generator_random_exponential::generate(const Generator_variables *variables) { 118 | return -log(uniform.generate(variables)); 119 | } 120 | 121 | Generator_random_poisson::Generator_random_poisson(const vector *input): 122 | Generator(NULL), uniform(NULL) { 123 | double lambda; 124 | 125 | syntax_assert(input && input->size() == 1 && (*input)[0]->is_constant()); 126 | 127 | lambda = (*input)[0]->generate(NULL); 128 | syntax_assert(lambda >= 1 && lambda <= 20); 129 | L = exp(-lambda); 130 | } 131 | 132 | double Generator_random_poisson::generate(const Generator_variables *variables) { 133 | double p; 134 | int k; 135 | 136 | for (p = 1.0, k = 0; k < 100; k++) { 137 | p *= uniform.generate(variables); 138 | if (p <= L) 139 | break; 140 | } 141 | 142 | return k; 143 | } 144 | 145 | Generator_file::Generator_file(const char *file): Generator(NULL) { 146 | input = fopen(file, "r"); 147 | if (!input) { 148 | fprintf(stderr, "can't open %s\n", file); 149 | exit(1); 150 | } 151 | } 152 | 153 | Generator_file::~Generator_file() { 154 | fclose(input); 155 | } 156 | 157 | double Generator_file::generate(const Generator_variables *variables) { 158 | double x; 159 | 160 | while (1) { 161 | if (fscanf(input, "%lf", &x) != 1) { 162 | if (feof(input)) { 163 | fseek(input, 0, SEEK_SET); 164 | continue; 165 | } 166 | assert(0); 167 | } 168 | break; 169 | } 170 | return x; 171 | } 172 | 173 | Generator_wave_pulse::Generator_wave_pulse(const vector *input): 174 | Generator(NULL) { 175 | syntax_assert(input && input->size() == 2 && 176 | (*input)[0]->is_constant() && (*input)[1]->is_constant()); 177 | high = (*input)[0]->generate(NULL); 178 | low = (*input)[1]->generate(NULL); 179 | counter = 0; 180 | } 181 | 182 | double Generator_wave_pulse::generate(const Generator_variables *variables) { 183 | counter++; 184 | if (counter > high + low) 185 | counter = 1; 186 | if (counter <= high) 187 | return 1.0; 188 | return -1.0; 189 | } 190 | 191 | Generator_wave_sine::Generator_wave_sine(const vector *input): 192 | Generator(NULL) { 193 | syntax_assert(input && input->size() == 1 && (*input)[0]->is_constant()); 194 | length = (*input)[0]->generate(NULL); 195 | counter = 0; 196 | } 197 | 198 | double Generator_wave_sine::generate(const Generator_variables *variables) { 199 | return sin(counter++ / length * 2 * M_PI); 200 | } 201 | 202 | Generator_wave_cosine::Generator_wave_cosine(const vector *input): 203 | Generator(NULL) { 204 | syntax_assert(input && input->size() == 1 && (*input)[0]->is_constant()); 205 | length = (*input)[0]->generate(NULL); 206 | counter = 0; 207 | } 208 | 209 | double Generator_wave_cosine::generate(const Generator_variables *variables) { 210 | return cos(counter++ / length * 2 * M_PI); 211 | } 212 | 213 | Generator_wave_triangle::Generator_wave_triangle(const vector *input): 214 | Generator(NULL) { 215 | syntax_assert(input && input->size() == 1 && (*input)[0]->is_constant()); 216 | length = (*input)[0]->generate(NULL); 217 | counter = 0; 218 | } 219 | 220 | double Generator_wave_triangle::generate(const Generator_variables *variables) { 221 | double phase; 222 | phase = counter / length - floor(counter / length); 223 | counter++; 224 | return -4.0 * (fabs(phase - 0.5) - 0.25); 225 | 226 | } 227 | 228 | Generator_sum::Generator_sum(const vector *input): 229 | Generator(input) { 230 | sum = 0.0; 231 | } 232 | 233 | double Generator_sum::generate(const Generator_variables *variables) { 234 | unsigned int i; 235 | 236 | for (i = 0; i < input.size(); i++) 237 | sum += input[i]->generate(variables); 238 | return sum; 239 | } 240 | 241 | Generator_multiply::Generator_multiply(const vector *input): 242 | Generator(input) { 243 | } 244 | 245 | double Generator_multiply::generate(const Generator_variables *variables) { 246 | unsigned int i; 247 | double x = 1.0; 248 | 249 | for (i = 0; i < input.size(); i++) 250 | x *= input[i]->generate(variables); 251 | return x; 252 | } 253 | 254 | Generator_add::Generator_add(const vector *input): 255 | Generator(input) { 256 | } 257 | 258 | double Generator_add::generate(const Generator_variables *variables) { 259 | unsigned int i; 260 | double x = 0.0; 261 | 262 | for (i = 0; i < input.size(); i++) 263 | x += input[i]->generate(variables); 264 | return x; 265 | } 266 | 267 | Generator_modulo::Generator_modulo(const vector *input): 268 | Generator(input) { 269 | syntax_assert(input && input->size() > 0); 270 | } 271 | 272 | double Generator_modulo::generate(const Generator_variables *variables) { 273 | unsigned int i; 274 | double x = input[0]->generate(variables); 275 | 276 | for (i = 1; i < input.size(); i++) 277 | x = fmod(x, input[i]->generate(variables)); 278 | 279 | return x; 280 | } 281 | 282 | Generator_equal::Generator_equal(const vector *input): 283 | Generator(input) { 284 | syntax_assert(input && input->size() > 0); 285 | } 286 | 287 | double Generator_equal::generate(const Generator_variables *variables) { 288 | unsigned int i; 289 | double x, min = 0.0, max = 0.0, epsilon = input[0]->generate(variables); 290 | 291 | for (i = 1; i < input.size(); i++) { 292 | x = input[i]->generate(variables); 293 | if (i == 1 || min > x) 294 | min = x; 295 | if (i == 1 || max < x) 296 | max = x; 297 | } 298 | 299 | return max - min <= epsilon ? 1.0 : 0.0; 300 | } 301 | 302 | Generator_max::Generator_max(const vector *input): 303 | Generator(input) { 304 | syntax_assert(input && input->size() > 0); 305 | } 306 | 307 | double Generator_max::generate(const Generator_variables *variables) { 308 | unsigned int i; 309 | double x, max = 0.0; 310 | 311 | for (i = 0; i < input.size(); i++) { 312 | x = input[i]->generate(variables); 313 | if (!i || max < x) 314 | max = x; 315 | } 316 | 317 | return max; 318 | } 319 | 320 | Generator_min::Generator_min(const vector *input): 321 | Generator(input) { 322 | syntax_assert(input && input->size() > 0); 323 | } 324 | 325 | double Generator_min::generate(const Generator_variables *variables) { 326 | unsigned int i; 327 | double x, min = 0.0; 328 | 329 | for (i = 0; i < input.size(); i++) { 330 | x = input[i]->generate(variables); 331 | if (!i || min > x) 332 | min = x; 333 | } 334 | 335 | return min; 336 | } 337 | 338 | Generator_generator::Generator_generator() { 339 | } 340 | 341 | Generator_generator::~Generator_generator() { 342 | } 343 | 344 | Generator *Generator_generator::generate(char *code) const { 345 | const char *ws = " \t\n\r", *wsp = " \t\n\r()"; 346 | int len, paren; 347 | Generator *ret; 348 | vector generators; 349 | char *arg, *name, *end, *string = NULL; 350 | 351 | //printf("code: |%s|\n", code); 352 | len = strlen(code); 353 | end = code + len; 354 | 355 | if (code[0] == '(') { 356 | syntax_assert(len > 2 && code[len - 1] == ')'); 357 | code[len - 1] = '\0'; 358 | code++; 359 | end = code + len - 2; 360 | } 361 | 362 | code += strspn(code, ws); 363 | 364 | name = code; 365 | 366 | code += strcspn(code, wsp); 367 | code[0] = '\0'; 368 | code++; 369 | 370 | code += strspn(code, ws); 371 | 372 | while (code < end) { 373 | arg = code; 374 | 375 | if (arg[0] == '(') { 376 | code = ++arg; 377 | for (paren = 1; code < end; code++) { 378 | if (code[0] == '(') 379 | paren++; 380 | else if (code[0] == ')') 381 | paren--; 382 | if (paren == 0) 383 | break; 384 | } 385 | 386 | syntax_assert(paren == 0 && code[0] == ')'); 387 | code[0] = '\0'; 388 | code++; 389 | 390 | //printf("generator: %s\n", arg); 391 | generators.push_back(generate(arg)); 392 | syntax_assert(generators.back()); 393 | } else if (arg[0] == '"') { 394 | string = code = ++arg; 395 | code += strcspn(code, "\""); 396 | syntax_assert(code[0] == '"'); 397 | code[0] = '\0'; 398 | code++; 399 | //printf("string: |%s|\n", string); 400 | } else { 401 | code += strcspn(code, wsp); 402 | syntax_assert(code[0] != ')' && code[0] != '('); 403 | code[0] = '\0'; 404 | code++; 405 | if (isalpha(arg[0])) { 406 | generators.push_back(new Generator_variable(arg)); 407 | //printf("variable: %s\n", arg); 408 | } else { 409 | generators.push_back(new Generator_float(atof(arg))); 410 | //printf("float: %f\n", generators.back()->generate()); 411 | } 412 | } 413 | 414 | code += strspn(code, ws); 415 | } 416 | 417 | if (strcmp(name, "*") == 0) 418 | ret = new Generator_multiply(&generators); 419 | else if (strcmp(name, "+") == 0) 420 | ret = new Generator_add(&generators); 421 | else if (strcmp(name, "%") == 0) 422 | ret = new Generator_modulo(&generators); 423 | else if (strcmp(name, "sum") == 0) 424 | ret = new Generator_sum(&generators); 425 | else if (strcmp(name, "uniform") == 0) 426 | ret = new Generator_random_uniform(&generators); 427 | else if (strcmp(name, "normal") == 0) 428 | ret = new Generator_random_normal(&generators); 429 | else if (strcmp(name, "exponential") == 0) 430 | ret = new Generator_random_exponential(&generators); 431 | else if (strcmp(name, "poisson") == 0) 432 | ret = new Generator_random_poisson(&generators); 433 | else if (strcmp(name, "file") == 0) 434 | ret = new Generator_file(string); 435 | else if (strcmp(name, "pulse") == 0) 436 | ret = new Generator_wave_pulse(&generators); 437 | else if (strcmp(name, "sine") == 0) 438 | ret = new Generator_wave_sine(&generators); 439 | else if (strcmp(name, "cosine") == 0) 440 | ret = new Generator_wave_cosine(&generators); 441 | else if (strcmp(name, "triangle") == 0) 442 | ret = new Generator_wave_triangle(&generators); 443 | else if (strcmp(name, "equal") == 0) 444 | ret = new Generator_equal(&generators); 445 | else if (strcmp(name, "max") == 0) 446 | ret = new Generator_max(&generators); 447 | else if (strcmp(name, "min") == 0) 448 | ret = new Generator_min(&generators); 449 | else { 450 | ret = NULL; 451 | syntax_assert(0); 452 | } 453 | 454 | return ret; 455 | } 456 | -------------------------------------------------------------------------------- /network.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Miroslav Lichvar 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "sysheaders.h" 19 | #include "network.h" 20 | 21 | #define CONNECT_TIMEOUT 10 22 | 23 | Packet_queue::Packet_queue() { 24 | } 25 | 26 | Packet_queue::~Packet_queue() { 27 | while (!queue.empty()) { 28 | delete queue.back(); 29 | queue.pop_back(); 30 | } 31 | } 32 | 33 | void Packet_queue::insert(struct Packet *packet) { 34 | deque::iterator i; 35 | 36 | for (i = queue.begin(); i < queue.end(); i++) 37 | if (packet->receive_time < (*i)->receive_time) 38 | break; 39 | queue.insert(i, packet); 40 | } 41 | 42 | struct Packet *Packet_queue::dequeue() { 43 | struct Packet *ret; 44 | 45 | assert(!queue.empty()); 46 | ret = queue.front(); 47 | queue.pop_front(); 48 | 49 | return ret; 50 | } 51 | 52 | double Packet_queue::get_timeout(double time) const { 53 | if (!queue.empty()) { 54 | return queue[0]->receive_time - time; 55 | } 56 | return 1e20; 57 | } 58 | 59 | Network::Network(const char *socket, const char *executable, 60 | unsigned int n, unsigned int subnets, unsigned int rate) { 61 | time = 0.0; 62 | this->subnets = subnets; 63 | socket_name = socket; 64 | update_executable = executable; 65 | update_rate = rate; 66 | update_count = 0; 67 | offset_log = NULL; 68 | freq_log = NULL; 69 | rawfreq_log = NULL; 70 | packet_log = NULL; 71 | 72 | assert(n > 0); 73 | 74 | while (nodes.size() < n) 75 | nodes.push_back(new Node(nodes.size(), this)); 76 | 77 | stats.resize(n); 78 | link_delays.resize(n * n); 79 | link_corrections.resize(n * n); 80 | } 81 | 82 | Network::~Network() { 83 | while (!nodes.empty()) { 84 | delete nodes.back(); 85 | nodes.pop_back(); 86 | } 87 | 88 | while (!link_delays.empty()) { 89 | delete link_delays.back(); 90 | link_delays.pop_back(); 91 | } 92 | 93 | while (!link_corrections.empty()) { 94 | delete link_corrections.back(); 95 | link_corrections.pop_back(); 96 | } 97 | 98 | unlink(socket_name); 99 | 100 | if (offset_log) 101 | fclose(offset_log); 102 | if (freq_log) 103 | fclose(freq_log); 104 | if (rawfreq_log) 105 | fclose(rawfreq_log); 106 | if (packet_log) 107 | fclose(packet_log); 108 | } 109 | 110 | 111 | bool Network::prepare_clients() { 112 | struct sockaddr_un s; 113 | struct timeval tv; 114 | int sockfd, fd; 115 | unsigned int i; 116 | 117 | s.sun_family = AF_UNIX; 118 | snprintf(s.sun_path, sizeof (s.sun_path), "%s", socket_name); 119 | 120 | sockfd = socket(AF_UNIX, SOCK_SEQPACKET, 0); 121 | if (sockfd < 0) { 122 | fprintf(stderr, "socket() failed: %s\n", strerror(errno)); 123 | return false; 124 | } 125 | 126 | unlink(socket_name); 127 | if (bind(sockfd, (struct sockaddr *)&s, sizeof (s)) < 0) { 128 | fprintf(stderr, "bind() failed: %s\n", strerror(errno)); 129 | return false; 130 | } 131 | 132 | if (listen(sockfd, nodes.size()) < 0) { 133 | fprintf(stderr, "listen() failed: %s\n", strerror(errno)); 134 | return false; 135 | } 136 | 137 | tv.tv_sec = CONNECT_TIMEOUT; 138 | tv.tv_usec = 0; 139 | 140 | if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof (tv))) { 141 | fprintf(stderr, "setsockopt() failed: %s\n", strerror(errno)); 142 | return false; 143 | } 144 | 145 | for (i = 0; i < nodes.size(); i++) { 146 | Request_packet req; 147 | unsigned int node; 148 | 149 | fprintf(stderr, "\rWaiting for %u clients...", (unsigned int)nodes.size() - i); 150 | fd = accept(sockfd, NULL, NULL); 151 | if (fd < 0) { 152 | fprintf(stderr, "\naccept() failed: %s\n", strerror(errno)); 153 | return false; 154 | } 155 | 156 | if (recv(fd, &req, sizeof (req), 0) != offsetof(Request_packet, data) + 157 | sizeof (Request_register) || req.header.request != REQ_REGISTER) { 158 | fprintf(stderr, "client didn't register correctly.\n"); 159 | return false; 160 | } 161 | node = req.data._register.node; 162 | assert(node < nodes.size() && nodes[node]->get_fd() < 0); 163 | nodes[node]->set_fd(fd); 164 | } 165 | fprintf(stderr, "done\n"); 166 | 167 | close(sockfd); 168 | 169 | update(); 170 | 171 | return true; 172 | } 173 | 174 | Node *Network::get_node(unsigned int node) { 175 | assert(node < nodes.size()); 176 | return nodes[node]; 177 | } 178 | 179 | void Network::set_link_delay_generator(unsigned int from, unsigned int to, Generator *generator) { 180 | unsigned int i; 181 | 182 | assert(from < nodes.size() && to < nodes.size()); 183 | 184 | i = from * nodes.size() + to; 185 | if (link_delays[i]) 186 | delete link_delays[i]; 187 | link_delays[i] = generator; 188 | } 189 | 190 | void Network::set_link_correction_generator(unsigned int from, unsigned int to, Generator *generator) { 191 | unsigned int i; 192 | 193 | assert(from < nodes.size() && to < nodes.size()); 194 | 195 | i = from * nodes.size() + to; 196 | if (link_corrections[i]) 197 | delete link_corrections[i]; 198 | link_corrections[i] = generator; 199 | } 200 | 201 | bool Network::run(double time_limit) { 202 | int i, n = nodes.size(), waiting; 203 | bool pending_update; 204 | double min_timeout, timeout, next_update; 205 | 206 | while (time < time_limit) { 207 | for (i = 0, waiting = 0; i < n; i++) 208 | if (nodes[i]->waiting()) 209 | waiting++; 210 | else 211 | stats[i].update_wakeup_stats(); 212 | 213 | while (waiting < n) { 214 | for (i = 0; i < n; i++) { 215 | if (nodes[i]->waiting()) 216 | continue; 217 | if (!nodes[i]->process_fd()) { 218 | fprintf(stderr, "client %d failed.\n", i + 1); 219 | return false; 220 | } 221 | if (nodes[i]->waiting()) 222 | waiting++; 223 | } 224 | } 225 | 226 | do { 227 | min_timeout = nodes[0]->get_timeout(); 228 | for (i = 1; i < n; i++) { 229 | timeout = nodes[i]->get_timeout(); 230 | if (min_timeout > timeout) 231 | min_timeout = timeout; 232 | } 233 | 234 | timeout = packet_queue.get_timeout(time); 235 | if (timeout <= min_timeout) 236 | min_timeout = timeout; 237 | 238 | next_update = floor(time) + (double)(update_count + 1) / update_rate; 239 | timeout = next_update - time; 240 | if (timeout <= min_timeout) { 241 | min_timeout = timeout; 242 | pending_update = true; 243 | } else 244 | pending_update = false; 245 | 246 | //min_timeout += 1e-12; 247 | assert(min_timeout >= 0.0); 248 | 249 | if (pending_update) 250 | time = next_update; 251 | else 252 | time += min_timeout; 253 | 254 | for (i = 0; i < n; i++) 255 | nodes[i]->get_clock()->advance(min_timeout); 256 | 257 | if (pending_update) 258 | update(); 259 | } while (pending_update && time < time_limit); 260 | 261 | for (i = 0; i < n; i++) 262 | nodes[i]->resume(); 263 | 264 | while (packet_queue.get_timeout(time) <= 0) { 265 | assert(packet_queue.get_timeout(time) > -1e-10); 266 | struct Packet *packet = packet_queue.dequeue(); 267 | stats[packet->to].update_packet_stats(true, time, packet->delay); 268 | nodes[packet->to]->receive(packet); 269 | } 270 | } 271 | 272 | return true; 273 | } 274 | 275 | void Network::update() { 276 | int i, n = nodes.size(); 277 | 278 | update_count++; 279 | update_count %= update_rate; 280 | 281 | for (i = 0; i < n; i++) { 282 | nodes[i]->get_clock()->update(update_count == 0); 283 | nodes[i]->get_refclock()->update(time, nodes[i]->get_clock()); 284 | } 285 | 286 | update_clock_stats(); 287 | 288 | if (update_executable) { 289 | pid_t pid = fork(); 290 | char buf[16]; 291 | 292 | if (pid == 0) { 293 | snprintf(buf, sizeof (buf), "%g", time); 294 | execl(update_executable, update_executable, buf, (char *)NULL); 295 | exit(1); 296 | } else if (pid > 0) { 297 | waitpid(pid, NULL, 0); 298 | } 299 | } 300 | } 301 | 302 | void Network::update_clock_stats() { 303 | int i, n = nodes.size(); 304 | 305 | if (offset_log) { 306 | for (i = 0; i < n; i++) 307 | fprintf(offset_log, "%.9f%c", nodes[i]->get_clock()->get_real_time() - time, i + 1 < n ? '\t' : '\n'); 308 | } 309 | if (freq_log) { 310 | for (i = 0; i < n; i++) 311 | fprintf(freq_log, "%e%c", nodes[i]->get_clock()->get_total_freq() - 1.0, i + 1 < n ? '\t' : '\n'); 312 | } 313 | if (rawfreq_log) { 314 | for (i = 0; i < n; i++) 315 | fprintf(rawfreq_log, "%e%c", nodes[i]->get_clock()->get_raw_freq() - 1.0, i + 1 < n ? '\t' : '\n'); 316 | } 317 | 318 | for (i = 0; i < n; i++) 319 | stats[i].update_clock_stats(nodes[i]->get_clock()->get_real_time() - time, 320 | nodes[i]->get_clock()->get_total_freq() - 1.0, 321 | nodes[i]->get_clock()->get_raw_freq() - 1.0); 322 | } 323 | 324 | void Network::write_correction(struct Packet *packet, double correction) { 325 | uint64_t c; 326 | 327 | /* one-step transparent end-to-end PTP clock */ 328 | 329 | if (packet->src_port != 319 || packet->dst_port != 319 || packet->len < 34 || 330 | ((packet->data[0] & 0xf) != 0 && (packet->data[0] & 0xf) != 1) || 331 | (packet->data[1] & 0xf) != 2) 332 | return; 333 | 334 | c = ((uint64_t)ntohl(*(uint32_t *)(packet->data + 8)) << 32) | 335 | ntohl(*(uint32_t *)(packet->data + 12)); 336 | c += (uint64_t)(correction * ((1 << 16) * 1.0e9)); 337 | *(uint32_t *)(packet->data + 8) = htonl(c >> 32); 338 | *(uint32_t *)(packet->data + 12) = htonl(c); 339 | } 340 | 341 | void Network::open_offset_log(const char *log) { 342 | offset_log = fopen(log, "w"); 343 | } 344 | 345 | void Network::open_freq_log(const char *log) { 346 | freq_log = fopen(log, "w"); 347 | } 348 | 349 | void Network::open_rawfreq_log(const char *log) { 350 | rawfreq_log = fopen(log, "w"); 351 | } 352 | 353 | void Network::open_packet_log(const char *log) { 354 | packet_log = fopen(log, "w"); 355 | } 356 | 357 | void Network::print_stats(int verbosity) const { 358 | int i, n = nodes.size(); 359 | 360 | if (verbosity <= 0) 361 | return; 362 | 363 | for (i = 0; i < n; i++) { 364 | if (verbosity > 1) 365 | printf("\n---------------------- Node %d ----------------------\n\n", i + 1); 366 | stats[i].print(verbosity); 367 | } 368 | if (verbosity == 1) 369 | printf("\n"); 370 | } 371 | 372 | void Network::reset_stats() { 373 | int i, n = nodes.size(); 374 | 375 | for (i = 0; i < n; i++) 376 | stats[i].reset(); 377 | } 378 | 379 | void Network::reset_clock_stats() { 380 | int i, n = nodes.size(); 381 | 382 | for (i = 0; i < n; i++) 383 | stats[i].reset_clock_stats(); 384 | } 385 | 386 | void Network::send(struct Packet *packet) { 387 | double delay = -1.0; 388 | unsigned int i; 389 | 390 | /* broadcast */ 391 | if (packet->to == (unsigned int)-1) { 392 | for (i = 0; i < nodes.size(); i++) { 393 | struct Packet *p; 394 | 395 | if (i == packet->from) 396 | continue; 397 | 398 | p = new struct Packet; 399 | memcpy(p, packet, sizeof (struct Packet)); 400 | p->to = i; 401 | 402 | send(p); 403 | } 404 | 405 | delete packet; 406 | return; 407 | } 408 | 409 | if (packet->to >= nodes.size() || packet->from >= nodes.size() || packet->subnet >= subnets) { 410 | #ifdef DEBUG 411 | printf("dropping packet of type %d from %d to %d:%d:%d at %f\n", 412 | packet->type, packet->from, packet->subnet, packet->to, 413 | packet->dst_port, time); 414 | #endif 415 | delete packet; 416 | return; 417 | } 418 | 419 | i = packet->from * nodes.size() + packet->to; 420 | 421 | if (packet->type != MSG_TYPE_UDP_DATA) { 422 | /* constant delay to not break the order of TCP packets */ 423 | delay = 1.0e-3; 424 | } else if (link_delays[i]) { 425 | link_delay_variables["time"] = time; 426 | link_delay_variables["from"] = packet->from + 1; 427 | link_delay_variables["to"] = packet->to + 1; 428 | link_delay_variables["subnet"] = packet->subnet + 1; 429 | link_delay_variables["port"] = packet->dst_port; 430 | link_delay_variables["length"] = packet->len; 431 | 432 | delay = link_delays[i]->generate(&link_delay_variables); 433 | } 434 | 435 | if (delay > 0.0 && link_corrections[i]) { 436 | link_correction_variables["delay"] = delay; 437 | link_correction_variables["length"] = packet->len; 438 | write_correction(packet, link_corrections[i]->generate(&link_correction_variables)); 439 | } 440 | 441 | stats[packet->from].update_packet_stats(false, time, delay); 442 | 443 | if (packet_log) 444 | fprintf(packet_log, "%e\t%d\t%d\t%e\t%d\t%d\t%d\t%d\t%d\n", time, 445 | packet->from + 1, packet->to + 1, delay, 446 | packet->src_port, packet->dst_port, 447 | packet->subnet + 1, packet->len, packet->type); 448 | 449 | if (delay > 0.0) { 450 | packet->receive_time = time + delay; 451 | packet->delay = delay; 452 | packet_queue.insert(packet); 453 | #ifdef DEBUG 454 | printf("sending packet of type %d from %d to %d:%d:%d at %f delay %f \n", 455 | packet->type, packet->from, packet->subnet, packet->to, 456 | packet->dst_port, time, delay); 457 | #endif 458 | } else { 459 | #ifdef DEBUG 460 | printf("dropping packet of type %d from %d to %d:%d:%d at %f\n", 461 | packet->type, packet->from, packet->subnet, packet->to, 462 | packet->dst_port, time); 463 | #endif 464 | delete packet; 465 | } 466 | } 467 | 468 | double Network::get_time() const { 469 | return time; 470 | } 471 | 472 | unsigned int Network::get_subnets() const { 473 | return subnets; 474 | } 475 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------