diff options
-rw-r--r-- | pkg/abi/linux/elf.go | 14 | ||||
-rw-r--r-- | pkg/sentry/platform/ptrace/ptrace.go | 2 | ||||
-rw-r--r-- | pkg/sentry/platform/ptrace/ptrace_amd64.go | 33 | ||||
-rw-r--r-- | pkg/sentry/platform/ptrace/ptrace_arm64.go | 30 | ||||
-rw-r--r-- | pkg/sentry/platform/ptrace/ptrace_unsafe.go | 46 | ||||
-rw-r--r-- | pkg/sentry/platform/ptrace/stub_arm64.s | 106 | ||||
-rw-r--r-- | pkg/sentry/platform/ptrace/subprocess.go | 12 | ||||
-rw-r--r-- | pkg/sentry/platform/ptrace/subprocess_amd64.go | 24 | ||||
-rw-r--r-- | pkg/sentry/platform/ptrace/subprocess_arm64.go | 126 | ||||
-rw-r--r-- | pkg/sentry/platform/ptrace/subprocess_linux.go | 2 | ||||
-rw-r--r-- | pkg/sentry/platform/ptrace/subprocess_linux_unsafe.go (renamed from pkg/sentry/platform/ptrace/subprocess_linux_amd64_unsafe.go) | 3 | ||||
-rwxr-xr-x | pkg/sentry/platform/ring0/defs_impl.go | 3 | ||||
-rw-r--r-- | pkg/tcpip/transport/tcp/snd.go | 32 | ||||
-rwxr-xr-x | pkg/tcpip/transport/tcp/tcp_state_autogen.go | 2 |
14 files changed, 391 insertions, 44 deletions
diff --git a/pkg/abi/linux/elf.go b/pkg/abi/linux/elf.go index fb1c679d2..40f0459a0 100644 --- a/pkg/abi/linux/elf.go +++ b/pkg/abi/linux/elf.go @@ -89,3 +89,17 @@ const ( // AT_SYSINFO_EHDR is the address of the VDSO. AT_SYSINFO_EHDR = 33 ) + +// ELF ET_CORE and ptrace GETREGSET/SETREGSET register set types. +// +// See include/uapi/linux/elf.h. +const ( + // NT_PRSTATUS is for general purpose register. + NT_PRSTATUS = 0x1 + + // NT_PRFPREG is for float point register. + NT_PRFPREG = 0x2 + + // NT_X86_XSTATE is for x86 extended state using xsave. + NT_X86_XSTATE = 0x202 +) diff --git a/pkg/sentry/platform/ptrace/ptrace.go b/pkg/sentry/platform/ptrace/ptrace.go index 6fd30ed25..7b120a15d 100644 --- a/pkg/sentry/platform/ptrace/ptrace.go +++ b/pkg/sentry/platform/ptrace/ptrace.go @@ -60,7 +60,7 @@ var ( // maximum user address. This is valid only after a call to stubInit. // // We attempt to link the stub here, and adjust downward as needed. - stubStart uintptr = 0x7fffffff0000 + stubStart uintptr = stubInitAddress // stubEnd is the first byte past the end of the stub, as with // stubStart this is valid only after a call to stubInit. diff --git a/pkg/sentry/platform/ptrace/ptrace_amd64.go b/pkg/sentry/platform/ptrace/ptrace_amd64.go new file mode 100644 index 000000000..db0212538 --- /dev/null +++ b/pkg/sentry/platform/ptrace/ptrace_amd64.go @@ -0,0 +1,33 @@ +// Copyright 2019 The gVisor Authors. +// +// 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 ptrace + +import ( + "syscall" + + "gvisor.dev/gvisor/pkg/abi/linux" +) + +// fpRegSet returns the GETREGSET/SETREGSET register set type to be used. +func fpRegSet(useXsave bool) uintptr { + if useXsave { + return linux.NT_X86_XSTATE + } + return linux.NT_PRFPREG +} + +func stackPointer(r *syscall.PtraceRegs) uintptr { + return uintptr(r.Rsp) +} diff --git a/pkg/sentry/platform/ptrace/ptrace_arm64.go b/pkg/sentry/platform/ptrace/ptrace_arm64.go new file mode 100644 index 000000000..4db28c534 --- /dev/null +++ b/pkg/sentry/platform/ptrace/ptrace_arm64.go @@ -0,0 +1,30 @@ +// Copyright 2019 The gVisor Authors. +// +// 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 ptrace + +import ( + "syscall" + + "gvisor.dev/gvisor/pkg/abi/linux" +) + +// fpRegSet returns the GETREGSET/SETREGSET register set type to be used. +func fpRegSet(_ bool) uintptr { + return linux.NT_PRFPREG +} + +func stackPointer(r *syscall.PtraceRegs) uintptr { + return uintptr(r.Sp) +} diff --git a/pkg/sentry/platform/ptrace/ptrace_unsafe.go b/pkg/sentry/platform/ptrace/ptrace_unsafe.go index 2706039a5..47957bb3b 100644 --- a/pkg/sentry/platform/ptrace/ptrace_unsafe.go +++ b/pkg/sentry/platform/ptrace/ptrace_unsafe.go @@ -18,37 +18,23 @@ import ( "syscall" "unsafe" + "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/sentry/arch" "gvisor.dev/gvisor/pkg/sentry/usermem" ) -// GETREGSET/SETREGSET register set types. -// -// See include/uapi/linux/elf.h. -const ( - // _NT_PRFPREG is for x86 floating-point state without using xsave. - _NT_PRFPREG = 0x2 - - // _NT_X86_XSTATE is for x86 extended state using xsave. - _NT_X86_XSTATE = 0x202 -) - -// fpRegSet returns the GETREGSET/SETREGSET register set type to be used. -func fpRegSet(useXsave bool) uintptr { - if useXsave { - return _NT_X86_XSTATE - } - return _NT_PRFPREG -} - -// getRegs sets the regular register set. +// getRegs gets the general purpose register set. func (t *thread) getRegs(regs *syscall.PtraceRegs) error { + iovec := syscall.Iovec{ + Base: (*byte)(unsafe.Pointer(regs)), + Len: uint64(unsafe.Sizeof(*regs)), + } _, _, errno := syscall.RawSyscall6( syscall.SYS_PTRACE, - syscall.PTRACE_GETREGS, + syscall.PTRACE_GETREGSET, uintptr(t.tid), - 0, - uintptr(unsafe.Pointer(regs)), + linux.NT_PRSTATUS, + uintptr(unsafe.Pointer(&iovec)), 0, 0) if errno != 0 { return errno @@ -56,14 +42,18 @@ func (t *thread) getRegs(regs *syscall.PtraceRegs) error { return nil } -// setRegs sets the regular register set. +// setRegs sets the general purpose register set. func (t *thread) setRegs(regs *syscall.PtraceRegs) error { + iovec := syscall.Iovec{ + Base: (*byte)(unsafe.Pointer(regs)), + Len: uint64(unsafe.Sizeof(*regs)), + } _, _, errno := syscall.RawSyscall6( syscall.SYS_PTRACE, - syscall.PTRACE_SETREGS, + syscall.PTRACE_SETREGSET, uintptr(t.tid), - 0, - uintptr(unsafe.Pointer(regs)), + linux.NT_PRSTATUS, + uintptr(unsafe.Pointer(&iovec)), 0, 0) if errno != 0 { return errno @@ -131,7 +121,7 @@ func (t *thread) getSignalInfo(si *arch.SignalInfo) error { // // Precondition: the OS thread must be locked and own t. func (t *thread) clone() (*thread, error) { - r, ok := usermem.Addr(t.initRegs.Rsp).RoundUp() + r, ok := usermem.Addr(stackPointer(&t.initRegs)).RoundUp() if !ok { return nil, syscall.EINVAL } diff --git a/pkg/sentry/platform/ptrace/stub_arm64.s b/pkg/sentry/platform/ptrace/stub_arm64.s new file mode 100644 index 000000000..2c5e4d5cb --- /dev/null +++ b/pkg/sentry/platform/ptrace/stub_arm64.s @@ -0,0 +1,106 @@ +// Copyright 2019 The gVisor Authors. +// +// 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. + +#include "funcdata.h" +#include "textflag.h" + +#define SYS_GETPID 172 +#define SYS_EXIT 93 +#define SYS_KILL 129 +#define SYS_GETPPID 173 +#define SYS_PRCTL 167 + +#define SIGKILL 9 +#define SIGSTOP 19 + +#define PR_SET_PDEATHSIG 1 + +// stub bootstraps the child and sends itself SIGSTOP to wait for attach. +// +// R7 contains the expected PPID. +// +// This should not be used outside the context of a new ptrace child (as the +// function is otherwise a bunch of nonsense). +TEXT ·stub(SB),NOSPLIT,$0 +begin: + // N.B. This loop only executes in the context of a single-threaded + // fork child. + + MOVD $SYS_PRCTL, R8 + MOVD $PR_SET_PDEATHSIG, R0 + MOVD $SIGKILL, R1 + SVC + + CMN $4095, R0 + BCS error + + // If the parent already died before we called PR_SET_DEATHSIG then + // we'll have an unexpected PPID. + MOVD $SYS_GETPPID, R8 + SVC + + CMP R0, R7 + BNE parent_dead + + MOVD $SYS_GETPID, R8 + SVC + + CMP $0x0, R0 + BLT error + + // SIGSTOP to wait for attach. + // + // The SYSCALL instruction will be used for future syscall injection by + // thread.syscall. + MOVD $SYS_KILL, R8 + MOVD $SIGSTOP, R1 + SVC + // The tracer may "detach" and/or allow code execution here in three cases: + // + // 1. New (traced) stub threads are explicitly detached by the + // goroutine in newSubprocess. However, they are detached while in + // group-stop, so they do not execute code here. + // + // 2. If a tracer thread exits, it implicitly detaches from the stub, + // potentially allowing code execution here. However, the Go runtime + // never exits individual threads, so this case never occurs. + // + // 3. subprocess.createStub clones a new stub process that is untraced, + // thus executing this code. We setup the PDEATHSIG before SIGSTOPing + // ourselves for attach by the tracer. + // + // R7 has been updated with the expected PPID. + B begin + +error: + // Exit with -errno. + NEG R0, R0 + MOVD $SYS_EXIT, R8 + SVC + HLT + +parent_dead: + MOVD $SYS_EXIT, R8 + MOVD $1, R0 + SVC + HLT + +// stubCall calls the stub function at the given address with the given PPID. +// +// This is a distinct function because stub, above, may be mapped at any +// arbitrary location, and stub has a specific binary API (see above). +TEXT ·stubCall(SB),NOSPLIT,$0-16 + MOVD addr+0(FP), R0 + MOVD pid+8(FP), R7 + B (R0) diff --git a/pkg/sentry/platform/ptrace/subprocess.go b/pkg/sentry/platform/ptrace/subprocess.go index 15e84735e..79501682d 100644 --- a/pkg/sentry/platform/ptrace/subprocess.go +++ b/pkg/sentry/platform/ptrace/subprocess.go @@ -28,6 +28,16 @@ import ( "gvisor.dev/gvisor/pkg/sentry/usermem" ) +// Linux kernel errnos which "should never be seen by user programs", but will +// be revealed to ptrace syscall exit tracing. +// +// These constants are only used in subprocess.go. +const ( + ERESTARTSYS = syscall.Errno(512) + ERESTARTNOINTR = syscall.Errno(513) + ERESTARTNOHAND = syscall.Errno(514) +) + // globalPool exists to solve two distinct problems: // // 1) Subprocesses can't always be killed properly (see Release). @@ -282,7 +292,7 @@ func (t *thread) grabInitRegs() { if err := t.getRegs(&t.initRegs); err != nil { panic(fmt.Sprintf("ptrace get regs failed: %v", err)) } - t.initRegs.Rip -= initRegsRipAdjustment + t.adjustInitRegsRip() } // detach detaches from the thread. diff --git a/pkg/sentry/platform/ptrace/subprocess_amd64.go b/pkg/sentry/platform/ptrace/subprocess_amd64.go index a70512913..4649a94a7 100644 --- a/pkg/sentry/platform/ptrace/subprocess_amd64.go +++ b/pkg/sentry/platform/ptrace/subprocess_amd64.go @@ -28,20 +28,13 @@ const ( // maximumUserAddress is the largest possible user address. maximumUserAddress = 0x7ffffffff000 + // stubInitAddress is the initial attempt link address for the stub. + stubInitAddress = 0x7fffffff0000 + // initRegsRipAdjustment is the size of the syscall instruction. initRegsRipAdjustment = 2 ) -// Linux kernel errnos which "should never be seen by user programs", but will -// be revealed to ptrace syscall exit tracing. -// -// These constants are used in subprocess.go. -const ( - ERESTARTSYS = syscall.Errno(512) - ERESTARTNOINTR = syscall.Errno(513) - ERESTARTNOHAND = syscall.Errno(514) -) - // resetSysemuRegs sets up emulation registers. // // This should be called prior to calling sysemu. @@ -139,3 +132,14 @@ func dumpRegs(regs *syscall.PtraceRegs) string { return m.String() } + +// adjustInitregsRip adjust the current register RIP value to +// be just before the system call instruction excution +func (t *thread) adjustInitRegsRip() { + t.initRegs.Rip -= initRegsRipAdjustment +} + +// Pass the expected PPID to the child via R15 when creating stub process +func initChildProcessPPID(initregs *syscall.PtraceRegs, ppid int32) { + initregs.R15 = uint64(ppid) +} diff --git a/pkg/sentry/platform/ptrace/subprocess_arm64.go b/pkg/sentry/platform/ptrace/subprocess_arm64.go new file mode 100644 index 000000000..bec884ba5 --- /dev/null +++ b/pkg/sentry/platform/ptrace/subprocess_arm64.go @@ -0,0 +1,126 @@ +// Copyright 2019 The gVisor Authors. +// +// 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. + +// +build arm64 + +package ptrace + +import ( + "syscall" + + "gvisor.dev/gvisor/pkg/sentry/arch" +) + +const ( + // maximumUserAddress is the largest possible user address. + maximumUserAddress = 0xfffffffff000 + + // stubInitAddress is the initial attempt link address for the stub. + // Only support 48bits VA currently. + stubInitAddress = 0xffffffff0000 + + // initRegsRipAdjustment is the size of the svc instruction. + initRegsRipAdjustment = 4 +) + +// resetSysemuRegs sets up emulation registers. +// +// This should be called prior to calling sysemu. +func (s *subprocess) resetSysemuRegs(regs *syscall.PtraceRegs) { +} + +// createSyscallRegs sets up syscall registers. +// +// This should be called to generate registers for a system call. +func createSyscallRegs(initRegs *syscall.PtraceRegs, sysno uintptr, args ...arch.SyscallArgument) syscall.PtraceRegs { + // Copy initial registers (Pc, Sp, etc.). + regs := *initRegs + + // Set our syscall number. + // r8 for the syscall number. + // r0-r6 is used to store the parameters. + regs.Regs[8] = uint64(sysno) + if len(args) >= 1 { + regs.Regs[0] = args[0].Uint64() + } + if len(args) >= 2 { + regs.Regs[1] = args[1].Uint64() + } + if len(args) >= 3 { + regs.Regs[2] = args[2].Uint64() + } + if len(args) >= 4 { + regs.Regs[3] = args[3].Uint64() + } + if len(args) >= 5 { + regs.Regs[4] = args[4].Uint64() + } + if len(args) >= 6 { + regs.Regs[5] = args[5].Uint64() + } + + return regs +} + +// isSingleStepping determines if the registers indicate single-stepping. +func isSingleStepping(regs *syscall.PtraceRegs) bool { + // Refer to the ARM SDM D2.12.3: software step state machine + // return (regs.Pstate.SS == 1) && (MDSCR_EL1.SS == 1). + // + // Since the host Linux kernel will set MDSCR_EL1.SS on our behalf + // when we call a single-step ptrace command, we only need to check + // the Pstate.SS bit here. + return (regs.Pstate & arch.ARMTrapFlag) != 0 +} + +// updateSyscallRegs updates registers after finishing sysemu. +func updateSyscallRegs(regs *syscall.PtraceRegs) { + // No special work is necessary. + return +} + +// syscallReturnValue extracts a sensible return from registers. +func syscallReturnValue(regs *syscall.PtraceRegs) (uintptr, error) { + rval := int64(regs.Regs[0]) + if rval < 0 { + return 0, syscall.Errno(-rval) + } + return uintptr(rval), nil +} + +func dumpRegs(regs *syscall.PtraceRegs) string { + var m strings.Builder + + fmt.Fprintf(&m, "Registers:\n") + + for i := 0; i < 31; i++ { + fmt.Fprintf(&m, "\tRegs[%d]\t = %016x\n", i, regs.Regs[i]) + } + fmt.Fprintf(&m, "\tSp\t = %016x\n", regs.Sp) + fmt.Fprintf(&m, "\tPc\t = %016x\n", regs.Pc) + fmt.Fprintf(&m, "\tPstate\t = %016x\n", regs.Pstate) + + return m.String() +} + +// adjustInitregsRip adjust the current register RIP value to +// be just before the system call instruction excution +func (t *thread) adjustInitRegsRip() { + t.initRegs.Pc -= initRegsRipAdjustment +} + +// Pass the expected PPID to the child via X7 when creating stub process +func initChildProcessPPID(initregs *syscall.PtraceRegs, ppid int32) { + initregs.Regs[7] = uint64(ppid) +} diff --git a/pkg/sentry/platform/ptrace/subprocess_linux.go b/pkg/sentry/platform/ptrace/subprocess_linux.go index 87ded0bbd..f09b0b3d0 100644 --- a/pkg/sentry/platform/ptrace/subprocess_linux.go +++ b/pkg/sentry/platform/ptrace/subprocess_linux.go @@ -284,7 +284,7 @@ func (s *subprocess) createStub() (*thread, error) { // Pass the expected PPID to the child via R15. regs := t.initRegs - regs.R15 = uint64(t.tgid) + initChildProcessPPID(®s, t.tgid) // Call fork in a subprocess. // diff --git a/pkg/sentry/platform/ptrace/subprocess_linux_amd64_unsafe.go b/pkg/sentry/platform/ptrace/subprocess_linux_unsafe.go index e977992f9..de6783fb0 100644 --- a/pkg/sentry/platform/ptrace/subprocess_linux_amd64_unsafe.go +++ b/pkg/sentry/platform/ptrace/subprocess_linux_unsafe.go @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build amd64 linux +// +build linux +// +build amd64 arm64 package ptrace diff --git a/pkg/sentry/platform/ring0/defs_impl.go b/pkg/sentry/platform/ring0/defs_impl.go index d4bfc5a4a..5032ac56e 100755 --- a/pkg/sentry/platform/ring0/defs_impl.go +++ b/pkg/sentry/platform/ring0/defs_impl.go @@ -1,14 +1,13 @@ package ring0 import ( - "syscall" - "fmt" "gvisor.dev/gvisor/pkg/cpuid" "gvisor.dev/gvisor/pkg/sentry/platform/ring0/pagetables" "gvisor.dev/gvisor/pkg/sentry/usermem" "io" "reflect" + "syscall" ) var ( diff --git a/pkg/tcpip/transport/tcp/snd.go b/pkg/tcpip/transport/tcp/snd.go index 0fee7ab72..1f9b1e0ef 100644 --- a/pkg/tcpip/transport/tcp/snd.go +++ b/pkg/tcpip/transport/tcp/snd.go @@ -39,6 +39,28 @@ const ( nDupAckThreshold = 3 ) +// ccState indicates the current congestion control state for this sender. +type ccState int + +const ( + // Open indicates that the sender is receiving acks in order and + // no loss or dupACK's etc have been detected. + Open ccState = iota + // RTORecovery indicates that an RTO has occurred and the sender + // has entered an RTO based recovery phase. + RTORecovery + // FastRecovery indicates that the sender has entered FastRecovery + // based on receiving nDupAck's. This state is entered only when + // SACK is not in use. + FastRecovery + // SACKRecovery indicates that the sender has entered SACK based + // recovery. + SACKRecovery + // Disorder indicates the sender either received some SACK blocks + // or dupACK's. + Disorder +) + // congestionControl is an interface that must be implemented by any supported // congestion control algorithm. type congestionControl interface { @@ -138,6 +160,9 @@ type sender struct { // maxSentAck is the maxium acknowledgement actually sent. maxSentAck seqnum.Value + // state is the current state of congestion control for this endpoint. + state ccState + // cc is the congestion control algorithm in use for this sender. cc congestionControl } @@ -435,6 +460,7 @@ func (s *sender) retransmitTimerExpired() bool { s.leaveFastRecovery() } + s.state = RTORecovery s.cc.HandleRTOExpired() // Mark the next segment to be sent as the first unacknowledged one and @@ -820,9 +846,11 @@ func (s *sender) enterFastRecovery() { s.fr.last = s.sndNxt - 1 s.fr.maxCwnd = s.sndCwnd + s.outstanding if s.ep.sackPermitted { + s.state = SACKRecovery s.ep.stack.Stats().TCP.SACKRecovery.Increment() return } + s.state = FastRecovery s.ep.stack.Stats().TCP.FastRecovery.Increment() } @@ -981,6 +1009,7 @@ func (s *sender) checkDuplicateAck(seg *segment) (rtx bool) { s.fr.highRxt = s.sndUna - 1 // Do run SetPipe() to calculate the outstanding segments. s.SetPipe() + s.state = Disorder return false } @@ -1112,6 +1141,9 @@ func (s *sender) handleRcvdSegment(seg *segment) { // window based on the number of acknowledged packets. if !s.fr.active { s.cc.Update(originalOutstanding - s.outstanding) + if s.fr.last.LessThan(s.sndUna) { + s.state = Open + } } // It is possible for s.outstanding to drop below zero if we get diff --git a/pkg/tcpip/transport/tcp/tcp_state_autogen.go b/pkg/tcpip/transport/tcp/tcp_state_autogen.go index 159444cc2..d92c1b67d 100755 --- a/pkg/tcpip/transport/tcp/tcp_state_autogen.go +++ b/pkg/tcpip/transport/tcp/tcp_state_autogen.go @@ -341,6 +341,7 @@ func (x *sender) save(m state.Map) { m.Save("gso", &x.gso) m.Save("sndWndScale", &x.sndWndScale) m.Save("maxSentAck", &x.maxSentAck) + m.Save("state", &x.state) m.Save("cc", &x.cc) } @@ -366,6 +367,7 @@ func (x *sender) load(m state.Map) { m.Load("gso", &x.gso) m.Load("sndWndScale", &x.sndWndScale) m.Load("maxSentAck", &x.maxSentAck) + m.Load("state", &x.state) m.Load("cc", &x.cc) m.LoadValue("lastSendTime", new(unixTime), func(y interface{}) { x.loadLastSendTime(y.(unixTime)) }) m.LoadValue("rttMeasureTime", new(unixTime), func(y interface{}) { x.loadRttMeasureTime(y.(unixTime)) }) |