From d256d50c123242c3e8ad5cda727b78f449c4d1f1 Mon Sep 17 00:00:00 2001 From: Jenny Zhang Date: Wed, 21 Jun 2023 14:19:48 -0400 Subject: [PATCH 01/12] [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 00000000000000..7a8432ed7e7ee7 --- /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 00000000000000..e7dc68cfba12d6 --- /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 2fb13834a120c94240c747e41659f08760311922 Mon Sep 17 00:00:00 2001 From: Jenny Zhang Date: Wed, 21 Jun 2023 14:22:00 -0400 Subject: [PATCH 02/12] [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 01b5ec9ed05642..5f1cafd17e143d 100644 --- a/src/net/tcpsock_posix.go +++ b/src/net/tcpsock_posix.go @@ -175,11 +175,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 50350598317eb9..21d92df4ca2a51 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 d88c63d4bab1db59398ad0603292abd222b1ec48 Mon Sep 17 00:00:00 2001 From: Jenny Zhang Date: Wed, 21 Jun 2023 14:26:30 -0400 Subject: [PATCH 03/12] [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 32e59b446a5d9b..a687709300a102 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 fa904e1cfd4e4f605749728e193be193b5440882 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Thu, 2 Feb 2023 12:41:57 -0800 Subject: [PATCH 04/12] [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 884e9d729a6a35..5c1daecd4c3d98 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 043e09a65caadc86988fa14dc1de135ab224fb92 Mon Sep 17 00:00:00 2001 From: Jenny Zhang Date: Wed, 21 Jun 2023 14:31:48 -0400 Subject: [PATCH 05/12] [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 00000000000000..dd6cb2c37d580e --- /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 a6565c3ce5d13b..2e8239da0831f0 100644 --- a/src/net/dial.go +++ b/src/net/dial.go @@ -258,6 +258,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. @@ -280,6 +298,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 @@ -584,9 +609,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 109113bdfb2bd42f6d9b9fabf0cfb7d013d63e9f Mon Sep 17 00:00:00 2001 From: Jenny Zhang Date: Wed, 21 Jun 2023 14:46:08 -0400 Subject: [PATCH 06/12] [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 dd6cb2c37d580e..752e6be3447d0c 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 ffb9bcf8b9e8ca..7f3aeff580c95d 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 d04c26e7ef449c..cd36c2ab6bb5bb 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 00000000000000..57af2be8b75e3e --- /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 51a96ad6414edd8c39affd9923a614e4c8d1b221 Mon Sep 17 00:00:00 2001 From: Jenny Zhang Date: Wed, 21 Jun 2023 14:46:28 -0400 Subject: [PATCH 07/12] [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 752e6be3447d0c..a4b85913919892 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 7f3aeff580c95d..5c88b50cdae49c 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 cd36c2ab6bb5bb..7f0ff9bc910967 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 57af2be8b75e3e..b02a8d12484d4e 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 From 8df94885917d49c181b75af509ef16e8c8d6cedf Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sat, 22 Jul 2023 20:07:20 -0700 Subject: [PATCH 08/12] [tailscale] net/http: add enforcement hook to Transport.RoundTrip, like our net ones Updates tailscale/go#55 Updates tailscale/corp#12702 Signed-off-by: Brad Fitzpatrick --- api/go1.99999.txt | 1 + src/net/http/tailscale.go | 21 +++++++++++++++++++++ src/net/http/transport.go | 5 +++++ 3 files changed, 27 insertions(+) create mode 100644 src/net/http/tailscale.go diff --git a/api/go1.99999.txt b/api/go1.99999.txt index a4b85913919892..3c59e814c5ca5a 100644 --- a/api/go1.99999.txt +++ b/api/go1.99999.txt @@ -1,5 +1,6 @@ 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/http, func SetRoundTripEnforcer(func(*Request) 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 diff --git a/src/net/http/tailscale.go b/src/net/http/tailscale.go new file mode 100644 index 00000000000000..b2a13893d3eaaa --- /dev/null +++ b/src/net/http/tailscale.go @@ -0,0 +1,21 @@ +package http + +var roundTripEnforcer func(*Request) error + +// SetRoundTripEnforcer set a program-global resolver enforcer that can cause +// RoundTrip calls to fail based on the request and its context. +// +// f must be non-nil. +// +// SetRoundTripEnforcer can only be called once, and must not be called +// concurrent with any RoundTrip call; it's expected to be registered during +// init. +func SetRoundTripEnforcer(f func(*Request) error) { + if f == nil { + panic("nil func") + } + if roundTripEnforcer != nil { + panic("already called") + } + roundTripEnforcer = f +} diff --git a/src/net/http/transport.go b/src/net/http/transport.go index 57c70e72f9522c..e7185ed805cc17 100644 --- a/src/net/http/transport.go +++ b/src/net/http/transport.go @@ -515,6 +515,11 @@ func (t *Transport) alternateRoundTripper(req *Request) RoundTripper { // roundTrip implements a RoundTripper over HTTP. func (t *Transport) roundTrip(req *Request) (*Response, error) { + if roundTripEnforcer != nil { + if err := roundTripEnforcer(req); err != nil { + return nil, err + } + } t.nextProtoOnce.Do(t.onceSetNextProtoDefaults) ctx := req.Context() trace := httptrace.ContextClientTrace(ctx) From 3215ec2d607e288c9b9327130064b11c8d2b671c Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sun, 22 Oct 2023 09:21:21 -0700 Subject: [PATCH 09/12] [tailscale] cmd/go/internal/cache: enable cacheprog GOEXPERIMENT by default Updates #77 Updates tailscale/tailscale#9841 Signed-off-by: Brad Fitzpatrick --- src/cmd/go/internal/cache/default.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cmd/go/internal/cache/default.go b/src/cmd/go/internal/cache/default.go index b5650eac669b46..63aa26f81e357a 100644 --- a/src/cmd/go/internal/cache/default.go +++ b/src/cmd/go/internal/cache/default.go @@ -59,7 +59,9 @@ func initDefaultCache() { base.Fatalf("failed to initialize build cache at %s: %s\n", dir, err) } - if v := cfg.Getenv("GOCACHEPROG"); v != "" && goexperiment.CacheProg { + // We don't require the GOEXPERIMENT in Tailscale's Go tree. + const isTailscaleGoTree = true + if v := cfg.Getenv("GOCACHEPROG"); v != "" && (isTailscaleGoTree || goexperiment.CacheProg) { defaultCache = startCacheProg(v, diskCache) } else { defaultCache = diskCache From 2daea653d6a4e30627b7acb0dd9ceea645475527 Mon Sep 17 00:00:00 2001 From: Andrew Lytvynov Date: Fri, 5 Jan 2024 09:47:12 -0700 Subject: [PATCH 10/12] [tailscale] .github/workflows/build: support re-building old releases Need to rebuild 1.20.5 release to make `tool/gocross.sh` work in OSS for 1.44 release branch for a backport. Add manual dispatch to the release build action to allow that. Updates tailscale/corp#15405 --- .github/workflows/build.yml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7a8432ed7e7ee7..feb6be91b9f734 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,12 @@ on: pull_request: branches: - '*' + workflow_dispatch: + inputs: + ref: + description: Branch, commit or tag to build from + required: true + default: 'tailscale.go1.21' jobs: test: @@ -18,6 +24,8 @@ jobs: steps: - name: checkout uses: actions/checkout@v3 + with: + ref: ${{ inputs.ref || github.ref }} - name: test run: cd src && ./all.bash @@ -27,10 +35,12 @@ jobs: GOOS: ["linux", "darwin"] GOARCH: ["amd64", "arm64"] runs-on: ubuntu-20.04 - if: github.event_name == 'push' + if: contains(fromJSON('["push", "workflow_dispatch"]'), github.event_name) steps: - name: checkout uses: actions/checkout@v3 + with: + ref: ${{ inputs.ref || github.ref }} - name: build run: cd src && ./make.bash env: @@ -57,7 +67,7 @@ jobs: create_release: runs-on: ubuntu-20.04 - if: github.event_name == 'push' + if: contains(fromJSON('["push", "workflow_dispatch"]'), github.event_name) needs: [test, build_release] outputs: url: ${{ steps.create_release.outputs.upload_url }} @@ -80,7 +90,7 @@ jobs: GOOS: ["linux", "darwin"] GOARCH: ["amd64", "arm64"] runs-on: ubuntu-20.04 - if: github.event_name == 'push' + if: contains(fromJSON('["push", "workflow_dispatch"]'), github.event_name) needs: [create_release] steps: - name: download artifact @@ -99,10 +109,14 @@ jobs: clean_old: runs-on: ubuntu-20.04 + # Do not clean up old builds on workflow_dispatch to allow temporarily + # re-creating old releases for backports. if: github.event_name == 'push' needs: [upload_release] steps: - name: checkout uses: actions/checkout@v3 + with: + ref: ${{ inputs.ref || github.ref }} - name: Delete older builds run: ./.github/workflows/prune_old_builds.sh "${{ secrets.GITHUB_TOKEN }}" From 1b85329a12cf56aceb75e2b8f18437717365c68b Mon Sep 17 00:00:00 2001 From: Andrew Lytvynov Date: Fri, 5 Jan 2024 11:10:39 -0700 Subject: [PATCH 11/12] [tailscale] .github/workflows/build: fix release name on manual builds Use the ref provided in input instead of latest main branch SHA to tag the release. --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index feb6be91b9f734..642bb7ff70951f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -79,8 +79,8 @@ jobs: 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 }} + tag_name: build-${{ inputs.ref || github.sha }} + release_name: ${{ inputs.ref || github.sha }} draft: false prerelease: true From aff7d044b7f4636b8561ecc024c8fa3be397a096 Mon Sep 17 00:00:00 2001 From: Andrew Lytvynov Date: Wed, 7 Feb 2024 08:46:46 -0800 Subject: [PATCH 12/12] [tailscale] net: fix newRawConn usage --- src/net/sock_posix.go | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/net/sock_posix.go b/src/net/sock_posix.go index 7f0ff9bc910967..349e095b248cd1 100644 --- a/src/net/sock_posix.go +++ b/src/net/sock_posix.go @@ -32,17 +32,13 @@ func socket(ctx context.Context, net string, family, sotype, proto int, ipv6only 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) - } + c := newRawConn(fd) + if trace.DidCreateTCPConn != nil { + trace.DidCreateTCPConn(c) + } + if trace.WillCloseTCPConn != nil { + fd.closeHook = func() { + trace.WillCloseTCPConn(c) } } }