diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 51679015..733b366c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,9 +7,9 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: - go-version: 1.24 + go-version: 1.25 - run: sudo apt-get install libpcap-dev - - uses: golangci/golangci-lint-action@v3 + - uses: golangci/golangci-lint-action@v9 - run: | go get -v -u github.com/u-root/u-root/tools/checklicenses go run github.com/u-root/u-root/tools/checklicenses -c .github/workflows/config.json diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a8f88d38..2a7e56e7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: - go-version: 1.24 + go-version: 1.25 - run: sudo apt-get install libpcap-dev libcap2-bin - run: go build -v ./... # fuzzing, need to specify each package separately @@ -15,6 +15,6 @@ jobs: - run: go test -v -fuzz='.*' -fuzztime=10s ./ntp/protocol/ - run: go test -v -fuzz='.*' -fuzztime=10s ./ntp/chrony/ - name: Run coverage - run: sudo capsh --inh=cap_net_raw --print -- -c "go test -v -race -coverprofile=coverage.txt -covermode=atomic ./..." + run: sudo --preserve-env=PATH capsh --inh=cap_net_raw --print -- -c "go test -v -race -coverprofile=coverage.txt -covermode=atomic ./..." - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 diff --git a/.golangci.yml b/.golangci.yml index d1929736..f664ab3b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,24 +1,40 @@ +version: "2" linters: - # extra linters to enable with default set: - # https://golangci-lint.run/usage/linters/#enabled-by-default enable: - - revive # replacement of golint - - asciicheck # code does not contain non-ASCII identifiers - - contextcheck # check when function use a non-inherited context - - errorlint # error wrapping - - gofmt # formatting - - misspell # commonly misspelled English words in comments - - prealloc # slice declarations that could potentially be pre-allocated - - nilerr # code that returns nil even if it checks that the error is not nil - - predeclared # code that shadows one of Go's predeclared identifiers - - reassign # package variables are not reassigned - - unconvert # unnecessary type conversions - - unparam # unused function parameters - - usestdlibvars # possibility to use variables/constants from the Go standard library - - whitespace # leading and trailing whitespace -issues: - exclude-rules: - # not worth it - - path: _test\.go - linters: - - revive + - asciicheck + - contextcheck + - errorlint + - misspell + - nilerr + - prealloc + - predeclared + - reassign + - revive + - unconvert + - unparam + - usestdlibvars + - whitespace + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - revive + path: _test\.go + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/.packit.yaml b/.packit.yaml index 8d554642..32b45dc7 100644 --- a/.packit.yaml +++ b/.packit.yaml @@ -13,7 +13,7 @@ actions: - "bash -c \"curl -s https://src.fedoraproject.org/rpms/facebook-time/raw/main/f/facebook-time.spec | sed -e '/^Patch[0-9]/d' -e 's|^%global commit.*|%global commit '$(git rev-parse HEAD)'|' -e 's|^%global date.*|%global date '$(date +%Y%m%d)'|' -e 's|mv fbclock-bin %{gobuilddir}/bin/fbclock-bin|mkdir -p %{gobuilddir}/bin \\&\\& mv fbclock-bin %{gobuilddir}/bin/fbclock-bin|' > facebook-time.spec\"" - "curl -s -o go-vendor-tools.toml https://src.fedoraproject.org/rpms/facebook-time/raw/main/f/go-vendor-tools.toml" create-archive: - - "spectool -g facebook-time.spec" + - "bash -c 'git archive --format=tar.gz --prefix=time-$(git rev-parse HEAD)/ HEAD -o time-$(git rev-parse HEAD).tar.gz'" - "go_vendor_archive create facebook-time.spec" - "bash -c 'echo time-$(git rev-parse HEAD).tar.gz'" diff --git a/clock/clock_64bit.go b/clock/clock_64bit.go index edf06d56..9ab5701d 100644 --- a/clock/clock_64bit.go +++ b/clock/clock_64bit.go @@ -1,4 +1,4 @@ -//go:build !386 +//go:build linux && !386 /* Copyright (c) Facebook, Inc. and its affiliates. diff --git a/clock/clock_64bit_darwin.go b/clock/clock_64bit_darwin.go new file mode 100644 index 00000000..738c9e7c --- /dev/null +++ b/clock/clock_64bit_darwin.go @@ -0,0 +1,34 @@ +//go:build darwin && !386 + +/* +Copyright (c) Facebook, Inc. and its affiliates. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package clock + +import ( + "time" + + "github.com/facebook/time/phc/unix" +) + +func setFreq(tx *unix.Timex, freqPPB float64) { + tx.Freq = int64(freqPPB * PPBToTimexPPM) +} + +func setTime(tx *unix.Timex, sec, usec time.Duration) { + tx.Time.Sec = int64(sec) + tx.Time.Usec = int32(usec) +} diff --git a/clock/clock_linux_test.go b/clock/clock_linux_test.go new file mode 100644 index 00000000..b0f107a8 --- /dev/null +++ b/clock/clock_linux_test.go @@ -0,0 +1,61 @@ +/* +Copyright (c) Facebook, Inc. and its affiliates. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package clock + +import ( + "testing" + "time" + + "github.com/facebook/time/phc/unix" + "github.com/stretchr/testify/require" +) + +func TestSetTime(t *testing.T) { + tx := &unix.Timex{} + setTime(tx, 5*time.Second, 123*time.Nanosecond) + require.Equal(t, int64(5*time.Second), tx.Time.Sec) + require.Equal(t, int64(123*time.Nanosecond), tx.Time.Usec) +} + +func TestSetTimeNegative(t *testing.T) { + tx := &unix.Timex{} + setTime(tx, -1*time.Second, -500*time.Nanosecond) + require.Equal(t, int64(-1*time.Second), tx.Time.Sec) + require.Equal(t, int64(-500*time.Nanosecond), tx.Time.Usec) +} + +func TestFrequencyPPB(t *testing.T) { + tx := &unix.Timex{} + wantState, err := unix.Adjtimex(tx) + require.NoError(t, err) + + freqPPB, state, err := FrequencyPPB(0) + require.NoError(t, err) + require.Equal(t, wantState, state) + require.Equal(t, float64(tx.Freq)/PPBToTimexPPM, freqPPB) +} + +func TestMaxFreqPPB(t *testing.T) { + tx := &unix.Timex{} + wantState, err := unix.Adjtimex(tx) + require.NoError(t, err) + + maxFreq, state, err := MaxFreqPPB(0) + require.NoError(t, err) + require.Equal(t, wantState, state) + require.Equal(t, 500000.0, maxFreq) +} diff --git a/clock/clock_test.go b/clock/clock_test.go index 847d22d3..cbfae9f3 100644 --- a/clock/clock_test.go +++ b/clock/clock_test.go @@ -18,10 +18,9 @@ package clock import ( "testing" - "time" + "github.com/facebook/time/phc/unix" "github.com/stretchr/testify/require" - "golang.org/x/sys/unix" ) func TestSetFreq(t *testing.T) { @@ -36,42 +35,6 @@ func TestSetFreq(t *testing.T) { require.Equal(t, int64(0), tx.Freq) } -func TestSetTime(t *testing.T) { - tx := &unix.Timex{} - setTime(tx, 5*time.Second, 123*time.Nanosecond) - require.Equal(t, int64(5*time.Second), tx.Time.Sec) - require.Equal(t, int64(123*time.Nanosecond), tx.Time.Usec) -} - -func TestSetTimeNegative(t *testing.T) { - tx := &unix.Timex{} - setTime(tx, -1*time.Second, -500*time.Nanosecond) - require.Equal(t, int64(-1*time.Second), tx.Time.Sec) - require.Equal(t, int64(-500*time.Nanosecond), tx.Time.Usec) -} - -func TestFrequencyPPB(t *testing.T) { - tx := &unix.Timex{} - wantState, err := unix.Adjtimex(tx) - require.NoError(t, err) - - freqPPB, state, err := FrequencyPPB(0) - require.NoError(t, err) - require.Equal(t, wantState, state) - require.Equal(t, float64(tx.Freq)/PPBToTimexPPM, freqPPB) -} - -func TestMaxFreqPPB(t *testing.T) { - tx := &unix.Timex{} - wantState, err := unix.Adjtimex(tx) - require.NoError(t, err) - - maxFreq, state, err := MaxFreqPPB(0) - require.NoError(t, err) - require.Equal(t, wantState, state) - require.Equal(t, 500000.0, maxFreq) -} - func TestFrequencyPPBInvalidClock(t *testing.T) { _, _, err := FrequencyPPB(-1) require.Error(t, err) diff --git a/cmd/ntpcheck/checker/chrony.go b/cmd/ntpcheck/checker/chrony.go index af9f01b4..9477a748 100644 --- a/cmd/ntpcheck/checker/chrony.go +++ b/cmd/ntpcheck/checker/chrony.go @@ -120,7 +120,7 @@ func (n *ChronyCheck) Run() (*NTPCheckResult, error) { } sourceData, ok := packet.(*chrony.ReplySourceData) if !ok { - return nil, fmt.Errorf("Got wrong 'sourcedata' response %+v", packet) + return nil, fmt.Errorf("got wrong 'sourcedata' response %+v", packet) } // get ntpdata when using a unix socket // Skip sources with unresolved addresses (IPADDR_ID family) as they don't have valid IPs @@ -137,7 +137,7 @@ func (n *ChronyCheck) Run() (*NTPCheckResult, error) { } else { rpyNTPData2, ok := packet.(*chrony.ReplyNTPData2) if !ok { - return nil, fmt.Errorf("Got wrong 'ntpdata' response %+v", packet) + return nil, fmt.Errorf("got wrong 'ntpdata' response %+v", packet) } ntpData = &rpyNTPData2.NTPData } @@ -153,7 +153,7 @@ func (n *ChronyCheck) Run() (*NTPCheckResult, error) { } ntpSourceName, ok = packet.(*chrony.ReplyNTPSourceName) if !ok { - return nil, fmt.Errorf("Got wrong 'sourcename' response %+v", packet) + return nil, fmt.Errorf("got wrong 'sourcename' response %+v", packet) } } peer, err := NewPeerFromChrony(sourceData, ntpData, ntpSourceName) @@ -192,7 +192,7 @@ func (n *ChronyCheck) ServerStats() (*ServerStats, error) { case *chrony.ReplyServerStats4: serverStats = NewServerStatsFromChrony4(stats) default: - return nil, fmt.Errorf("Got wrong 'serverstats' response %+v", packet) + return nil, fmt.Errorf("got wrong 'serverstats' response %+v", packet) } log.Debugf("ServerStats: %v", serverStats) return serverStats, nil diff --git a/cmd/ntpcheck/checker/ntpd.go b/cmd/ntpcheck/checker/ntpd.go index f6253cbc..8df4e9e2 100644 --- a/cmd/ntpcheck/checker/ntpd.go +++ b/cmd/ntpcheck/checker/ntpd.go @@ -175,7 +175,7 @@ func (n *NTPCheck) ServerStats() (*ServerStats, error) { log.Debugf("Data string: '%s'", string(serverVars.Data)) if serverVars.HasError() || (len(serverVars.Data) <= 0) { - return nil, fmt.Errorf("Got bad 'server variables' response %+v", serverVars) + return nil, fmt.Errorf("got bad 'server variables' response %+v", serverVars) } serverStats, err := NewServerStatsFromNTP(serverVars) diff --git a/cmd/ntpcheck/checker/peer.go b/cmd/ntpcheck/checker/peer.go index db606181..0eb2163f 100644 --- a/cmd/ntpcheck/checker/peer.go +++ b/cmd/ntpcheck/checker/peer.go @@ -73,13 +73,13 @@ type Peer struct { // sanityCheckPeerVars checks if we parsed enough info from NTPD response func sanityCheckPeerVars(p *Peer) error { if p == nil { - return errors.New("No peer") + return errors.New("no peer") } if p.Stratum == 0 { - return errors.New("Incomplete data, stratum 0 in peer variables") + return errors.New("incomplete data, stratum 0 in peer variables") } if p.PPoll == 0 || p.HPoll == 0 { - return errors.New("Incomplete data, poll 0 in peer variables") + return errors.New("incomplete data, poll 0 in peer variables") } return nil } diff --git a/cmd/ntpcheck/checker/system.go b/cmd/ntpcheck/checker/system.go index 877969fa..49af3384 100644 --- a/cmd/ntpcheck/checker/system.go +++ b/cmd/ntpcheck/checker/system.go @@ -51,10 +51,10 @@ type SystemVariables struct { // sanityCheckSysVars checks if we parsed enough info from NTPD response func sanityCheckSysVars(sysVars *SystemVariables) error { if sysVars == nil { - return errors.New("No system variables") + return errors.New("no system variables") } if sysVars.Stratum == 0 { - return errors.New("Incomplete data, stratum 0 in system variables") + return errors.New("incomplete data, stratum 0 in system variables") } return nil } diff --git a/cmd/ntpcheck/cmd/utils.go b/cmd/ntpcheck/cmd/utils.go index 7a19fe04..2f6848a0 100644 --- a/cmd/ntpcheck/cmd/utils.go +++ b/cmd/ntpcheck/cmd/utils.go @@ -42,7 +42,7 @@ import ( // refID converts ip into ReFID format and prints it on stdout func refID(ipStr string) error { if ipStr == "" { - return fmt.Errorf("Error: no IP provided") + return fmt.Errorf("no IP provided") } ip := net.ParseIP(ipStr) diff --git a/cmd/ziffy/node/lookup.go b/cmd/ziffy/node/lookup.go new file mode 100644 index 00000000..3738f68a --- /dev/null +++ b/cmd/ziffy/node/lookup.go @@ -0,0 +1,29 @@ +/* +Copyright (c) Facebook, Inc. and its affiliates. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package node + +import ( + "net" +) + +func getLookUpName(ip string) string { + addr, err := net.LookupAddr(ip) + if err != nil || len(addr) == 0 { + return ip + } + return addr[0] +} diff --git a/cmd/ziffy/node/packets.go b/cmd/ziffy/node/packets.go index c6a0e85e..6cff507e 100644 --- a/cmd/ziffy/node/packets.go +++ b/cmd/ziffy/node/packets.go @@ -22,42 +22,6 @@ import ( ptp "github.com/facebook/time/ptp/protocol" ) -// formSignalingPacket creates PTP SIGNALING packet -// SequenceId contains origin hop; PortNumber contains origin port; -// ControlField contains the Zi(0xff)y identifier -func formSignalingPacket(hop int, routeIndex int) *ptp.Signaling { - l := binary.Size(ptp.Header{}) + binary.Size(ptp.PortIdentity{}) + binary.Size(ptp.RequestUnicastTransmissionTLV{}) - return &ptp.Signaling{ - Header: ptp.Header{ - SdoIDAndMsgType: ptp.NewSdoIDAndMsgType(ptp.MessageSignaling, 0), - Version: ptp.Version, - SequenceID: uint16(hop), - MessageLength: uint16(l), - FlagField: ptp.FlagUnicast, - SourcePortIdentity: ptp.PortIdentity{ - PortNumber: uint16(routeIndex), - }, - ControlField: ZiffyHexa, //identifier for zi(0xff)y - LogMessageInterval: 0x7f, - }, - TargetPortIdentity: ptp.PortIdentity{ - PortNumber: 0xffff, - ClockIdentity: 0xffffffffffffffff, - }, - TLVs: []ptp.TLV{ - &ptp.RequestUnicastTransmissionTLV{ - TLVHead: ptp.TLVHead{ - TLVType: ptp.TLVRequestUnicastTransmission, - LengthField: uint16(binary.Size(ptp.RequestUnicastTransmissionTLV{}) - binary.Size(ptp.TLVHead{})), - }, - MsgTypeAndReserved: ptp.NewUnicastMsgTypeAndFlags(ptp.MessageSync, 0), - LogInterMessagePeriod: 1, - DurationField: 0, // seconds - }, - }, - } -} - // formSyncPacket creates PTP SYNC packet // SequenceId contains origin hop; PortNumber contains origin port; // ControlField contains the Zi(0xff)y identifier diff --git a/cmd/ziffy/node/receiver.go b/cmd/ziffy/node/receiver_linux.go similarity index 99% rename from cmd/ziffy/node/receiver.go rename to cmd/ziffy/node/receiver_linux.go index fad2be09..218b7337 100644 --- a/cmd/ziffy/node/receiver.go +++ b/cmd/ziffy/node/receiver_linux.go @@ -1,3 +1,5 @@ +//go:build linux + /* Copyright (c) Facebook, Inc. and its affiliates. diff --git a/cmd/ziffy/node/receiver_notlinux.go b/cmd/ziffy/node/receiver_notlinux.go new file mode 100644 index 00000000..23e38da3 --- /dev/null +++ b/cmd/ziffy/node/receiver_notlinux.go @@ -0,0 +1,89 @@ +//go:build !linux + +/* +Copyright (c) Facebook, Inc. and its affiliates. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package node + +import ( + "errors" + "net" + "time" + + "github.com/facebook/time/ptp/protocol" + "github.com/google/gopacket" +) + +type Receiver struct { + Config *Config + runningHandlers int64 +} + +type Sender struct { + Config *Config + inputQueue []chan *SwitchTrafficInfo +} + +func (r *Receiver) incRunningHandlers() int64 { + r.runningHandlers++ + return r.runningHandlers +} + +func (r *Receiver) decRunningHandlers() int64 { + r.runningHandlers-- + return r.runningHandlers +} + +func (r *Receiver) Start() error { + return errors.New("receiver unsupported on non-linux") +} + +func (s *Sender) Start() ([]*PathInfo, error) { + return nil, errors.New("sender unsupported on non-linux") +} + +func NewReceiver(...any) (*Receiver, error) { + return nil, errors.New("receiver unsupported on non-linux") +} + +func NewSender(...any) (*Sender, error) { + return nil, errors.New("sender unsupported on non-linux") +} + +func (s *Sender) popAllQueue(_ []*PathInfo) { + s.inputQueue = nil +} + +func (r *Receiver) handlePacket(_ gopacket.Packet) { +} + +func parseSyncPacket(_ gopacket.Packet) (*protocol.SyncDelayReq, string, string, error) { + return nil, "", "", errors.New("unsupported on darwin") +} + +func (s *Sender) clearPaths(routes []*PathInfo) []*PathInfo { + return routes +} + +func sortSwitchesByHop(_ []SwitchTrafficInfo) {} + +func formNewDest(_ *Config, _ int) net.IP { + return nil +} + +func rackSwHostnameMonitor(_ string, _ time.Duration) (string, error) { + return "", errors.New("unsupported on darwin") +} diff --git a/cmd/ziffy/node/sender.go b/cmd/ziffy/node/sender_linux.go similarity index 91% rename from cmd/ziffy/node/sender.go rename to cmd/ziffy/node/sender_linux.go index 6c30bbca..170a0266 100644 --- a/cmd/ziffy/node/sender.go +++ b/cmd/ziffy/node/sender_linux.go @@ -1,3 +1,5 @@ +//go:build linux + /* Copyright (c) Facebook, Inc. and its affiliates. @@ -17,6 +19,7 @@ limitations under the License. package node import ( + "encoding/binary" "errors" "fmt" "net" @@ -426,10 +429,35 @@ func rackSwHostnameMonitor(device string, lldpTimeout time.Duration) (string, er } } -func getLookUpName(ip string) string { - addr, err := net.LookupAddr(ip) - if err != nil || len(addr) == 0 { - return ip +func formSignalingPacket(hop int, routeIndex int) *ptp.Signaling { + l := binary.Size(ptp.Header{}) + binary.Size(ptp.PortIdentity{}) + binary.Size(ptp.RequestUnicastTransmissionTLV{}) + return &ptp.Signaling{ + Header: ptp.Header{ + SdoIDAndMsgType: ptp.NewSdoIDAndMsgType(ptp.MessageSignaling, 0), + Version: ptp.Version, + SequenceID: uint16(hop), + MessageLength: uint16(l), + FlagField: ptp.FlagUnicast, + SourcePortIdentity: ptp.PortIdentity{ + PortNumber: uint16(routeIndex), + }, + ControlField: ZiffyHexa, + LogMessageInterval: 0x7f, + }, + TargetPortIdentity: ptp.PortIdentity{ + PortNumber: 0xffff, + ClockIdentity: 0xffffffffffffffff, + }, + TLVs: []ptp.TLV{ + &ptp.RequestUnicastTransmissionTLV{ + TLVHead: ptp.TLVHead{ + TLVType: ptp.TLVRequestUnicastTransmission, + LengthField: uint16(binary.Size(ptp.RequestUnicastTransmissionTLV{}) - binary.Size(ptp.TLVHead{})), + }, + MsgTypeAndReserved: ptp.NewUnicastMsgTypeAndFlags(ptp.MessageSync, 0), + LogInterMessagePeriod: 1, + DurationField: 0, + }, + }, } - return addr[0] } diff --git a/fbclock/daemon/config.go b/fbclock/daemon/config.go index 08760664..fa087415 100644 --- a/fbclock/daemon/config.go +++ b/fbclock/daemon/config.go @@ -21,8 +21,8 @@ import ( "os" "time" + "github.com/facebook/time/phc/unix" log "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" yaml "gopkg.in/yaml.v2" ) diff --git a/fbclock/daemon/daemon.go b/fbclock/daemon/daemon.go index cd2570af..622af1f1 100644 --- a/fbclock/daemon/daemon.go +++ b/fbclock/daemon/daemon.go @@ -366,7 +366,7 @@ func (s *Daemon) doWork(shm *fbclock.Shm, data *DataPoint) error { // use clock_gettime as the fastest and widely available method phcTime, err := s.getPHCTime() if err != nil { - return fmt.Errorf("Failed to get PHC time from %s: %w", s.cfg.Iface, errors.Join(errNoPHC, err)) + return fmt.Errorf("failed to get PHC time from %s: %w", s.cfg.Iface, errors.Join(errNoPHC, err)) } if data.IngressTimeNS > 0 { diff --git a/fbclock/fbclock.go b/fbclock/fbclock.go index 5d8c02cf..0fe9f7f2 100644 --- a/fbclock/fbclock.go +++ b/fbclock/fbclock.go @@ -1,3 +1,5 @@ +//go:build linux + /* Copyright (c) Facebook, Inc. and its affiliates. @@ -38,12 +40,6 @@ func strerror(errCode C.int) string { return C.GoString(cStr) } -// TrueTime is a time interval we are confident the clock is right now -type TrueTime struct { - Earliest time.Time - Latest time.Time -} - // FBClock wraps around fbclock C lib type FBClock struct { cFBClock *C.fbclock_lib diff --git a/fbclock/notlinux.go b/fbclock/notlinux.go new file mode 100644 index 00000000..e14123dd --- /dev/null +++ b/fbclock/notlinux.go @@ -0,0 +1,50 @@ +//go:build !linux + +/* +Copyright (c) Facebook, Inc. and its affiliates. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fbclock + +import ( + "errors" +) + +// FBClock is a stub — the C library requires Linux PTP ioctls. +type FBClock struct{} + +func NewFBClockCustom(_ string) (*FBClock, error) { + return nil, errors.New("fbclock requires Linux PTP support") +} + +func NewFBClock() (*FBClock, error) { + return nil, errors.New("fbclock requires Linux PTP support") +} + +func NewFBClockV2() (*FBClock, error) { + return nil, errors.New("fbclock requires Linux PTP support") +} + +func (c *FBClock) Close() error { + return nil +} + +func (c *FBClock) GetTime() (*TrueTime, error) { + return nil, errors.New("fbclock requires Linux PTP support") +} + +func (c *FBClock) GetTimeUTC() (*TrueTime, error) { + return nil, errors.New("fbclock requires Linux PTP support") +} diff --git a/fbclock/shmem.go b/fbclock/shmem.go index 7de1eea8..d9c5446d 100644 --- a/fbclock/shmem.go +++ b/fbclock/shmem.go @@ -1,3 +1,5 @@ +//go:build linux + /* Copyright (c) Facebook, Inc. and its affiliates. @@ -32,7 +34,6 @@ import "C" import ( "fmt" - "math" "os" "unsafe" @@ -42,13 +43,6 @@ import ( // PTPPath is the path we set for PTP device const PTPPath = C.FBCLOCK_PTPPATH -// Shm is POSIX shared memory -type Shm struct { - Path string - File *os.File - Version int -} - // OpenShm opens POSIX shared memory func OpenShm(path string, flags int, permissions os.FileMode) (*Shm, error) { var err error @@ -65,42 +59,6 @@ func OpenShm(path string, flags int, permissions os.FileMode) (*Shm, error) { return &Shm{File: file, Path: path}, nil } -// Close cleans up open POSIX shm resources -func (s *Shm) Close() error { - if err := s.File.Close(); err != nil { - return err - } - - cPath := C.CString(s.Path) - defer C.free(unsafe.Pointer(cPath)) - return nil -} - -// Data is a Go equivalent of what we want to store in shared memory for fbclock to use -type Data struct { - IngressTimeNS int64 - ErrorBoundNS uint64 - HoldoverMultiplierNS float64 // float stored as multiplier of 2**16 - SmearingStartS uint64 // Smearing starts before the Leap Second Event Time (midnight on June-30 or Dec-31) - SmearingEndS uint64 // Smearing ends after the Leap Second Event Time (midnight on June-30 or Dec-31) - UTCOffsetPreS int32 // UTC Offset before Leap Second Event - UTCOffsetPostS int32 // UTC Offset after Leap Second Event -} - -// DataV2 is a Go equivalent of what we want to store in shared memory for fbclock to use -type DataV2 struct { - IngressTimeNS int64 - ErrorBoundNS uint64 - HoldoverMultiplierNS float64 // float stored as multiplier of 2**16 - SmearingStartS uint64 // Smearing starts before the Leap Second Event Time (midnight on June-30 or Dec-31) - UTCOffsetPreS int16 // UTC Offset before Leap Second Event - UTCOffsetPostS int16 // UTC Offset after Leap Second Event - ClockID uint32 // Clock ID of SysclockTimeNS (MONOTONIC_RAW or REALTIME) - PHCTimeNS int64 // Periodically updated PHC time used to calculate real PHC time - SysclockTimeNS int64 // Periodically updated system clock time (MONOTONIC_RAW or REALTIME) received with PHC time - CoefPPB int64 // Coefficient of the approximation of the PHC time -} - // OpenFBClockShmCustom returns opened POSIX shared mem used by fbclock, // with custom path and version specified func OpenFBClockShmCustom(path string) (*Shm, error) { @@ -140,30 +98,6 @@ func OpenFBClockSHMv2() (*Shm, error) { return OpenFBClockShmCustomVer(C.FBCLOCK_PATH_V2, 2) } -// FloatAsUint32 stores float as multiplier of 2**16. -// Effectively this means we can store max 65k like this. -func FloatAsUint32(val float64) uint32 { - valAsUint := C.FBCLOCK_POW2_16 * val - if valAsUint > math.MaxUint32 { - valAsUint = math.MaxUint32 - } - return uint32(valAsUint) -} - -// Uint32AsFloat restores float that was stored as a multiplier of 2**16. -func Uint32AsFloat(val uint32) float64 { - return float64(val) / C.FBCLOCK_POW2_16 -} - -// Uint64ToUint32 converts uint64 to uint32, handling the overflow. -// If the uint64 value is more than MaxUint32, result is set to MaxUint32. -func Uint64ToUint32(val uint64) uint32 { - if val > math.MaxUint32 { - val = math.MaxUint32 - } - return uint32(val) -} - // StoreFBClockData will store fbclock data in shared mem, // fd param should be open file descriptor of that shared mem. func StoreFBClockData(fd uintptr, d Data) error { diff --git a/fbclock/shmem_notlinux.go b/fbclock/shmem_notlinux.go new file mode 100644 index 00000000..d6e14b53 --- /dev/null +++ b/fbclock/shmem_notlinux.go @@ -0,0 +1,227 @@ +//go:build !linux + +/* +Copyright (c) Facebook, Inc. and its affiliates. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fbclock + +import ( + "encoding/binary" + "fmt" + "os" + "sync/atomic" + "syscall" + "time" + "unsafe" + + "golang.org/x/sys/unix" +) + +const PTPPath = "/dev/null" + +const ( + shmDataSize = 48 // sizeof(fbclock_shmdata): 8 (atomic_uint64) + 40 (fbclock_clockdata) + shmDataV2Size = 64 // sizeof(fbclock_shmdata_v2): 8 (atomic_uint64) + 56 (fbclock_clockdata_v2) + shmPath = "/run/fbclock_data_v1" + shmPathV2 = "/run/fbclock_data_v2" + maxReadTries = 1000 +) + +// OpenShm opens a shared memory file +func OpenShm(path string, flags int, permissions os.FileMode) (*Shm, error) { + oldUmask := syscall.Umask(0) + defer syscall.Umask(oldUmask) + file, err := os.OpenFile(path, flags, permissions) + if err != nil { + return nil, err + } + return &Shm{File: file, Path: path}, nil +} + +// OpenFBClockShmCustom returns opened POSIX shared mem used by fbclock +func OpenFBClockShmCustom(path string) (*Shm, error) { + return OpenFBClockShmCustomVer(path, 1) +} + +// OpenFBClockShmCustomVer returns opened POSIX shared mem used by fbclock +func OpenFBClockShmCustomVer(path string, version int) (*Shm, error) { + shm, err := OpenShm(path, os.O_CREATE|os.O_RDWR, 0o644) + if err != nil { + return nil, err + } + size := int64(shmDataSize) + if version == 2 { + size = int64(shmDataV2Size) + } + if err := shm.File.Truncate(size); err != nil { + shm.Close() + return nil, err + } + shm.Version = version + return shm, nil +} + +// OpenFBClockSHM returns opened POSIX shared mem used by fbclock +func OpenFBClockSHM() (*Shm, error) { + return OpenFBClockShmCustom(shmPath) +} + +// OpenFBClockSHMv2 returns opened POSIX shared mem used by fbclock +func OpenFBClockSHMv2() (*Shm, error) { + return OpenFBClockShmCustomVer(shmPathV2, 2) +} + +// MmapShmpData mmaps open file as fbclock shared memory +func MmapShmpData(fd uintptr) (unsafe.Pointer, error) { + data, err := unix.Mmap(int(fd), 0, shmDataSize, unix.PROT_READ, unix.MAP_SHARED) + if err != nil { + return nil, err + } + return unsafe.Pointer(&data[0]), nil +} + +// MmapShmpDataV2 mmaps open file as fbclock v2 shared memory +func MmapShmpDataV2(fd uintptr) (unsafe.Pointer, error) { + data, err := unix.Mmap(int(fd), 0, shmDataV2Size, unix.PROT_READ, unix.MAP_SHARED) + if err != nil { + return nil, err + } + return unsafe.Pointer(&data[0]), nil +} + +func clockDataCRC(d *[40]byte) uint64 { + ingress := binary.LittleEndian.Uint64(d[0:8]) + errorBound := uint64(binary.LittleEndian.Uint32(d[8:12])) + holdover := uint64(binary.LittleEndian.Uint32(d[12:16])) + counter := uint64(0xFFFFFFFF) ^ ingress + counter ^= errorBound + counter ^= holdover + return counter ^ 0xFFFFFFFF +} + +// StoreFBClockData writes fbclock data to shared memory via mmap +func StoreFBClockData(fd uintptr, d Data) error { + data, err := unix.Mmap(int(fd), 0, shmDataSize, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED) + if err != nil { + return fmt.Errorf("mmap failed: %w", err) + } + defer func() { _ = unix.Munmap(data) }() + + buf := (*[40]byte)(unsafe.Pointer(&data[8])) + binary.LittleEndian.PutUint64(buf[0:8], uint64(d.IngressTimeNS)) + binary.LittleEndian.PutUint32(buf[8:12], Uint64ToUint32(d.ErrorBoundNS)) + binary.LittleEndian.PutUint32(buf[12:16], FloatAsUint32(d.HoldoverMultiplierNS)) + binary.LittleEndian.PutUint64(buf[16:24], d.SmearingStartS) + binary.LittleEndian.PutUint64(buf[24:32], d.SmearingEndS) + binary.LittleEndian.PutUint32(buf[32:36], uint32(d.UTCOffsetPreS)) + binary.LittleEndian.PutUint32(buf[36:40], uint32(d.UTCOffsetPostS)) + + crc := clockDataCRC(buf) + atomic.StoreUint64((*uint64)(unsafe.Pointer(&data[0])), crc) + return nil +} + +// ReadFBClockData reads Data from mmaped fbclock shared memory +func ReadFBClockData(shmp unsafe.Pointer) (*Data, error) { + for range maxReadTries { + base := (*[shmDataSize]byte)(shmp) + var buf [40]byte + copy(buf[:], base[8:48]) + crc := atomic.LoadUint64((*uint64)(unsafe.Pointer(&base[0]))) + if clockDataCRC(&buf) == crc { + return &Data{ + IngressTimeNS: int64(binary.LittleEndian.Uint64(buf[0:8])), + ErrorBoundNS: uint64(binary.LittleEndian.Uint32(buf[8:12])), + HoldoverMultiplierNS: Uint32AsFloat(binary.LittleEndian.Uint32(buf[12:16])), + }, nil + } + } + return nil, fmt.Errorf("CRC check failed after %d tries", maxReadTries) +} + +// StoreFBClockDataV2 writes fbclock v2 data to shared memory via mmap using seqlock +func StoreFBClockDataV2(fd uintptr, d DataV2) error { + data, err := unix.Mmap(int(fd), 0, shmDataV2Size, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED) + if err != nil { + return fmt.Errorf("mmap failed: %w", err) + } + defer func() { _ = unix.Munmap(data) }() + + seqPtr := (*uint64)(unsafe.Pointer(&data[0])) + + for range maxReadTries { + seq := atomic.LoadUint64(seqPtr) + if seq&1 != 0 { + time.Sleep(time.Microsecond) + continue + } + seq = (seq &^ 1) + 1 + atomic.StoreUint64(seqPtr, seq) + seq++ + + buf := data[8:64] + binary.LittleEndian.PutUint64(buf[0:8], uint64(d.IngressTimeNS)) + binary.LittleEndian.PutUint32(buf[8:12], Uint64ToUint32(d.ErrorBoundNS)) + binary.LittleEndian.PutUint32(buf[12:16], FloatAsUint32(d.HoldoverMultiplierNS)) + binary.LittleEndian.PutUint64(buf[16:24], d.SmearingStartS) + binary.LittleEndian.PutUint16(buf[24:26], uint16(d.UTCOffsetPreS)) + binary.LittleEndian.PutUint16(buf[26:28], uint16(d.UTCOffsetPostS)) + binary.LittleEndian.PutUint32(buf[28:32], d.ClockID) + binary.LittleEndian.PutUint64(buf[32:40], uint64(d.PHCTimeNS)) + binary.LittleEndian.PutUint64(buf[40:48], uint64(d.SysclockTimeNS)) + binary.LittleEndian.PutUint64(buf[48:56], uint64(d.CoefPPB)) + + if seq == 0 { + seq = 2 + } + atomic.StoreUint64(seqPtr, seq) + return nil + } + return fmt.Errorf("seqlock contention after %d tries", maxReadTries) +} + +// ReadFBClockDataV2 reads DataV2 from mmaped fbclock shared memory using seqlock +func ReadFBClockDataV2(shmp unsafe.Pointer) (*DataV2, error) { + base := (*[shmDataV2Size]byte)(shmp) + seqPtr := (*uint64)(unsafe.Pointer(&base[0])) + + for range maxReadTries { + seq := atomic.LoadUint64(seqPtr) + if seq == 0 { + time.Sleep(10 * time.Microsecond) + continue + } + if seq&1 != 0 { + continue + } + var buf [56]byte + copy(buf[:], base[8:64]) + if seq == atomic.LoadUint64(seqPtr) { + return &DataV2{ + IngressTimeNS: int64(binary.LittleEndian.Uint64(buf[0:8])), + ErrorBoundNS: uint64(binary.LittleEndian.Uint32(buf[8:12])), + HoldoverMultiplierNS: Uint32AsFloat(binary.LittleEndian.Uint32(buf[12:16])), + UTCOffsetPreS: int16(binary.LittleEndian.Uint16(buf[24:26])), + UTCOffsetPostS: int16(binary.LittleEndian.Uint16(buf[26:28])), + ClockID: binary.LittleEndian.Uint32(buf[28:32]), + PHCTimeNS: int64(binary.LittleEndian.Uint64(buf[32:40])), + SysclockTimeNS: int64(binary.LittleEndian.Uint64(buf[40:48])), + CoefPPB: int64(binary.LittleEndian.Uint64(buf[48:56])), + }, nil + } + } + return nil, fmt.Errorf("seqlock read failed after %d tries", maxReadTries) +} diff --git a/fbclock/stats.go b/fbclock/stats.go index f5f36c4a..06f2cdca 100644 --- a/fbclock/stats.go +++ b/fbclock/stats.go @@ -16,31 +16,10 @@ limitations under the License. package fbclock -import "C" - import ( "time" ) -// Stats aggregate stats for fbclock GetTime results -type Stats struct { - Requests int64 - Errors int64 - WOUAvg int64 - WOUMax int64 - WOUlt10us int64 - WOUlt100us int64 - WOUlt1000us int64 - WOUge1000us int64 -} - -// StatsCollector collects stats based on GetTime results -type StatsCollector struct { - stats Stats - // internal stuff - wouSum int64 -} - // Update processes result of GetTime call and updates Stats func (s *StatsCollector) Update(tt *TrueTime, err error) { s.stats.Requests++ @@ -64,8 +43,3 @@ func (s *StatsCollector) Update(tt *TrueTime, err error) { s.stats.WOUMax = int64(wou) } } - -// Stats returns collected stats -func (s *StatsCollector) Stats() Stats { - return s.stats -} diff --git a/fbclock/types.go b/fbclock/types.go new file mode 100644 index 00000000..01ca1fcc --- /dev/null +++ b/fbclock/types.go @@ -0,0 +1,116 @@ +/* +Copyright (c) Facebook, Inc. and its affiliates. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fbclock + +import ( + "math" + "os" + "time" +) + +// TrueTime is a time interval we are confident the clock is right now +type TrueTime struct { + Earliest time.Time + Latest time.Time +} + +// Data is a Go equivalent of what we want to store in shared memory for fbclock to use +type Data struct { + IngressTimeNS int64 + ErrorBoundNS uint64 + HoldoverMultiplierNS float64 + SmearingStartS uint64 + SmearingEndS uint64 + UTCOffsetPreS int32 + UTCOffsetPostS int32 +} + +// DataV2 is a Go equivalent of what we want to store in shared memory for fbclock to use +type DataV2 struct { + IngressTimeNS int64 + ErrorBoundNS uint64 + HoldoverMultiplierNS float64 + SmearingStartS uint64 + UTCOffsetPreS int16 + UTCOffsetPostS int16 + ClockID uint32 + PHCTimeNS int64 + SysclockTimeNS int64 + CoefPPB int64 +} + +// Shm is POSIX shared memory +type Shm struct { + Path string + File *os.File + Version int +} + +// Stats aggregate stats for fbclock GetTime results +type Stats struct { + Requests int64 + Errors int64 + WOUAvg int64 + WOUMax int64 + WOUlt10us int64 + WOUlt100us int64 + WOUlt1000us int64 + WOUge1000us int64 +} + +// StatsCollector collects stats based on GetTime results +type StatsCollector struct { + stats Stats + wouSum int64 +} + +// Stats returns collected stats +func (s *StatsCollector) Stats() Stats { + return s.stats +} + +// Close cleans up Shm resources +func (s *Shm) Close() error { + if s.File != nil { + return s.File.Close() + } + return nil +} + +const pow2_16 = float64(1 << 16) //nolint:revive + +// FloatAsUint32 stores float as multiplier of 2**16 +func FloatAsUint32(val float64) uint32 { + valAsUint := pow2_16 * val + if valAsUint > math.MaxUint32 { + valAsUint = math.MaxUint32 + } + return uint32(valAsUint) +} + +// Uint32AsFloat restores float that was stored as a multiplier of 2**16 +func Uint32AsFloat(val uint32) float64 { + return float64(val) / pow2_16 +} + +// Uint64ToUint32 converts uint64 to uint32, capping at MaxUint32 +func Uint64ToUint32(val uint64) uint32 { + if val > math.MaxUint32 { + val = math.MaxUint32 + } + return uint32(val) +} diff --git a/go.mod b/go.mod index 5a3064dc..9e89617f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/facebook/time -go 1.24.0 +go 1.25.0 require ( github.com/Knetic/govaluate v3.0.0+incompatible @@ -25,7 +25,6 @@ require ( golang.org/x/net v0.38.0 golang.org/x/sync v0.5.0 golang.org/x/sys v0.31.0 - golang.org/x/term v0.30.0 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 1dcbd699..8e61c9d1 100644 --- a/go.sum +++ b/go.sum @@ -439,8 +439,6 @@ golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/ntp/chrony/packet_test.go b/ntp/chrony/packet_test.go index f57d4b1e..d20d4341 100644 --- a/ntp/chrony/packet_test.go +++ b/ntp/chrony/packet_test.go @@ -162,21 +162,21 @@ func TestDecodeSourceDataWithIPAddrID(t *testing.T) { // Verify the ID value is correctly stored in the IP field require.Equal(t, uint8(0x00), reply.IPAddr.IP[0]) require.Equal(t, uint8(0x00), reply.IPAddr.IP[1]) - require.Equal(t, uint8(0x00), reply.SourceData.IPAddr.IP[2]) - require.Equal(t, uint8(0x09), reply.SourceData.IPAddr.IP[3]) + require.Equal(t, uint8(0x00), reply.IPAddr.IP[2]) + require.Equal(t, uint8(0x09), reply.IPAddr.IP[3]) // Verify ToNetIP returns nil for unresolved addresses - require.Nil(t, reply.SourceData.IPAddr.ToNetIP()) + require.Nil(t, reply.IPAddr.ToNetIP()) // Verify String() returns the correct ID format - require.Equal(t, "ID#0000000009", reply.SourceData.IPAddr.String()) + require.Equal(t, "ID#0000000009", reply.IPAddr.String()) // Verify other fields are correctly parsed - require.Equal(t, int16(10), reply.SourceData.Poll) - require.Equal(t, uint16(2), reply.SourceData.Stratum) - require.Equal(t, SourceStateType(4), reply.SourceData.State) - require.Equal(t, uint16(255), reply.SourceData.Reachability) - require.Equal(t, uint32(1705), reply.SourceData.SinceSample) + require.Equal(t, int16(10), reply.Poll) + require.Equal(t, uint16(2), reply.Stratum) + require.Equal(t, SourceStateType(4), reply.State) + require.Equal(t, uint16(255), reply.Reachability) + require.Equal(t, uint32(1705), reply.SinceSample) } func TestDecodeSourceStats(t *testing.T) { diff --git a/ntp/control/packet.go b/ntp/control/packet.go index 035e28af..3134d62a 100644 --- a/ntp/control/packet.go +++ b/ntp/control/packet.go @@ -51,7 +51,7 @@ func NormalizeData(data []byte) (map[string]string, error) { result[k] = v } if len(result) == 0 { - return result, fmt.Errorf("Malformed packet, no k=v pairs decoded") + return result, fmt.Errorf("malformed packet, no k=v pairs decoded") } return result, nil } diff --git a/ntp/responder/server/server_darwin.go b/ntp/responder/server/server_darwin.go index 933ddcf9..85ce6390 100644 --- a/ntp/responder/server/server_darwin.go +++ b/ntp/responder/server/server_darwin.go @@ -77,6 +77,6 @@ func deleteIfaceIP(iface *net.Interface, addr *net.IP) error { // PHCOffset periodically checks for PHC-SYS offset and updates it in the config // PHC reading is not supported on Darwin -func phcOffset(iface string) (time.Duration, error) { +func phcOffset(_ string) (time.Duration, error) { return 0, nil } diff --git a/ntp/shm/ntpshm.go b/ntp/shm/ntpshm.go index 43c5e3e8..141a810b 100644 --- a/ntp/shm/ntpshm.go +++ b/ntp/shm/ntpshm.go @@ -58,7 +58,7 @@ type NTPSHM struct { // Create a segment in SHM and return the ID func Create() (uintptr, error) { - shmID, _, errno := unix.Syscall(unix.SYS_SHMGET, uintptr(SHMKEY), 0, uintptr(IPCCREAT|0600)) + shmID, _, errno := unix.Syscall(unix.SYS_SHMGET, uintptr(SHMKEY), 0, uintptr(IPCCREAT|0600)) //nolint:staticcheck if errno != 0 { return 0, fmt.Errorf("failed get shm: %s", unix.ErrnoName(errno)) } @@ -86,7 +86,7 @@ func ptrToNTPSHM(shmptr uintptr) (*NTPSHM, error) { // ReadID reads SHM segment by ID func ReadID(id uintptr) (*NTPSHM, error) { - shmptr, _, errno := unix.Syscall(unix.SYS_SHMAT, id, 0, 0) + shmptr, _, errno := unix.Syscall(unix.SYS_SHMAT, id, 0, 0) //nolint:staticcheck if errno != 0 { return nil, fmt.Errorf("failed to attach to shm: %s", unix.ErrnoName(errno)) } @@ -96,7 +96,7 @@ func ReadID(id uintptr) (*NTPSHM, error) { // Read SHM segment func Read() (*NTPSHM, error) { - shmID, _, errno := unix.Syscall(unix.SYS_SHMGET, uintptr(SHMKEY), 0, uintptr(0400)) + shmID, _, errno := unix.Syscall(unix.SYS_SHMGET, uintptr(SHMKEY), 0, uintptr(0400)) //nolint:staticcheck if errno != 0 { return nil, fmt.Errorf("failed get shm: %s", unix.ErrnoName(errno)) } diff --git a/phc/pps_source_test.go b/phc/pps_source_test.go index 4691ac02..e5937e2e 100644 --- a/phc/pps_source_test.go +++ b/phc/pps_source_test.go @@ -405,8 +405,8 @@ func TestNewPiServoUseMaxFreq(t *testing.T) { servo, err := NewPiServo(time.Duration(1), time.Duration(1), time.Duration(0), mockFrequencyGetter, 2.0) require.NoError(t, err) - require.Equal(t, int64(1), servo.Servo.FirstStepThreshold) - require.Equal(t, true, servo.Servo.FirstUpdate) + require.Equal(t, int64(1), servo.FirstStepThreshold) + require.Equal(t, true, servo.FirstUpdate) require.Equal(t, -1.0, servo.MeanFreq()) require.Equal(t, "INIT", servo.GetState().String()) require.Equal(t, 2.0, servo.GetMaxFreq()) @@ -423,12 +423,12 @@ func TestNewPiServoStepth(t *testing.T) { servo, err := NewPiServo(time.Duration(1), time.Duration(1), time.Duration(10), mockFrequencyGetter, 2.0) require.NoError(t, err) - require.Equal(t, int64(1), servo.Servo.FirstStepThreshold) - require.Equal(t, true, servo.Servo.FirstUpdate) + require.Equal(t, int64(1), servo.FirstStepThreshold) + require.Equal(t, true, servo.FirstUpdate) require.Equal(t, -1.0, servo.MeanFreq()) require.Equal(t, "INIT", servo.GetState().String()) require.Equal(t, 2.0, servo.GetMaxFreq()) - require.Equal(t, int64(10), servo.Servo.StepThreshold) + require.Equal(t, int64(10), servo.StepThreshold) } func TestNewPiServoNoFirstStep(t *testing.T) { @@ -442,7 +442,7 @@ func TestNewPiServoNoFirstStep(t *testing.T) { servo, err := NewPiServo(time.Duration(1), time.Duration(0), time.Duration(0), mockFrequencyGetter, 2.0) require.NoError(t, err) - require.Equal(t, false, servo.Servo.FirstUpdate) + require.Equal(t, false, servo.FirstUpdate) require.Equal(t, -1.0, servo.MeanFreq()) require.Equal(t, "INIT", servo.GetState().String()) require.Equal(t, 2.0, servo.GetMaxFreq()) diff --git a/phc/unix/linux.go b/phc/unix/linux.go index 4120db2b..07a6639e 100644 --- a/phc/unix/linux.go +++ b/phc/unix/linux.go @@ -21,38 +21,44 @@ package unix import ( "syscall" - "time" "unsafe" "golang.org/x/sys/unix" ) -// https://go-review.googlesource.com/c/sys/+/620376 +// Linux-only type aliases +type RawSockaddrInet4 = unix.RawSockaddrInet4 +type Timex = unix.Timex -// HwTstampConfig is used in SIOCGHWTSTAMP and SIOCSHWTSTAMP ioctls -type HwTstampConfig struct { - Flags int32 //nolint:revive - Tx_type int32 //nolint:revive - Rx_filter int32 //nolint:revive +// Linux-only function wrappers +func ClockAdjtime(c int32, t *Timex) (int, error) { return unix.ClockAdjtime(c, t) } +func Adjtimex(buf *Timex) (state int, err error) { return unix.Adjtimex(buf) } + +// ClockSettime calls the CLOCK_SETTIME syscall +func ClockSettime(clockid int32, time *Timespec) (err error) { + _, _, e1 := Syscall(SYS_CLOCK_SETTIME, uintptr(clockid), uintptr(unsafe.Pointer(time)), 0) + if e1 != 0 { + err = errnoErr(e1) + } + return } +// FdToClockID derives the clock ID from the file descriptor number +func FdToClockID(fd int) int32 { return int32(((^fd) << 3) | 3) } + // IoctlGetHwTstamp retrieves the hardware timestamping configuration -// for the network device specified by ifname. func IoctlGetHwTstamp(fd int, ifname string) (*HwTstampConfig, error) { ifr, err := NewIfreq(ifname) if err != nil { return nil, err } - value := HwTstampConfig{} ifrd := ifr.withData(unsafe.Pointer(&value)) - err = ioctlIfreqData(fd, SIOCGHWTSTAMP, &ifrd) return &value, err } -// IoctlSetHwTstamp updates the hardware timestamping configuration for -// the network device specified by ifname. +// IoctlSetHwTstamp updates the hardware timestamping configuration func IoctlSetHwTstamp(fd int, ifname string, cfg *HwTstampConfig) error { ifr, err := NewIfreq(ifname) if err != nil { @@ -62,156 +68,18 @@ func IoctlSetHwTstamp(fd int, ifname string, cfg *HwTstampConfig) error { return ioctlIfreqData(fd, SIOCSHWTSTAMP, &ifrd) } -const ( - HWTSTAMP_FILTER_NONE = 0x0 //nolint:revive - HWTSTAMP_FILTER_ALL = 0x1 //nolint:revive - HWTSTAMP_FILTER_SOME = 0x2 //nolint:revive - HWTSTAMP_FILTER_PTP_V1_L4_EVENT = 0x3 //nolint:revive - HWTSTAMP_FILTER_PTP_V2_L4_EVENT = 0x6 //nolint:revive - HWTSTAMP_FILTER_PTP_V2_L2_EVENT = 0x9 //nolint:revive - HWTSTAMP_FILTER_PTP_V2_EVENT = 0xc //nolint:revive -) - -const ( - HWTSTAMP_TX_OFF = 0x0 //nolint:revive - HWTSTAMP_TX_ON = 0x1 //nolint:revive - HWTSTAMP_TX_ONESTEP_SYNC = 0x2 //nolint:revive -) - -// https://go-review.googlesource.com/c/sys/+/619335 - -// EthtoolTsInfo a struct returned by ETHTOOL_GET_TS_INFO function of -// SIOCETHTOOL ioctl. -type EthtoolTsInfo struct { - Cmd uint32 - So_timestamping uint32 //nolint:revive - Phc_index int32 //nolint:revive - Tx_types uint32 //nolint:revive - Tx_reserved [3]uint32 //nolint:revive - Rx_filters uint32 //nolint:revive - Rx_reserved [3]uint32 //nolint:revive -} - -// IoctlGetEthtoolTsInfo fetches ethtool timestamping and PHC -// association for the network device specified by ifname. +// IoctlGetEthtoolTsInfo fetches ethtool timestamping and PHC association func IoctlGetEthtoolTsInfo(fd int, ifname string) (*EthtoolTsInfo, error) { ifr, err := NewIfreq(ifname) if err != nil { return nil, err } - value := EthtoolTsInfo{Cmd: ETHTOOL_GET_TS_INFO} ifrd := ifr.withData(unsafe.Pointer(&value)) - err = ioctlIfreqData(fd, SIOCETHTOOL, &ifrd) return &value, err } -// https://go-review.googlesource.com/c/sys/+/619255 - -// ClockSettime calls the CLOCK_SETTIME syscall -func ClockSettime(clockid int32, time *Timespec) (err error) { - _, _, e1 := Syscall(SYS_CLOCK_SETTIME, uintptr(clockid), uintptr(unsafe.Pointer(time)), 0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// https://go-review.googlesource.com/c/sys/+/621375 - -type ( - PtpClockCaps struct { - Max_adj int32 //nolint:revive - N_alarm int32 //nolint:revive - N_ext_ts int32 //nolint:revive - N_per_out int32 //nolint:revive - Pps int32 - N_pins int32 //nolint:revive - Cross_timestamping int32 //nolint:revive - Adjust_phase int32 //nolint:revive - Max_phase_adj int32 //nolint:revive - Rsv [11]int32 - } - PtpClockTime struct { - Sec int64 - Nsec uint32 - Reserved uint32 - } - PtpExttsEvent struct { - T PtpClockTime - Index uint32 - Flags uint32 - Rsv [2]uint32 - } - PtpExttsRequest struct { - Index uint32 - Flags uint32 - Rsv [2]uint32 - } - PtpPeroutRequest struct { - StartOrPhase PtpClockTime - Period PtpClockTime - Index uint32 - Flags uint32 - On PtpClockTime - } - PtpPinDesc struct { - Name [64]byte - Index uint32 - Func uint32 - Chan uint32 - Rsv [5]uint32 - } - PtpSysOffset struct { - Samples uint32 - Rsv [3]uint32 - Ts [51]PtpClockTime - } - PtpSysOffsetExtended struct { - Samples uint32 - ClockID uint32 - Rsv [2]uint32 - Ts [25][3]PtpClockTime - } - PtpSysOffsetPrecise struct { - Device PtpClockTime - Realtime PtpClockTime - Monoraw PtpClockTime - Rsv [4]uint32 - } -) - -const ( - PTP_CLK_MAGIC = '=' //nolint:revive - PTP_ENABLE_FEATURE = 0x1 //nolint:revive - PTP_EXTTS_EDGES = 0x6 //nolint:revive - PTP_EXTTS_EVENT_VALID = 0x1 //nolint:revive - PTP_EXTTS_V1_VALID_FLAGS = 0x7 //nolint:revive - PTP_EXTTS_VALID_FLAGS = 0x1f //nolint:revive - PTP_EXT_OFFSET = 0x10 //nolint:revive - PTP_FALLING_EDGE = 0x4 //nolint:revive - PTP_MAX_SAMPLES = 0x19 //nolint:revive - PTP_PEROUT_DUTY_CYCLE = 0x2 //nolint:revive - PTP_PEROUT_ONE_SHOT = 0x1 //nolint:revive - PTP_PEROUT_PHASE = 0x4 //nolint:revive - PTP_PEROUT_V1_VALID_FLAGS = 0x0 //nolint:revive - PTP_PEROUT_VALID_FLAGS = 0x7 //nolint:revive - PTP_PIN_GETFUNC = 0xc0603d06 //nolint:revive - PTP_PIN_GETFUNC2 = 0xc0603d0f //nolint:revive - PTP_RISING_EDGE = 0x2 //nolint:revive - PTP_STRICT_FLAGS = 0x8 //nolint:revive - PTP_SYS_OFFSET_EXTENDED = 0xc4c03d09 //nolint:revive - PTP_SYS_OFFSET_EXTENDED2 = 0xc4c03d12 //nolint:revive - PTP_SYS_OFFSET_PRECISE = 0xc0403d08 //nolint:revive - PTP_SYS_OFFSET_PRECISE2 = 0xc0403d11 //nolint:revive -) - -// FdToClockID derives the clock ID from the file descriptor number -// - see clock_gettime(3), FD_TO_CLOCKID macros. The resulting ID is -// suitable for system calls like ClockGettime. -func FdToClockID(fd int) int32 { return int32(((^fd) << 3) | 3) } - // IoctlPtpClockGetcaps returns the description of a given PTP device. func IoctlPtpClockGetcaps(fd int) (*PtpClockCaps, error) { var value PtpClockCaps @@ -219,112 +87,98 @@ func IoctlPtpClockGetcaps(fd int) (*PtpClockCaps, error) { return &value, err } -// IoctlPtpSysOffsetPrecise returns a description of the clock -// offset compared to the system clock. +// IoctlPtpSysOffsetPrecise returns a description of the clock offset compared to the system clock. func IoctlPtpSysOffsetPrecise(fd int) (*PtpSysOffsetPrecise, error) { var value PtpSysOffsetPrecise err := ioctlPtr(fd, PTP_SYS_OFFSET_PRECISE2, unsafe.Pointer(&value)) return &value, err } -// IoctlPtpSysOffsetExtended returns an extended description of the -// clock offset compared to the system clock. The samples parameter -// specifies the desired number of measurements. +// IoctlPtpSysOffsetExtended returns an extended description of the clock offset. func IoctlPtpSysOffsetExtended(fd int, samples uint) (*PtpSysOffsetExtended, error) { return IoctlPtpSysOffsetExtendedClock(fd, unix.CLOCK_REALTIME, samples) } -// IoctlPtpSysOffsetExtendedClock returns an extended description of the -// clock offset compared to the system clock. The samples parameter -// specifies the desired number of measurements. +// IoctlPtpSysOffsetExtendedClock returns an extended description of the clock offset. func IoctlPtpSysOffsetExtendedClock(fd int, clockid uint32, samples uint) (*PtpSysOffsetExtended, error) { value := PtpSysOffsetExtended{Samples: uint32(samples), ClockID: clockid, Rsv: [2]uint32{0, 0}} err := ioctlPtr(fd, PTP_SYS_OFFSET_EXTENDED2, unsafe.Pointer(&value)) return &value, err } -// IoctlPtpPinGetfunc returns the configuration of the specified -// I/O pin on given PTP device. +// IoctlPtpPinGetfunc returns the configuration of the specified I/O pin on given PTP device. func IoctlPtpPinGetfunc(fd int, index uint) (*PtpPinDesc, error) { value := PtpPinDesc{Index: uint32(index)} err := ioctlPtr(fd, PTP_PIN_GETFUNC2, unsafe.Pointer(&value)) return &value, err } -// IoctlPtpPinSetfunc updates configuration of the specified PTP -// I/O pin. +// IoctlPtpPinSetfunc updates configuration of the specified PTP I/O pin. func IoctlPtpPinSetfunc(fd int, pd *PtpPinDesc) error { return ioctlPtr(fd, PTP_PIN_SETFUNC2, unsafe.Pointer(pd)) } -// IoctlPtpPeroutRequest configures the periodic output mode of the -// PTP I/O pins. +// IoctlPtpPeroutRequest configures the periodic output mode of the PTP I/O pins. func IoctlPtpPeroutRequest(fd int, r *PtpPeroutRequest) error { return ioctlPtr(fd, PTP_PEROUT_REQUEST2, unsafe.Pointer(r)) } -// IoctlPtpExttsRequest configures the external timestamping mode -// of the PTP I/O pins. +// IoctlPtpExttsRequest configures the external timestamping mode of the PTP I/O pins. func IoctlPtpExttsRequest(fd int, r *PtpExttsRequest) error { return ioctlPtr(fd, PTP_EXTTS_REQUEST2, unsafe.Pointer(r)) } -// https://go-review.googlesource.com/c/sys/+/621735 +// Linux-only constants +//nolint:revive const ( - PTP_PF_NONE = iota //nolint:revive - PTP_PF_EXTTS //nolint:revive - PTP_PF_PEROUT //nolint:revive - PTP_PF_PHYSYNC //nolint:revive + HWTSTAMP_FILTER_NONE = 0x0 + HWTSTAMP_FILTER_ALL = 0x1 + HWTSTAMP_FILTER_SOME = 0x2 + HWTSTAMP_FILTER_PTP_V1_L4_EVENT = 0x3 + HWTSTAMP_FILTER_PTP_V2_L4_EVENT = 0x6 + HWTSTAMP_FILTER_PTP_V2_L2_EVENT = 0x9 + HWTSTAMP_FILTER_PTP_V2_EVENT = 0xc ) -// bridging to upstream - -type Cmsghdr = unix.Cmsghdr -type Errno = unix.Errno -type Msghdr = unix.Msghdr -type PollFd = unix.PollFd -type RawSockaddrInet4 = unix.RawSockaddrInet4 -type SockaddrInet4 = unix.SockaddrInet4 -type SockaddrInet6 = unix.SockaddrInet6 -type Sockaddr = unix.Sockaddr -type Timespec = unix.Timespec -type Timex = unix.Timex -type Utsname = unix.Utsname +//nolint:revive +const ( + HWTSTAMP_TX_OFF = 0x0 + HWTSTAMP_TX_ON = 0x1 + HWTSTAMP_TX_ONESTEP_SYNC = 0x2 +) -func ByteSliceToString(b []byte) string { return unix.ByteSliceToString(b) } -func ClockAdjtime(c int32, t *Timex) (int, error) { return unix.ClockAdjtime(c, t) } -func ClockGettime(c int32, t *Timespec) error { return unix.ClockGettime(c, t) } -func Close(fd int) (err error) { return unix.Close(fd) } -func ErrnoName(e syscall.Errno) string { return unix.ErrnoName(e) } -func Poll(f []PollFd, t int) (int, error) { return unix.Poll(f, t) } -func Recvmsg(a int, b, c []byte, d int) (int, int, int, Sockaddr, error) { - return unix.Recvmsg(a, b, c, d) -} -func SetsockoptInt(a, b, c, d int) error { return unix.SetsockoptInt(a, b, c, d) } -func Socket(domain, typ, proto int) (fd int, err error) { return unix.Socket(domain, typ, proto) } -func Syscall(a, b, c, d uintptr) (uintptr, uintptr, Errno) { return unix.Syscall(a, b, c, d) } -func TimeToTimespec(t time.Time) (Timespec, error) { return unix.TimeToTimespec(t) } -func Uname(s *Utsname) error { return unix.Uname(s) } +//nolint:revive +const ( + PTP_CLK_MAGIC = '=' + PTP_EXTTS_EDGES = 0x6 + PTP_EXTTS_EVENT_VALID = 0x1 + PTP_EXTTS_V1_VALID_FLAGS = 0x7 + PTP_EXTTS_VALID_FLAGS = 0x1f + PTP_EXT_OFFSET = 0x10 + PTP_FALLING_EDGE = 0x4 + PTP_PEROUT_DUTY_CYCLE = 0x2 + PTP_PEROUT_ONE_SHOT = 0x1 + PTP_PEROUT_PHASE = 0x4 + PTP_PEROUT_V1_VALID_FLAGS = 0x0 + PTP_PEROUT_VALID_FLAGS = 0x7 + PTP_PIN_GETFUNC = 0xc0603d06 + PTP_PIN_GETFUNC2 = 0xc0603d0f + PTP_STRICT_FLAGS = 0x8 + PTP_SYS_OFFSET_EXTENDED = 0xc4c03d09 + PTP_SYS_OFFSET_EXTENDED2 = 0xc4c03d12 + PTP_SYS_OFFSET_PRECISE = 0xc0403d08 + PTP_SYS_OFFSET_PRECISE2 = 0xc0403d11 +) const ( - AF_INET = unix.AF_INET //nolint:revive - EAGAIN = unix.EAGAIN //nolint:revive - EINVAL = unix.EINVAL //nolint:revive - ENOENT = unix.ENOENT //nolint:revive - ENOTSUP = unix.ENOTSUP //nolint:revive - ETHTOOL_GET_TS_INFO = unix.ETHTOOL_GET_TS_INFO //nolint:revive - IFNAMSIZ = unix.IFNAMSIZ //nolint:revive - MSG_ERRQUEUE = unix.MSG_ERRQUEUE //nolint:revive - POLLERR = unix.POLLERR //nolint:revive - POLLIN = unix.POLLIN - POLLPRI = unix.POLLPRI - SIOCETHTOOL = unix.SIOCETHTOOL //nolint:revive - SIOCGHWTSTAMP = unix.SIOCGHWTSTAMP //nolint:revive - SIOCSHWTSTAMP = unix.SIOCSHWTSTAMP //nolint:revive - SizeofPtr = unix.SizeofPtr - SizeofSockaddrInet4 = unix.SizeofSockaddrInet4 - SOCK_DGRAM = unix.SOCK_DGRAM //nolint:revive + SYS_IOCTL = unix.SYS_IOCTL //nolint:revive + SYS_RECVMSG = unix.SYS_RECVMSG //nolint:revive + ETHTOOL_GET_TS_INFO = unix.ETHTOOL_GET_TS_INFO //nolint:revive + MSG_ERRQUEUE = unix.MSG_ERRQUEUE //nolint:revive + SIOCETHTOOL = unix.SIOCETHTOOL //nolint:revive + SIOCGHWTSTAMP = unix.SIOCGHWTSTAMP //nolint:revive + SIOCSHWTSTAMP = unix.SIOCSHWTSTAMP //nolint:revive SOF_TIMESTAMPING_OPT_TSONLY = unix.SOF_TIMESTAMPING_OPT_TSONLY //nolint:revive SOF_TIMESTAMPING_RAW_HARDWARE = unix.SOF_TIMESTAMPING_RAW_HARDWARE //nolint:revive SOF_TIMESTAMPING_RX_HARDWARE = unix.SOF_TIMESTAMPING_RX_HARDWARE //nolint:revive @@ -332,16 +186,12 @@ const ( SOF_TIMESTAMPING_SOFTWARE = unix.SOF_TIMESTAMPING_SOFTWARE //nolint:revive SOF_TIMESTAMPING_TX_HARDWARE = unix.SOF_TIMESTAMPING_TX_HARDWARE //nolint:revive SOF_TIMESTAMPING_TX_SOFTWARE = unix.SOF_TIMESTAMPING_TX_SOFTWARE //nolint:revive - SOL_SOCKET = unix.SOL_SOCKET //nolint:revive SO_SELECT_ERR_QUEUE = unix.SO_SELECT_ERR_QUEUE //nolint:revive SO_TIMESTAMPING_NEW = unix.SO_TIMESTAMPING_NEW //nolint:revive SO_TIMESTAMPING = unix.SO_TIMESTAMPING //nolint:revive SYS_CLOCK_SETTIME = unix.SYS_CLOCK_SETTIME //nolint:revive - SYS_IOCTL = unix.SYS_IOCTL //nolint:revive - SYS_RECVMSG = unix.SYS_RECVMSG //nolint:revive TIME_OK = unix.TIME_OK //nolint:revive - CLOCK_REALTIME = unix.CLOCK_REALTIME //nolint:revive - CLOCK_MONOTONIC_RAW = unix.CLOCK_MONOTONIC_RAW //nolint:revive + CLOCK_BOOTTIME = unix.CLOCK_BOOTTIME //nolint:revive ) var ( @@ -350,11 +200,7 @@ var ( errENOENT error = syscall.ENOENT ) -// ioctlIfreqData performs an ioctl using an ifreqData structure for input -// and/or output. See the netdevice(7) man page for details. func ioctlIfreqData(fd int, req uint, value *ifreqData) error { - // The memory layout of IfreqData (type-safe) and ifreq (not type-safe) are - // identical so pass *IfreqData directly. return ioctlPtr(fd, req, unsafe.Pointer(value)) } @@ -366,8 +212,6 @@ func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { return } -// errnoErr returns common boxed Errno values, to prevent -// allocations at runtime. func errnoErr(e syscall.Errno) error { switch e { case 0: diff --git a/phc/unix/notlinux.go b/phc/unix/notlinux.go new file mode 100644 index 00000000..851cc793 --- /dev/null +++ b/phc/unix/notlinux.go @@ -0,0 +1,103 @@ +//go:build !linux + +/* +Copyright (c) Facebook, Inc. and its affiliates. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package unix + +import ( + "errors" +) + +// Timex is a simplified version for non-linux platforms. +// On Linux this is an alias for unix.Timex which has many more fields. +type Timex struct { + Modes uint32 + Freq int64 + Tolerance int64 + Time Timeval +} + +// Linux-only constants that need stub values on Darwin +// +//nolint:revive +const ( + TIME_OK = 0 + CLOCK_BOOTTIME = 7 +) + +// FdToClockID is not supported on non-linux. +func FdToClockID(_ int) int32 { return 0 } + +// ClockAdjtime is not supported on non-linux. +func ClockAdjtime(_ int32, _ *Timex) (int, error) { + return 0, errors.New("clock adjtime is unsupported on this platform") +} + +// Adjtimex is not supported on non-linux. +func Adjtimex(_ *Timex) (int, error) { + return 0, errors.New("adjtimex is unsupported on this platform") +} + +// ClockSettime is not supported on non-linux. +func ClockSettime(_ int32, _ *Timespec) error { + return errors.New("clock settime is unsupported on this platform") +} + +// IoctlPtpSysOffsetExtendedClock is not supported on non-linux. +func IoctlPtpSysOffsetExtendedClock(_ int, _ uint32, _ uint) (*PtpSysOffsetExtended, error) { + return nil, errors.New("unsupported on this platform") +} + +// IoctlPtpSysOffsetPrecise is not supported on non-linux. +func IoctlPtpSysOffsetPrecise(_ int) (*PtpSysOffsetPrecise, error) { + return nil, errors.New("unsupported on this platform") +} + +// IoctlPtpClockGetcaps is not supported on non-linux. +func IoctlPtpClockGetcaps(_ int) (*PtpClockCaps, error) { + return nil, errors.New("unsupported on this platform") +} + +// IoctlPtpPinSetfunc is not supported on non-linux. +func IoctlPtpPinSetfunc(_ int, _ *PtpPinDesc) error { + return errors.New("unsupported on this platform") +} + +// IoctlPtpPeroutRequest is not supported on non-linux. +func IoctlPtpPeroutRequest(_ int, _ *PtpPeroutRequest) error { + return errors.New("unsupported on this platform") +} + +// IoctlPtpExttsRequest is not supported on non-linux. +func IoctlPtpExttsRequest(_ int, _ *PtpExttsRequest) error { + return errors.New("unsupported on this platform") +} + +// IoctlGetEthtoolTsInfo is not supported on non-linux. +func IoctlGetEthtoolTsInfo(_ int, _ string) (*EthtoolTsInfo, error) { + return nil, errors.New("unsupported on this platform") +} + +// IoctlGetHwTstamp is not supported on non-linux. +func IoctlGetHwTstamp(_ int, _ string) (*HwTstampConfig, error) { + return nil, errors.New("unsupported on this platform") +} + +// IoctlPtpPinGetfunc is not supported on non-linux. +func IoctlPtpPinGetfunc(_ int, _ uint) (*PtpPinDesc, error) { + return nil, errors.New("unsupported on this platform") +} diff --git a/phc/unix/types.go b/phc/unix/types.go new file mode 100644 index 00000000..6f20388e --- /dev/null +++ b/phc/unix/types.go @@ -0,0 +1,174 @@ +/* +Copyright (c) Facebook, Inc. and its affiliates. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package unix + +import ( + "syscall" + "time" + + "golang.org/x/sys/unix" +) + +// Type aliases bridging to golang.org/x/sys/unix +type Cmsghdr = unix.Cmsghdr +type Errno = syscall.Errno +type Msghdr = unix.Msghdr +type PollFd = unix.PollFd +type Sockaddr = unix.Sockaddr +type SockaddrInet4 = unix.SockaddrInet4 +type SockaddrInet6 = unix.SockaddrInet6 +type Timespec = unix.Timespec +type Timeval = unix.Timeval +type Utsname = unix.Utsname + +// PTP types + +type PtpClockTime struct { + Sec int64 + Nsec uint32 + Reserved uint32 +} + +type PtpClockCaps struct { + Max_adj int32 //nolint:revive + N_alarm int32 //nolint:revive + N_ext_ts int32 //nolint:revive + N_per_out int32 //nolint:revive + Pps int32 + N_pins int32 //nolint:revive + Cross_timestamping int32 //nolint:revive + Adjust_phase int32 //nolint:revive + Max_phase_adj int32 //nolint:revive + Rsv [11]int32 +} + +type PtpPinDesc struct { + Name [64]byte + Index uint32 + Func uint32 + Chan uint32 + Rsv [5]uint32 +} + +type PtpSysOffsetPrecise struct { + Device PtpClockTime + Realtime PtpClockTime + Monoraw PtpClockTime + Rsv [4]uint32 +} + +type PtpSysOffsetExtended struct { + Samples uint32 + ClockID uint32 + Rsv [2]uint32 + Ts [PTP_MAX_SAMPLES][3]PtpClockTime +} + +type PtpPeroutRequest struct { + StartOrPhase PtpClockTime + Period PtpClockTime + Index uint32 + Flags uint32 + On PtpClockTime +} + +type PtpExttsEvent struct { + T PtpClockTime + Index uint32 + Flags uint32 + Rsv [2]uint32 +} + +type PtpExttsRequest struct { + Index uint32 + Flags uint32 + Rsv [2]uint32 +} + +// HwTstampConfig is used in SIOCGHWTSTAMP and SIOCSHWTSTAMP ioctls +type HwTstampConfig struct { + Flags int32 //nolint:revive + Tx_type int32 //nolint:revive + Rx_filter int32 //nolint:revive +} + +// EthtoolTsInfo is returned by ETHTOOL_GET_TS_INFO function of SIOCETHTOOL ioctl +type EthtoolTsInfo struct { + Cmd uint32 + So_timestamping uint32 //nolint:revive + Phc_index int32 //nolint:revive + Tx_types uint32 //nolint:revive + Tx_reserved [3]uint32 //nolint:revive + Rx_filters uint32 //nolint:revive + Rx_reserved [3]uint32 //nolint:revive +} + +// Constants available on all platforms + +//nolint:revive +const ( + PTP_MAX_SAMPLES = 25 + PTP_PF_NONE = iota //nolint:revive + PTP_PF_EXTTS //nolint:revive + PTP_PF_PEROUT //nolint:revive + PTP_PF_PHYSYNC //nolint:revive + PTP_ENABLE_FEATURE = 0x1 + PTP_RISING_EDGE = 0x2 +) + +// Function wrappers bridging to golang.org/x/sys/unix + +func ByteSliceToString(b []byte) string { return unix.ByteSliceToString(b) } +func ClockGettime(c int32, t *Timespec) error { return unix.ClockGettime(c, t) } +func Close(fd int) (err error) { return unix.Close(fd) } +func ErrnoName(e syscall.Errno) string { return unix.ErrnoName(e) } +func Poll(f []PollFd, t int) (int, error) { return unix.Poll(f, t) } +func SetsockoptInt(a, b, c, d int) error { return unix.SetsockoptInt(a, b, c, d) } +func Socket(domain, typ, proto int) (fd int, err error) { return unix.Socket(domain, typ, proto) } +func Syscall(a, b, c, d uintptr) (uintptr, uintptr, Errno) { return unix.Syscall(a, b, c, d) } +func TimeToTimespec(t time.Time) (Timespec, error) { return unix.TimeToTimespec(t) } +func Uname(s *Utsname) error { return unix.Uname(s) } +func NsecToTimeval(nsec int64) Timeval { return unix.NsecToTimeval(nsec) } +func SetsockoptTimeval(fd, level, opt int, tv *Timeval) error { + return unix.SetsockoptTimeval(fd, level, opt, tv) +} + +func Recvmsg(a int, b, c []byte, d int) (int, int, int, Sockaddr, error) { + return unix.Recvmsg(a, b, c, d) +} + +// Cross-platform constants from golang.org/x/sys/unix + +const ( + AF_INET = unix.AF_INET //nolint:revive + EAGAIN = unix.EAGAIN //nolint:revive + EINVAL = unix.EINVAL //nolint:revive + ENOENT = unix.ENOENT //nolint:revive + ENOTSUP = unix.ENOTSUP //nolint:revive + IFNAMSIZ = unix.IFNAMSIZ //nolint:revive + POLLERR = unix.POLLERR //nolint:revive + POLLIN = unix.POLLIN + POLLPRI = unix.POLLPRI + SizeofPtr = unix.SizeofPtr + SizeofSockaddrInet4 = unix.SizeofSockaddrInet4 + SOCK_DGRAM = unix.SOCK_DGRAM //nolint:revive + SOL_SOCKET = unix.SOL_SOCKET //nolint:revive + SO_RCVTIMEO = unix.SO_RCVTIMEO //nolint:revive + SO_TIMESTAMP = unix.SO_TIMESTAMP //nolint:revive + CLOCK_REALTIME = unix.CLOCK_REALTIME //nolint:revive + CLOCK_MONOTONIC_RAW = unix.CLOCK_MONOTONIC_RAW //nolint:revive +) diff --git a/ptp/protocol/management.go b/ptp/protocol/management.go index 41a672ec..6dbdc004 100644 --- a/ptp/protocol/management.go +++ b/ptp/protocol/management.go @@ -187,9 +187,9 @@ func (p *ManagementMsgErrorStatus) UnmarshalBinary(rawBytes []byte) error { // packet can have trailing bytes, let's make sure we don't try to read past given length toRead := int(p.MessageLength) toRead -= binary.Size(p.ManagementMsgHead) - toRead -= binary.Size(p.ManagementErrorStatusTLV.TLVHead) - toRead -= binary.Size(p.ManagementErrorStatusTLV.ManagementErrorID) - toRead -= binary.Size(p.ManagementErrorStatusTLV.ManagementID) + toRead -= binary.Size(p.TLVHead) + toRead -= binary.Size(p.ManagementErrorID) + toRead -= binary.Size(p.ManagementID) toRead -= binary.Size(p.ManagementErrorStatusTLV.Reserved) if reader.Len() == 0 || toRead <= 0 { @@ -212,13 +212,13 @@ func (p *ManagementMsgErrorStatus) MarshalBinaryToBuf(bytes io.Writer) error { if err := binary.Write(bytes, be, &p.ManagementMsgHead); err != nil { return fmt.Errorf("writing ManagementMsgErrorStatus ManagementMsgHead: %w", err) } - if err := binary.Write(bytes, be, &p.ManagementErrorStatusTLV.TLVHead); err != nil { + if err := binary.Write(bytes, be, &p.TLVHead); err != nil { return fmt.Errorf("writing ManagementMsgErrorStatus TLVHead: %w", err) } - if err := binary.Write(bytes, be, &p.ManagementErrorStatusTLV.ManagementErrorID); err != nil { + if err := binary.Write(bytes, be, &p.ManagementErrorID); err != nil { return fmt.Errorf("writing ManagementMsgErrorStatus ManagementErrorID: %w", err) } - if err := binary.Write(bytes, be, &p.ManagementErrorStatusTLV.ManagementID); err != nil { + if err := binary.Write(bytes, be, &p.ManagementID); err != nil { return fmt.Errorf("writing ManagementMsgErrorStatus ManagementID: %w", err) } if err := binary.Write(bytes, be, &p.ManagementErrorStatusTLV.Reserved); err != nil { diff --git a/ptp/protocol/management_client.go b/ptp/protocol/management_client.go index b2e3fe8e..7c3e53ba 100644 --- a/ptp/protocol/management_client.go +++ b/ptp/protocol/management_client.go @@ -66,7 +66,7 @@ func (c *MgmtClient) Communicate(packet *Management) (*Management, error) { } errorPacket, ok := res.(*ManagementMsgErrorStatus) if ok { - return nil, fmt.Errorf("got Management Error in response: %w", errorPacket.ManagementErrorStatusTLV.ManagementErrorID) + return nil, fmt.Errorf("got Management Error in response: %w", errorPacket.ManagementErrorID) } p, ok := res.(*Management) if !ok { diff --git a/ptp/protocol/ptp4l_test.go b/ptp/protocol/ptp4l_test.go index a1b30b87..dcc3ee25 100644 --- a/ptp/protocol/ptp4l_test.go +++ b/ptp/protocol/ptp4l_test.go @@ -100,7 +100,7 @@ func TestParseTimeStatusNP(t *testing.T) { func TestTimeStatusNPRequest(t *testing.T) { req := TimeStatusNPRequest() // it's normally generated from PID, set to know value - req.Header.SourcePortIdentity.PortNumber = 12345 + req.SourcePortIdentity.PortNumber = 12345 raw, err := Bytes(req) want := []byte{ @@ -172,7 +172,7 @@ func TestParsePortStatsNP(t *testing.T) { func TestPortStatsNPRequest(t *testing.T) { req := PortStatsNPRequest() // it's normally generated from PID, set to know value - req.Header.SourcePortIdentity.PortNumber = 12345 + req.SourcePortIdentity.PortNumber = 12345 raw, err := Bytes(req) want := []byte{ @@ -252,7 +252,7 @@ func TestParsePortServiceStatsNP(t *testing.T) { func TestPortServiceStatsNPRequest(t *testing.T) { req := PortServiceStatsNPRequest() // it's normally generated from PID, set to know value - req.ManagementMsgHead.Header.SourcePortIdentity.PortNumber = 12345 + req.SourcePortIdentity.PortNumber = 12345 raw, err := Bytes(req) want := []byte{ @@ -319,7 +319,7 @@ func TestParsePortPropertiesNP(t *testing.T) { func TestPortPropertiesNPRequest(t *testing.T) { req := PortPropertiesNPRequest() // it's normally generated from PID, set to know value - req.ManagementMsgHead.Header.SourcePortIdentity.PortNumber = 12345 + req.SourcePortIdentity.PortNumber = 12345 raw, err := Bytes(req) want := []byte{ @@ -528,7 +528,7 @@ func TestParseUnicastMasterTableNP(t *testing.T) { func TestUnicastMasterTableNPRequest(t *testing.T) { req := UnicastMasterTableNPRequest() // it's normally generated from PID, set to know value - req.ManagementMsgHead.Header.SourcePortIdentity.PortNumber = 12345 + req.SourcePortIdentity.PortNumber = 12345 raw, err := Bytes(req) want := []byte{ diff --git a/ptp/protocol/types_test.go b/ptp/protocol/types_test.go index efacedff..7e43de7e 100644 --- a/ptp/protocol/types_test.go +++ b/ptp/protocol/types_test.go @@ -548,7 +548,8 @@ func TestPortAddressUint16Overflow(t *testing.T) { } func TestPortAddressUint16OverflowShortBuffer(t *testing.T) { - buf := []byte{0x00, 0x01, 0xFF, 0xFF} + buf := make([]byte, 0, 1024) + buf = append(buf, 0x00, 0x01, 0xFF, 0xFF) buf = append(buf, make([]byte, 1020)...) var addr PortAddress diff --git a/ptp/protocol/unicast_test.go b/ptp/protocol/unicast_test.go index dace2663..9a1e24db 100644 --- a/ptp/protocol/unicast_test.go +++ b/ptp/protocol/unicast_test.go @@ -103,12 +103,13 @@ func TestParseRequestUnicastTransmissionMultiTLV(t *testing.T) { } func TestParseRequestUnicastTransmissionExtraBytes(t *testing.T) { - raw := []uint8{0x0c, 0x02, 0x00, 0x40, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x59, 0x9f, 0xff, 0xfe, 0x55, 0xaf, 0x4e, 0x00, 0x01, 0x00, 0x00, 0x05, 0x7f, 0xff, 0xff, + raw := make([]uint8, 0, 68) + raw = append(raw, 0x0c, 0x02, 0x00, 0x40, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x59, 0x9f, 0xff, 0xfe, 0x55, 0xaf, 0x4e, 0x00, 0x01, 0x00, 0x00, 0x05, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x04, 0x00, 0x06, 0xb0, 0x01, 0x00, 0x00, 0x00, 0x3c, // first TLV 0x00, 0x04, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, // second TLV 0x00, 0x00, // extra 2 bytes for udp6 checksum - } + ) // packets can arrive with trailing bytes, simulate it here extraBytes := []byte{0x00, 0x00} raw = append(raw, extraBytes...) diff --git a/ptp/ptp4u/server/server.go b/ptp/ptp4u/server/server.go index c5463380..dd891b4b 100644 --- a/ptp/ptp4u/server/server.go +++ b/ptp/ptp4u/server/server.go @@ -340,11 +340,11 @@ func (s *Server) handleEventMessages(eFD, rworker int) { } } - worker = s.findWorker(dReq.Header.SourcePortIdentity, workerOffset) + worker = s.findWorker(dReq.SourcePortIdentity, workerOffset) if dReq.FlagField == ptp.FlagProfileSpecific1|ptp.FlagUnicast { expire = time.Now().Add(subscriptionDuration) // SYNC DELAY_REQUEST and ANNOUNCE - if sc = worker.FindSubscription(dReq.Header.SourcePortIdentity, ptp.MessageDelayReq); sc == nil { + if sc = worker.FindSubscription(dReq.SourcePortIdentity, ptp.MessageDelayReq); sc == nil { // if the port number is > 10, it's a ptping request which expects announce to come to the same ephemeral port if dReq.SourcePortIdentity.PortNumber > 10 { gclisa = eclisa @@ -353,7 +353,7 @@ func (s *Server) handleEventMessages(eFD, rworker int) { } // Create a new subscription sc = NewSubscriptionClient(worker.queue, worker.signalingQueue, eclisa, gclisa, ptp.MessageDelayReq, s.Config, subscriptionDuration, expire) - worker.RegisterSubscription(dReq.Header.SourcePortIdentity, ptp.MessageDelayReq, sc) + worker.RegisterSubscription(dReq.SourcePortIdentity, ptp.MessageDelayReq, sc) go sc.Start(s.ctx) } else { // bump the subscription @@ -371,7 +371,7 @@ func (s *Server) handleEventMessages(eFD, rworker int) { sc.UpdateAnnounceDelayReq(dReq.CorrectionField, dReq.SequenceID) } else { // DELAY_RESPONSE - if sc = worker.FindSubscription(dReq.Header.SourcePortIdentity, ptp.MessageDelayResp); sc == nil { + if sc = worker.FindSubscription(dReq.SourcePortIdentity, ptp.MessageDelayResp); sc == nil { log.Infof("Delay request from %s is not in the subscription list", timestamp.SockaddrToIP(eclisa)) continue } diff --git a/ptp/ptp4u/server/subscription.go b/ptp/ptp4u/server/subscription.go index 6cc314f8..702025c0 100644 --- a/ptp/ptp4u/server/subscription.go +++ b/ptp/ptp4u/server/subscription.go @@ -382,15 +382,15 @@ func (sc *SubscriptionClient) initSignaling() { // UpdateSignalingGrant updates ptp Signaling packet granting the requested subscription func (sc *SubscriptionClient) UpdateSignalingGrant(sg *ptp.Signaling, mt ptp.UnicastMsgTypeAndFlags, interval ptp.LogInterval, duration uint32) { - sc.signaling.Header.MessageLength = uint16(binary.Size(ptp.Header{}) + binary.Size(ptp.PortIdentity{}) + binary.Size(ptp.GrantUnicastTransmissionTLV{})) - sc.signaling.Header.SdoIDAndMsgType = sg.Header.SdoIDAndMsgType - sc.signaling.Header.DomainNumber = sg.Header.DomainNumber - sc.signaling.Header.MinorSdoID = sg.Header.MinorSdoID - sc.signaling.Header.CorrectionField = sg.Header.CorrectionField - sc.signaling.Header.MessageTypeSpecific = sg.Header.MessageTypeSpecific - sc.signaling.Header.SequenceID = sg.Header.SequenceID - sc.signaling.Header.ControlField = sg.Header.ControlField - sc.signaling.Header.LogMessageInterval = sg.Header.LogMessageInterval + sc.signaling.MessageLength = uint16(binary.Size(ptp.Header{}) + binary.Size(ptp.PortIdentity{}) + binary.Size(ptp.GrantUnicastTransmissionTLV{})) + sc.signaling.SdoIDAndMsgType = sg.SdoIDAndMsgType + sc.signaling.DomainNumber = sg.DomainNumber + sc.signaling.MinorSdoID = sg.MinorSdoID + sc.signaling.CorrectionField = sg.CorrectionField + sc.signaling.MessageTypeSpecific = sg.MessageTypeSpecific + sc.signaling.SequenceID = sg.SequenceID + sc.signaling.ControlField = sg.ControlField + sc.signaling.LogMessageInterval = sg.LogMessageInterval sc.signaling.TargetPortIdentity = sg.SourcePortIdentity sc.signaling.TLVs = []ptp.TLV{ @@ -407,7 +407,7 @@ func (sc *SubscriptionClient) UpdateSignalingGrant(sg *ptp.Signaling, mt ptp.Uni // UpdateSignalingCancel updates ptp Signaling packet canceling the requested subscription func (sc *SubscriptionClient) UpdateSignalingCancel() { - sc.signaling.Header.MessageLength = uint16(binary.Size(ptp.Header{}) + binary.Size(ptp.PortIdentity{}) + binary.Size(ptp.CancelUnicastTransmissionTLV{})) + sc.signaling.MessageLength = uint16(binary.Size(ptp.Header{}) + binary.Size(ptp.PortIdentity{}) + binary.Size(ptp.CancelUnicastTransmissionTLV{})) sc.signaling.TLVs = []ptp.TLV{ &ptp.CancelUnicastTransmissionTLV{ TLVHead: ptp.TLVHead{TLVType: ptp.TLVCancelUnicastTransmission, LengthField: uint16(binary.Size(ptp.CancelUnicastTransmissionTLV{}) - binary.Size(ptp.TLVHead{}))}, diff --git a/ptp/ptp4u/server/subscription_test.go b/ptp/ptp4u/server/subscription_test.go index a76aa8d5..0421e3df 100644 --- a/ptp/ptp4u/server/subscription_test.go +++ b/ptp/ptp4u/server/subscription_test.go @@ -111,7 +111,7 @@ func TestSubscriptionStop(t *testing.T) { require.Equal(t, 1, len(w.signalingQueue)) s := <-w.signalingQueue require.Equal(t, ptp.TLVCancelUnicastTransmission, s.signaling.TLVs[0].(*ptp.CancelUnicastTransmissionTLV).TLVType) - require.Equal(t, uint16(binary.Size(ptp.Header{})+binary.Size(ptp.PortIdentity{})+binary.Size(ptp.CancelUnicastTransmissionTLV{})), s.signaling.Header.MessageLength) + require.Equal(t, uint16(binary.Size(ptp.Header{})+binary.Size(ptp.PortIdentity{})+binary.Size(ptp.CancelUnicastTransmissionTLV{})), s.signaling.MessageLength) } func TestSubscriptionEnd(t *testing.T) { @@ -144,9 +144,9 @@ func TestSubscriptionflags(t *testing.T) { sc.UpdateSync() sc.UpdateFollowup(time.Now()) sc.UpdateAnnounce() - require.Equal(t, ptp.FlagUnicast|ptp.FlagTwoStep, sc.Sync().Header.FlagField) - require.Equal(t, ptp.FlagUnicast, sc.Followup().Header.FlagField) - require.Equal(t, ptp.FlagUnicast|ptp.FlagPTPTimescale, sc.Announce().Header.FlagField) + require.Equal(t, ptp.FlagUnicast|ptp.FlagTwoStep, sc.Sync().FlagField) + require.Equal(t, ptp.FlagUnicast, sc.Followup().FlagField) + require.Equal(t, ptp.FlagUnicast|ptp.FlagPTPTimescale, sc.Announce().FlagField) } func TestSyncPacket(t *testing.T) { @@ -167,9 +167,9 @@ func TestSyncPacket(t *testing.T) { sc.initSync() sc.IncSequenceID() sc.UpdateSync() - require.Equal(t, uint16(44), sc.Sync().Header.MessageLength) // check packet length - require.Equal(t, sequenceID+1, sc.Sync().Header.SequenceID) - require.Equal(t, domainNumber, sc.Sync().Header.DomainNumber) + require.Equal(t, uint16(44), sc.Sync().MessageLength) // check packet length + require.Equal(t, sequenceID+1, sc.Sync().SequenceID) + require.Equal(t, domainNumber, sc.Sync().DomainNumber) } func TestSyncDelayReqPacket(t *testing.T) { @@ -190,9 +190,9 @@ func TestSyncDelayReqPacket(t *testing.T) { sc.initSync() sc.UpdateSyncDelayReq(received, sequenceID) - require.Equal(t, uint16(44), sc.Sync().Header.MessageLength) // check packet length - require.Equal(t, sequenceID, sc.Sync().Header.SequenceID) - require.Equal(t, domainNumber, sc.Sync().Header.DomainNumber) + require.Equal(t, uint16(44), sc.Sync().MessageLength) // check packet length + require.Equal(t, sequenceID, sc.Sync().SequenceID) + require.Equal(t, domainNumber, sc.Sync().DomainNumber) require.Equal(t, ptp.NewTimestamp(received), sc.Sync().OriginTimestamp) } @@ -221,11 +221,11 @@ func TestFollowupPacket(t *testing.T) { sc.initFollowup() sc.IncSequenceID() sc.UpdateFollowup(now) - require.Equal(t, uint16(44), sc.Followup().Header.MessageLength) // check packet length - require.Equal(t, sequenceID+1, sc.Followup().Header.SequenceID) - require.Equal(t, i, sc.Followup().Header.LogMessageInterval) + require.Equal(t, uint16(44), sc.Followup().MessageLength) // check packet length + require.Equal(t, sequenceID+1, sc.Followup().SequenceID) + require.Equal(t, i, sc.Followup().LogMessageInterval) require.Equal(t, now.Unix(), sc.Followup().FollowUpBody.PreciseOriginTimestamp.Time().Unix()) - require.Equal(t, domainNumber, sc.Followup().Header.DomainNumber) + require.Equal(t, domainNumber, sc.Followup().DomainNumber) } func TestAnnouncePacket(t *testing.T) { @@ -264,14 +264,14 @@ func TestAnnouncePacket(t *testing.T) { sc.initAnnounce() sc.IncSequenceID() sc.UpdateAnnounce() - require.Equal(t, uint16(64), sc.Announce().Header.MessageLength) // check packet length - require.Equal(t, sequenceID+1, sc.Announce().Header.SequenceID) - require.Equal(t, sp, sc.Announce().Header.SourcePortIdentity) - require.Equal(t, i, sc.Announce().Header.LogMessageInterval) + require.Equal(t, uint16(64), sc.Announce().MessageLength) // check packet length + require.Equal(t, sequenceID+1, sc.Announce().SequenceID) + require.Equal(t, sp, sc.Announce().SourcePortIdentity) + require.Equal(t, i, sc.Announce().LogMessageInterval) require.Equal(t, ptp.ClockClass7, sc.Announce().GrandmasterClockQuality.ClockClass) require.Equal(t, ptp.ClockAccuracyMicrosecond1, sc.Announce().GrandmasterClockQuality.ClockAccuracy) require.Equal(t, int16(UTCOffset.Seconds()), sc.Announce().CurrentUTCOffset) - require.Equal(t, domainNumber, sc.Announce().Header.DomainNumber) + require.Equal(t, domainNumber, sc.Announce().DomainNumber) } func TestAnnounceDelayReqPacket(t *testing.T) { @@ -308,15 +308,15 @@ func TestAnnounceDelayReqPacket(t *testing.T) { sc.UpdateAnnounceDelayReq(correctionField, sequenceID) sc.UpdateAnnounceFollowUp(now) - require.Equal(t, uint16(64), sc.Announce().Header.MessageLength) // check packet length - require.Equal(t, sequenceID, sc.Announce().Header.SequenceID) - require.Equal(t, sp, sc.Announce().Header.SourcePortIdentity) - require.Equal(t, ptp.ClockClass7, sc.Announce().AnnounceBody.GrandmasterClockQuality.ClockClass) - require.Equal(t, ptp.ClockAccuracyMicrosecond1, sc.Announce().AnnounceBody.GrandmasterClockQuality.ClockAccuracy) - require.Equal(t, int16(UTCOffset.Seconds()), sc.Announce().AnnounceBody.CurrentUTCOffset) - require.Equal(t, domainNumber, sc.Announce().Header.DomainNumber) - require.Equal(t, correctionField, sc.Announce().Header.CorrectionField) - require.Equal(t, transmit, sc.Announce().AnnounceBody.OriginTimestamp) + require.Equal(t, uint16(64), sc.Announce().MessageLength) // check packet length + require.Equal(t, sequenceID, sc.Announce().SequenceID) + require.Equal(t, sp, sc.Announce().SourcePortIdentity) + require.Equal(t, ptp.ClockClass7, sc.Announce().GrandmasterClockQuality.ClockClass) + require.Equal(t, ptp.ClockAccuracyMicrosecond1, sc.Announce().GrandmasterClockQuality.ClockAccuracy) + require.Equal(t, int16(UTCOffset.Seconds()), sc.Announce().CurrentUTCOffset) + require.Equal(t, domainNumber, sc.Announce().DomainNumber) + require.Equal(t, correctionField, sc.Announce().CorrectionField) + require.Equal(t, transmit, sc.Announce().OriginTimestamp) } func TestDelayRespPacket(t *testing.T) { @@ -346,13 +346,13 @@ func TestDelayRespPacket(t *testing.T) { sc.initDelayResp() sc.UpdateDelayResp(h, now) - require.Equal(t, uint16(54), sc.DelayResp().Header.MessageLength) // check packet length - require.Equal(t, sequenceID, sc.DelayResp().Header.SequenceID) - require.Equal(t, 100500, int(sc.DelayResp().Header.CorrectionField.Nanoseconds())) - require.Equal(t, sp, sc.DelayResp().Header.SourcePortIdentity) + require.Equal(t, uint16(54), sc.DelayResp().MessageLength) // check packet length + require.Equal(t, sequenceID, sc.DelayResp().SequenceID) + require.Equal(t, 100500, int(sc.DelayResp().CorrectionField.Nanoseconds())) + require.Equal(t, sp, sc.DelayResp().SourcePortIdentity) require.Equal(t, now.Unix(), sc.DelayResp().DelayRespBody.ReceiveTimestamp.Time().Unix()) - require.Equal(t, ptp.FlagUnicast, sc.DelayResp().Header.FlagField) - require.Equal(t, domainNumber, sc.DelayResp().Header.DomainNumber) + require.Equal(t, ptp.FlagUnicast, sc.DelayResp().FlagField) + require.Equal(t, domainNumber, sc.DelayResp().DomainNumber) } func TestSignalingGrantPacket(t *testing.T) { @@ -384,7 +384,7 @@ func TestSignalingGrantPacket(t *testing.T) { sc.initSignaling() sc.UpdateSignalingGrant(sg, mt, i, duration) - require.Equal(t, uint16(56), sc.Signaling().Header.MessageLength) // check packet length + require.Equal(t, uint16(56), sc.Signaling().MessageLength) // check packet length require.Equal(t, tlv, sc.Signaling().TLVs[0]) } @@ -394,7 +394,7 @@ func TestSignalingCancelPacket(t *testing.T) { sa := timestamp.IPToSockaddr(net.ParseIP("127.0.0.1"), 123) sc := NewSubscriptionClient(w.queue, w.signalingQueue, sa, sa, ptp.MessageAnnounce, c, time.Second, time.Time{}) - sc.signaling.Header.MessageLength = uint16(binary.Size(ptp.Header{}) + binary.Size(ptp.PortIdentity{}) + binary.Size(ptp.CancelUnicastTransmissionTLV{})) + sc.signaling.MessageLength = uint16(binary.Size(ptp.Header{}) + binary.Size(ptp.PortIdentity{}) + binary.Size(ptp.CancelUnicastTransmissionTLV{})) tlv := &ptp.CancelUnicastTransmissionTLV{ TLVHead: ptp.TLVHead{TLVType: ptp.TLVCancelUnicastTransmission, LengthField: uint16(binary.Size(ptp.CancelUnicastTransmissionTLV{}) - binary.Size(ptp.TLVHead{}))}, Reserved: 0, @@ -404,7 +404,7 @@ func TestSignalingCancelPacket(t *testing.T) { sc.initSignaling() sc.UpdateSignalingCancel() - require.Equal(t, uint16(50), sc.Signaling().Header.MessageLength) // check packet length + require.Equal(t, uint16(50), sc.Signaling().MessageLength) // check packet length require.Equal(t, tlv, sc.Signaling().TLVs[0]) } @@ -428,7 +428,7 @@ func TestSendSignalingGrant(t *testing.T) { s := <-w.signalingQueue require.Equal(t, ptp.TLVGrantUnicastTransmission, s.signaling.TLVs[0].(*ptp.GrantUnicastTransmissionTLV).TLVType) - require.Equal(t, uint16(binary.Size(ptp.Header{})+binary.Size(ptp.PortIdentity{})+binary.Size(ptp.GrantUnicastTransmissionTLV{})), s.signaling.Header.MessageLength) + require.Equal(t, uint16(binary.Size(ptp.Header{})+binary.Size(ptp.PortIdentity{})+binary.Size(ptp.GrantUnicastTransmissionTLV{})), s.signaling.MessageLength) } func TestSendSignalingCancel(t *testing.T) { @@ -450,6 +450,6 @@ func TestSendSignalingCancel(t *testing.T) { require.Equal(t, 1, len(w.signalingQueue)) s := <-w.signalingQueue - require.Equal(t, ptp.TLVCancelUnicastTransmission, s.signaling.TLVs[0].(*ptp.CancelUnicastTransmissionTLV).TLVHead.TLVType) - require.Equal(t, uint16(binary.Size(ptp.Header{})+binary.Size(ptp.PortIdentity{})+binary.Size(ptp.CancelUnicastTransmissionTLV{})), s.signaling.Header.MessageLength) + require.Equal(t, ptp.TLVCancelUnicastTransmission, s.signaling.TLVs[0].(*ptp.CancelUnicastTransmissionTLV).TLVType) + require.Equal(t, uint16(binary.Size(ptp.Header{})+binary.Size(ptp.PortIdentity{})+binary.Size(ptp.CancelUnicastTransmissionTLV{})), s.signaling.MessageLength) } diff --git a/ptp/ptp4u/server/worker.go b/ptp/ptp4u/server/worker.go index 2a429676..edd6d489 100644 --- a/ptp/ptp4u/server/worker.go +++ b/ptp/ptp4u/server/worker.go @@ -188,7 +188,7 @@ func (s *sendWorker) Start() { txTS = txTS.Add(s.config.UTCOffset) } } else { - seqID := uint32(c.Sync().Header.SequenceID) + seqID := uint32(c.Sync().SequenceID) log.Debug("Sending sync (SCM_TS_OPT_ID set)") timestamp.SeqIDSocketControlMessage(seqID, soob) err = unix.Sendmsg(eFd, buf[:n], soob, c.eclisa, 0) @@ -289,7 +289,7 @@ func (s *sendWorker) Start() { txTS = txTS.Add(s.config.UTCOffset) } } else { - seqID := uint32(c.Sync().Header.SequenceID) + seqID := uint32(c.Sync().SequenceID) log.Debug("Sending sync (SCM_TS_OPT_ID set)") timestamp.SeqIDSocketControlMessage(seqID, soob) err = unix.Sendmsg(eFd, buf[:n], soob, c.eclisa, 0) diff --git a/ptp/sptp/bmc/bmc.go b/ptp/sptp/bmc/bmc.go index 6a3d9d48..7ddb9434 100644 --- a/ptp/sptp/bmc/bmc.go +++ b/ptp/sptp/bmc/bmc.go @@ -38,14 +38,14 @@ const ( // Dscmp2 finds better Announce based on network topology func Dscmp2(a, b *ptp.Announce) ComparisonResult { - if a.AnnounceBody.StepsRemoved+1 < b.AnnounceBody.StepsRemoved { + if a.StepsRemoved+1 < b.StepsRemoved { return ABetter } - if b.AnnounceBody.StepsRemoved+1 < a.AnnounceBody.StepsRemoved { + if b.StepsRemoved+1 < a.StepsRemoved { return BBetter } - p1, p2 := a.Header.SourcePortIdentity, b.Header.SourcePortIdentity + p1, p2 := a.SourcePortIdentity, b.SourcePortIdentity switch p1.Compare(p2) { case -1: return ABetterTopo @@ -58,28 +58,28 @@ func Dscmp2(a, b *ptp.Announce) ComparisonResult { // Base comparison on all attributes func dscmp(a *ptp.Announce, b *ptp.Announce) ComparisonResult { - if a.AnnounceBody.GrandmasterClockQuality.ClockClass < b.AnnounceBody.GrandmasterClockQuality.ClockClass { + if a.GrandmasterClockQuality.ClockClass < b.GrandmasterClockQuality.ClockClass { return ABetter } - if a.AnnounceBody.GrandmasterClockQuality.ClockClass > b.AnnounceBody.GrandmasterClockQuality.ClockClass { + if a.GrandmasterClockQuality.ClockClass > b.GrandmasterClockQuality.ClockClass { return BBetter } - if a.AnnounceBody.GrandmasterClockQuality.ClockAccuracy < b.AnnounceBody.GrandmasterClockQuality.ClockAccuracy { + if a.GrandmasterClockQuality.ClockAccuracy < b.GrandmasterClockQuality.ClockAccuracy { return ABetter } - if a.AnnounceBody.GrandmasterClockQuality.ClockAccuracy > b.AnnounceBody.GrandmasterClockQuality.ClockAccuracy { + if a.GrandmasterClockQuality.ClockAccuracy > b.GrandmasterClockQuality.ClockAccuracy { return BBetter } - if a.AnnounceBody.GrandmasterClockQuality.OffsetScaledLogVariance < b.AnnounceBody.GrandmasterClockQuality.OffsetScaledLogVariance { + if a.GrandmasterClockQuality.OffsetScaledLogVariance < b.GrandmasterClockQuality.OffsetScaledLogVariance { return ABetter } - if a.AnnounceBody.GrandmasterClockQuality.OffsetScaledLogVariance > b.AnnounceBody.GrandmasterClockQuality.OffsetScaledLogVariance { + if a.GrandmasterClockQuality.OffsetScaledLogVariance > b.GrandmasterClockQuality.OffsetScaledLogVariance { return BBetter } - if a.AnnounceBody.GrandmasterPriority2 < b.AnnounceBody.GrandmasterPriority2 { + if a.GrandmasterPriority2 < b.GrandmasterPriority2 { return ABetter } - if a.AnnounceBody.GrandmasterPriority2 > b.AnnounceBody.GrandmasterPriority2 { + if a.GrandmasterPriority2 > b.GrandmasterPriority2 { return BBetter } @@ -91,14 +91,14 @@ func Dscmp(a *ptp.Announce, b *ptp.Announce) ComparisonResult { if a.AnnounceBody == b.AnnounceBody { return Unknown } - diff := int64(a.AnnounceBody.GrandmasterIdentity) - int64(b.AnnounceBody.GrandmasterIdentity) + diff := int64(a.GrandmasterIdentity) - int64(b.GrandmasterIdentity) if diff == 0 { return Dscmp2(a, b) } - if a.AnnounceBody.GrandmasterPriority1 < b.AnnounceBody.GrandmasterPriority1 { + if a.GrandmasterPriority1 < b.GrandmasterPriority1 { return ABetter } - if a.AnnounceBody.GrandmasterPriority1 > b.AnnounceBody.GrandmasterPriority1 { + if a.GrandmasterPriority1 > b.GrandmasterPriority1 { return BBetter } @@ -136,10 +136,10 @@ func TelcoDscmp(a *ptp.Announce, b *ptp.Announce, localPrioA int, localPrioB int if localPrioA > localPrioB { return BBetter } - if a.AnnounceBody.GrandmasterClockQuality.ClockClass <= 127 { + if a.GrandmasterClockQuality.ClockClass <= 127 { return Dscmp2(a, b) } - diff := int64(a.AnnounceBody.GrandmasterIdentity) - int64(b.AnnounceBody.GrandmasterIdentity) + diff := int64(a.GrandmasterIdentity) - int64(b.GrandmasterIdentity) if diff == 0 { return Dscmp2(a, b) } diff --git a/ptp/sptp/client/asymmetry.go b/ptp/sptp/client/asymmetry.go index d4e1ca34..c1ccb270 100644 --- a/ptp/sptp/client/asymmetry.go +++ b/ptp/sptp/client/asymmetry.go @@ -211,5 +211,5 @@ func isAsymmetric(result *RunResult, asymmetryThreshold time.Duration) bool { if result.Measurement.BadDelay { return false } - return result.Measurement.Announce.AnnounceBody.GrandmasterClockQuality.ClockClass == ptp.ClockClass6 && math.Abs(float64(result.Measurement.Offset)) > float64(asymmetryThreshold) + return result.Measurement.Announce.GrandmasterClockQuality.ClockClass == ptp.ClockClass6 && math.Abs(float64(result.Measurement.Offset)) > float64(asymmetryThreshold) } diff --git a/ptp/sptp/client/bmca.go b/ptp/sptp/client/bmca.go index 93e8f615..b23da27d 100644 --- a/ptp/sptp/client/bmca.go +++ b/ptp/sptp/client/bmca.go @@ -39,14 +39,14 @@ func bmca(results map[netip.Addr]*RunResult, prios map[ptp.ClockIdentity]int, cf } a := best b := &result.Measurement.Announce - localPrioA := prios[a.AnnounceBody.GrandmasterIdentity] - localPrioB := prios[b.AnnounceBody.GrandmasterIdentity] + localPrioA := prios[a.GrandmasterIdentity] + localPrioB := prios[b.GrandmasterIdentity] if bmc.TelcoDscmp(a, b, localPrioA, localPrioB) < 0 { best = b } } // Never select GM if worse (greater) than MaxClockClass or with clock accuracy worse than MaxClockAccuracy - if best == nil || best.AnnounceBody.GrandmasterClockQuality.ClockClass > cfg.MaxClockClass || best.AnnounceBody.GrandmasterClockQuality.ClockAccuracy > cfg.MaxClockAccuracy { + if best == nil || best.GrandmasterClockQuality.ClockClass > cfg.MaxClockClass || best.GrandmasterClockQuality.ClockAccuracy > cfg.MaxClockAccuracy { return nil } return best diff --git a/ptp/sptp/client/clock.go b/ptp/sptp/client/clock.go index cff42045..90309c4c 100644 --- a/ptp/sptp/client/clock.go +++ b/ptp/sptp/client/clock.go @@ -23,8 +23,8 @@ import ( "github.com/facebook/time/clock" "github.com/facebook/time/phc" + "github.com/facebook/time/phc/unix" log "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" ) // Clock is the iface for clock device controls diff --git a/timestamp/timestamp_darwin.go b/timestamp/timestamp_darwin.go index 84ccca83..c84ec4c6 100644 --- a/timestamp/timestamp_darwin.go +++ b/timestamp/timestamp_darwin.go @@ -18,6 +18,7 @@ package timestamp import ( "encoding/binary" + "errors" "fmt" "net" "time" @@ -26,11 +27,10 @@ import ( "golang.org/x/sys/unix" ) -// unix.Cmsghdr size differs depending on platform -var socketControlMessageHeaderOffset = binary.Size(unix.Cmsghdr{}) - var timestamping = unix.SO_TIMESTAMP +var ErrNoTimestamp = errors.New("no timestamp available") + // Here we have basic HW and SW timestamping support // byteToTime converts bytes into a timestamp @@ -63,7 +63,7 @@ func socketControlMessageTimestamp(b []byte, _ int) (time.Time, error) { } // socketControlMessageDrops is not implemented for darwin -func socketControlMessageDrops(b []byte, _ int) uint32 { +func socketControlMessageDrops(_ []byte, _ int) uint32 { return 0 } @@ -78,15 +78,45 @@ func EnableTimestamps(ts Timestamp, connFd int, _ *net.Interface) error { switch ts { case SW: if err := EnableSWTimestampsRx(connFd); err != nil { - return fmt.Errorf("Cannot enable software timestamps: %w", err) + return fmt.Errorf("cannot enable software timestamps: %w", err) } default: - return fmt.Errorf("Unrecognized timestamp type: %s", ts) + return fmt.Errorf("unrecognized timestamp type: %s", ts) } return nil } // EnableRXQueueOverflow is not implemented for darwin -func EnableRXQueueOverflow(connFd int) error { - return err.Errorf("RX queue overflow is not supported on darwin") +func EnableRXQueueOverflow(_ int) error { + return fmt.Errorf("RX queue overflow is not supported on darwin") +} + +// ReadTXtimestamp is not supported on darwin. +func ReadTXtimestamp(_ int) (time.Time, int, error) { + return time.Time{}, 0, errors.New("TX timestamping is not supported on darwin") +} + +// ReadTXtimestampBuf is not supported on darwin. +func ReadTXtimestampBuf(_ int, _, _ []byte) (time.Time, int, error) { + return time.Time{}, 0, errors.New("TX timestamping is unsupported on darwin") +} + +// SeqIDSocketControlMessage is not supported on darwin. +func SeqIDSocketControlMessage(_ uint32, _ []byte) { + // no-op on darwin +} + +// ReadTimeStampSeqIDBuf is not supported on darwin. +func ReadTimeStampSeqIDBuf(_ int, _ []byte, _ uint32) (time.Time, int, error) { + return time.Time{}, 0, errors.New("timestamp sequence ID control messages are unsupported on darwin") +} + +// EnableHWTimestamps is not supported on darwin. +func EnableHWTimestamps(_ int, _ *net.Interface) error { + return errors.New("hardware timestamping is not supported on darwin") +} + +// EnableHWTimestampsRx is not supported on darwin. +func EnableHWTimestampsRx(_ int, _ *net.Interface) error { + return errors.New("hardware RX timestamping is not supported on darwin") }