diff --git a/net/tstun/wrap.go b/net/tstun/wrap.go index 5bebf16ca..f9da0e7d3 100644 --- a/net/tstun/wrap.go +++ b/net/tstun/wrap.go @@ -18,6 +18,7 @@ "golang.zx2c4.com/wireguard/tun" "inet.af/netaddr" "tailscale.com/net/packet" + "tailscale.com/tstime" "tailscale.com/types/ipproto" "tailscale.com/types/logger" "tailscale.com/wgengine/filter" @@ -363,7 +364,7 @@ func (t *Wrapper) filterOut(p *packet.Parsed) filter.Response { // noteActivity records that there was a read or write at the current time. func (t *Wrapper) noteActivity() { - atomic.StoreInt64(&t.lastActivityAtomic, time.Now().Unix()) + atomic.StoreInt64(&t.lastActivityAtomic, tstime.MonotonicCoarse()) } // IdleDuration reports how long it's been since the last read or write to this device. @@ -371,8 +372,8 @@ func (t *Wrapper) noteActivity() { // Its value is only accurate to roughly second granularity. // If there's never been activity, the duration is since 1970. func (t *Wrapper) IdleDuration() time.Duration { - sec := atomic.LoadInt64(&t.lastActivityAtomic) - return time.Since(time.Unix(sec, 0)) + elapsed := tstime.MonotonicCoarse() - atomic.LoadInt64(&t.lastActivityAtomic) + return time.Duration(elapsed) * time.Second } func (t *Wrapper) Read(buf []byte, offset int) (int, error) { diff --git a/tstime/monocoarse_asm.go b/tstime/monocoarse_asm.go new file mode 100644 index 000000000..0c88c1d73 --- /dev/null +++ b/tstime/monocoarse_asm.go @@ -0,0 +1,11 @@ +// +build go1.16,!go1.17 +// +build linux,amd64 linux,arm64 + +package tstime + +// MonotonicCoarse returns the number of monotonic seconds elapsed +// since an unspecified starting point, at low precision. +// It is only meaningful when compared to the +// result of previous MonotonicCoarse calls. +// On some platforms, MonotonicCoarse is much faster than time.Now. +func MonotonicCoarse() int64 diff --git a/tstime/monocoarse_linux_amd64.s b/tstime/monocoarse_linux_amd64.s new file mode 100644 index 000000000..16a99aca5 --- /dev/null +++ b/tstime/monocoarse_linux_amd64.s @@ -0,0 +1,105 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Adapted from code in the Go runtime package at Go 1.16.6: + +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.16 +// +build !go1.17 + +#include "go_asm.h" +#include "textflag.h" + +#define get_tls(r) MOVQ TLS, r +#define g(r) 0(r)(TLS*1) + +#define SYS_clock_gettime 228 + +// Hard-coded offsets into runtime structs. +// Generated by adding the following code +// to package runtime and then executing +// and empty func main: +// +// func init() { +// println("#define g_m", unsafe.Offsetof(g{}.m)) +// println("#define g_sched", unsafe.Offsetof(g{}.sched)) +// println("#define gobuf_sp", unsafe.Offsetof(g{}.sched.sp)) +// println("#define m_g0", unsafe.Offsetof(m{}.g0)) +// println("#define m_curg", unsafe.Offsetof(m{}.curg)) +// println("#define m_vdsoSP", unsafe.Offsetof(m{}.vdsoSP)) +// println("#define m_vdsoPC", unsafe.Offsetof(m{}.vdsoPC)) +// } + +#define g_m 48 +#define g_sched 56 +#define gobuf_sp 0 +#define m_g0 0 +#define m_curg 192 +#define m_vdsoSP 832 +#define m_vdsoPC 840 + +#define CLOCK_MONOTONIC_COARSE 6 + +// func MonotonicCoarse() int64 +TEXT ·MonotonicCoarse(SB),NOSPLIT,$16-8 + // Switch to g0 stack. + + MOVQ SP, R12 // Save old SP; R12 unchanged by C code. + + get_tls(CX) + MOVQ g(CX), AX + MOVQ g_m(AX), BX // BX unchanged by C code. + + // Set vdsoPC and vdsoSP for SIGPROF traceback. + // Save the old values on stack and restore them on exit, + // so this function is reentrant. + MOVQ m_vdsoPC(BX), CX + MOVQ m_vdsoSP(BX), DX + MOVQ CX, 0(SP) + MOVQ DX, 8(SP) + + LEAQ ret+0(FP), DX + MOVQ -8(DX), CX + MOVQ CX, m_vdsoPC(BX) + MOVQ DX, m_vdsoSP(BX) + + CMPQ AX, m_curg(BX) // Only switch if on curg. + JNE noswitch + + MOVQ m_g0(BX), DX + MOVQ (g_sched+gobuf_sp)(DX), SP // Set SP to g0 stack + +noswitch: + SUBQ $16, SP // Space for results + ANDQ $~15, SP // Align for C code + + MOVL $CLOCK_MONOTONIC_COARSE, DI + LEAQ 0(SP), SI + MOVQ runtime·vdsoClockgettimeSym(SB), AX + CMPQ AX, $0 + JEQ fallback + CALL AX +ret: + MOVQ 0(SP), AX // sec + MOVQ 8(SP), DX // nsec + MOVQ R12, SP // Restore real SP + // Restore vdsoPC, vdsoSP + // We don't worry about being signaled between the two stores. + // If we are not in a signal handler, we'll restore vdsoSP to 0, + // and no one will care about vdsoPC. If we are in a signal handler, + // we cannot receive another signal. + MOVQ 8(SP), CX + MOVQ CX, m_vdsoSP(BX) + MOVQ 0(SP), CX + MOVQ CX, m_vdsoPC(BX) + // sec is in AX; return it + MOVQ AX, ret+0(FP) + RET +fallback: + MOVQ $SYS_clock_gettime, AX + SYSCALL + JMP ret diff --git a/tstime/monocoarse_linux_arm64.s b/tstime/monocoarse_linux_arm64.s new file mode 100644 index 000000000..c2c3605fc --- /dev/null +++ b/tstime/monocoarse_linux_arm64.s @@ -0,0 +1,132 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Adapted from code in the Go runtime package at Go 1.16.6: + +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.16 +// +build !go1.17 + +#include "go_asm.h" +#include "textflag.h" + +#define SYS_clock_gettime 113 + +// Hard-coded offsets into runtime structs. +// Generated by adding the following code +// to package runtime and then executing +// and empty func main: +// +// func init() { +// println("#define g_m", unsafe.Offsetof(g{}.m)) +// println("#define g_sched", unsafe.Offsetof(g{}.sched)) +// println("#define gobuf_sp", unsafe.Offsetof(g{}.sched.sp)) +// println("#define g_stack", unsafe.Offsetof(g{}.stack)) +// println("#define stack_lo", unsafe.Offsetof(g{}.stack.lo)) +// println("#define m_g0", unsafe.Offsetof(m{}.g0)) +// println("#define m_curg", unsafe.Offsetof(m{}.curg)) +// println("#define m_vdsoSP", unsafe.Offsetof(m{}.vdsoSP)) +// println("#define m_vdsoPC", unsafe.Offsetof(m{}.vdsoPC)) +// println("#define m_gsignal", unsafe.Offsetof(m{}.gsignal)) +// } + +#define g_m 48 +#define g_sched 56 +#define gobuf_sp 0 +#define g_stack 0 +#define stack_lo 0 +#define m_g0 0 +#define m_curg 192 +#define m_vdsoSP 832 +#define m_vdsoPC 840 +#define m_gsignal 80 + +#define CLOCK_MONOTONIC_COARSE 6 + +// func MonotonicCoarse() int64 +TEXT ·MonotonicCoarse(SB),NOSPLIT,$24-8 + MOVD RSP, R20 // R20 is unchanged by C code + MOVD RSP, R1 + + MOVD g_m(g), R21 // R21 = m + + // Set vdsoPC and vdsoSP for SIGPROF traceback. + // Save the old values on stack and restore them on exit, + // so this function is reentrant. + MOVD m_vdsoPC(R21), R2 + MOVD m_vdsoSP(R21), R3 + MOVD R2, 8(RSP) + MOVD R3, 16(RSP) + + MOVD LR, m_vdsoPC(R21) + MOVD R20, m_vdsoSP(R21) + + MOVD m_curg(R21), R0 + CMP g, R0 + BNE noswitch + + MOVD m_g0(R21), R3 + MOVD (g_sched+gobuf_sp)(R3), R1 // Set RSP to g0 stack + +noswitch: + SUB $32, R1 + BIC $15, R1 + MOVD R1, RSP + + MOVW $CLOCK_MONOTONIC_COARSE, R0 + MOVD runtime·vdsoClockgettimeSym(SB), R2 + CBZ R2, fallback + + // Store g on gsignal's stack, so if we receive a signal + // during VDSO code we can find the g. + // If we don't have a signal stack, we won't receive signal, + // so don't bother saving g. + // When using cgo, we already saved g on TLS, also don't save + // g here. + // Also don't save g if we are already on the signal stack. + // We won't get a nested signal. + MOVBU runtime·iscgo(SB), R22 + CBNZ R22, nosaveg + MOVD m_gsignal(R21), R22 // g.m.gsignal + CBZ R22, nosaveg + CMP g, R22 + BEQ nosaveg + MOVD (g_stack+stack_lo)(R22), R22 // g.m.gsignal.stack.lo + MOVD g, (R22) + + BL (R2) + + MOVD ZR, (R22) // clear g slot, R22 is unchanged by C code + + B finish + +nosaveg: + BL (R2) + B finish + +fallback: + MOVD $SYS_clock_gettime, R8 + SVC + +finish: + MOVD 0(RSP), R3 // sec + MOVD 8(RSP), R5 // nsec + + MOVD R20, RSP // restore SP + // Restore vdsoPC, vdsoSP + // We don't worry about being signaled between the two stores. + // If we are not in a signal handler, we'll restore vdsoSP to 0, + // and no one will care about vdsoPC. If we are in a signal handler, + // we cannot receive another signal. + MOVD 16(RSP), R1 + MOVD R1, m_vdsoSP(R21) + MOVD 8(RSP), R1 + MOVD R1, m_vdsoPC(R21) + + // sec is in R3; return it + MOVD R3, ret+0(FP) + RET diff --git a/tstime/monocoarse_std.go b/tstime/monocoarse_std.go new file mode 100644 index 000000000..8a289dcee --- /dev/null +++ b/tstime/monocoarse_std.go @@ -0,0 +1,16 @@ +// +build !go1.16 go1.17 !linux !amd64,!arm64 + +package tstime + +import "time" + +var referenceTime = time.Now() + +// MonotonicCoarse returns the number of monotonic seconds elapsed +// since an unspecified starting point, at low precision. +// It is only meaningful when compared to the +// result of previous MonotonicCoarse calls. +// On some platforms, MonotonicCoarse is much faster than time.Now. +func MonotonicCoarse() int64 { + return int64(time.Since(referenceTime).Seconds()) +} diff --git a/tstime/monocoarse_test.go b/tstime/monocoarse_test.go new file mode 100644 index 000000000..f2fdc0659 --- /dev/null +++ b/tstime/monocoarse_test.go @@ -0,0 +1,29 @@ +package tstime + +import ( + "testing" + "time" +) + +func TestMonotonicCoarse(t *testing.T) { + t.Parallel() + start := MonotonicCoarse() + for n := 0; n < 30; n++ { + end := MonotonicCoarse() + if end == start { + time.Sleep(100 * time.Millisecond) + continue + } + if end-start != 1 { + t.Errorf("monotonic coarse time jumped: %v seconds", end-start) + } + return // success + } + t.Errorf("monotonic coarse time did not progress after 3s") +} + +func BenchmarkMonotonicCoarse(b *testing.B) { + for i := 0; i < b.N; i++ { + MonotonicCoarse() + } +}