From 447e9f1b890600d6374d532d3d288dfea2c8bb9a Mon Sep 17 00:00:00 2001 From: Jenny Zhang Date: Wed, 21 Jun 2023 14:19:48 -0400 Subject: [PATCH 1/7] [tailscale] ./github/workflows: use matrices to reduce copypasta Signed-off-by: Jenny Zhang --- .github/workflows/build.yml | 108 ++++++++++++++++++++++++++ .github/workflows/prune_old_builds.sh | 24 ++++++ 2 files changed, 132 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100755 .github/workflows/prune_old_builds.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000000..7a8432ed7e7ee --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,108 @@ +name: Build toolchain + +permissions: + contents: write + +on: + push: + branches: + - tailscale + - 'tailscale.go1.21' + pull_request: + branches: + - '*' + +jobs: + test: + runs-on: ubuntu-20.04 + steps: + - name: checkout + uses: actions/checkout@v3 + - name: test + run: cd src && ./all.bash + + build_release: + strategy: + matrix: + GOOS: ["linux", "darwin"] + GOARCH: ["amd64", "arm64"] + runs-on: ubuntu-20.04 + if: github.event_name == 'push' + steps: + - name: checkout + uses: actions/checkout@v3 + - name: build + run: cd src && ./make.bash + env: + GOOS: "${{ matrix.GOOS }}" + GOARCH: "${{ matrix.GOARCH }}" + CGO_ENABLED: "0" + - name: trim unnecessary bits + run: | + rm -rf pkg/*_* + mv pkg/tool/${{ matrix.GOOS }}_${{ matrix.GOARCH }} pkg + rm -rf pkg/tool/*_* + mv -f bin/${{ matrix.GOOS }}_${{ matrix.GOARCH }}/* bin/ || true + rm -rf bin/${{ matrix.GOOS }}_${{ matrix.GOARCH }} + mv pkg/${{ matrix.GOOS }}_${{ matrix.GOARCH }} pkg/tool + find . -type d -name 'testdata' -print0 | xargs -0 rm -rf + find . -name '*_test.go' -delete + - name: archive + run: cd .. && tar --exclude-vcs -zcf ${{ matrix.GOOS }}-${{ matrix.GOARCH }}.tar.gz go + - name: save + uses: actions/upload-artifact@v1 + with: + name: ${{ matrix.GOOS }}-${{ matrix.GOARCH }} + path: ../${{ matrix.GOOS }}-${{ matrix.GOARCH }}.tar.gz + + create_release: + runs-on: ubuntu-20.04 + if: github.event_name == 'push' + needs: [test, build_release] + outputs: + url: ${{ steps.create_release.outputs.upload_url }} + steps: + - name: create release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Release name can't be the same as tag name, sigh + tag_name: build-${{ github.sha }} + release_name: ${{ github.sha }} + draft: false + prerelease: true + + upload_release: + strategy: + matrix: + GOOS: ["linux", "darwin"] + GOARCH: ["amd64", "arm64"] + runs-on: ubuntu-20.04 + if: github.event_name == 'push' + needs: [create_release] + steps: + - name: download artifact + uses: actions/download-artifact@v1 + with: + name: ${{ matrix.GOOS }}-${{ matrix.GOARCH }} + - name: upload artifact + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create_release.outputs.url }} + asset_path: ${{ matrix.GOOS }}-${{ matrix.GOARCH }}/${{ matrix.GOOS }}-${{ matrix.GOARCH }}.tar.gz + asset_name: ${{ matrix.GOOS }}-${{ matrix.GOARCH }}.tar.gz + asset_content_type: application/gzip + + clean_old: + runs-on: ubuntu-20.04 + if: github.event_name == 'push' + needs: [upload_release] + steps: + - name: checkout + uses: actions/checkout@v3 + - name: Delete older builds + run: ./.github/workflows/prune_old_builds.sh "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/prune_old_builds.sh b/.github/workflows/prune_old_builds.sh new file mode 100755 index 0000000000000..e7dc68cfba12d --- /dev/null +++ b/.github/workflows/prune_old_builds.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +set -euo pipefail + +KEEP=10 +GITHUB_TOKEN=$1 + +delete_release() { + release_id=$1 + tag_name=$2 + set -x + curl -X DELETE --header "Authorization: Bearer $GITHUB_TOKEN" "https://api.github.com/repos/tailscale/go/releases/$release_id" + curl -X DELETE --header "Authorization: Bearer $GITHUB_TOKEN" "https://api.github.com/repos/tailscale/go/git/refs/tags/$tag_name" + set +x +} + +curl https://api.github.com/repos/tailscale/go/releases 2>/dev/null |\ + jq -r '.[] | "\(.published_at) \(.id) \(.tag_name)"' |\ + egrep '[^ ]+ [^ ]+ build-[0-9a-f]{40}' |\ + sort |\ + head --lines=-${KEEP}|\ + while read date release_id tag_name; do + delete_release "$release_id" "$tag_name" + done From fef7dc56bf3944ac987105e2acc549b0e1fd6a33 Mon Sep 17 00:00:00 2001 From: Jenny Zhang Date: Wed, 21 Jun 2023 14:22:00 -0400 Subject: [PATCH 2/7] [tailscale] net: add TS_PANIC_ON_TEST_LISTEN_UNSPEC env to panic on unspecified addr listens For debugging Mac firewall dialog boxes blocking+failing tests. Change-Id: Ic1a0cd51de7fe553de1c1c9333fa1cc75b8490f2 (cherry picked from commit 8c9eff4) --- src/net/tcpsock_posix.go | 22 ++++++++++++++++++++++ src/net/udpsock_posix.go | 4 ++++ 2 files changed, 26 insertions(+) diff --git a/src/net/tcpsock_posix.go b/src/net/tcpsock_posix.go index e6f425b1cd0d4..cc3d983a381cc 100644 --- a/src/net/tcpsock_posix.go +++ b/src/net/tcpsock_posix.go @@ -168,11 +168,33 @@ func (ln *TCPListener) file() (*os.File, error) { return f, nil } +// Tailscale addition: if TS_PANIC_ON_TEST_LISTEN_UNSPEC is set, panic +// if a listen tries to listen on all interfaces (for debugging Mac +// firewall dialogs in tests). +func panicOnUnspecListen(ip IP) bool { + if ip != nil && !ip.IsUnspecified() { + return false + } + v := os.Getenv("TS_PANIC_ON_TEST_LISTEN_UNSPEC") + if v == "" { + return false + } + switch v[0] { + case 't', 'T', '1': + return true + } + return false +} + func (sl *sysListener) listenTCP(ctx context.Context, laddr *TCPAddr) (*TCPListener, error) { return sl.listenTCPProto(ctx, laddr, 0) } func (sl *sysListener) listenTCPProto(ctx context.Context, laddr *TCPAddr, proto int) (*TCPListener, error) { + if panicOnUnspecListen(laddr.IP) { + panic("tailscale: can't listen on unspecified address in test") + } + var ctrlCtxFn func(cxt context.Context, network, address string, c syscall.RawConn) error if sl.ListenConfig.Control != nil { ctrlCtxFn = func(cxt context.Context, network, address string, c syscall.RawConn) error { diff --git a/src/net/udpsock_posix.go b/src/net/udpsock_posix.go index f3dbcfec00b77..af7f1390a3879 100644 --- a/src/net/udpsock_posix.go +++ b/src/net/udpsock_posix.go @@ -217,6 +217,10 @@ func (sd *sysDialer) dialUDP(ctx context.Context, laddr, raddr *UDPAddr) (*UDPCo } func (sl *sysListener) listenUDP(ctx context.Context, laddr *UDPAddr) (*UDPConn, error) { + if panicOnUnspecListen(laddr.IP) { + panic("tailscale: can't listen on unspecified address in test") + } + var ctrlCtxFn func(cxt context.Context, network, address string, c syscall.RawConn) error if sl.ListenConfig.Control != nil { ctrlCtxFn = func(cxt context.Context, network, address string, c syscall.RawConn) error { From d8361ce7bad35dcf7f59879d05c0bba881da01ce Mon Sep 17 00:00:00 2001 From: Jenny Zhang Date: Wed, 21 Jun 2023 14:26:30 -0400 Subject: [PATCH 3/7] [tailscale] cmd/dist: support embedding of toolchain rev by env var Git checkouts are not reproducible (in particular, the .git directory's hashes vary according to the moods of git re: packing and other local settings), so building our Go toolchain via Nix leads to source hashes that change from machine to machine and over time. Since Nix already knows the exact git revision it's building from, this change lets Nix provide that hash to cmd/dist directly, skipping the rooting around in .git to find the right hash. Signed-off-by: Jenny Zhang (cherry picked from commit 6a17f14) --- src/cmd/dist/build.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/cmd/dist/build.go b/src/cmd/dist/build.go index 4b77ed36f720e..dc586afd6ac8e 100644 --- a/src/cmd/dist/build.go +++ b/src/cmd/dist/build.go @@ -393,6 +393,12 @@ func findgoversion() string { // its content if available, which is empty at this point. // Only use the VERSION file if it is non-empty. if b != "" { + if rev := os.Getenv("TAILSCALE_TOOLCHAIN_REV"); rev != "" { + if len(rev) > 10 { + rev = rev[:10] + } + b += "-ts" + chomp(rev) + } return b } } From d909a7f6aa02e9143c1e4ec3d2c70b6ac145d5b5 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Thu, 2 Feb 2023 12:41:57 -0800 Subject: [PATCH 4/7] [tailscale] cmd/dist: always default to CGO_ENABLED="" Lets us build a statically linked toolchain that has the same cgo behavior as the standard dynamic toolchain. Signed-off-by: Jenny Zhang (Cherry-picked from a2f29de) --- src/cmd/dist/buildgo.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cmd/dist/buildgo.go b/src/cmd/dist/buildgo.go index 884e9d729a6a3..5c1daecd4c3d9 100644 --- a/src/cmd/dist/buildgo.go +++ b/src/cmd/dist/buildgo.go @@ -7,7 +7,6 @@ package main import ( "fmt" "io" - "os" "path/filepath" "sort" "strings" @@ -119,7 +118,7 @@ func mkzcgo(dir, file string) { writeHeader(&buf) fmt.Fprintf(&buf, "package build\n") fmt.Fprintln(&buf) - fmt.Fprintf(&buf, "const defaultCGO_ENABLED = %s\n", quote(os.Getenv("CGO_ENABLED"))) + fmt.Fprintf(&buf, "const defaultCGO_ENABLED = %q\n", "") writefile(buf.String(), file, writeSkipSame) } From 9c10558c0ab4a1b9b8689416bd7b819b5af4bfe9 Mon Sep 17 00:00:00 2001 From: Jenny Zhang Date: Wed, 21 Jun 2023 14:31:48 -0400 Subject: [PATCH 5/7] [tailscale] net: add enforcement hooks Updates tailscale/go#55 Updates tailscale/corp#8944 Signed-off-by: Jenny Zhang (Cherry-picked from 13373ca) --- api/go1.99999.txt | 2 ++ src/net/dial.go | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 api/go1.99999.txt diff --git a/api/go1.99999.txt b/api/go1.99999.txt new file mode 100644 index 0000000000000..dd6cb2c37d580 --- /dev/null +++ b/api/go1.99999.txt @@ -0,0 +1,2 @@ +pkg net, func SetDialEnforcer(func(context.Context, []Addr) error) #55 +pkg net, func SetResolveEnforcer(func(context.Context, string, string, string, Addr) error) #55 diff --git a/src/net/dial.go b/src/net/dial.go index fd1da1ebef070..593cc220f3999 100644 --- a/src/net/dial.go +++ b/src/net/dial.go @@ -247,6 +247,24 @@ func parseNetwork(ctx context.Context, network string, needsProto bool) (afnet s return "", 0, UnknownNetworkError(network) } +// SetResolveEnforcer set a program-global resolver enforcer that can cause resolvers to +// fail based on the context and/or other arguments. +// +// f must be non-nil, it can only be called once, and must not be called +// concurrent with any dial/resolve. +func SetResolveEnforcer(f func(ctx context.Context, op, network, addr string, hint Addr) error) { + if f == nil { + panic("nil func") + } + if resolveEnforcer != nil { + panic("already called") + } + resolveEnforcer = f +} + +// resolveEnforcer, if non-nil, is the installed hook from SetResolveEnforcer. +var resolveEnforcer func(ctx context.Context, op, network, addr string, hint Addr) error + // resolveAddrList resolves addr using hint and returns a list of // addresses. The result contains at least one address when error is // nil. @@ -269,6 +287,13 @@ func (r *Resolver) resolveAddrList(ctx context.Context, op, network, addr string } return addrList{addr}, nil } + + if resolveEnforcer != nil { + if err := resolveEnforcer(ctx, op, network, addr, hint); err != nil { + return nil, err + } + } + addrs, err := r.internetAddrList(ctx, afnet, addr) if err != nil || op != "dial" || hint == nil { return addrs, err @@ -572,9 +597,32 @@ func (sd *sysDialer) dialParallel(ctx context.Context, primaries, fallbacks addr } } +// SetDialEnforcer set a program-global dial enforcer that can cause dials to +// fail based on the context and/or Addr(s). +// +// f must be non-nil, it can only be called once, and must not be called +// concurrent with any dial. +func SetDialEnforcer(f func(context.Context, []Addr) error) { + if f == nil { + panic("nil func") + } + if dialEnforcer != nil { + panic("already called") + } + dialEnforcer = f +} + +// dialEnforce, if non-nil, is any installed hook from SetDialEnforcer. +var dialEnforcer func(context.Context, []Addr) error + // dialSerial connects to a list of addresses in sequence, returning // either the first successful connection, or the first error. func (sd *sysDialer) dialSerial(ctx context.Context, ras addrList) (Conn, error) { + if dialEnforcer != nil { + if err := dialEnforcer(ctx, ras); err != nil { + return nil, err + } + } var firstErr error // The error from the first address is most relevant. for i, ra := range ras { From 584ea7885bd7977196df9809652d23e2ae74debf Mon Sep 17 00:00:00 2001 From: Jenny Zhang Date: Wed, 21 Jun 2023 14:46:08 -0400 Subject: [PATCH 6/7] [tailscale] net: add SockTrace API Loosely inspired by nettrace/httptrace, allows functions to be called when sockets are read from or written to. The hooks are specified via the context (with a WithSockTrace function). Only implemented for network sockets on POSIX systems. Updates tailscale/corp#9230 Updates #58 Signed-off-by: Jenny Zhang (Cherry-picked from fb11c0d) --- api/go1.99999.txt | 6 ++++++ src/net/fd_posix.go | 47 +++++++++++++++++++++++++++++++++++++++++++ src/net/sock_posix.go | 4 ++++ src/net/socktrace.go | 46 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 src/net/socktrace.go diff --git a/api/go1.99999.txt b/api/go1.99999.txt index dd6cb2c37d580..752e6be3447d0 100644 --- a/api/go1.99999.txt +++ b/api/go1.99999.txt @@ -1,2 +1,8 @@ pkg net, func SetDialEnforcer(func(context.Context, []Addr) error) #55 pkg net, func SetResolveEnforcer(func(context.Context, string, string, string, Addr) error) #55 +pkg net, func WithSockTrace(context.Context, *SockTrace) context.Context #58 +pkg net, func ContextSockTrace(context.Context) *SockTrace #58 +pkg net, type SockTrace struct #58 +pkg net, type SockTrace struct, DidRead func(int) #58 +pkg net, type SockTrace struct, DidWrite func(int) #58 +pkg net, type SockTrace struct, WillOverwrite func(*SockTrace) #58 diff --git a/src/net/fd_posix.go b/src/net/fd_posix.go index ffb9bcf8b9e8c..7f3aeff580c95 100644 --- a/src/net/fd_posix.go +++ b/src/net/fd_posix.go @@ -24,6 +24,11 @@ type netFD struct { net string laddr Addr raddr Addr + + // hooks (if provided) are called after successful reads or writes with the + // number of bytes transferred. + readHook func(int) + writeHook func(int) } func (fd *netFD) setAddr(laddr, raddr Addr) { @@ -53,83 +58,125 @@ func (fd *netFD) closeWrite() error { func (fd *netFD) Read(p []byte) (n int, err error) { n, err = fd.pfd.Read(p) + if fd.readHook != nil && err == nil { + fd.readHook(n) + } runtime.KeepAlive(fd) return n, wrapSyscallError(readSyscallName, err) } func (fd *netFD) readFrom(p []byte) (n int, sa syscall.Sockaddr, err error) { n, sa, err = fd.pfd.ReadFrom(p) + if fd.readHook != nil && err == nil { + fd.readHook(n) + } runtime.KeepAlive(fd) return n, sa, wrapSyscallError(readFromSyscallName, err) } func (fd *netFD) readFromInet4(p []byte, from *syscall.SockaddrInet4) (n int, err error) { n, err = fd.pfd.ReadFromInet4(p, from) + if fd.readHook != nil && err == nil { + fd.readHook(n) + } runtime.KeepAlive(fd) return n, wrapSyscallError(readFromSyscallName, err) } func (fd *netFD) readFromInet6(p []byte, from *syscall.SockaddrInet6) (n int, err error) { n, err = fd.pfd.ReadFromInet6(p, from) + if fd.readHook != nil && err == nil { + fd.readHook(n) + } runtime.KeepAlive(fd) return n, wrapSyscallError(readFromSyscallName, err) } func (fd *netFD) readMsg(p []byte, oob []byte, flags int) (n, oobn, retflags int, sa syscall.Sockaddr, err error) { n, oobn, retflags, sa, err = fd.pfd.ReadMsg(p, oob, flags) + if fd.readHook != nil && err == nil { + fd.readHook(n + oobn) + } runtime.KeepAlive(fd) return n, oobn, retflags, sa, wrapSyscallError(readMsgSyscallName, err) } func (fd *netFD) readMsgInet4(p []byte, oob []byte, flags int, sa *syscall.SockaddrInet4) (n, oobn, retflags int, err error) { n, oobn, retflags, err = fd.pfd.ReadMsgInet4(p, oob, flags, sa) + if fd.readHook != nil && err == nil { + fd.readHook(n + oobn) + } runtime.KeepAlive(fd) return n, oobn, retflags, wrapSyscallError(readMsgSyscallName, err) } func (fd *netFD) readMsgInet6(p []byte, oob []byte, flags int, sa *syscall.SockaddrInet6) (n, oobn, retflags int, err error) { n, oobn, retflags, err = fd.pfd.ReadMsgInet6(p, oob, flags, sa) + if fd.readHook != nil && err == nil { + fd.readHook(n + oobn) + } runtime.KeepAlive(fd) return n, oobn, retflags, wrapSyscallError(readMsgSyscallName, err) } func (fd *netFD) Write(p []byte) (nn int, err error) { nn, err = fd.pfd.Write(p) + if fd.writeHook != nil && err == nil { + fd.writeHook(nn) + } runtime.KeepAlive(fd) return nn, wrapSyscallError(writeSyscallName, err) } func (fd *netFD) writeTo(p []byte, sa syscall.Sockaddr) (n int, err error) { n, err = fd.pfd.WriteTo(p, sa) + if fd.writeHook != nil && err == nil { + fd.writeHook(n) + } runtime.KeepAlive(fd) return n, wrapSyscallError(writeToSyscallName, err) } func (fd *netFD) writeToInet4(p []byte, sa *syscall.SockaddrInet4) (n int, err error) { n, err = fd.pfd.WriteToInet4(p, sa) + if fd.writeHook != nil && err == nil { + fd.writeHook(n) + } runtime.KeepAlive(fd) return n, wrapSyscallError(writeToSyscallName, err) } func (fd *netFD) writeToInet6(p []byte, sa *syscall.SockaddrInet6) (n int, err error) { n, err = fd.pfd.WriteToInet6(p, sa) + if fd.writeHook != nil && err == nil { + fd.writeHook(n) + } runtime.KeepAlive(fd) return n, wrapSyscallError(writeToSyscallName, err) } func (fd *netFD) writeMsg(p []byte, oob []byte, sa syscall.Sockaddr) (n int, oobn int, err error) { n, oobn, err = fd.pfd.WriteMsg(p, oob, sa) + if fd.writeHook != nil && err == nil { + fd.writeHook(n + oobn) + } runtime.KeepAlive(fd) return n, oobn, wrapSyscallError(writeMsgSyscallName, err) } func (fd *netFD) writeMsgInet4(p []byte, oob []byte, sa *syscall.SockaddrInet4) (n int, oobn int, err error) { n, oobn, err = fd.pfd.WriteMsgInet4(p, oob, sa) + if fd.writeHook != nil && err == nil { + fd.writeHook(n + oobn) + } runtime.KeepAlive(fd) return n, oobn, wrapSyscallError(writeMsgSyscallName, err) } func (fd *netFD) writeMsgInet6(p []byte, oob []byte, sa *syscall.SockaddrInet6) (n int, oobn int, err error) { n, oobn, err = fd.pfd.WriteMsgInet6(p, oob, sa) + if fd.writeHook != nil && err == nil { + fd.writeHook(n + oobn) + } runtime.KeepAlive(fd) return n, oobn, wrapSyscallError(writeMsgSyscallName, err) } diff --git a/src/net/sock_posix.go b/src/net/sock_posix.go index b3e1806ba9f48..5c5340c1b756f 100644 --- a/src/net/sock_posix.go +++ b/src/net/sock_posix.go @@ -28,6 +28,10 @@ func socket(ctx context.Context, net string, family, sotype, proto int, ipv6only poll.CloseFunc(s) return nil, err } + if trace := ContextSockTrace(ctx); trace != nil { + fd.readHook = trace.DidRead + fd.writeHook = trace.DidWrite + } // This function makes a network file descriptor for the // following applications: diff --git a/src/net/socktrace.go b/src/net/socktrace.go new file mode 100644 index 0000000000000..57af2be8b75e3 --- /dev/null +++ b/src/net/socktrace.go @@ -0,0 +1,46 @@ +// Copyright 2023 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. + +package net + +import ( + "context" +) + +// SockTrace is a set of hooks to run at various operations on a network socket. +// Any particular hook may be nil. Functions may be called concurrently from +// different goroutines. +type SockTrace struct { + // DidRead is called after a successful read from the socket, where n bytes + // were read. + DidRead func(n int) + // DidWrite is called after a successful write to the socket, where n bytes + // were written. + DidWrite func(n int) + // WillOverwrite is called when the registered trace is overwritten by a + // subsequent call to WithSockTrace. The provided trace is the new trace + // that will be used. + WillOverwrite func(trace *SockTrace) +} + +// WithSockTrace returns a new context based on the provided parent +// ctx. Socket reads and writes made with the returned context will use +// the provided trace hooks. Any previous hooks registered with ctx are +// ovewritten (their WillOverwrite hook will be called). +func WithSockTrace(ctx context.Context, trace *SockTrace) context.Context { + if previous := ContextSockTrace(ctx); previous != nil && previous.WillOverwrite != nil { + previous.WillOverwrite(trace) + } + return context.WithValue(ctx, sockTraceKey{}, trace) +} + +// ContextSockTrace returns the SockTrace associated with the +// provided context. If none, it returns nil. +func ContextSockTrace(ctx context.Context) *SockTrace { + trace, _ := ctx.Value(sockTraceKey{}).(*SockTrace) + return trace +} + +// unique type to prevent assignment. +type sockTraceKey struct{} From 4d51c4a16fdf0d3f45c2b37c94ab2280c330c9a0 Mon Sep 17 00:00:00 2001 From: Jenny Zhang Date: Wed, 21 Jun 2023 14:46:28 -0400 Subject: [PATCH 7/7] [tailscale] net: add TCP socket creation/close hooks to SockTrace API Extends the hooks added by #45 to also expose when TCP sockets are created or closed (meant to allow TCP stats to be read from them). We don't do this for all socket types since stats are not available for UDP sockets, and they tend to be short-lived, thus invoking the hooks would be useless overhead. Also fixes read/write hooks to not count out-of-band data, since that's usually not sent over the wire. Updates tailscale/corp#9230 Updates #58 Signed-off-by: Jenny Zhang (Cherry-picked from db4dc90) --- api/go1.99999.txt | 2 ++ src/net/fd_posix.go | 22 ++++++++++++++++------ src/net/sock_posix.go | 15 +++++++++++++++ src/net/socktrace.go | 7 +++++++ 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/api/go1.99999.txt b/api/go1.99999.txt index 752e6be3447d0..a4b8591391989 100644 --- a/api/go1.99999.txt +++ b/api/go1.99999.txt @@ -6,3 +6,5 @@ pkg net, type SockTrace struct #58 pkg net, type SockTrace struct, DidRead func(int) #58 pkg net, type SockTrace struct, DidWrite func(int) #58 pkg net, type SockTrace struct, WillOverwrite func(*SockTrace) #58 +pkg net, type SockTrace struct, DidCreateTCPConn func(syscall.RawConn) #58 +pkg net, type SockTrace struct, WillCloseTCPConn func(syscall.RawConn) #58 diff --git a/src/net/fd_posix.go b/src/net/fd_posix.go index 7f3aeff580c95..5c88b50cdae49 100644 --- a/src/net/fd_posix.go +++ b/src/net/fd_posix.go @@ -29,6 +29,7 @@ type netFD struct { // number of bytes transferred. readHook func(int) writeHook func(int) + closeHook func() } func (fd *netFD) setAddr(laddr, raddr Addr) { @@ -39,6 +40,9 @@ func (fd *netFD) setAddr(laddr, raddr Addr) { func (fd *netFD) Close() error { runtime.SetFinalizer(fd, nil) + if fd.closeHook != nil { + fd.closeHook() + } return fd.pfd.Close() } @@ -49,10 +53,16 @@ func (fd *netFD) shutdown(how int) error { } func (fd *netFD) closeRead() error { + if fd.closeHook != nil { + fd.closeHook() + } return fd.shutdown(syscall.SHUT_RD) } func (fd *netFD) closeWrite() error { + if fd.closeHook != nil { + fd.closeHook() + } return fd.shutdown(syscall.SHUT_WR) } @@ -94,7 +104,7 @@ func (fd *netFD) readFromInet6(p []byte, from *syscall.SockaddrInet6) (n int, er func (fd *netFD) readMsg(p []byte, oob []byte, flags int) (n, oobn, retflags int, sa syscall.Sockaddr, err error) { n, oobn, retflags, sa, err = fd.pfd.ReadMsg(p, oob, flags) if fd.readHook != nil && err == nil { - fd.readHook(n + oobn) + fd.readHook(n) } runtime.KeepAlive(fd) return n, oobn, retflags, sa, wrapSyscallError(readMsgSyscallName, err) @@ -103,7 +113,7 @@ func (fd *netFD) readMsg(p []byte, oob []byte, flags int) (n, oobn, retflags int func (fd *netFD) readMsgInet4(p []byte, oob []byte, flags int, sa *syscall.SockaddrInet4) (n, oobn, retflags int, err error) { n, oobn, retflags, err = fd.pfd.ReadMsgInet4(p, oob, flags, sa) if fd.readHook != nil && err == nil { - fd.readHook(n + oobn) + fd.readHook(n) } runtime.KeepAlive(fd) return n, oobn, retflags, wrapSyscallError(readMsgSyscallName, err) @@ -112,7 +122,7 @@ func (fd *netFD) readMsgInet4(p []byte, oob []byte, flags int, sa *syscall.Socka func (fd *netFD) readMsgInet6(p []byte, oob []byte, flags int, sa *syscall.SockaddrInet6) (n, oobn, retflags int, err error) { n, oobn, retflags, err = fd.pfd.ReadMsgInet6(p, oob, flags, sa) if fd.readHook != nil && err == nil { - fd.readHook(n + oobn) + fd.readHook(n) } runtime.KeepAlive(fd) return n, oobn, retflags, wrapSyscallError(readMsgSyscallName, err) @@ -157,7 +167,7 @@ func (fd *netFD) writeToInet6(p []byte, sa *syscall.SockaddrInet6) (n int, err e func (fd *netFD) writeMsg(p []byte, oob []byte, sa syscall.Sockaddr) (n int, oobn int, err error) { n, oobn, err = fd.pfd.WriteMsg(p, oob, sa) if fd.writeHook != nil && err == nil { - fd.writeHook(n + oobn) + fd.writeHook(n) } runtime.KeepAlive(fd) return n, oobn, wrapSyscallError(writeMsgSyscallName, err) @@ -166,7 +176,7 @@ func (fd *netFD) writeMsg(p []byte, oob []byte, sa syscall.Sockaddr) (n int, oob func (fd *netFD) writeMsgInet4(p []byte, oob []byte, sa *syscall.SockaddrInet4) (n int, oobn int, err error) { n, oobn, err = fd.pfd.WriteMsgInet4(p, oob, sa) if fd.writeHook != nil && err == nil { - fd.writeHook(n + oobn) + fd.writeHook(n) } runtime.KeepAlive(fd) return n, oobn, wrapSyscallError(writeMsgSyscallName, err) @@ -175,7 +185,7 @@ func (fd *netFD) writeMsgInet4(p []byte, oob []byte, sa *syscall.SockaddrInet4) func (fd *netFD) writeMsgInet6(p []byte, oob []byte, sa *syscall.SockaddrInet6) (n int, oobn int, err error) { n, oobn, err = fd.pfd.WriteMsgInet6(p, oob, sa) if fd.writeHook != nil && err == nil { - fd.writeHook(n + oobn) + fd.writeHook(n) } runtime.KeepAlive(fd) return n, oobn, wrapSyscallError(writeMsgSyscallName, err) diff --git a/src/net/sock_posix.go b/src/net/sock_posix.go index 5c5340c1b756f..759abe872b15c 100644 --- a/src/net/sock_posix.go +++ b/src/net/sock_posix.go @@ -31,6 +31,21 @@ func socket(ctx context.Context, net string, family, sotype, proto int, ipv6only if trace := ContextSockTrace(ctx); trace != nil { fd.readHook = trace.DidRead fd.writeHook = trace.DidWrite + if (trace.DidCreateTCPConn != nil || trace.WillCloseTCPConn != nil) && len(net) >= 3 && net[0:3] == "tcp" { + // Ignore newRawConn errors (they're not possible in the current + // implementation, but even if they were, we don't want to + // affect socket operations for a trace hook invocation). + if c, err := newRawConn(fd); err == nil { + if trace.DidCreateTCPConn != nil { + trace.DidCreateTCPConn(c) + } + if trace.WillCloseTCPConn != nil { + fd.closeHook = func() { + trace.WillCloseTCPConn(c) + } + } + } + } } // This function makes a network file descriptor for the diff --git a/src/net/socktrace.go b/src/net/socktrace.go index 57af2be8b75e3..b02a8d12484d4 100644 --- a/src/net/socktrace.go +++ b/src/net/socktrace.go @@ -6,12 +6,16 @@ package net import ( "context" + "syscall" ) // SockTrace is a set of hooks to run at various operations on a network socket. // Any particular hook may be nil. Functions may be called concurrently from // different goroutines. type SockTrace struct { + // DidOpenTCPConn is called when a TCP socket was created. The + // underlying raw network connection that was created is provided. + DidCreateTCPConn func(c syscall.RawConn) // DidRead is called after a successful read from the socket, where n bytes // were read. DidRead func(n int) @@ -22,6 +26,9 @@ type SockTrace struct { // subsequent call to WithSockTrace. The provided trace is the new trace // that will be used. WillOverwrite func(trace *SockTrace) + // WillCloseTCPConn is called when a TCP socket is about to be closed. The + // underlying raw network connection that is being closed is provided. + WillCloseTCPConn func(c syscall.RawConn) } // WithSockTrace returns a new context based on the provided parent